前置き
gophercloud とは、OpenStack プロジェクトを生み出した Rackspace 社が提供する公式の golang の SDK で、複数のクラウドプロバイダで共通の WebAPI を叩けるようになるというもの(と言っても今扱えるプロバイダは Rackspace と OpenStack しかなさそう)。
https://developer.rackspace.com/sdks/golang/
golang を使って上手く OpenStack を操作できないかな〜と思ってたところ、こういうものがあると知って一通り触ってみたので、どういう風に使うのかをまとめてみました。確かめた OpenStack のバージョンは Liberty だと思います(間違ってたら訂正します)。
参考資料
-
OpenStack の WebAPI ドキュメント: OpenStack API Complete Reference
- どの API が DEPRECATED になったか定期的に確認した方が良さそう。
-
gophercloud のホームページ
- 中身が古くなっている箇所や間違い箇所も多々見受けられるので、あまり役に立たない。
-
gophercloud パッケージの API ドキュメント
- 下位ディレクトリを辿っていくと、各クラウドプロバイダの API 仕様を確認可能。
-
gophercloud のソース
- 最新情報を確認する時はここ。
準備
gophercloud の API を使用するために、OpenStack の認証に必要な情報を環境変数に読み込んでおく必要があります(以下で説明しますが、OS_AUTH_URL
や OS_TOKEN
など)。
また、認証には不要ですが、各サービスの API を叩くためのエンドポイントのリージョン指定や、LBaaS 作成時にテナントの指定が必要なので、以下の環境変数も適宜設定(または、コード内で直接指定)する必要があります。
ただし、自ユーザーが所属する以外のテナントを指定することができるのは admin 権限を持つユーザーだけみたい。
-
OS_REGION_NAME
: リージョン名 -
OS_TENANT_ID
: テナントID -
OS_TENANT_NAME
: テナント名
認証方法
以下は、環境変数から認証に必要な情報をオプション (opts) として読み込み、そのオプションを元に認証するだけのサンプルコードを示します (pc は ProviderClient の略)。この処理を実施した以降は API が叩けるようになります。
認証するために必要な環境変数の設定は以下3パターンです。どのパターンでも認証することが可能です。
- パターン1
-
OS_AUTH_URL
: 認証を行うURL、かつ -
OS_TOKEN
: トークン
-
- パターン2
-
OS_AUTH_URL
: 認証を行うURL、かつ -
OS_USERNAME
: ユーザー名、かつ -
OS_PASSWORD
: パスワード
-
- パターン3
-
OS_AUTH_URL
: 認証を行うURL、かつ -
OS_USERID
: ユーザーID、かつ -
OS_PASSWORD
: パスワード
-
gophercloud.AuthOptions
構造体のメンバとの対応関係は以下の通りです。直接 AuthOptions
構造体を作成し、メンバに値を埋め込んでオプション情報を作成することはできますが、シークレット情報 (パスワードやToken) はシークレットストアを経由して渡すようにしましょう。
メンバ変数 | 環境変数 |
---|---|
IdentityEndpoint |
OS_AUTH_URL |
UserID |
OS_USERID |
Username |
OS_USERNAME |
Password |
OS_PASSWORD |
package main
import (
"fmt"
"github.com/rackspace/gophercloud/openstack"
)
func main() {
opts, err := openstack.AuthOptionsFromEnv()
if err != nil {
fmt.Println(err)
return
}
pc, err := openstack.AuthenticatedClient(opts)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(pc)
}
flavor に関する操作
API 仕様は以下URLです (WebAPI のドキュメントはこちら) 。
https://godoc.org/github.com/rackspace/gophercloud/openstack/compute/v2/flavors
基本的に認証済みの ProviderClient を用いて ServiceClient (sc) を作成し、それに対して WebAPI が発行されるイメージとなります。
ListDetail()
以下に flavor のリストを出力するサンプルコードを示します。
import "os"
import "github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
import "github.com/rackspace/gophercloud/pagination"
...(省略)...
sc, err := openstack.NewComputeV2(pc, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
if err != nil {
fmt.Println(err)
return
}
pager := flavors.ListDetail(sc, nil)
pager.EachPage(func(page pagination.Page) (bool, error) {
flavorList, err := flavors.ExtractFlavors(page)
if err != nil {
fmt.Println(err)
return false, err
}
for _, f := range flavorList {
fmt.Println(f.ID, f.Name)
}
return true, nil
})
Get()
flavor の名前からIDを引いて Flavor
構造体を GET するなら以下の通り。
flavor_name := "m1.medium"
flavor_id, err := flavors.IDFromName(sc, flavor_name)
if err != nil {
fmt.Println(err)
return
}
f1, err := flavors.Get(sc, flavor_id).Extract()
if err != nil {
fmt.Println(err)
return
}
image に関する操作
OpenStack の Glance に該当する部分ですね。
API 仕様は以下URLです (WebAPI のドキュメントはこちら) 。ただ、members は登録したイメージに対してアクセスできるメンバーを細かく管理するためのもので、細かく image の管理をしたいという用途以外あまり必要そうでないので、今回は割愛させていただきます。
https://godoc.org/github.com/rackspace/gophercloud/openstack/imageservice/v2/images
https://godoc.org/github.com/rackspace/gophercloud/openstack/imageservice/v2/members
gophercloud/openstack/compute/v2/images については、Compute API のドキュメントを確認すると DEPRECATED となっているので使うべきではないでしょう。
List()
以下に、image のリストを出力するサンプルコードを示します。ServiceClient (sc) は imageservice 用の API (NewImageServiceV2) から取得します。
import "github.com/rackspace/gophercloud/openstack/imageservice/v2/images"
import "github.com/rackspace/gophercloud/pagination"
...(省略)...
sc, err := openstack.NewImageServiceV2(pc, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
if err != nil {
fmt.Println(err)
return
}
listopts := images.ListOpts{}
pager := images.List(sc, listopts)
pager.EachPage(func(page pagination.Page) (bool, error) {
imageList, err := images.ExtractImages(page)
if err != nil {
fmt.Println(err)
return false, err
}
for _, i := range imageList {
fmt.Println(i)
}
return true, nil
})
Create()
, Upload()
新しいイメージを作成してアップロードするサンプルは以下の通りです。アップロード元のイメージは、例えば CoreOS を使用するならばこういうところを参考にして事前にダウンロード・解凍しておくとよいでしょう。
import "time"
...(省略)...
copts := images.CreateOpts{
Name: "CoreOS-1068.8.0-Stable",
ContainerFormat: "bare",
DiskFormat: "qcow2",
}
image, err := images.Create(sc, copts).Extract()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(image)
id := image.ID
file, err := os.Open("./coreos_production_openstack_image.img")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
fmt.Println("Start Uploading image")
go func() {
images.Upload(sc, id, file)
}()
start := time.Now()
for {
img, err := images.Get(sc, id).Extract()
if err != nil {
fmt.Println(err)
return
}
if img.Status == "active" {
fmt.Println("Uploading image is completed!")
break
}
fmt.Println("... waiting")
time.Sleep(5 * time.Second)
if time.Now().Sub(start) > (3 * time.Minute) {
fmt.Println("Uploading image is not completed in 3 minutes.")
break
}
}
Delete()
イメージ削除のサンプルは以下の通りです。List()
で削除したいイメージにマッチするものを検索して Delete()
するだけの処理となっていますが、本当に削除して良いか確認する処理を入れた方が良いと思います。
listopts := images.ListOpts{Name: "CoreOS-1068.8.0-Stable"}
pager := images.List(sc, listopts)
pager.EachPage(func(page pagination.Page) (bool, error) {
imageList, err := images.ExtractImages(page)
if err != nil {
fmt.Println(err)
return false, err
}
for _, i := range imageList {
fmt.Println(i.ID, i.Name)
result := images.Delete(sc, i.ID)
if result.Err != nil {
fmt.Println(err)
return false, err
}
fmt.Println("Deleting image is completed!")
}
return true, nil
})
server (VM) に関する操作
こちらは主に Nova に対応する部分です。API 仕様は以下URLです (WebAPI のドキュメントはこちら) 。
https://godoc.org/github.com/rackspace/gophercloud/openstack/compute/v2/servers
List()
以下に、server のリストを出力するサンプルコードを示します。ServiceClient (sc) は compute 用の API (NewComputeV2) から取得します。
import "os"
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
import "github.com/rackspace/gophercloud/pagination"
・・・(省略)・・・
sc, err := openstack.NewComputeV2(pc, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
if err != nil {
fmt.Println(err)
return
}
pager := servers.List(sc, nil)
pager.EachPage(func(page pagination.Page) (bool, error) {
serverList, err := servers.ExtractServers(page)
if err != nil {
fmt.Println(err)
return false, err
}
for _, s := range serverList {
fmt.Println(s.ID, s.Name, s.Status)
}
return true, nil
})
Create()
, Delete()
server の作成例は以下の通りです。
terraform
コマンドを使ってマシンを作成するのと同様に、flavor名、OSイメージ名などを指定して作成します。この例ではキーペアを作成済みである場合に server 作成を中止していますが、通常は既存のキーペアをそのまま用いると思います。また、キーペア作成時の秘密鍵をファイル test-private-key
に出力していますが、これは例えば Vault などのシークレットストアに保存して使用する時だけ取り出すようにした方が良いでしょう。
server 作成時のキーペアの指定は optional であり、servers.CreateOpts
には含まれていない extension のフィールドであることに注意してください。keypairs.CreateOptsExt
構造体の KeyName
にキーペア名を入れる必要があります。
userdata については、yaml 形式のファイル sample_userdata.yml
の中身を []byte
型にして読み込むことで処理しています。userdata は、API が POST を実行する際に Base64 でエンコードされます。
server のステータスの種類と各 action に対するステータスの遷移についてはここを確認してください。以下の例では、ステータスが ACTIVE になるまで待ち合わせていますが、他の条件も合わせて待ちたいならば gophercloud.WaitFor()
を用いて、自分で待ち合わせ条件を作ることもできます。
import "io/ioutil"
import "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
・・・(省略)・・・
kopts := keypairs.CreateOpts{
Name: "test-key",
}
stored_kp, _ := keypairs.Get(sc, kopts.Name).Extract()
if stored_kp != nil {
fmt.Println("Already created keypair:", kopts.Name)
return
}
kp, err := keypairs.Create(sc, kopts).Extract()
if err != nil {
fmt.Println(err)
return
}
err = ioutil.WriteFile("./test-private-key", []byte(kp.PrivateKey), 0400)
if err != nil {
fmt.Println(err)
return
}
userdata, err := ioutil.ReadFile("./sample_userdata.yml")
if err != nil {
fmt.Println(err)
return
}
copts := servers.CreateOpts{
Name: "test.server.sandbox.pluto",
ImageName: "CoreOS-899.17.0-Stable",
FlavorName: "m1.medium",
SecurityGroups: []string{"all"},
UserData: userdata,
Networks: []servers.Network{{UUID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}},
}
coptsext := keypairs.CreateOptsExt{
copts,
kp.Name,
}
server, err := servers.Create(sc, coptsext).Extract()
if err != nil {
fmt.Println(err)
return
}
sid := server.ID
err = servers.WaitForStatus(sc, sid, "ACTIVE", 60)
if err != nil {
fmt.Println(err)
return
}
server, err = servers.Get(sc, sid).Extract()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(server)
server の削除は、作成した server の ID を servers.Delete()
の引数として入れるだけです。返り値として DeleteResult
型が返されるので、ExtractErr()
を使えばエラーの中身を確認できます。
Start()
, Stop()
以下に、server を起動するサンプルコードを示します。存在する server のIDを取得して、起動をかけます。停止処理をする場合は、startstop.Start()
を startstop.Stop()
に変更するだけです。
server の action 操作で起動・停止だけ compute/v2/extensions/startstop
のパッケージに分かれているので注意してください。
import "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop"
・・・(省略)・・・
sid, err := servers.IDFromName(sc, "test-server")
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Start machine:", sid)
err = startstop.Start(sc, sid).ExtractErr()
if err != nil {
fmt.Println(err)
return
}
その他の action
Reboot()
ソフトリブートするかハードリブートするかは Reboot()
の第三引数に RebootMethod
により指定可能です。以下の例ではソフトリブート servers.OSReboot
を指定しています。
sid, err := servers.IDFromName(sc, "test.server")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println("Reboot server:", sid)
err = servers.Reboot(sc, sid, servers.OSReboot).ExtractErr()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = servers.WaitForStatus(sc, sid, "ACTIVE", 300)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
server, err := servers.Get(sc, sid).Extract()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println("server:", server)
Resize()
servers.Resize()
でリサイズ出来るはずだと思ってましたが、API を発行したところHTTP 403 が返ってきました。以下のような感じです。
Expected HTTP response code [201 202] when accessing [POST http://省略], but got 403 instead
{"forbidden": {"message": "Policy doesn't allow os_compute_api:servers:resize to be performed.", "code": 403}}
自分の環境上で Horizon からインスタンスのリサイズを実行したところ、「リサイズを予約しました」と表示されるも実際はリサイズされない結果となったので、gophercloud でやっても同じ結果となるのは当然かなと思います。まぁ、できるようになったらまたトライしてみようかな。
ForceDelete()
ある server に対してどんな操作をしてもエラーになるなど、どうしようもなくなった時に強制削除を使用するケースがあると思いますので、そういう時はこれを使いましょう。
sid, err := servers.IDFromName(sc, "test.server")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = servers.ForceDelete(sc, sid).ExtractErr()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
baremetal に関する操作
WebAPI のドキュメントはこちらですが、これらの API は Mitaka 5.1.0 から追加されたようです。
もし、ベアメタルを扱っていても、認証先のURLをベアメタルを扱っているところに向ければ、Nova (compute) と同じ API でも操作可能なようです。分かり次第、また追記したいと思います。
LBaaS に関する操作
ここは、Neutron に該当する部分ですね。API 仕様は以下URLです (WebAPI のドキュメントはこちら) 。
https://godoc.org/github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2
以下では、LB として Octavia を主に使用しています。
Loadbalancer
List()
以下に、loadbalancer のリストを出力するサンプルコードを示します。ServiceClient (sc) は network 用の API (NewNetworkV2) から取得します。
import "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers"
・・・(省略)・・・
sc, err := openstack.NewNetworkV2(pc, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
if err != nil {
fmt.Println(err)
return
}
pager := loadbalancers.List(sc, nil)
pager.EachPage(func(page pagination.Page) (bool, error) {
lbList, err := loadbalancers.ExtractLoadbalancers(page)
if err != nil {
fmt.Println(err)
return false, err
}
for _, lb := range lbList {
fmt.Println(lb)
}
return true, nil
})
Create()
, Delete()
以下に、loadbalancer を作成するサンプルコードを示します。loadbalancer には WaitForStatus()
が存在しないので、gophercloud.WaitFor()
を使用してステータスが ACTIVE になるのを監視しています(おそらく、LoadBalancer
型に ProvisioningStatus
と OperatingStatus
の2種類のステータスがあるため、わざと作ってないように思います)。
copts := loadbalancers.CreateOpts{
Name: "test.lb.gophercloud",
VipSubnetID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
TenantID: os.Getenv("OS_TENANT_ID"),
AdminStateUp: loadbalancers.Up,
Provider: "octavia",
}
loadbalancer, err := loadbalancers.Create(sc, copts).Extract()
if err != nil {
fmt.Println(err)
return
}
lbID := loadbalancer.ID
gophercloud.WaitFor(120, func() (bool, error) {
lb, err := loadbalancers.Get(sc, lbID).Extract()
if err != nil {
return false, err
}
if lb.ProvisioningStatus != "ACTIVE" {
return false, nil
}
return true, nil
})
lb1, err := loadbalancers.Get(sc, lbID).Extract()
if err != nil {
fmt.Println(err)
return
}
fmt.Println("loadbalancer:", lb1)
loadbalancer の削除は、作成した loadbalancer の ID を loadbalancers.Delete()
の引数として入れるだけです。返り値として DeleteResult
型が返されるので、ExtractErr()
を使えばエラーの中身を確認できます。
Listener
List()
以下に、listener のリストを出力するサンプルコードを示します。
import "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/listeners"
import "github.com/rackspace/gophercloud/pagination"
・・・(省略)・・・
lbID := "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
lopts := listeners.ListOpts{
LoadbalancerID: lbID,
}
pager := listeners.List(sc, lopts)
pager.EachPage(func(page pagination.Page) (bool, error) {
listenerList, err := listeners.ExtractListeners(page)
if err != nil {
fmt.Println(err)
return false, err
}
for _, l := range listenerList {
fmt.Println(l)
}
return true, nil
})
Create()
, Delete()
以下に、listener を作成するサンプルコードを示します。作成時は loadbalancer のステータスを確認しておく方が良いと思います。
import "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers"
・・・(省略)・・・
lbID := "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
http_copts := listeners.CreateOpts{
Protocol: listeners.ProtocolHTTP,
ProtocolPort: 80,
TenantID: os.Getenv("OS_TENANT_ID"),
LoadbalancerID: lbID,
Name: "test.listener.http",
}
http_listener, err := listeners.Create(sc, http_copts).Extract()
if err != nil {
fmt.Println(err)
return
}
gophercloud.WaitFor(120, func() (bool, error) {
lb, err := loadbalancers.Get(sc, lbID).Extract()
if err != nil {
return false, err
}
if lb.ProvisioningStatus != "ACTIVE" {
return false, nil
}
return true, nil
})
https_copts := listeners.CreateOpts{
Protocol: listeners.ProtocolHTTPS,
ProtocolPort: 443,
TenantID: os.Getenv("OS_TENANT_ID"),
LoadbalancerID: lbID,
Name: "test.listener.https",
}
https_listener, err := listeners.Create(sc, https_copts).Extract()
if err != nil {
fmt.Println(err)
return
}
gophercloud.WaitFor(120, func() (bool, error) {
lb, err := loadbalancers.Get(sc, lbID).Extract()
if err != nil {
return false, err
}
if lb.ProvisioningStatus != "ACTIVE" {
return false, nil
}
return true, nil
})
listener の削除は、作成した listener の ID を listeners.Delete()
の引数として入れるだけです。返り値として DeleteResult
型が返されるので、ExtractErr()
を使えばエラーの中身を確認できます。
Pool
List()
以下に、pool のリストを出力するサンプルコードを示します。
import "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
import "github.com/rackspace/gophercloud/pagination"
・・・(省略)・・・
lbID := "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
lopts := pools.ListOpts{
LoadbalancerID: lbID,
}
pager := pools.List(sc, lopts)
pager.EachPage(func(page pagination.Page) (bool, error) {
poolList, err := pools.ExtractPools(page)
if err != nil {
fmt.Println(err)
return false, err
}
for _, p := range poolList {
fmt.Println(p)
}
return true, nil
})
Create()
, Delete()
以下に、pool を作成するサンプルコードを示します。作成時は loadbalancer のステータスを確認しておく方が良いと思います(以下の例では、listener や loadbalancer の ID (listenerID
, lbID
) は仮の値を入れてます)。
import "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas_v2/loadbalancers"
・・・(省略)・・・
listenerID := "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
copts := pools.CreateOpts{
LBMethod: pools.LBMethodRoundRobin,
Protocol: pools.ProtocolHTTPS,
ListenerID: listenerID,
Name: "test.pool.https",
}
pool, err := pools.Create(sc, copts).Extract()
if err != nil {
fmt.Println(err)
return
}
lbID := "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
gophercloud.WaitFor(120, func() (bool, error) {
lb, err := loadbalancers.Get(sc, lbID).Extract()
if err != nil {
return false, err
}
if lb.ProvisioningStatus != "ACTIVE" {
return false, nil
}
return true, nil
})
p1, err := pools.Get(sc, pool.ID).Extract()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(p1)
pool の削除は、作成した pool の ID を pools.Delete()
の引数として入れるだけです。返り値として DeleteResult
型が返されるので、ExtractErr()
を使えばエラーの中身を確認できます。
CreateAssociateMember()
, DeleteMember()
作成した pool に member を追加するサンプルコードを以下に示します。
ソースを見ると、SubnetID
を指定しない場合は loadbalancer の vip_subnet_id
を使用すると記載されてますが、実際に指定しない場合はエラーが返ってきました ({"NeutronError": {"message": "Failed to parse request. Required attribute 'subnet_id' not specified", "type": "HTTPBadRequest", "detail": ""}}
)。そのため、使用するサブネットの ID は指定しないとダメみたいですね。
poolID := "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
mcopts := pools.MemberCreateOpts{
Address: "192.168.20.12",
ProtocolPort: 80,
SubnetID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
}
member, err := pools.CreateAssociateMember(sc, poolID, mcopts).Extract()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(member)
member の削除は、作成した member の ID と追加した pool の ID を pools.DeleteMember()
の引数として入れるだけです。返り値として DeleteResult
型が返されるので、ExtractErr()
を使えばエラーの中身を確認できます。
ブロックストレージに関する操作
ここは、Cinder に該当する処理です。API 仕様は以下URLです (WebAPI のドキュメントはこちら) 。
https://godoc.org/github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes
https://godoc.org/github.com/rackspace/gophercloud/openstack/blockstorage/v2/extensions/volumeactions
ただし、作成した volume を server にアタッチ・デタッチする操作は volumeattach パッケージを使用してください。現状、volumeactions パッケージでは正しく処理が完了しないようです1 2。
Block Storage API v1 の方は DEPRECATED になっていますので、v1 系の API は使わない方が良いでしょう。
List()
以下に、volume のリストを出力するサンプルコードを示します。ServiceClient (sc) は volume 用の API (NewBlockStrageV2) から取得します。
import "github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes"
import "github.com/rackspace/gophercloud/pagination"
・・・(省略)・・・
sc, err := openstack.NewBlockStorageV2(sp, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
if err != nil {
fmt.Println(err)
return
}
pager := volumes.List(sc, nil)
pager.EachPage(func(page pagination.Page) (bool, error) {
volumeList, err := volumes.ExtractVolumes(page)
if err != nil {
fmt.Println(err)
return false, err
}
for _, v := range volumeList {
fmt.Println(v)
}
return true, nil
})
Create()
, Delete()
以下に、volume を作成するサンプルコードを示します。例では作成した volume のステータスが利用可能状態 (available) になるまで10秒待ち合わせる処理を入れてます。ステータスの種類はここで確認してください。ステータスは文字列で扱うことに注意ください。
copts := volumes.CreateOpts{
Description: "Test for gophercloud",
Name: "test.volume",
Size: 5,
VolumeType: "NetappSATA",
}
volume, err := volumes.Create(sc, copts).Extract()
if err != nil {
fmt.Println(err)
return
}
err = volumes.WaitForStatus(sc, volume.ID, "available", 10)
if err != nil {
fmt.Println(err)
return
}
volume, err = volumes.Get(sc, volume.ID).Extract()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(volume)
volume の削除は、作成した volume の ID を volumes.Delete()
の引数として入れるだけです。返り値として DeleteResult
型が返されるので、ExtractErr()
を使えばエラーの中身を確認できます。
volumeattach.Create()
, volumeattach.Delete()
以下に、volume を attach/detach するサンプルコードを示します (volume と server の ID は仮の値としているので、そのまま使用しないでください)。ここでは volumeattach パッケージを使用するので、ServiceClient (sc) は NewComputeV2
で取得しています。
volumeattach.Create()
時に返り値として受け取った VolumeAttachment
型の ID メンバ の値は (現時点は) VolumeID と同じ値が入っているようです。
import "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
・・・(省略)・・・
sc, err := openstack.NewComputeV2(pc, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
if err != nil {
fmt.Println(err)
return
}
volumeID := "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
serverID := "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
// Attach volume
copts := volumeattach.CreateOpts{
Device: "/dev/vdb",
VolumeID: volumeID,
}
volat, err := volumeattach.Create(sc, serverID, copts).Extract()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(volat)
// Detach volume
err = volumeattach.Delete(sc, serverId, volat.ID).ExtractErr()
if err != nil {
fmt.Println(err)
return
}
-
volumeactions パッケージを使用してみましたが、アタッチの API を叩いてみると volume のステータスはアタッチされた状態になっているのに、server のステータスは何もアタッチしていない状態のままになっていました。リクエストの body や POST する URL は WebAPI ドキュメント通りであることは確認したので、gophercloud には問題ないと思います。おそらく、現状の OpenStack 側に問題があり、バージョンアップで対応される可能性があります。 ↩
-
volumeactions パッケージでは
AttachOpts
型でアタッチ先のサーバを指定することができ、アタッチ先が VM であればインスタンス ID をAttachOpts.InstanceUUID
に指定、アタッチ先がベアメタルであればホスト名をAttachOpts.HostName
に指定する仕様になっています (これらの指定は排他)。今後このパッケージを使えるようになれば、Cinder で作成した volume をベアメタルにアタッチする処理も可能になってくるのではと考えられます。 ↩