danglingfarpointer's memoization

仕事周りでの気付き、メモ、愚痴などを書いていきます。

バージョン番号の考え方

外(外部組織や不特定多数)に対して、ソフトウェアや文書のような継続的に見直しのかかるアーティファクトを展開していく場合、バージョン番号のルールを決めておくのが常だと思われる。

しかし、ざっと調べる限りではあるが、これがデファクト〜!と謳われているルールは見受けられない。

一方で、ある程度汎用的な考え方はあると思われるので整理してみる。

まず、バージョン番号は3つの番号の組からなる。例: 1.0.5, 2.12.128

先頭のものから順に「メジャーバージョン」、「マイナーバージョン」、「リビジョン」と呼ぶ。それぞれの意味は次の通り。

  • メジャーバージョン: 互換性のない変更を表す。例えばある機能が削除されることや変更されること(ただし単に増えることは除く)、その他大幅な変更を表す。利用者にとっては、メジャーバージョンがアップすると、今までと同じ使用方法が保証されないことを意味する。メジャーバージョンは通常1からスタートする。メジャーバージョンが0である場合は、まだ大幅な見直しのかかる可能性があることを意味する。

  • マイナーバージョン: 互換性のある変更を表す。例えば機能が単に増えることや使用の前提条件が緩和されることを意味する。利用者は、マイナーバージョンがアップしても、今までと同じ使用方法を期待してよい。

  • リビジョン: 仕様修正の伴わない軽微な変更を表す。例えば不具合修正など。メジャーとマイナーバージョンが同一ならば、通常はリビジョンがアップするほど安定的になる。

なお、内部管理用のバージョン番号については、上記の運用と必ずしも同一にする必要はないだろう。

MacからRaspberry Piのシリアルコンソールに接続

普段はSSHRaspberry Piに接続しているが、ものは試しにシリアルコンソールで接続してみた。

まずはUSB-TTLシリアル変換ケーブルを購入。Amazonで320円のものがある。

次にEl Capitan用のドライバをダウンロードしてインストール。

http://www.prolific.com.tw/US/ShowProduct.aspx?p_id=229&pcid=41

そして以下の写真のようにRaspberry PiのGPIOのピンに接続。(ただしこれはRaspberry Pi 1 Model Bの場合の接続であることに注意)

f:id:danglingfarpointer:20160619165115j:plain

  • 黒ケーブルを6番ポート(アース)
  • 白ケーブルを8番ポート(TXD; 送信信号)
  • 緑ケーブルを10番ポート(RXD; 受信信号)
  • 赤ケーブルは接続しない

接続後、Mac側でscreenコマンドでコンソールを立ち上げる。

screen /dev/tty.usbserial 115200

最後の引数はシリアル通信の転送速度を表す。ここではRaspberry Piの既定値である115200に合わせている。

Raspberry Piの電源を入れると、カーネル起動時の出力の後、めでたくログインプロンプトが出現。 f:id:danglingfarpointer:20160619220024p:plain

screenからデタッチする時はCtrl-a d

その後再びアタッチする時は、screen -lsでセッション名を確認後、-rオプションでセッション名を指定して起動する。

screen -r <ID>

Raspbianのファイルシステム自動拡張を避ける

Raspbian Jessie liteでは、初回ブート時にファイルシステムが自動で拡張されてしまい、SDカードの空き領域を全てrootファイルシステムが使ってしまう。

しかし、ある理由のため、どうしてもSDカードの空き領域を残しておきたかった。

Jessieの初回起動時には、/etc/init.d/resize2fs_onceというスクリプトが走り、その中でファイルシステムの拡張(resize2fs)を行っているようだ。(2回目以降の起動では走らないように、自分自身をrmすることもやっている)

そこで、Jessieの初回ブート前にこのスクリプトを削除するために、Jessieのイメージをマウントするところから始めた。

Mac OS Xext3をマウントするのはそれなりに面倒そうだったので、VMCentOS 6を準備。CentOSからまずはイメージのパーティション構成を確認。

$ fdisk -u -l 2016-05-10-raspbian-jessie-lite.img 
You must set cylinders.
You can do this from the extra functions menu.

Disk 2016-05-10-raspbian-jessie-lite.img: 0 MB, 0 bytes
255 heads, 63 sectors/track, 0 cylinders, total 0 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x84f9d19f

                              Device Boot      Start         End      Blocks   Id  System
2016-05-10-raspbian-jessie-lite.img1            8192      137215       64512    c  W95 FAT32 (LBA)
Partition 1 does not end on cylinder boundary.
2016-05-10-raspbian-jessie-lite.img2          137216     2707455     1285120   83  Linux
Partition 2 does not end on cylinder boundary.

Linuxのrootパーティションは2つめで、137216 * 512 = 70254592バイトから始まっているようだ。この位置からイメージをマウントするには、以下のコマンドを打つ。

$ sudo mount -oloop,offset=70254592 2016-05-10-raspbian-jessie-lite.img /mnt/jessie/

マウント後、問題のスクリプト(とそれへのシンボリックリンク)を消せばOK.

$ sudo rm /mnt/jessie/etc/rc3.d/S01resize2fs_once
$ sudo rm /mnt/jessie/etc/init.d/resize2fs_once
$ sudo umount /mnt/jessie

Raspberry PiとMacBookのブリッジ接続

Raspberry PiMacBookからSSH越しで操作する際に、MacBookとRPiをブリッジ接続すると便利な場合があります。

ブリッジ接続に必要なのは以下の2つです。

  • Macbook用の有線LANアダプタ
  • 短めのケーブル

これらさえあれば、MacBookのそばにRPiを置いておくことができ、動作確認やデバッグに便利です。

f:id:danglingfarpointer:20160613125543j:plain

MacとRPiをブリッジ接続する手順を簡単に示します。

1. インターネット共有を有効にする

システム環境設定>共有>インターネット共有にチェックを入れます。

2. RPiとMacbookを接続

RPiとMacbookをLANケーブルで接続し、Raspberry Piの電源をON.

3. RPiのIPアドレスを確認

netstatコマンドでルーティングテーブルを表示し、Network I/Fがbridge#となっているエントリを見つけます。

 $ netstat -rn -finet
Routing tables

Internet:
Destination        Gateway            Flags        Refs      Use   Netif Expire
...<略>
192.168.2          link#7             UC              2        0 bridge1
192.168.2.2        b8.27.eb.3f.5e.31  UHLWIi          3       72 bridge1   1112
192.168.2.255      ff.ff.ff.ff.ff.ff  UHLWbI          0        2 bridge1
...<略>

上記の場合、192.168.2.2が、MacbookからRPiに与えられたIPアドレスとなります。このアドレスに対してSSH接続すればOK.

 $ ssh pi@192.168.2.2

AWSのネットワークACL

ちょっとハマったのでメモ。

AWSのネットワークACLは、セキュリティグループとは異なり、ステートレスな記述である。そのため、サブネット内のインスタンスからのアウトバウンドな通信を許可する場合は、戻りの通信、つまりインバウンドな通信もきちんと許可しなければならない。具体的には、Linuxインスタンスである場合は、32768-61000に対するインバウンドトラフィックを許可する必要がある。

Linuxでは32768-61000エフェメラルポートと呼ばれ、クライアントの送信元ポート(サーバーからの受信用ポート)として一時的に使われるものである。だから、これらに対するインバウンドトラフィックを許可しないとサーバーからの戻り値を受け取れない。

Linuxの場合、エフェメラルポートの範囲は/proc/sys/net/ipv4/ip_local_port_rangeに記述されている。なお他のOSではやや範囲が異なるので注意。

一般的なソケットライブラリを扱う限り、エフェメラルポートは通常は意識しなくてよい(はず)。そのためか少しハマってしまったのでした。

PythonでSQLite3

PythonでSQLite3の使い方のメモです。

SQLite3のライブラリインポート

import sqlite3

データベースへのコネクションを取得

conn = sqlite3.connect('warehouse.db')

引数でDBファイルを指定します。

カーソルの取得

cur = conn.cursor()

コネクションからカーソルを取得します。カーソルを通してテーブルから値を取り出したり、更新したりします。

SQL文実行

cur.execute('....<SQL>...')

結果の取得

cur.fetchall()

SQLの実行結果を取得します。1つだけ取得するfetchone()もあります。

with構文でコネクションのクローズ

withの中でコネクションを生成すると、withを抜けた際に自動でコネクションをクローズしてくれて便利です。

with sqlite3.connect('warehouse.db') as conn:
    cur = conn.cursor()

サンプル

import sqlite3

class Warehouse:
    # DB初期化
    def __init__(self):
        with sqlite3.connect('warehouse.db') as conn:
            cur = conn.cursor()
            cur.execute('CREATE TABLE IF NOT EXISTS \
                             stocks(name VARCHAR(128) UNIQUE, amount INT)')

    # データ追加
    def add_stock(self, name, amount):
        with sqlite3.connect('warehouse.db') as conn:
            cur = conn.cursor()
            cur.execute('INSERT INTO stocks(name, amount) VALUES (?, ?)',
                        (name, amount))

    # データ一覧取得
    def list_stocks(self):
        with sqlite3.connect('warehouse.db') as conn:
            cur = conn.cursor()
            cur.execute('SELECT name, amount from stocks ORDER BY name')
            return cur.fetchall()

withとtryを一緒に使って、DB操作時の例外処理もわりとシンプルにかけます。

    def add_stock(self, name, amount):
        try:
            with sqlite3.connect('warehouse.db') as conn:
                cur = conn.cursor()
                cur.execute('INSERT INTO stocks(name, amount) VALUES (?, ?)',
                            (name, amount))
        except:
            # do some thing...
            raise BaseException("Already inserted: " + name)

Pythonのunittestとcoverage

PythonのUnit Testとカバレッジ計測のメモです。

Pythonの標準的なUnit Testフレームワークとして、unittestがあります。 テストの書き方はいわゆるxUnitとよく似ています。

テスト対象のサンプルクラスuser.pyです。2つのメソッドを定義しています。

class User:
    def __init__(self, name, email):
        self.__name = name
        self.__email = email

    def __str__(self):
        return '[' + self.__name + ', ' + self.__email + ']'

    def get_name(self):
        return self.__name

    def get_email(self):
        return self.__email

そしてunittestを使ったコードtest_user.pyです。Userの2つのメソッドについてテストを書いています。

import unittest
from user import User

class TestUser(unittest.TestCase):
    def setUp(self):
        self._user = User('taro', 'taro@example.com')
        
    def test_get_name(self):
        self.assertEqual('taro', self._user.get_name())

    def test_get_email(self):
        self.assertEqual('taro@example.com', self._user.get_email())

if __name__ == '__main__':
    unittest.main()

以下のようにテストを実行できます(python test_user.pyと直接実行してもOK)

 $ python -m unittest test_user

..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

次はカバレッジです。カバレッジを計測するツールとして、coverageがあります。

http://blanktar.jp/blog/2015/03/python-unittest-coverage.html

pipでインストール後、coverage runコマンドでunittestのテストケースを走らせます。

 $ coverage run test_user.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

カバレッジ測定結果が.coverageに生成されます。それをhtmlに変換して、ブラウザで見てみます。

 $ coverage html user.py
 $ open htmlcov/index.html 

ファイルごとの実行割合がまず一覧で表示されます。ここでは1ファイルしかありませんが。 f:id:danglingfarpointer:20160128212908p:plain

ファイル名をクリックすると、そのファイルで実行されなかった文が一目でわかります。いい感じです。 f:id:danglingfarpointer:20160128213025p:plain