1
// goose/nova - Go package to interact with OpenStack Compute (Nova) API.
2
// See http://docs.openstack.org/api/openstack-compute/2/content/.
12
"gopkg.in/goose.v1/client"
13
"gopkg.in/goose.v1/errors"
14
goosehttp "gopkg.in/goose.v1/http"
19
apiFlavors = "flavors"
20
apiFlavorsDetail = "flavors/detail"
21
apiServers = "servers"
22
apiServersDetail = "servers/detail"
23
apiSecurityGroups = "os-security-groups"
24
apiSecurityGroupRules = "os-security-group-rules"
25
apiFloatingIPs = "os-floating-ips"
26
apiAvailabilityZone = "os-availability-zone"
27
apiVolumeAttachments = "os-volume_attachments"
30
// Server status values.
32
StatusActive = "ACTIVE" // The server is active.
33
StatusBuild = "BUILD" // The server has not finished the original build process.
34
StatusBuildSpawning = "BUILD(spawning)" // The server has not finished the original build process but networking works (HP Cloud specific)
35
StatusDeleted = "DELETED" // The server is deleted.
36
StatusError = "ERROR" // The server is in error.
37
StatusHardReboot = "HARD_REBOOT" // The server is hard rebooting.
38
StatusPassword = "PASSWORD" // The password is being reset on the server.
39
StatusReboot = "REBOOT" // The server is in a soft reboot state.
40
StatusRebuild = "REBUILD" // The server is currently being rebuilt from an image.
41
StatusRescue = "RESCUE" // The server is in rescue mode.
42
StatusResize = "RESIZE" // Server is performing the differential copy of data that changed during its initial copy.
43
StatusShutoff = "SHUTOFF" // The virtual machine (VM) was powered down by the user, but not through the OpenStack Compute API.
44
StatusSuspended = "SUSPENDED" // The server is suspended, either by request or necessity.
45
StatusUnknown = "UNKNOWN" // The state of the server is unknown. Contact your cloud provider.
46
StatusVerifyResize = "VERIFY_RESIZE" // System is awaiting confirmation that the server is operational after a move or resize.
51
FilterStatus = "status" // The server status. See Server Status Values.
52
FilterImage = "image" // The image reference specified as an ID or full URL.
53
FilterFlavor = "flavor" // The flavor reference specified as an ID or full URL.
54
FilterServer = "name" // The server name.
55
FilterMarker = "marker" // The ID of the last item in the previous list.
56
FilterLimit = "limit" // The page size.
57
FilterChangesSince = "changes-since" // The changes-since time. The list contains servers that have been deleted since the changes-since time.
60
// Client provides a means to access the OpenStack Compute Service.
65
// New creates a new Client.
66
func New(client client.Client) *Client {
67
return &Client{client}
70
// ----------------------------------------------------------------------------
73
// Filter builds filtering parameters to be used in an OpenStack query which supports
74
// filtering. For example:
76
// filter := NewFilter()
77
// filter.Set(nova.FilterServer, "server_name")
78
// filter.Set(nova.FilterStatus, nova.StatusBuild)
79
// resp, err := nova.ListServers(filter)
85
// NewFilter creates a new Filter.
86
func NewFilter() *Filter {
87
return &Filter{make(url.Values)}
90
func (f *Filter) Set(filter, value string) {
91
f.v.Set(filter, value)
94
// Link describes a link to a flavor or server.
101
// Entity describe a basic information about a flavor or server.
104
UUID string `json:"uuid"`
105
Links []Link `json:"links"`
106
Name string `json:"name"`
109
func stringValue(item interface{}, attr string) string {
110
return reflect.ValueOf(item).FieldByName(attr).String()
113
// Allow Entity slices to be sorted by named attribute.
114
type EntitySortBy struct {
119
func (e EntitySortBy) Len() int {
120
return len(e.Entities)
123
func (e EntitySortBy) Less(i, j int) bool {
124
return stringValue(e.Entities[i], e.Attr) < stringValue(e.Entities[j], e.Attr)
127
func (e EntitySortBy) Swap(i, j int) {
128
e.Entities[i], e.Entities[j] = e.Entities[j], e.Entities[i]
131
// ListFlavours lists IDs, names, and links for available flavors.
132
func (c *Client) ListFlavors() ([]Entity, error) {
136
requestData := goosehttp.RequestData{RespValue: &resp}
137
err := c.client.SendRequest(client.GET, "compute", apiFlavors, &requestData)
139
return nil, errors.Newf(err, "failed to get list of flavours")
141
return resp.Flavors, nil
144
// FlavorDetail describes detailed information about a flavor.
145
type FlavorDetail struct {
147
RAM int // Available RAM, in MB
148
VCPUs int // Number of virtual CPU (cores)
149
Disk int // Available root partition space, in GB
154
// Allow FlavorDetail slices to be sorted by named attribute.
155
type FlavorDetailSortBy struct {
157
FlavorDetails []FlavorDetail
160
func (e FlavorDetailSortBy) Len() int {
161
return len(e.FlavorDetails)
164
func (e FlavorDetailSortBy) Less(i, j int) bool {
165
return stringValue(e.FlavorDetails[i], e.Attr) < stringValue(e.FlavorDetails[j], e.Attr)
168
func (e FlavorDetailSortBy) Swap(i, j int) {
169
e.FlavorDetails[i], e.FlavorDetails[j] = e.FlavorDetails[j], e.FlavorDetails[i]
172
// ListFlavorsDetail lists all details for available flavors.
173
func (c *Client) ListFlavorsDetail() ([]FlavorDetail, error) {
175
Flavors []FlavorDetail
177
requestData := goosehttp.RequestData{RespValue: &resp}
178
err := c.client.SendRequest(client.GET, "compute", apiFlavorsDetail, &requestData)
180
return nil, errors.Newf(err, "failed to get list of flavour details")
182
return resp.Flavors, nil
185
// ListServers lists IDs, names, and links for all servers.
186
func (c *Client) ListServers(filter *Filter) ([]Entity, error) {
190
var params *url.Values
194
requestData := goosehttp.RequestData{RespValue: &resp, Params: params, ExpectedStatus: []int{http.StatusOK}}
195
err := c.client.SendRequest(client.GET, "compute", apiServers, &requestData)
197
return nil, errors.Newf(err, "failed to get list of servers")
199
return resp.Servers, nil
202
// IPAddress describes a single IPv4/6 address of a server.
203
type IPAddress struct {
204
Version int `json:"version"`
205
Address string `json:"addr"`
208
// ServerDetail describes a server in more detail.
209
// See: http://docs.openstack.org/api/openstack-compute/2/content/Extensions-d1e1444.html#ServersCBSJ
210
type ServerDetail struct {
211
// AddressIPv4 and AddressIPv6 hold the first public IPv4 or IPv6
212
// address of the server, or "" if no floating IP is assigned.
216
// Addresses holds the list of all IP addresses assigned to this
217
// server, grouped by "network" name ("public", "private" or a
219
Addresses map[string][]IPAddress
221
// Created holds the creation timestamp of the server
222
// in RFC3339 format.
232
Metadata map[string]string
234
// HP Cloud returns security groups in server details.
235
Groups []Entity `json:"security_groups"`
237
// Progress holds the completion percentage of
238
// the current operation
241
// Status holds the current status of the server,
242
// one of the Status* constants.
245
TenantId string `json:"tenant_id"`
247
// Updated holds the timestamp of the last update
248
// to the server in RFC3339 format.
251
UserId string `json:"user_id"`
253
AvailabilityZone string `json:"OS-EXT-AZ:availability_zone"`
256
// ListServersDetail lists all details for available servers.
257
func (c *Client) ListServersDetail(filter *Filter) ([]ServerDetail, error) {
259
Servers []ServerDetail
261
var params *url.Values
265
requestData := goosehttp.RequestData{RespValue: &resp, Params: params}
266
err := c.client.SendRequest(client.GET, "compute", apiServersDetail, &requestData)
268
return nil, errors.Newf(err, "failed to get list of server details")
270
return resp.Servers, nil
273
// GetServer lists details for the specified server.
274
func (c *Client) GetServer(serverId string) (*ServerDetail, error) {
278
url := fmt.Sprintf("%s/%s", apiServers, serverId)
279
requestData := goosehttp.RequestData{RespValue: &resp}
280
err := c.client.SendRequest(client.GET, "compute", url, &requestData)
282
return nil, errors.Newf(err, "failed to get details for serverId: %s", serverId)
284
return &resp.Server, nil
287
// DeleteServer terminates the specified server.
288
func (c *Client) DeleteServer(serverId string) error {
292
url := fmt.Sprintf("%s/%s", apiServers, serverId)
293
requestData := goosehttp.RequestData{RespValue: &resp, ExpectedStatus: []int{http.StatusNoContent}}
294
err := c.client.SendRequest(client.DELETE, "compute", url, &requestData)
296
err = errors.Newf(err, "failed to delete server with serverId: %s", serverId)
301
type SecurityGroupName struct {
302
Name string `json:"name"`
305
// ServerNetworks sets what networks a server should be connected to on boot.
306
// - FixedIp may be supplied only when NetworkId is also given.
307
// - PortId may be supplied only if neither NetworkId or FixedIp is set.
308
type ServerNetworks struct {
309
NetworkId string `json:"uuid,omitempty"`
310
FixedIp string `json:"fixed_ip,omitempty"`
311
PortId string `json:"port,omitempty"`
314
// RunServerOpts defines required and optional arguments for RunServer().
315
type RunServerOpts struct {
316
Name string `json:"name"` // Required
317
FlavorId string `json:"flavorRef"` // Required
318
ImageId string `json:"imageRef"` // Required
319
UserData []byte `json:"user_data"` // Optional
320
SecurityGroupNames []SecurityGroupName `json:"security_groups"` // Optional
321
Networks []ServerNetworks `json:"networks"` // Optional
322
AvailabilityZone string `json:"availability_zone,omitempty"` // Optional
323
Metadata map[string]string `json:"metadata,omitempty"` // Optional
324
ConfigDrive bool `json:"config_drive,omitempty"` // Optional
327
// RunServer creates a new server, based on the given RunServerOpts.
328
func (c *Client) RunServer(opts RunServerOpts) (*Entity, error) {
330
Server RunServerOpts `json:"server"`
333
// opts.UserData gets serialized to base64-encoded string automatically
335
Server Entity `json:"server"`
337
requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusAccepted}}
338
err := c.client.SendRequest(client.POST, "compute", apiServers, &requestData)
340
return nil, errors.Newf(err, "failed to run a server with %#v", opts)
342
return &resp.Server, nil
345
type serverUpdateNameOpts struct {
346
Name string `json:"name"`
349
// UpdateServerName updates the name of the given server.
350
func (c *Client) UpdateServerName(serverID, name string) (*Entity, error) {
352
Server serverUpdateNameOpts `json:"server"`
355
Server Entity `json:"server"`
357
req.Server = serverUpdateNameOpts{Name: name}
358
requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusOK}}
359
url := fmt.Sprintf("%s/%s", apiServers, serverID)
360
err := c.client.SendRequest(client.PUT, "compute", url, &requestData)
362
return nil, errors.Newf(err, "failed to update server name to %q", name)
364
return &resp.Server, nil
367
// SecurityGroupRef refers to an existing named security group
368
type SecurityGroupRef struct {
369
TenantId string `json:"tenant_id"`
370
Name string `json:"name"`
373
// SecurityGroupRule describes a rule of a security group. There are 2
374
// basic rule types: ingress and group rules (see RuleInfo struct).
375
type SecurityGroupRule struct {
376
FromPort *int `json:"from_port"` // Can be nil
377
IPProtocol *string `json:"ip_protocol"` // Can be nil
378
ToPort *int `json:"to_port"` // Can be nil
379
ParentGroupId string `json:"-"`
380
IPRange map[string]string `json:"ip_range"` // Can be empty
382
Group SecurityGroupRef
385
// SecurityGroup describes a single security group in OpenStack.
386
type SecurityGroup struct {
387
Rules []SecurityGroupRule
388
TenantId string `json:"tenant_id"`
394
// ListSecurityGroups lists IDs, names, and other details for all security groups.
395
func (c *Client) ListSecurityGroups() ([]SecurityGroup, error) {
397
Groups []SecurityGroup `json:"security_groups"`
399
requestData := goosehttp.RequestData{RespValue: &resp}
400
err := c.client.SendRequest(client.GET, "compute", apiSecurityGroups, &requestData)
402
return nil, errors.Newf(err, "failed to list security groups")
404
return resp.Groups, nil
407
// GetSecurityGroupByName returns the named security group.
408
// Note: due to lack of filtering support when querying security groups, this is not an efficient implementation
409
// but it's all we can do for now.
410
func (c *Client) SecurityGroupByName(name string) (*SecurityGroup, error) {
411
// OpenStack does not support group filtering, so we need to load them all and manually search by name.
412
groups, err := c.ListSecurityGroups()
416
for _, group := range groups {
417
if group.Name == name {
421
return nil, errors.NewNotFoundf(nil, "", "Security group %s not found.", name)
424
// GetServerSecurityGroups list security groups for a specific server.
425
func (c *Client) GetServerSecurityGroups(serverId string) ([]SecurityGroup, error) {
428
Groups []SecurityGroup `json:"security_groups"`
430
url := fmt.Sprintf("%s/%s/%s", apiServers, serverId, apiSecurityGroups)
431
requestData := goosehttp.RequestData{RespValue: &resp}
432
err := c.client.SendRequest(client.GET, "compute", url, &requestData)
434
// Sadly HP Cloud lacks the necessary API and also doesn't provide full SecurityGroup lookup.
435
// The best we can do for now is to use just the Id and Name from the group entities.
436
if errors.IsNotFound(err) {
437
serverDetails, err := c.GetServer(serverId)
439
result := make([]SecurityGroup, len(serverDetails.Groups))
440
for i, e := range serverDetails.Groups {
441
result[i] = SecurityGroup{
449
return nil, errors.Newf(err, "failed to list server (%s) security groups", serverId)
451
return resp.Groups, nil
454
// CreateSecurityGroup creates a new security group.
455
func (c *Client) CreateSecurityGroup(name, description string) (*SecurityGroup, error) {
457
SecurityGroup struct {
458
Name string `json:"name"`
459
Description string `json:"description"`
460
} `json:"security_group"`
462
req.SecurityGroup.Name = name
463
req.SecurityGroup.Description = description
466
SecurityGroup SecurityGroup `json:"security_group"`
468
requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusOK}}
469
err := c.client.SendRequest(client.POST, "compute", apiSecurityGroups, &requestData)
471
return nil, errors.Newf(err, "failed to create a security group with name: %s", name)
473
return &resp.SecurityGroup, nil
476
// DeleteSecurityGroup deletes the specified security group.
477
func (c *Client) DeleteSecurityGroup(groupId string) error {
478
url := fmt.Sprintf("%s/%s", apiSecurityGroups, groupId)
479
requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusAccepted}}
480
err := c.client.SendRequest(client.DELETE, "compute", url, &requestData)
482
err = errors.Newf(err, "failed to delete security group with id: %s", groupId)
487
// UpdateSecurityGroup updates the name and description of the given group.
488
func (c *Client) UpdateSecurityGroup(groupId, name, description string) (*SecurityGroup, error) {
490
SecurityGroup struct {
491
Name string `json:"name"`
492
Description string `json:"description"`
493
} `json:"security_group"`
495
req.SecurityGroup.Name = name
496
req.SecurityGroup.Description = description
498
SecurityGroup SecurityGroup `json:"security_group"`
500
url := fmt.Sprintf("%s/%s", apiSecurityGroups, groupId)
501
requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusOK}}
502
err := c.client.SendRequest(client.PUT, "compute", url, &requestData)
504
return nil, errors.Newf(err, "failed to update security group with Id %s to name: %s", groupId, name)
506
return &resp.SecurityGroup, nil
509
// RuleInfo allows the callers of CreateSecurityGroupRule() to
510
// create 2 types of security group rules: ingress rules and group
511
// rules. The difference stems from how the "source" is defined.
513
// 1. Ingress rules - specified directly with any valid subnet mask
514
// in CIDR format (e.g. "192.168.0.0/16");
515
// 2. Group rules - specified indirectly by giving a source group,
516
// which can be any user's group (different tenant ID).
518
// Every rule works as an iptables ACCEPT rule, thus a group/ with no
519
// rules does not allow ingress at all. Rules can be added and removed
520
// while the server(s) are running. The set of security groups that
521
// apply to a server is changed only when the server is
522
// started. Adding or removing a security group on a running server
523
// will not take effect until that server is restarted. However,
524
// changing rules of existing groups will take effect immediately.
526
// For more information:
527
// http://docs.openstack.org/developer/nova/nova.concepts.html#concept-security-groups
528
// Nova source: https://github.com/openstack/nova.git
529
type RuleInfo struct {
530
/// IPProtocol is optional, and if specified must be "tcp", "udp" or
531
// "icmp" (in this case, both FromPort and ToPort can be -1).
532
IPProtocol string `json:"ip_protocol"`
534
// FromPort and ToPort are both optional, and if specifed must be
535
// integers between 1 and 65535 (valid TCP port numbers). -1 is a
536
// special value, meaning "use default" (e.g. for ICMP).
537
FromPort int `json:"from_port"`
538
ToPort int `json:"to_port"`
540
// Cidr cannot be specified with GroupId. Ingress rules need a valid
541
// subnet mast in CIDR format here, while if GroupID is specifed, it
542
// means you're adding a group rule, specifying source group ID, which
543
// must exist already and can be equal to ParentGroupId).
545
Cidr string `json:"cidr"`
546
GroupId *string `json:"-"`
548
// ParentGroupId is always required and specifies the group to which
549
// the rule is added.
550
ParentGroupId string `json:"-"`
553
// CreateSecurityGroupRule creates a security group rule.
554
// It can either be an ingress rule or group rule (see the
555
// description of RuleInfo).
556
func (c *Client) CreateSecurityGroupRule(ruleInfo RuleInfo) (*SecurityGroupRule, error) {
558
SecurityGroupRule RuleInfo `json:"security_group_rule"`
560
req.SecurityGroupRule = ruleInfo
563
SecurityGroupRule SecurityGroupRule `json:"security_group_rule"`
566
requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp}
567
err := c.client.SendRequest(client.POST, "compute", apiSecurityGroupRules, &requestData)
569
return nil, errors.Newf(err, "failed to create a rule for the security group with id: %v", ruleInfo.GroupId)
571
return &resp.SecurityGroupRule, nil
574
// DeleteSecurityGroupRule deletes the specified security group rule.
575
func (c *Client) DeleteSecurityGroupRule(ruleId string) error {
576
url := fmt.Sprintf("%s/%s", apiSecurityGroupRules, ruleId)
577
requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusAccepted}}
578
err := c.client.SendRequest(client.DELETE, "compute", url, &requestData)
580
err = errors.Newf(err, "failed to delete security group rule with id: %s", ruleId)
585
// AddServerSecurityGroup adds a security group to the specified server.
586
func (c *Client) AddServerSecurityGroup(serverId, groupName string) error {
588
AddSecurityGroup struct {
589
Name string `json:"name"`
590
} `json:"addSecurityGroup"`
592
req.AddSecurityGroup.Name = groupName
594
url := fmt.Sprintf("%s/%s/action", apiServers, serverId)
595
requestData := goosehttp.RequestData{ReqValue: req, ExpectedStatus: []int{http.StatusAccepted}}
596
err := c.client.SendRequest(client.POST, "compute", url, &requestData)
598
err = errors.Newf(err, "failed to add security group '%s' to server with id: %s", groupName, serverId)
603
// RemoveServerSecurityGroup removes a security group from the specified server.
604
func (c *Client) RemoveServerSecurityGroup(serverId, groupName string) error {
606
RemoveSecurityGroup struct {
607
Name string `json:"name"`
608
} `json:"removeSecurityGroup"`
610
req.RemoveSecurityGroup.Name = groupName
612
url := fmt.Sprintf("%s/%s/action", apiServers, serverId)
613
requestData := goosehttp.RequestData{ReqValue: req, ExpectedStatus: []int{http.StatusAccepted}}
614
err := c.client.SendRequest(client.POST, "compute", url, &requestData)
616
err = errors.Newf(err, "failed to remove security group '%s' from server with id: %s", groupName, serverId)
621
// FloatingIP describes a floating (public) IP address, which can be
622
// assigned to a server, thus allowing connections from outside.
623
type FloatingIP struct {
624
// FixedIP holds the private IP address of the machine (when assigned)
625
FixedIP *string `json:"fixed_ip"`
627
// InstanceId holds the instance id of the machine, if this FIP is assigned to one
628
InstanceId *string `json:"-"`
629
IP string `json:"ip"`
630
Pool string `json:"pool"`
633
// ListFloatingIPs lists floating IP addresses associated with the tenant or account.
634
func (c *Client) ListFloatingIPs() ([]FloatingIP, error) {
636
FloatingIPs []FloatingIP `json:"floating_ips"`
639
requestData := goosehttp.RequestData{RespValue: &resp}
640
err := c.client.SendRequest(client.GET, "compute", apiFloatingIPs, &requestData)
642
return nil, errors.Newf(err, "failed to list floating ips")
644
return resp.FloatingIPs, nil
647
// GetFloatingIP lists details of the floating IP address associated with specified id.
648
func (c *Client) GetFloatingIP(ipId string) (*FloatingIP, error) {
650
FloatingIP FloatingIP `json:"floating_ip"`
653
url := fmt.Sprintf("%s/%s", apiFloatingIPs, ipId)
654
requestData := goosehttp.RequestData{RespValue: &resp}
655
err := c.client.SendRequest(client.GET, "compute", url, &requestData)
657
return nil, errors.Newf(err, "failed to get floating ip %s details", ipId)
659
return &resp.FloatingIP, nil
662
// AllocateFloatingIP allocates a new floating IP address to a tenant or account.
663
func (c *Client) AllocateFloatingIP() (*FloatingIP, error) {
665
FloatingIP FloatingIP `json:"floating_ip"`
668
requestData := goosehttp.RequestData{RespValue: &resp}
669
err := c.client.SendRequest(client.POST, "compute", apiFloatingIPs, &requestData)
671
return nil, errors.Newf(err, "failed to allocate a floating ip")
673
return &resp.FloatingIP, nil
676
// DeleteFloatingIP deallocates the floating IP address associated with the specified id.
677
func (c *Client) DeleteFloatingIP(ipId string) error {
678
url := fmt.Sprintf("%s/%s", apiFloatingIPs, ipId)
679
requestData := goosehttp.RequestData{ExpectedStatus: []int{http.StatusAccepted}}
680
err := c.client.SendRequest(client.DELETE, "compute", url, &requestData)
682
err = errors.Newf(err, "failed to delete floating ip %s details", ipId)
687
// AddServerFloatingIP assigns a floating IP address to the specified server.
688
func (c *Client) AddServerFloatingIP(serverId, address string) error {
690
AddFloatingIP struct {
691
Address string `json:"address"`
692
} `json:"addFloatingIp"`
694
req.AddFloatingIP.Address = address
696
url := fmt.Sprintf("%s/%s/action", apiServers, serverId)
697
requestData := goosehttp.RequestData{ReqValue: req, ExpectedStatus: []int{http.StatusAccepted}}
698
err := c.client.SendRequest(client.POST, "compute", url, &requestData)
700
err = errors.Newf(err, "failed to add floating ip %s to server with id: %s", address, serverId)
705
// RemoveServerFloatingIP removes a floating IP address from the specified server.
706
func (c *Client) RemoveServerFloatingIP(serverId, address string) error {
708
RemoveFloatingIP struct {
709
Address string `json:"address"`
710
} `json:"removeFloatingIp"`
712
req.RemoveFloatingIP.Address = address
714
url := fmt.Sprintf("%s/%s/action", apiServers, serverId)
715
requestData := goosehttp.RequestData{ReqValue: req, ExpectedStatus: []int{http.StatusAccepted}}
716
err := c.client.SendRequest(client.POST, "compute", url, &requestData)
718
err = errors.Newf(err, "failed to remove floating ip %s from server with id: %s", address, serverId)
723
// AvailabilityZone identifies an availability zone, and describes its state.
724
type AvailabilityZone struct {
725
Name string `json:"zoneName"`
726
State AvailabilityZoneState `json:"zoneState"`
729
// AvailabilityZoneState describes an availability zone's state.
730
type AvailabilityZoneState struct {
734
// ListAvailabilityZones lists all availability zones.
736
// Availability zones are an OpenStack extension; if the server does not
737
// support them, then an error satisfying errors.IsNotImplemented will be
739
func (c *Client) ListAvailabilityZones() ([]AvailabilityZone, error) {
741
AvailabilityZoneInfo []AvailabilityZone
743
requestData := goosehttp.RequestData{RespValue: &resp}
744
err := c.client.SendRequest(client.GET, "compute", apiAvailabilityZone, &requestData)
745
if errors.IsNotFound(err) {
746
// Availability zones are an extension, so don't
747
// return an error if the API does not exist.
748
return nil, errors.NewNotImplementedf(
749
err, nil, "the server does not support availability zones",
753
return nil, errors.Newf(err, "failed to get list of availability zones")
755
return resp.AvailabilityZoneInfo, nil
758
// VolumeAttachment represents both the request and response for
759
// attaching volumes.
760
type VolumeAttachment struct {
761
Device string `json:"device"`
762
Id string `json:"id"`
763
ServerId string `json:"serverId"`
764
VolumeId string `json:"volumeId"`
767
// AttachVolume attaches the given volumeId to the given serverId at
768
// mount point specified in device. Note that the server must support
769
// the os-volume_attachments attachment; if it does not, an error will
770
// be returned stating such.
771
func (c *Client) AttachVolume(serverId, volumeId, device string) (*VolumeAttachment, error) {
773
type volumeAttachment struct {
774
VolumeAttachment VolumeAttachment `json:"volumeAttachment"`
777
var resp volumeAttachment
778
requestData := goosehttp.RequestData{
779
ReqValue: &volumeAttachment{VolumeAttachment{
786
url := fmt.Sprintf("%s/%s/%s", apiServers, serverId, apiVolumeAttachments)
787
err := c.client.SendRequest(client.POST, "compute", url, &requestData)
788
if errors.IsNotFound(err) {
789
return nil, errors.NewNotImplementedf(
790
err, nil, "the server does not support attaching volumes",
794
return nil, errors.Newf(err, "failed to attach volume")
796
return &resp.VolumeAttachment, nil
799
// DetachVolume detaches the volume with the given attachmentId from
800
// the server with the given serverId.
801
func (c *Client) DetachVolume(serverId, attachmentId string) error {
802
requestData := goosehttp.RequestData{
803
ExpectedStatus: []int{http.StatusAccepted},
805
url := fmt.Sprintf("%s/%s/%s/%s", apiServers, serverId, apiVolumeAttachments, attachmentId)
806
err := c.client.SendRequest(client.DELETE, "compute", url, &requestData)
807
if errors.IsNotFound(err) {
808
return errors.NewNotImplementedf(
809
err, nil, "the server does not support deleting attached volumes",
813
return errors.Newf(err, "failed to delete volume attachment")
818
// ListVolumeAttachments lists the volumes currently attached to the
819
// server with the given serverId.
820
func (c *Client) ListVolumeAttachments(serverId string) ([]VolumeAttachment, error) {
823
VolumeAttachments []VolumeAttachment `json:"volumeAttachments"`
825
requestData := goosehttp.RequestData{
828
url := fmt.Sprintf("%s/%s/%s", apiServers, serverId, apiVolumeAttachments)
829
err := c.client.SendRequest(client.GET, "compute", url, &requestData)
830
if errors.IsNotFound(err) {
831
return nil, errors.NewNotImplementedf(
832
err, nil, "the server does not support listing attached volumes",
836
return nil, errors.Newf(err, "failed to list volume attachments")
838
return resp.VolumeAttachments, nil
841
// SetServerMetadata sets metadata on a server.
842
func (c *Client) SetServerMetadata(serverId string, metadata map[string]string) error {
844
Metadata map[string]string `json:"metadata"`
847
url := fmt.Sprintf("%s/%s/metadata", apiServers, serverId)
848
requestData := goosehttp.RequestData{
849
ReqValue: req, ExpectedStatus: []int{http.StatusOK},
851
err := c.client.SendRequest(client.POST, "compute", url, &requestData)
853
err = errors.Newf(err, "failed to set metadata %v on server with id: %s", metadata, serverId)