読者です 読者をやめる 読者になる 読者になる

WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

チーム開発するエンジニアがポモドーロ・テクニックを実践するためのアイデアとツールの紹介

開発

ネイティブアプリケーション事業部サーバエンジニアの桐島です。

先日、社内エンジニアLT会にて、ポモドーロ・テクニックについて発表したところ、 「自分はこんな感じでやってるよー」という意見が出たり、気になっているひとが結構多い感じだったので、ブログにも書いてみます。

ポモドーロ・テクニックとは

Francesco Cirillo さんが、1980年代後半に考案した、時間管理法です。

小休憩を細かく取ることで生産性を向上させる、という考えがベースになっています。

ポモドーロ・テクニックのやり方

具体的な実践方法は以下となります。

  1. やることを決める
  2. 25分後に鳴るようにタイマーを設定する
  3. タイマーが鳴るまでタスクを処理する
  4. タイマーが鳴ったら、短い休憩(3〜5分)をして、1に戻る(これが1ポモドーロ)
  5. 4ポモドーロしたら、より長い休憩(15-30分)をして、1に戻る

とてもシンプルですね。

とは言うものの、ポモドーロは個人の時間管理方法であり、会社組織でのチームワークのための時間管理方法ではないので、ある日突然自分の時間だけをポモドーロ単位で区切っても上手くいきません。

そこで、自分の仕事環境に合った形で実践してみることにしました。

自分流の実践方法

  • ポモドーロ管理対象タスクを絞り込む
    • 自分だけで完了するタスク(他のひとが絡まない範囲のタスク)
    • 今日中に終われば良いタスク(xx時xx分まで、という近々の〆がない)
    • 1ポモドーロ単位(25分)で完了するタスク
  • ポモドーロができる時間帯を確保する
  • ポモドーロの休憩 = 通知系の確認時間 とする
  • 1日の最後のタスクに取り掛かっていて、良い感じに集中しているときは小休憩は不要とする

一日に回せるポモドーロ数は少なくなってしまうのですが、 しばらくこのやり方で試してみようと考えています。

ポモドーロの効果

単純な仕掛けですが、新しいポモドーロを開始すると、このタスクはこのポモドーロ内でやり切るぞ、という心理が働きます。

残り時間が迫ってくると、タスクを完了させるために自然と全力が出ます。

夏休みが終わる前日に凄まじい勢いで宿題をやり切る、あのイメージです(僕は夏休みに入る前に終わらせるタイプでしたが)。

結果として、生産性は明確に向上していると思います。

タスクを25分単位で区切るやり方は初めてなので、まだ1ポモドーロの見積りが良い感じにできていないですが、 小さな休憩と集中を繰り返す方法は自分には合っていると感じています。

ポモドーロするためのサポートツール

Pomodoro Timer on BitBar

BitBarのプラグインです。 シンプルなポモドーロタイマーで、導入も使い方もとても簡単で素晴らしいです。 ポモドーロに取り組み始めるタイミングでは丁度良いツールかと思います。

KanbanFlow

しばらくポモドーロを実践していると、その実績・効果を管理・計測したくなってきます。 それを実現してくれるのがKanbanFlowというツールです。

KanbanFlowの簡単な紹介

一見するとTrelloの様なカンバンツールなのですが、ポモドーロ管理のための機能が揃っています。

例えば、以下の様に、ポモドーロタイマーを開始することができます。

f:id:s-krsm:20161130000032p:plain

開始したポモドーロを停止すると、その理由を設定することができます。 理由の項目は自分でも追加可能です。

f:id:s-krsm:20161130000221p:plain

もちろん履歴も確認できます(ブログを書くタスクが終わらない様子が伝わってきます)。

f:id:s-krsm:20161130000710p:plain

そして、タスク単位の履歴だけでなく、任意の期間での集計も行えます。

しばらく、このツールを使ってポモドーロ・ライフを送りたいと思います!

おまけ

「ポモドーロ」という名称ですが、 Cirilloさんが、時間管理のためにトマト型のキッチンタイマーを使用していたことが由来らしいです。 (トマトはイタリア語で「pomodoro」)

GatlingでWeb APIのシグネチャ検証に対応する

Scala

サーバーエンジニアの原 @zetta1985 です。

認証済みで使用されるWeb APIの多くは、リクエストURLやパラメータをベースに生成したHMACをHTTPリクエストヘッダーに指定することで、サーバーサイドでリクエストの改ざんを検知する仕組みになっています。

応用が効かない負荷テストツールでは、この手のシグネチャ生成をサポートするのが手間な事があるのですが、 GatlingではScalaによってシグネチャ生成処理を自由に書くことができます。

Gatlingは、Java製の非同期HTTPクライアントとして有名なAsyncHttpClient を使用しています。 AsyncHttpClientはシグネチャ生成のためのフック処理をサポートしており、GatlingにおいてもAsyncHttpClientが提供しているSignatureCalculator を実装することで、リクエストの直前にシグネチャを生成し、任意のHTTPリクエストヘッダーに値を設定する事が可能となっています。

SignatureCalculator は以下のような定義になっています。

public interface SignatureCalculator {
  void calculateAndAddSignature(Request request, RequestBuilderBase<?> requestBuilder);
}

第一引数の request には、Gatling DSLで構築したURLやリクエストヘッダー/ボディが設定されています。 この request の値を使用して、シグネチャを生成し、第二引数の requestBuilder に対してシグネチャ用のHTTPリクエストヘッダーを設定することになります。

以下に、一例を示します。SignatureCalculator はsingle method interfaceなので、ここでは関数として定義します。

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import com.hunorkovacs.koauth.service.Arithmetics

object Utils {

  // シグネチャ生成用秘密鍵
  val secret = sys.env.getOrElse("API_SECRET", "your secret")

  /** シグネチャ生成およびHTTPリクエストヘッダーへの設定 */
  def generateAndAddSignature(request: Request, rb: RequestBuilderBase[_]): Unit = {
    val token = request.getHeaders().get("Authorization").replace("Bearer ", "")
    val signingKey = s"$token&$secret"

    val method = request.getMethod()
    val url = request.getUrl()

    val encodedUrl = Arithmetics.urlEncode(url)
    // getByteDataでbodyのバイト配列が取得できないためボディの文字列からバイト配列を取得
    val body = Option(request.getStringData()).map(_.getBytes()).map(new String(_)).getOrElse("")
    val encodedBody = Arithmetics.urlEncode(body)
    val baseStr = s"$method&$encodedUrl&$encodedBody".toUpperCase()

    val mac = Mac.getInstance("HmacSHA256")
    val key = new SecretKeySpec(signingKey.getBytes("utf-8"), "HmacSHA256")
    mac.init(key)
    val signature = mac.doFinal(baseStr.getBytes("utf-8"))
    // bytes to Hex
    val sigHex = signature.map("%02x" format _).mkString
    rb.addHeader("X-Signature", sigHex)
    rb.addHeader("X-Signature-Method", "HMAC-SHA256")
  }

このgenerateAndAddSignature は、以下のようにして使用します。

  val getPlayerStatus = http("player status")
    .get("/v1/players/me")
    .header("Authorization", "Bearer " + authToken)
    .signatureCalculator(generateAndAddSignature _)  // eta-expansionでメソッドを値にして、signatureCalculatorメソッドに渡す
    .check(status.in(200 to 210))

まとめ

今回は、Gatlingでのシグネチャ検証にどのように対応するかをHMACの生成の一例とともにご紹介しました。

Scalaで自由にアルゴリズムを実装できるのは、Gatlingの強みです。 負荷テスト用にシグネチャ検証をバイパスするのではなく、シグネチャ検証も含めた負荷テストを実施するように心がけましょう。

遠隔地のメンバーと一緒に開発するためにやってること

開発

今回の担当はエンジニアの大橋です。 よろしくおねがいします。

唐突ですが、まず弊社のオフィス事情から……。
現在、弊社のオフィスは東京と名古屋の2箇所にあるのですが、
プロジェクトごとに東京か名古屋かで分かれているのではなく、
ひとつのプロジェクト内でも東京勤務の人と名古屋勤務の人で分かれている状況です。
自分が今担当しているプロジェクトのクライアント開発チームのプログラマーも
東京と名古屋で分かれています。

そのため、情報共有やコミュニケーションのしづらさが、とても大きな問題としてあります。
この問題を少しでも解消できないものかとチーム内でいろいろ試しているのですが、
まだまだ解消には至らないといった状況です。

ですが、今回の記事では、問題全体の解消には至らないまでも、
現在チーム内で取り組んでいることを書かせてもらおうかと思います。

グループチャット

チームメンバー全員が参加するチャットグループを作り、メンバー間でのやりとりをしています。
以前はChatWorkを使っていましたが、現在はSlackを使っています。
https://slack.com/

けれどもグループチャットにはいくつか問題があって、
これだけではコミュニケーション不足になりがちな印象です。

・文章で伝えづらいことが多々ある
・複数の話題が同時進行するとカオス
・会話の流れとは別のことを書きにくい

要所要所の連絡には十分かもしれませんが、 それ以上のことはなかなか難しい印象です。

毎日朝会

毎朝、東京と名古屋でビデオチャットを繋ぎ、メンバー全員で朝会をしています。
これはだいたい15分くらいのもので、
スケジュールなどの共有や、各メンバーごとに昨日やったことと今日やることの報告をしています。
また、それ以外にもメンバー全員に共有したほうがいいことはこの場で話します。
ビデオチャットにはZoomを使っています。
https://zoom.us/

これによってメンバーそれぞれが今何をやっているかある程度わかりますし、
口頭なので細かいニュアンスも伝えやすいいです。
ただ、次のような問題点も感じています。

・形式的になりやすい
・個々の細かい話題まで話せない(メンバー全員でやってるので)

気楽にビデオチャット

文章で伝えづらかったり書くのが面倒だったりすることや、込み入った話しになりそうなことは、
その都度、気楽にビデオチャットするようにしています。
ビデオチャットする前に会議室押さえて準備して、っていうような時間はかけたくないので、
たいてい自分のデスクや、デスクの島からちょっと離れたところで済ませます。
それほど大人数でなければ、ノートPC付属のカメラとマイクで十分ですので。

これにもZoomを使っています。
Zoomではチャット部屋のURLを伝えれば、それを踏むことでチャットに参加できるので、
チャットしたい人にURLだけ投げておいて、その人が入ってくるのを別の作業をして待つ、
というのが個人的にはいいかな、と思ってます。

気楽にビデオチャットをすることで、
チャットグループの「文章で伝えづらいことが多々ある」というのと、
朝会の「個々の細かい話題まで話せない」というのは解消されているのではないかなと思います。

timelineでつぶやく

ビデオチャットは強力ですが、基本的には何か必要があったときにするものなので、
今あの人は何をやっているんだろう?というのは、なかなかわかりません。
かといって、みんながグループチャットにいろいろ書き込みまくっては、
グループチャットがカオスになるだけです。
ということで導入したのがtimelineです。

Slackに個々のtimeline用チャンネルを作り、
今やってることとか、作業中に発生した問題とかを、それぞれ自分のtimelineにつぶやくようにしました。
また、誰かに質問したいことがあったときは、その人のtimelineで質問したりしています。

これによって、グループチャットだけだと問題だった
「複数の話題が同時進行するとカオス」「会話の流れとは別のことを書きにくい」
が、かなり解消された気がします。

オフィスに常時接続のビデオチャットディスプレイが設置されてる

オフィス内に1台、ビデオチャットで東京と名古屋が常時接続された大きめのディスプレイが設置されてます。
相手の人がチャットなどの呼びかけに気づく時間さえも惜しいときに、
この常設ディスプレイで呼びかけれれるので、普段はあまり使わないのですが、
たまに地味に役立ちます。

共有するという意識が必要

以上のようなことをチーム内でやってみてるのですが、
情報共有やコミュニケーションに関して少しずつ改善できているとは思いつつ、
まだまだ十分できているとは言えないというのが正直なところだったりします。

まぁ、いろいろやってみる中で思うのは、
結局はどんなことも意識的に共有しようとすることが必要という、
当たり前のようなことなんですが、
今の自分自身はちゃんとできてない気がするので、反省する毎日だったりします。

よくわからない反省文みたいになってしまいましたが、何かの参考になれば幸いです。

エラーにならなくなったLINQメソッド(Unity iOS)

Unity iOS

クライアントエンジニアの加賀です。

昔は何も知らずにLINQのメソッドを使うと、iOS実機で動かした時に以下のようなエラーが出ることがありました。

System.ExecutionEngineException: Attempting to JIT compile method

だいぶ前からですが、すこしずつ改善がされてきているので、現在の状況を一度纏めてみました。
※メソッドの実行結果が正しいかは、今回確認していません。
 あくまで、実行時にエラーが起きるかどうかを確認しています。

今回確認した環境は以下の通りです。

  • 2016/11/22
  • Unity 5.4.2f2
  • Mac OS X 10.11.6
  • iOS 9.2.1

検証結果を記載しますが、Result値の意味は以下のとおりです。

Result値 意味
o エラーにならない
x エラーになる

Average

エラーの条件

  • 引数に変換関数を渡すオーバーロード
  • TSource値型
  • TSource参照型(条件によってはエラーにならない場合もあり)

現在の状況

Scripting Backend Result
Mono2x o
IL2CPP o

Max、Min、Sum

エラーの条件

  • 引数に変換関数を渡すオーバーロード
  • TSource参照型

現在の状況

Scripting Backend Method Result
Mono2x Max
Min
Sum
x
x
o
IL2CPP Max
Min
Sum
o
o
o

FirstOrDefault、Last、LastOrDefault、Single、SingleOrDefault

似たメソッドとしてFirstがありますが、こちらは以前から問題ありませんでした。

エラーの条件

  • 引数に何も渡さないオーバーロード
  • TSource値型

現在の状況

Scripting Backend Method Result
Mono2x FirstOrDefault
Last
LastOrDefault
Single
SingleOrDefault
o
o
o
o
o
IL2CPP FirstOrDefault
Last
LastOrDefault
Single
SingleOrDefault
o
o
o
o
o

ToDictionary

エラーの条件

  • 引数にFunc<TSource, TElement> elementSelector渡さないオーバーロード
  • TSource値型

現在の状況

Scripting Backend Result
Mono2x x
IL2CPP o

ToLookup

エラーの条件

  • 引数にFunc<TSource, TElement> elementSelector渡さないオーバーロード
  • TSource値型

現在の状況

Scripting Backend Result
Mono2x x
IL2CPP o

または

  • 引数にFunc<TSource, TKey> keySelectorIEqualityComparer<TKey> comparer2つを渡すオーバーロード
  • TSource参照型
  • TKey値型

現在の状況

Scripting Backend Result
Mono2x x
IL2CPP o

Join、GroupJoin

エラーの条件

  • JoinGroupJoinのすべてのオーバーロード
  • TInner(相手のストリーム型)が値型

現在の状況

Scripting Backend Method Result
Mono2x Join
GroupJoin
x
x
IL2CPP Join
GroupJoin
o
o

または

  • JoinGroupJoinのすべてのオーバーロード
  • TInner(相手のストリーム型)が参照型
  • TKey値型

現在の状況

Scripting Backend Method Result
Mono2x Join
GroupJoin
x
x
IL2CPP Join
GroupJoin
o
o

OrderBy、OrderByDescending

エラーの条件

  • TSource値型

現在の状況

Scripting Backend Method Result
Mono2x OrderBy
OrderByDescending
x
x
IL2CPP OrderBy
OrderByDescending
o
o

ThenBy、ThenByDescending

エラーの条件

  • Func<TSource, TKey> keySelecterTKey値型

現在の状況

Scripting Backend Method Result
Mono2x ThenBy
ThenByDescending
x
x
IL2CPP ThenBy
ThenByDescending
o
o

enum配列のToArray

現在の状況

Scripting Backend Result
Mono2x x
IL2CPP o

まとめ

なんと、IL2CPPで出力した場合は、記事内の全てのLINQメソッドでエラーが起きませんでした!(全オーバーロードは流石に確認できていません)
また、Mono2xでも一部のLINQメソッドで改善が見られるようです。

iOSで64bit対応が必須になり、IL2CPPが使用され始めました。
その時点で、LINQはAOTエラーと決別出来ていたようです。

これで少しは安心してLINQを使うことができそうです。

クラッシュフィーバーのキャラクター指示書ができるまで

開発

デザイナーの伊藤です。

 

前回のデザイナーの記事『キャラクターイラストができる道程』では

指示書とは“キャラクターの設計図” “キャラクターの根幹を決める部分”と紹介されていました。

今回は、そのキャラクターの指示書がどのような事を考えて作られているのか

クラッシュフィーバーのオズを参考例にしてご紹介致します!

 

オズ 

f:id:itoc:20161107231203p:plainf:id:itoc:20161107231827p:plain

 

 

先ずはキャラクターの価値を決める

とっても重要な部分で“ユーザーさまに届けたい価値”を具体的に決めます。

場合によっては“このキャラクターを通してどんな体験をして貰いたいか”も決めます。

オズは世界観の表現とユーザーさまへの訴求力の比重がだいたい半々で考えられており

仮想空間感やSF感が好きなユーザーさまに強く刺さるキャラクター

と価値を定めました。

今後全ての工程がこの価値を指針に考えられていきます。

 

 

背景設定を考える

 原作のオズはドロシーたちが探し求めている魔法使いですが

実は魔法使いではなくただのマジシャンだったというオチがあります。

 

これをクラッシュフィーバーでは

王国を築き上げたオズの実体は“電子ドラッグ”で

実態のないデータが人の形を成した存在だった

そしてアバターの視覚を操り様々な享楽をもたらしていた

 

こんな風に、先程決めた価値に合わせて

クラッシュフィーバーでのオズはどんな人物なのかを軽く考えます。

 

開発途中の設定ですので実際の設定とは異なる場合があります。

 

 

ヴィジュアル面の価値や個性を深める

ここから更にヴィジュアル面での価値や個性を深めていく段階です。

データが露出している箇所やマシン要素を入れるなど

仮想空間感やSF感が好きな人に強く刺さるような個性を持たせていきます。

 

 

キャラクターの詳細を決める

指示内容を価値や個性にそって考えていきます。

決める箇所は多岐にわたり、性格、体型、表情から服装、

武器、アイテム、キャラクターのポーズ、エフェクトなど。

 

 特に個性であるデータの腕やマシンの足が目立つようにと詳細を詰めていきます。

更に個性を出すために尖った性格にしたり

関わりのあるキャラクターの背景設定を考慮して小ネタを入れたりします。 

 

 

そして完成!

以上が決まれば指示書は完成です!

あとはイラストが完成して

クエストができるのをワクワクしながら待ちます(笑

 

 

今回は指示書がどのように作られているのか

ざっくりとご紹介しましたが如何でしたか?

指示書ではユーザの皆様に楽しんでいただけるような

キャラクター作りを心がけています。

もしも最初に決めた価値が違うものであれば

また違ったヴィジュアルのオズが出来上がっていたかもしれませんね!

スイッチロールで実現する快適な権限・リソース管理 - JAWS Festa 東海道 2016

AWS

ネイティブアプリケーション事業部サーバエンジニアの桐島です。

好きなAWSは、Amazon Auroraです。

2016年10月22日に開催された JAWS Festa 東海道 2016にて、 クラッシュフィーバーで活用しているスイッチロールのテクニックについて発表してきました。

地味だけど重要なIAM管理

発表内容は、スイッチロールの導入背景、効果、導入方法、Tipsです。

クラッシュフィーバーで遭遇した問題、そしてその解決方法をまとめました。

Aurora等に比較するとIAMというサービスは地味なイメージがありますが、IAMはセキュリティ的にも運用コスト的にも非常に"効く"サービスです。 インフラ設計の初期段階からIAM運用方法をしっかり決めておくことで、AWSリソース及び認証/承認情報を安心安全に運用できる様になります。 (ある程度の規模のシステムでは、運用途中からスイッチロール運用に切り替えるのはそれなりにコストが掛かるため、事前に設計しておくのが良いです)

IAM運用で悩まれている方のヒントになれば幸いです。

JenkinsのSlaveが原因不明で切断されるのを力技で解決する

Jenkins

R&D事業部の岩原です。

今回は、クラッシュフィーバーのJenkinsの構成と、長い間悩まされ続けていた問題の解決案をご紹介したいと思います。

クラッシュフィーバーのJenkins構成

以前書いた記事に似た構成ではありますが、
大体こんな感じになってます。

f:id:m_iwahara:20161028160827p:plain

EC2上にJenkinsマスターを建て、スレーブとして社内MacMini2台とEC2を1台ぶら下げているような構成です。
MacMiniはそれぞれiOS用とAndroid用のアプリビルド&配布用サーバーへのアップロードを行うようにし、EC2はツールのデプロイ用にしています。
よくある構成ですね。

しかし、この構成にしてからずっと悩まされ続けている問題がありました。
その問題は、題名にもありますが「社内のSlaveが原因不明で切断される」というものです。

社内Slave切断問題

運用してしばらくすると、社内のMac MiniのスレーブがConnection Resetを吐いてよく切断されてしまう事象がよく発生していました。
吐き出されるログは以下の様なものでした。

10 27, 2016 5:26:29 午後 hudson.remoting.SynchronousCommandTransport$ReaderThread run
重大: I/O error in channel channel
java.net.SocketException: Connection reset
    at java.net.SocketInputStream.read(SocketInputStream.java:209)
    at java.net.SocketInputStream.read(SocketInputStream.java:141)
    at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
    at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
    at hudson.remoting.FlightRecorderInputStream.read(FlightRecorderInputStream.java:82)
    at hudson.remoting.ChunkedInputStream.readHeader(ChunkedInputStream.java:72)
    at hudson.remoting.ChunkedInputStream.readUntilBreak(ChunkedInputStream.java:103)
    at hudson.remoting.ChunkedCommandTransport.readBlock(ChunkedCommandTransport.java:39)
    at hudson.remoting.AbstractSynchronousByteArrayCommandTransport.read(AbstractSynchronousByteArrayCommandTransport.java:34)
    at hudson.remoting.SynchronousCommandTransport$ReaderThread.run(SynchronousCommandTransport.java:48)

この状態になると、マスター側は接続していると認識しているが、スレーブ側は切断した扱いになってしまい、
ジョブのビルドが上手くいかなくなりました。
初めは、Jenkinsのバージョンが古いからか?と思いましたが、EC2側のスレーブは切断されたことが無かったので、
社内環境的な問題かと推測しましたが、未だに原因不明です。

このままではおちおち家に帰っていられないので、対処療法的に対応することにしました。

対応

Slave起動のスクリプト化

まず取り掛かったのは、Slave起動のスクリプト化です。
Slaveの処理はフォアグラウンドで動く&切断されるまで処理が戻ってこないので、それを利用して無限ループ化することにしました。
ただし、その場合再接続してもマスター側がSlave接続エラー状態になるまで「すでに接続しているよエラー」がマスターから帰ってきてしまうので、
再接続前にSlaveの切断をしてから再接続を行うようにしています。Slaveの切断はJenkins-cliを利用して行います。
また、規則性などの調査のため、Slaveの処理が終了した際はSlackへ通知をするようにしてから、再接続を行うようにしています。
こちらはシェルスクリプトで書くことにしました。
大体こんな感じです。

[JenkinsのURL]、[SSH秘密鍵へのパス]、[Slave名]、[シークレットキー]、 [SlackのInComming WebhookのURL]、[Jenkinsのログインユーザー名]、[Jenkinsのトークン]あたりは秘密情報なので伏せさせてもらいます。
Jenkinsに認証を掛けているため、Jenkins-cliではSSH秘密鍵、Jenkins WebAPIではAPIトークンを使用しています。
スクリプトと同じディレクトリに「slave.jar」、「jenkins-cli.jar」、「jenkins_slave_job_restarter.py(後述)」が存在している前提です。

#!/bin/bash
while :
do
java -jar jenkins-cli.jar -s [JenkinsのURL] -i [SSH秘密鍵へのパス] disconnect-node [Slave名]
java -jar -Dorg.jenkinsci.plugins.gitclient.Git.timeOut=120 slave.jar -jnlpUrl [JenkinsのURL]/computer/mac_slave/slave-agent.jnlp -secret [シークレットキー]
sleep 60
curl -X POST --data-urlencode 'payload={ "username": "[Slave名]", "text": "私は死にました(´・ω・`)", "icon_url": "http://mirrors.jenkins.io/art/jenkins-logo/48x48/logo.png"}' [SlackのInComming WebhookのURL]

python jenkins_slave_job_restarter.py [Jenkinsのログインユーザー名] [Jenkinsのトークン] [Slave名]

done

上記に出てくる[jenkins_slave_job_restarter.py]は後ほど紹介します。

スレーブで実行していたジョブの再実行

再接続時、Slaveで実行していたジョブは、切断されたことが検知できていないので、実行され続けてしまいます。
このままでは終了しないので、いったん該当Slaveのジョブのビルドを中断し、再度実行し直すようにします。
こちらはJenkins-cliでは出来ないので、JenkinsのWebAPIを使用します。
こちらはJsonなどを扱うのでPythonで書くことにしました。

[JenkinsのURL]は秘密情報なので伏せさせてもらいます。

# -*- coding: utf-8 -*-

import sys
import json
import urllib2
import base64

import os
import traceback

JENKINS_URL_BASE = "[JenkinsのURL]"

def start_job(user, pw, job_url):
    try:
        url = os.path.join(job_url, "../build")
        auth_header = 'Basic ' + base64.b64encode('{}:{}'.format(user, pw)).strip()
        headers = {'Authorization': auth_header}

        req = urllib2.Request(url, "\r\n" ,headers )
        urllib2.urlopen(req)
        return True
    except Exception as e:
        print(e)
        print(traceback.format_exc())
        return False

def stop_job(user, pw, job_url):
    try:
        url = os.path.join(job_url, "stop")
        auth_header = 'Basic ' + base64.b64encode('{}:{}'.format(user, pw)).strip()
        headers = {'Authorization': auth_header}

        req = urllib2.Request(url, "\r\n" ,headers )
        urllib2.urlopen(req)
        return True
    except Exception as e:
        print(e)
        print(traceback.format_exc())
        return False

def get_node_info(node_info_list_json, node_name):
    computer_list = node_info_list_json["computer"]
    node_info = [computer for computer in computer_list if computer["displayName"] == node_name]
    return node_info

def get_node_info_list(user, pw):
    url = os.path.join(JENKINS_URL_BASE, "computer/api/json?pretty=true&depth=1")
    auth_header = 'Basic ' + base64.b64encode('{}:{}'.format(user, pw)).strip()
    headers = {'Authorization': auth_header}

    req = urllib2.Request(url, "\r\n" ,headers )
    r = urllib2.urlopen(req)
    return json.loads(r.read())

def main(args):
    if len(args) < 4:
        print("引数が足りない")
        return 0
    user = args[1]
    pw = args[2]
    node_name = args[3]
    node_info_list_json = get_node_info_list(user, pw)
    node_info_json_list = get_node_info(node_info_list_json, node_name)
    if len(node_info_json_list) < 1:
        print("ノードが見つかりませんでした")
        return 0
    node_executors_list = node_info_json_list[0]["executors"]
    for executors in node_executors_list:
        if executors["currentExecutable"] is None:
            print("ビルドジョブなし")
            continue
        job_url = executors["currentExecutable"]["url"]
        ret = stop_job(user, pw, job_url)
        if not ret:
            print("ジョブの停止に失敗したわ")
            continue
        ret = start_job(user, pw, job_url)
    return 0


if __name__ == "__main__":
    args = sys.argv
    sys.exit(main(args))

ざっくり解説

Slaveで実行されているジョブを取得するためには、まずはSlaveのリストを取得する必要があります。
Slaveのリストは「computer/api/json」で取得できますが、depth=1を追加することで、さらに深い情報を取得することができ、Executor(ビルド実行状態)も合わせて取得できます。
あとは、コードを読んでいただければなんとなく理解できるかと。
なお、JenkinsのWebAPIはPython用の口もありますが、evalしてオブジェクト化するようなので、なんとなく避けました。

まとめ

上記で書いたシェルスクリプトとPythonスクリプトを組み合わせれば、SlaveがConnection Resetで突然切断されても、

Slave切断

Slaveで実行されているジョブの停止&開始

Slaveの再接続

を行うことができるようになります。

あくまでも対処療法なので、いつか原因を突き止めたいのですが、当面の間はしのげるかと思います。

ワンダープラネットでは、この問題を根本解決出来るような人やJenkinsおじさんを探してます(´・ω・`)