2015年10月31日土曜日

Go で JSON を Unmarshal する

JSON を構造体に変換する方法をメモ。
package main

import (
    "encoding/json"
    "fmt"
)

type Doc map[string]string

type DataStruct struct {
    Docs []Doc
}

type Response struct {
    Data DataStruct
}

func main() {
    byt := []byte(`
        {
          "data": {
            "docs": [
              {
                "key00": "val00",
                "key01": "val01"
              },
              {
                "key10": "val10",
                "key11": "val11"
              }
            ]
          }
        }
    `)

    res := &Response{}
    if err := json.Unmarshal(byt, res); err != nil {
        panic(err)
    }
    fmt.Println(res.Data.Docs[0]["key00"])
    fmt.Println(res.Data.Docs[1]["key10"])
}
$ go run main.go
val00
val10

package main

import (
    "encoding/json"
    "fmt"
)

type Track struct {
    Artist string `json:"artist"`
    Title  string `json:"title"`
}

type Picture struct {
    Id  int    `json:"id"`
    Url string `json:"url"`
}

type Attachment struct {
    Type    string `json:"type"`
    Track   `json:"track"`
    Picture `json:"picture"`
}

type Response struct {
    Id          int `json:"id"`
    FromId      int `json:"from_id"`
    Attachments []Attachment
}

func main() {
    byt := []byte(`
        {
          "id": 1,
          "from_id": 1111,
          "attachments": [
            {
              "type": "track",
              "track": {
                "artist": "Johann",
                "title": "Air on G String"
              }
            },
            {
              "type": "picture",
              "picture": {
                "id": 2222,
                "url": "http://example.com/public/picture.jpg"
              }
            }
          ]
        }
    `)
    response := &Response{}
    if err := json.Unmarshal(byt, response); err != nil {
        panic(err)
    }
    fmt.Println(response.Attachments[0].Track.Title)
    fmt.Println(response.Attachments[1].Picture.Url)
}
$ go run main.go
Air on G String
http://example.com/public/picture.jpg

2015年9月27日日曜日

Golang の receiver の種類について

Go にはクラスがありませんが、struct type に method を設定することで同じようなことができます。method とは receiver を持つ function のことです。

receiver には2つの種類があり、1つは value, そして pointer です。
package main

import (
    "fmt"
)

type document struct {
    title  string
    author string
}

func (d document) display() {
    fmt.Printf("Title: %s, Author: %s\n",
        d.title,
        d.author,
    )
}

func (d *document) setTitle(title string) {
    d.title = title
}

func main() {
    d1 := &document{"A project report", "John"}
    d1.display()

    d2 := document{"A sales report", "Paul"}
    d2.setTitle("A business report")
    d2.display()
}
上記の場合、display() は value receiver, setTitle() は pointer receiver を持ちます。

ここで document の instance が value / pointer に関わらず、2つの method が使えているように見えるのは、Go が method の receiver に合うようにしてくれるからです。

例えば d1.display() は (*d1).display() のように呼び出してくれます。
また、d2.setTitle() は (&d2).setTitle() といった具合です。

しかし、interface を使用する場合には注意が必要です。
package main

import (
    "fmt"
)

type displayer interface {
    display()
}

type document struct {
    title  string
    author string
}

func (d *document) display() {
    fmt.Printf("Title: %s, Author: %s\n",
        d.title,
        d.author,
    )
}

func displayDocument(d displayer) {
    d.display()
}

func main() {
    d := document{"A project report", "John"}
    displayDocument(d)
}
$ go build main.go
./main.go:29: cannot use d (type document) as type displayer in argument to displayDocument:
        document does not implement displayer (display method has pointer receiver)
この場合、コンパイルがエラーになってしまいます。

d := document{"A project report", "John"}
display() が pointer receiver なのに、d が value instance だからです。


Go のドキュメントには以下のようにあります。
The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T

どの type T の method set も receiver type T で宣言された method で構成されます。 pointer type *T に対応する method set は *T もしくは T で宣言された method set です。
分かりづらいですが、value の method set は value receiver のみという制限があるんですね。pointer の method set には value receiver, pointer receiver どちらも使用できます。

d := &document{"A project report", "John"}
このように修正すると、コンパイルが通るようになります。

なぜ、このような制限があるのかというと、アドレスが常に取得できるわけではないからです。
package main

import (
        "fmt"
)

type price int

func (p *price) appendYen() string {
        return fmt.Sprintf("\u00A5%d", *p)
}

func main() {
        fmt.Println(price(1000).appendYen())
}

$ go build main.go
./main.go:14: cannot call pointer method on price(1000)
./main.go:14: cannot take the address of price(1000)

2014年1月26日日曜日

Puppet の manifests で長い行を改行する

exec resource の command などは、行が長くなってしまうことがあります。
exec { 'build-mosh':
  command => 'tar xvzf mosh-X.X.X.tar.gz && cd mosh-X.X.X && ./configure --prefix=/usr/local/mosh-X.X.X && make && make install',
  ...
}

manifests を見やすくするために、できれば1行を 80 文字程度にしたいですよね。
その場合、下記のページにあるように '\' の後に改行が続くようにすると、(少し違和感はありますが)次の行に続けることができます。


あとは、コマンドを変数に入れておいて exec から呼び出すようにすると、さらにすっきりしますね。
$build_mosh = 'tar xvzf mosh-X.X.X.tar.gz && cd mosh-X.X.X && \
  ./configure --prefix=/usr/local/mosh-X.X.X && make && make install'

exec { 'build-mosh':   command => $build_mosh,   ... }

2013年8月25日日曜日

ESXi の ローカルストレージ上のVMパフォーマンス

ホスト内部のディスクは、ESXi のドキュメントではローカルストレージと呼ばれています。


このローカルストレージ上に仮想マシンを格納した場合に、仮想マシンのパフォーマンスが低く困っていました。

具体的には、1つの仮想マシンが多くのディスク書込を実行した場合、同じホスト上にある他の仮想マシンが一時停止したような状態になってしまいます。

調べてみると、以下の Knowledge Base を見つけました。


「パフォーマンスが低くなる場合に、ホストで write-back cache が設定されているか確認してください」という内容です。

私の環境でも write-back cache が設定されていませんでした。

write-back とは、RAID の write policy の1つで、RAID コントローラに搭載されているキャッシュを使用して書込性能を向上させる仕組みです。これを使用する場合、予期しない電源断などの異常時にデータ損失を防ぐため、RAIDコントローラにバッテリバックアップが搭載されていることを確認しておく必要があります。

以下のページでは、write-back/write-through の比較、ESXi で write-back が重要になる理由が説明されており、とても参考になりました。

To compensate for write-through scenarios, a server will often use it’s own RAM to assist in the caching process.

write-through の状況を補うために、多くの場合、サーバは自身の RAM をキャッシング・プロセスを補助するために使用するでしょう。

The ESX hypervisor does not steal memory to perform this caching, and thus has to wait on disk directly when write-through is used. Remember, the hypervisor is designed to be as lightweight and non invasive as possible, and caching large writes into memory would require that the hypervisor either 1) have larger amounts of memory assigned to it, or 2) steal from the resources normally reserved for VM workloads.

ESX ハイパーバイザは、このキャッシングを実行するためにメモリを steal しません。そのため write-through が使用されている時、直接ディスクを待たなければなりません。ハイパーバイザは、可能な限り軽量・非侵襲であるようにデザインされていることを忘れないで下さい。多数の書込をメモリにキャッシュすることは、ハイパーバイザが1)それにアサインする多くのメモリを持つか、2)本来 VM のワークロードのために予約されたリソースから steal することを必要とするでしょう。

2013年7月20日土曜日

Cyberduck から S3 へアップロード時のパーミッション

Cyberduck から S3 へファイルをアップロードした際に、自動的に Everyone へ閲覧許可が設定されてしまい、Bucket Policies でアクセス制限をコントロールしたい場合に、不便に感じていました。

例えば Bucket Policies で、特定のIPアドレスからのみアクセスを許可していても、ファイルに Everyone の閲覧許可がされていると、そちらが優先されてしまいます。

アップロード時のパーミッション設定が、Cyberduck の環境設定には見つからなかったのですが、ドキュメントに説明がありました。
$ defaults write ch.sudo.cyberduck s3.bucket.acl.default private

上記の設定をすることで、Everyone への閲覧許可が自動的に設定されなくなります。

2013年1月12日土曜日

EC2 のインスタンスで chef-client が UndefinedConversionError

EC2 のインスタンスに chef-client をインストールして使っていたのですが、user-data を設定したインスタンスだけ、chef-client がエラーになってしまいます。

エラーメッセージは以下のようなもの。
FATAL: Encoding::UndefinedConversionError: "\x8B" from ASCII-8BIT to UTF-8

確認してみたところ、json への変換部分でエラーになっている模様。
lib/chef/node.rb

    # Serialize this object as a hash
    def to_json(*a)
      result = {
        "name" => name,
        "chef_environment" => chef_environment,
        'json_class' => self.class.name,
        "automatic" => automatic_attrs,
        "normal" => normal_attrs,
        "chef_type" => "node",
        "default" => default_attrs,
        "override" => override_attrs,
        #Render correctly for run_list items so malformed json does not result
        "run_list" => run_list.run_list.map { |item| item.to_s }
      }
      result["_rev"] = couchdb_rev if couchdb_rev
      result.to_json(*a)
    end
result の中身で "\x8B" が入っているところを探すと、userdata の部分にありました。
{ ... "userdata"=>"\x1F\x8B ... }

どうやら、user-data を作成する際に圧縮をしていたため、userdata にバイナリのデータが入っていたことが原因のようです。

CloudInit のページを参考に、user-data を圧縮していたのですが、16KB のサイズ制限を超えているわけでもないので、圧縮しないことで回避することにしました。

user-data の内容は、Node の Attribute に含まれていて、chef-server で Attribute を表示するためなどの目的で、json へ変換しているのですね。

2012年12月1日土曜日

Amazon Linux で起動時にホスト名を設定する

Amazon Linux で起動時にホスト名を設定したいと思い、調べてみたところ CloudInit というものがあることが分かりました。

CloudInit とは、cloud-platform(EC2 や Openstack など)の Ubuntu でインスタンスの初期化を扱うためのものです。Amazon Linux では、CloudInit が使用可能になっているんですね。

サンプルを参考に、以下の内容をインスタンス起動時に渡してみましたが、うまくいきません。

cloudinit: /doc/examples/cloud-config.txt
$ cat cloud-config.txt
--------------------------------------
#cloud-config
hostname: myhostname
--------------------------------------
$ ec2-run-instances ami-4e6cd34f -g [GROUP] -k [KEYPAIR] -t [INSTANCE_TYPE] -f cloud-config.txt

Ubuntu の AMI を使用すると、うまくホスト名が設定できるので もう少し調べてみたところ、このフォーラムにたどり着きました。

Amazon AMI w/ #cloud-config - struggling with the basics
For your question as to why setting the hostname isn't working, it is because the version of cloud-init found in the Amazon Linux AMI does not currently support those options.

今の Amazon Linux では、hostname のオプションは使えないそう。フォーラムには、別の方法での設定例があって、以下の方法でも実現できるんですね。
#cloud-config
runcmd:
 - [ echo, 'Setting custom hostname' ]
 - [ sed, -i, 's/^HOSTNAME=[a-zA-Z0-9\.\-]*$/HOSTNAME=MyName/g', /etc/sysconfig/network ]
 - [ hostname, 'MyName' ]