Travis-CIとCoverallsでJavaアプリケーションの単体テスト

はじめに

今更ながら継続的インテグレーション(CI)環境を整え、自動単体テストのための仕掛けを用意しましたので、 本エントリに実施した事項をまとめます。

以降では、先日紹介したroomba_clientプロジェクトを対象に説明します。

hiroki-sawano.hatenablog.com

テストコードの実装

次のようなテストコードを用意します。 今回は単にテストを動かすことが目的なので詳細は説明しませんが、 RoombaControllerTests::index()では"/"にアクセスした際のページ遷移先や、Viewに渡すデータが期待通りに設定されていることを確認しています。 RoombaControllerTests::addCommand()では"/invoke?add="へのPOST要求でDBへの行挿入のメソッドが呼び出されていることを確認しています。

@RunWith(SpringRunner.class)
@WebMvcTest(RoombaController.class)
public class RoombaControllerTests {
    @Autowired
    private MockMvc mvc;
    @MockBean
    private MyDataRepository repo;
    @MockBean
    private Settings settings;
    @MockBean
    private Roomba roomba;

    private Command c1 = new Command("c1", "seq1");
    private Command c2 = new Command("c2", "seq2");

    private Map<String, String> selectItems = new HashMap<String, String>() {
        private static final long serialVersionUID = 1L;
        {
            put("foo", "fooVal");
            put("bar", "barVal");
        }
    };

    @Before
    public void before() {
        given(this.roomba.connect("192.168.0.20", 9001)).willReturn(true);
        given(this.roomba.send("128")).willReturn(true);
        given(this.roomba.disconnect()).willReturn(true);
    }

    @Test
    public void index() throws Exception {
        List<Command> commandList = new ArrayList<Command>();
        commandList.add(c1);
        commandList.add(c2);
        given(this.settings.getSequences()).willReturn(selectItems);
        given(this.repo.findAll()).willReturn(commandList);
        MvcResult mvcResult = this.mvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
                .andExpect(view().name("index")).andExpect(model().attributeExists("userInput"))
                .andExpect(model().attribute("dataTable", commandList)).andReturn();
        ModelMap modelMap = mvcResult.getModelAndView().getModelMap();
        Object uio = modelMap.get("userInput");
        assertThat(uio, is(not(nullValue())));
        assertThat(uio, is(instanceOf(UserInput.class)));
        UserInput ui = (UserInput) uio;
        assertThat(ui.getSelectItems(), is(selectItems));
    }

    @Test
    public void addCommand() throws Exception {
        this.mvc.perform(post("/invoke?add=").param("name", "command_name").param("selectedSequence", "")
                .param("arbitrarySequence", "128 131")).andDo(print()).andExpect(redirectedUrl("/"))
                .andExpect(model().hasNoErrors());
        verify(repo).saveAndFlush(Mockito.any(Command.class));
    }

Travis-CIの設定

続けてTravis-CI(https://travis-ci.org/)の設定です。
試験対象プロジェクトのリポジトリと連携した後、 .travis.ymlをGitリポジトリのルートに配置します。 今回はSpring bootアプリなのでlanguageにはjavaを設定し、jdkを指定します。

.travis.yml

language: java
jdk:
 - oraclejdk8

以上でリモートリポジトリにPushする度に自動的にビルド(テスト含む)が実行されるようになりました。 f:id:hiroki-sawano:20171224165144p:plain

Coverallsプラグインの追加

Travis-CIで実行されたテストのカバレッジレポートを確認できるようにするため、Coveralls(https://coveralls.io)と連携します。 カバレッジレポートはJacocoプラグインで出力します。

まず、build.gradleにjacocoとcoveralls-gradle-pluginを追加します。 jacocoTestReportにはレポートの出力形式を指定できますが、どうやらCoverallsはXML形式のレポートが必要なようなので xml.enabled = trueを設定します。

build.gradle

buildscript {
    ext {
        springBootVersion = '1.5.7.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath("org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.6.3")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'jacoco'
apply plugin: 'com.github.kt3k.coveralls'

jacocoTestReport {
    reports {
        xml.enabled = true //coveralls plugin depends on xml format report
        html.enabled = true
    }
}

続けて、Travis-CIでビルド後にCoverallsにカバレッジ情報を転送するための設定です。
.travis.ymlのafter_successに次の設定を追記します。

.travis.yml

language: java
jdk:
 - oraclejdk8
after_success:
 - ./gradlew test jacocoTestReport coveralls

カバレッジレポートの確認

以上の設定により、GitにPushするとCoverallsで単体テストの結果が確認できるようになります。 f:id:hiroki-sawano:20171224162251p:plain

f:id:hiroki-sawano:20171224162508p:plain

f:id:hiroki-sawano:20171224162525p:plain

GithubのREADMEにバッジを追加

最後にGitのREADME.mdにビルド結果とカバレッジ率を示すバッジを貼り付ければ完成です。 f:id:hiroki-sawano:20171224170943p:plain f:id:hiroki-sawano:20171224162308p:plain f:id:hiroki-sawano:20171224161953p:plain

[Roomba Hack] Spring boot Webアプリからルンバ800(+RooWiFi)を操る

はじめに

ルンバ800をハックしました。 Webアプリからルンバと通信することで掃除をさせたり、音楽を流したり、スケジュールを変更したり、できるようにしました。

準備

ルンバと無線通信するため「RooWiFiルンバ用無線LANモジュール(RB-Roo-01)」をルンバに搭載しました。
http://www.robotshop.com/jp/ja/roowifi-wifi-module-roomba-v2.html

インストール手順はこちらを参照しました。
http://www.roowifi.com/wp-content/uploads/RwRemote_User_Guide_v2_rev15.pdf

ルンバとTCP/IP通信

ルンバを操作するためのインタフェースはThe Roomba Open Interface(OI)として公開されています。 こちらの仕様に記載のコマンドをソケット通信でルンバに送り込めばルンバを好きなように操ることができます。
http://www.irobot.lv/uploaded_files/File/iRobot_Roomba_500_Open_Interface_Spec.pdf

Spring bootアプリの構築

ルンバと通信するためのWebアプリケーション(roomba_client)を構築しました。
日々改良中ですが、形になってきたので公開します。 https://github.com/hiroki-sawano/roomba_clientgithub.com

roomba_clientの起動

アプリケーションを起動し、ブラウザよりアクセスすると次の画面が表示されます。 f:id:hiroki-sawano:20171203174800p:plain

アプリケーションの立ち上げ時にはルンバへの接続が成功したことを示すメッセージが出力されます。
roomba_client/event.log

2017-12-07 05:12:58.522 ... hs.roomba.client.RoombaController : Connected to Roomba

複数コマンドの連続実行

任意のコマンド名と前述したOIの仕様に従ったシリアルシーケンスを入力し、'ADD COMMAND'ボタンでデータテーブルにコマンドを実行順に追加していきます。
シリアルシーケンスは直接入力(Arbitrary Sequence)でもかまいませんが、いくつかセレクトボックス(Selectable Sequence)で選択可能としています。
こちらの例では、①セーフモードへの移行、②曲の登録、③曲の再生を実行し、ルンバから音楽を再生しています。
f:id:hiroki-sawano:20171207052956g:plain

ログ情報は次の通りです。
roomba_client/event.log

2017-12-07 05:16:42.356 ... hs.roomba.client.RoombaController : 
Adding new command : name=start serialSequence=128 131
2017-12-07 05:16:48.832 ... hs.roomba.client.RoombaController : 
Adding new command : name=song serialSequence=140 0 7 76 16 76 32 79 16 79 16 77 16 74 16 72 32
2017-12-07 05:16:55.268  ... hs.roomba.client.RoombaController : 
Adding new command : name=play serialSequence=141 0
2017-12-07 05:16:56.944  ... hs.roomba.client.RoombaController : 
Sending serial sequence : 128 131
2017-12-07 05:16:57.959  ... hs.roomba.client.RoombaController : 
Sending serial sequence : 140 0 7 76 16 76 32 79 16 79 16 77 16 74 16 72 32
2017-12-07 05:16:58.965  ... hs.roomba.client.RoombaController : 
Sending serial sequence : 141 0

単一コマンドの即時実行

コマンドを入力後に'EXECUTE COMMAND'を押下するとデータテーブルにコマンドを登録せずに即実行します。 この例では、③曲の再生を指示するシーケンスを直接入力してルンバに送信しています。
f:id:hiroki-sawano:20171207054704g:plain

roomba_client/event.log

2017-12-07 05:17:07.290 ... hs.roomba.client.RoombaController : 
Sending serial sequence : 141 0

その他機能

データテーブルに一度登録したコマンドは実行、編集、削除が可能です。

環境設定

使用する際にはapplication.ymlにルンバのIP(roomba.ip)とポート番号(roomba.port)を最低限設定することが必要です。
必要に応じて画面中のセレクトボックスで選択可能なシリアルシーケンス(roomba.sequences)を登録することもできます。
application.yml

roomba:
  ip: 192.168.0.19
  port: 9001
  sequences:
    start_and_safe: 128 131
    start_and_full: 128 132
    clean: 135
    max: 136
    spot: 134
    seek_dock: 143
    song: 140 0 7 76 16 76 32 79 16 79 16 77 16 74 16 72 32
    play: 141 0
    end: 128
    daily_schedule: 167 62 0 0 12 0 12 0 12 0 12 0 12 0 0 0

さいごに

今でも少し遊ぶ分には十分ですが実際に日常で役立つような使い方ができるように、組み立てたシーケンスの保存やコマンドの実行間隔の指定など今後改良を加えていきます。

ブロックチェーン基盤 Hyperledger Fabric v1.0 アプリケーションの実装

はじめに

今回はHyperledger Fabricのサンプルアプリケーションを動かしてみます。本エントリは次のページで紹介されている手順に従って進めていきます。

Writing Your First Application — hyperledger-fabricdocs master documentation

実行環境

  • Max OS High Sierra Version 10.13
  • Docker version 17.09.0-ce
  • Docker Compose version 1.16.1
  • Go version 1.9
  • Node.js version 8.6

Hyperledger Fabricのダウンロード

次のコマンドを実行します。

$ mkdir ~/fabric
$ cd ~/fabric
$ curl -sSL https://goo.gl/Q3YRTi | bash

これによりブロックチェーンネットワークを構築するために必要な次のバイナリを入手します。

  • cryptogen
  • configtxgen
  • configtxlator
  • peer
$ ls bin
configtxgen     configtxlator       cryptogen       get-docker-images.sh    orderer         peer

スクリプトはHyperledger FabricのdockerイメージをDocker Hubよりダウンロードします。

$ docker images
REPOSITORY                     TAG                    IMAGE ID            CREATED             SIZE
hyperledger/fabric-ca          latest                 2736904862db        10 days ago         218MB
hyperledger/fabric-ca          x86_64-1.1.0-preview   2736904862db        10 days ago         218MB
hyperledger/fabric-tools       latest                 c584c20ac82b        10 days ago         1.42GB
hyperledger/fabric-tools       x86_64-1.1.0-preview   c584c20ac82b        10 days ago         1.42GB
hyperledger/fabric-couchdb     latest                 5b8a15e6e972        10 days ago         1.57GB
hyperledger/fabric-couchdb     x86_64-1.1.0-preview   5b8a15e6e972        10 days ago         1.57GB
hyperledger/fabric-kafka       latest                 cf09c5534ef9        10 days ago         1.37GB
hyperledger/fabric-kafka       x86_64-1.1.0-preview   cf09c5534ef9        10 days ago         1.37GB
hyperledger/fabric-zookeeper   latest                 ac127485fdc7        10 days ago         1.37GB
hyperledger/fabric-zookeeper   x86_64-1.1.0-preview   ac127485fdc7        10 days ago         1.37GB
hyperledger/fabric-orderer     latest                 2fccc91736df        10 days ago         159MB
hyperledger/fabric-orderer     x86_64-1.1.0-preview   2fccc91736df        10 days ago         159MB
hyperledger/fabric-peer        latest                 337f3d90b452        10 days ago         165MB
hyperledger/fabric-peer        x86_64-1.1.0-preview   337f3d90b452        10 days ago         165MB
hyperledger/fabric-javaenv     latest                 cd459b218651        10 days ago         1.49GB
hyperledger/fabric-javaenv     x86_64-1.1.0-preview   cd459b218651        10 days ago         1.49GB
hyperledger/fabric-ccenv       latest                 82489d1c11e8        10 days ago         1.35GB
hyperledger/fabric-ccenv       x86_64-1.1.0-preview   82489d1c11e8        10 days ago         1.35GB

開発環境のセットアップ

Hyperledger Fabricのサンプルをgitでcloneします。

$ git clone https://github.com/hyperledger/fabric-samples.git
$ cd fabric-samples

以降では次のfabcarディレクトリで作業します。

$ cd fabcar
$ ls
enrollAdmin.js  invoke.js   package.json    query.js    registerUser.js startFabric.sh

念のためdockerコンテナとネットワークを削除しておきます。

$ docker rm -f $(docker ps -aq)
$ docker network prune

すでに以降の作業を実施済みの場合には次のコマンドでfabcarチェインコード用のdockerイメージも削除します。

$ docker rmi dev-peer0.org1.example.com-fabcar-1.0-5c906e402ed29f20260ae42283216aa75549c571e2e380f3615826365d8269ba

アプリケーションの実行に必要なライブラリをインストールするため次のコマンドを実行します。 以降ではCAサーバにアクセスするためにnode_modules/fabric-ca-client、 peerやordering serviceと対話するためにnode_modules/fabric-clientを使用していきます。

$ npm install

続けて次のスクリプトを実行することでネットワークを起動します。このコマンドはFabricの各コンテナを用意します。 fabcarネットワークの詳細はこちらに説明があります。
Understanding the Fabcar Network — hyperledger-fabricdocs master documentation

$ ./startFabric.sh
$ docker images
REPOSITORY                                                                                               TAG                    IMAGE ID            CREATED             SIZE
dev-peer0.org1.example.com-fabcar-1.0-5c906e402ed29f20260ae42283216aa75549c571e2e380f3615826365d8269ba   latest                 bf75bac8b3f9        2 minutes ago       151MB
...
$ docker ps
CONTAINER ID        IMAGE                                                                                                    COMMAND                  CREATED             STATUS              PORTS                                            NAMES
b4b2e2be6dc7        dev-peer0.org1.example.com-fabcar-1.0-5c906e402ed29f20260ae42283216aa75549c571e2e380f3615826365d8269ba   "chaincode -peer.a..."   4 minutes ago       Up 4 minutes                                                         dev-peer0.org1.example.com-fabcar-1.0
a4f01226a050        hyperledger/fabric-tools                                                                                 "/bin/bash"              6 minutes ago       Up 6 minutes                                                         cli
dbce2a81cb36        hyperledger/fabric-peer                                                                                  "peer node start"        7 minutes ago       Up 7 minutes        0.0.0.0:7051->7051/tcp, 0.0.0.0:7053->7053/tcp   peer0.org1.example.com
c83fd30320e2        hyperledger/fabric-couchdb                                                                               "tini -- /docker-e..."   7 minutes ago       Up 7 minutes        4369/tcp, 9100/tcp, 0.0.0.0:5984->5984/tcp       couchdb
0dd907f8cce3        hyperledger/fabric-orderer                                                                               "orderer"                7 minutes ago       Up 7 minutes        0.0.0.0:7050->7050/tcp                           orderer.example.com
86ef00f4705d        hyperledger/fabric-ca                                                                                    "sh -c 'fabric-ca-..."   7 minutes ago       Up 7 minutes        0.0.0.0:7054->7054/tcp                           ca.example.com

ユーザの登録

前述のネットワーク立ち上げ時にadminユーザが認証局(CA)に登録されています。
enrollAdmin.jsでadminの電子証明書(eCert)を取得します。 このプログラムは公開鍵と秘密鍵をhfc-key-storeに作成し、証明書署名要求(CSR)で公開鍵証明書を発行します。
続けて、registerUser.jsではadminユーザでCAと通信しLedgerにアクセスするuser1ユーザを登録します。このプログラムもまた、CSRを実行し証明書と鍵をhfc-key-storeに出力します。

$ node enrollAdmin.js
$ node registerUser.js
$ ls hfc-key-store/
admin                                   
...-pub
...-priv
user1
...-pub
...-priv

Ledgerの参照

query.jsを使用し、fabcarチェインコードのqueryAllCarsを呼び出します。

fabric-samples/fabcar/query.js

const request = {
    chaincodeId: 'fabcar',
    fcn: 'queryAllCars',
    args: ['']
};
$ node query.js
Store path:.../fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Query has completed, checking results
Response is  
[{"Key":"CAR0", "Record":{"colour":"blue","make":"Toyota","model":"Prius","owner":"Tomoko"}},
 {"Key":"CAR1", "Record":{"colour":"red","make":"Ford","model":"Mustang","owner":"Brad"}},
 {"Key":"CAR2", "Record":{"colour":"green","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}},
 {"Key":"CAR3", "Record":{"colour":"yellow","make":"Volkswagen","model":"Passat","owner":"Max"}},
 {"Key":"CAR4", "Record":{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}},
 {"Key":"CAR5", "Record":{"colour":"purple","make":"Peugeot","model":"205","owner":"Michel"}},
 {"Key":"CAR6", "Record":{"colour":"white","make":"Chery","model":"S22L","owner":"Aarav"}},
 {"Key":"CAR7", "Record":{"colour":"violet","make":"Fiat","model":"Punto","owner":"Pari"}},
 {"Key":"CAR8", "Record":{"colour":"indigo","make":"Tata","model":"Nano","owner":"Valeria"}},
 {"Key":"CAR9", "Record":{"colour":"brown","make":"Holden","model":"Barina","owner":"Shotaro"}}]

実行したチェインコードの関数は次の通りです。

fabric-samples/chaincode/fabcar/fabcar.go

func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {

    startKey := "CAR0"
    endKey := "CAR999"

    resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)

LedgerにアクセスするAPIの仕様はこちらにあります。 godoc.org github.com

Ledgerの更新

fabcarチェインコードのcreateCarで新たなCARをLedgerに追加します。

fabric-samples/fabcar/invoke.js

var request = {
    chaincodeId: 'fabcar',
    fcn: 'createCar',
    args: ['CAR10', 'Chevy', 'Volt', 'Red', 'Nick'],
    chainId: 'mychannel',
    txId: tx_id
};

fabric-samples/chaincode/fabcar/fabcar.go

func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

    if len(args) != 5 {
        return shim.Error("Incorrect number of arguments. Expecting 5")
    }

    var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4]}

    carAsBytes, _ := json.Marshal(car)
    APIstub.PutState(args[0], carAsBytes)

    return shim.Success(nil)
}
$ node invoke.js 
Store path:.../fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Assigning transaction_id:  97ae720f071443b8f73b5ab1e8ed1a87d8a04ba3904479f24cdb146486edcbfa
Transaction proposal was good
Successfully sent Proposal and received ProposalResponse: Status - 200, message - "OK"
info: [EventHub.js]: _connect - options {}
The transaction has been committed on peer localhost:7053
Send transaction promise and event listener promise have completed
Successfully sent transaction to the orderer.
Successfully committed the change to the ledger by the peer

最後にCAR10が正しく追加されているかを確認します。 fabcarのqueryCarに引数CAR10を渡し、query.jsを実行します。

query.js

const request = {
    chaincodeId: 'fabcar',
    fcn: 'queryCar',
    args: ['CAR10']
};

fabric-samples/chaincode/fabcar/fabcar.go

func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

    if len(args) != 1 {
        return shim.Error("Incorrect number of arguments. Expecting 1")
    }

    carAsBytes, _ := APIstub.GetState(args[0])
    return shim.Success(carAsBytes)
}
$ node query.js 
Store path:.../fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Query has completed, checking results
Response is  {"colour":"Red","make":"Chevy","model":"Volt","owner":"Nick"}

期待通りの結果が得られました。

さいごに

本エントリではサンプルアプリケーションfabcarを動かしてみました。 Ledgerにアクセスするアプリケーションの実装イメージが少し湧いた気がします。
次回はまだ未定ですが、JavaアプリケーションからHyperledger Fabric SDKを使用してみたいと考えてます。

vimからmake

はじめに

vimを開発環境として使う場合に標準搭載されているmakeコマンドを使用すると便利です。

サンプルプログラムの作成

次の通りCのプログラムとMakefileを用意します。

$ ls
Makefile    hello.c

hello.c

#include<stdio.h>

int main(){
    printf("Hello!\n");

    return 0;
}

Makefileにはbuildとrunのタスクを定義します。

Makefile

build :
         cc hello.c
run :
        ./a.out

vimrcにショートカットを設定

vimからMakefileに定義したタスクを簡単に実行するため、 ショートカットキーを設定しておきます。 これにより、vim上ではコマンドモード、挿入モードに関わらず F5でファイルの保存と:make buildの実行、F6でファイルの保存と:make runの実行ができるようになります。
makeの実行結果はQuickfix Listで確認できますが、毎度:copenするのは煩わしいので makeの後に自動でQuickfix Listが開くようにau[tocmd]も次の通り設定しておきます。

~/.vimrc

nnoremap <F5> :w<CR>:make build<CR>
inoremap <F5> <Esc>:w<CR>:make build<CR>
nnoremap <F6> :w<CR>:make run<CR>
inoremap <F6> <Esc>:w<CR>:make run<CR>
au QuickfixCmdPost make copen

動作確認

hello.cの最終行から閉じ括弧をわざと削除しておきます。
f:id:hiroki-sawano:20171117023708p:plain

F5でmake buildを実行します。
f:id:hiroki-sawano:20171117022533p:plain

実行結果はQuickfix Listで確認できます。
f:id:hiroki-sawano:20171117023755p:plain

コードを正しく修正し、再度make buildを行います。
f:id:hiroki-sawano:20171117023850p:plain

コンパイルが正常終了しました。
f:id:hiroki-sawano:20171117024015p:plain

F6でmake runを実行します。
f:id:hiroki-sawano:20171117023856p:plain

正常にプログラムが動作しました。
f:id:hiroki-sawano:20171117022839p:plain

ブロックチェーン基盤 Hyperledger Fabric v1.0 アーキテクチャ概説

はじめに

オープンソースブロックチェーン基盤であるHyperledger Fabric(v1.0)を最近学習中なので理解した範囲で本エントリーにまとめます。 Hyperledger Fabricのアーキテクチャトランザクションの流れを理解することや、 アプリケーションの実装方法を学ぶことが目的なので、ブロックチェーンそのものがどのような場面で適用できるかなどは述べる気はないです。そういった話はここを読むといいかもしれません。
Introduction — hyperledger-fabricdocs master documentation

システム・アーキテクチャ

以降ではここに書いてある説明を参考にしています。
http://hyperledger-fabric.readthedocs.io/en/latest/arch-deep-dive.html

まず、Hyperledger Fabricでは次に示すように複数のノードが協調して元帳(Ledger)の管理を実現しています。
f:id:hiroki-sawano:20171029200149p:plain

  • Application(SDK)
    トランザクションの実行を要求します。 ClientはNode.jsやJavaで実装できます(今後PythonやGo用のSDKも提供される予定)。
  • Membership
    PeerUserの登録・承認をすることで、許可されたノードだけがブロックチェーンネットワークに参加できます。 不特定多数が参加するBitcoinのようなブロックチェーンには存在しない、Enterprise用に実装されているHyperledger Fabricならではの機能です。
  • Peer
    すべてのPeerが同じデータをPeerLedgerに保持しています。 事前にPeerにデプロイしたChaincode(スマートコントラクト)によって、同トランザクション中に各PeerLedgerが更新されます。 ChaincodeはGoやJavaで実装できます。
  • Ordering-service
    Peerトランザクションの実行を承認した後、Ordering-serviceトランザクションの順序を整列します。続けて、すべてのPeerに対して更新情報を伝達します。

トランザクションの種類

公式では次の4種類のトランザクションについて触れられています。

  • Deploy transaction
    新しいChaincodeをデプロイします。Deploy transactionの成功にともなって、Chaincodeブロックチェーン上にインストールされます。
  • Invoke transaction
    事前にデプロイされたChaincodeが提供するオペレーションを実行します。 Invoke transactionの実行は、指定するオペレーションによってLedgerの更新や参照結果の返却を伴います。
  • Query transaction(included in v1)
    v1.0で追加となった読み取り専用トランザクションです。
  • Cross-chaincode transaction(post-v1 feature)
    複数のChaincodeを使用したトランザクションですが、v1.0では未実装です。

トランザクション承認の基本フロー

前述のInvoke Transactionを例に絵を書いてみました(大体あってるはずです..)。Deploy transactionの場合には、tx.txPayloadChaincodeソースコード等が設定される点で違いがありますが流れは基本的に同じようです。Query transactionは後日確認します。

f:id:hiroki-sawano:20171029183421p:plain

要約すると次の流れになります。

  1. [Client]トランザクションの提案
    PROPOSEメッセージでどのChaincodeのメソッドを実行したいのかをEndorsing Peer(承認者となるPeer)に伝える。

  2. [Peer]トランザクションのシミュレーション
    Endorsing Peerは自身のPeerLedger上でトランザクションを仮実行し、read version dependencies(readset)とstate updates(writeset)を取得する(おそらく下図のようなイメージです...)。

f:id:hiroki-sawano:20171029210749p:plain

  1. [Peer]トランザクションの承認
    Peerは内部的にtran-proposalを自身の承認ロジックに渡す。デフォルトでは単にtran-proposalに署名するだけだが、 任意の機能を実行することも可能(e.g. txtran-proposalを入力としたレガシーシステムとの連携によるトランザクション承認)。

  2. [Client]承認されたトランザクションOrdering-serviceに送信
    TRANSACTION-ENDORSEDメッセージをすべて受信した後にOrdering-servicebroadcast(blob)を実行する。 Ordering-serviceに渡すblobは署名されたTRANSACTION-ENDORSEDメッセージのコレクションであり、endorsementとも呼ばれる。 endorsementが集まらなかった場合にはトランザクション提案を取り下げる(後でリトライすることもできるらしい)。

  3. [Ordering-service]トランザクションを整列してPeerに更新情報を転送
    トランザクションを整列して、Peerdeliverイベントを実行する。 多くの場合には効率的に処理するため、Clientから送信された各々のトランザクション(blobs)を出力せずにグルーピングし、1つのdeliverイベントにblocksを設定するとのこと。

  4. [Peer]承認内容の検証
    今回処理するトランザクションseqno未満のseqnoで処理される状態の更新が完了した後に、 blob.endorsementが妥当であるかをChaincode(blob.tran-proposal.chaincodeID)のポリシーに従って検証する。

  5. [Peer]MVCC
    read version dependencies(blob.endorsement.tran-proposal.readset)が破られていないかを検証する。 つまり、承認時のLedgerの状態が他の更新系トランザクションによって変わっていないか(Anomalyの発生)を確認する必要があるということだと思う(i.e. MultiVersion Concurrency Controlを用いたSnapshot Isolationの実装(トランザクション分離レベルではいわゆるRead Commited))。 (MVCCについては理解が浅いので別途確認します.. )

  6. [Peer]トランザクションのコミット
    トランザクションをコミットし、blob.endorsement.tran-proposal.writesetブロックチェーンの状態に反映する。

さいごに

本エントリーでは、Hyperledger Fabric v1.0の構成要素やトランザクション実行の流れを説明しました。 次回は実際に開発環境を準備し、ブロックチェーンを活用したアプリケーションの実装方法を確認していきます。

Red5でニコニコ動画のようなストリーミングサービス構築

環境

  • CentOS7

概要

4年ぐらい前に実装したニコニコ動画もどきが見つかったので軽くリファクタリングして公開してみる。 当時はオンライン講義での利用を想定して設計をしたため、 教育機関に属するユーザのロールに基づいたコメント公開範囲の制御や、コメントの意図に応じた強調表示などの機能がある。
※実用したわけではなくただの遊びなのでいろいろ適当な箇所が多い...。改善するモチベーションを高めるためにあえて人の目に晒すことにする。

実装

クライアントサイドはAS3で実装。 https://github.com/hiroki-sawano/comment-overlay-videogithub.com

コメントの受付と配布を行うサーバー(以降、コメントサーバー)はJavaで実装。 https://github.com/hiroki-sawano/comment-servergithub.com

ストリーミングサーバーはRed5のサンプルアプリケーション'oflaDemo'をそのまま活用した。
※前述の通り随分古い環境で扱っていたためRed5 0.9.1でしか試してない。

動作確認

CentOS上でコメントサーバーを実行する。

# java -jar commentserver.jar 
2017/06/25 08:35:09.684 INFO  - port_num : 10007 maxNumUser : 100 commentListDir : /vagrant/tmp
2017/06/25 08:35:09.696 INFO  - Comment server started

Red5も起動。

sh red5.sh

Webサーバ上に配置したFlashアプリケーションにアクセスする。
配信者画面
http://your_server_ip_address/publish.html f:id:hiroki-sawano:20170625181535p:plain

聴講者画面
http:/your_server_ip_address/subscribe.html f:id:hiroki-sawano:20170625183847p:plain

まとめ

といった感じにRed5を使うと結構簡単にストリーミングサービスが作れて面白い。
ちゃんとした説明はREADMEにいずれ書くつもり..。

実行可能なJARファイル

問題

Mavenプロジェクトで生成されたJARファイルが'java -jar'コマンドで実行できないときの話。

$ java -jar your_app.jar
no main manifest attribute, in your_app.jar

原因

your_app.jarファイルを展開してみるとMANIFEST.MFにMain-Classの指定がない。

$ cat META-INF/MANIFEST.MF 
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: xxxxxxxxxx
Build-Jdk: 1.8.0_101

JARファイルの仕様についてはこちら。 JAR File Specification

対策

このような場合には、pom.xmlに以下のプラグインを追加することで、MANIFEST.MFの設定を行う。

<plugins>      
  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>2.5.2</version>
    <configuration>
      <finalName>executable_app</finalName>
      <descriptorRefs>
        <descriptorRef>jar-with-dependencies</descriptorRef>
      </descriptorRefs>
      <appendAssemblyId>false</appendAssemblyId>
      <archive>
        <manifest>
          <mainClass>com.mypackage.MyClass</mainClass>
        </manifest>
      </archive>
    </configuration>
    <executions>
      <execution>
        <id>make-assembly</id>
        <phase>package</phase>
        <goals>
          <goal>single</goal>
        </goals>
      </execution>
    </executions>
  </plugin>
</plugins>

結果

出力されたexecutable_app.jar内のMETA-INF/MANIFEST.MFにはmainClassタグの指定に従ってMain-Classが追加されている。

$ cat MANIFEST.MF 
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: xxxxxxxxxx
Created-By: Apache Maven 3.0.5
Build-Jdk: 1.8.0_101
Main-Class: com.mypackage.MyClass

executable_app.jarはコマンドラインから次の通り実行可能となる。

$ java -jar executable_app.jar