~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/gopkg.in/goose.v1/nova/nova.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// goose/nova - Go package to interact with OpenStack Compute (Nova) API.
 
2
// See http://docs.openstack.org/api/openstack-compute/2/content/.
 
3
 
 
4
package nova
 
5
 
 
6
import (
 
7
        "fmt"
 
8
        "net/http"
 
9
        "net/url"
 
10
        "reflect"
 
11
 
 
12
        "gopkg.in/goose.v1/client"
 
13
        "gopkg.in/goose.v1/errors"
 
14
        goosehttp "gopkg.in/goose.v1/http"
 
15
)
 
16
 
 
17
// API URL parts.
 
18
const (
 
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"
 
28
)
 
29
 
 
30
// Server status values.
 
31
const (
 
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.
 
47
)
 
48
 
 
49
// Filter keys.
 
50
const (
 
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.
 
58
)
 
59
 
 
60
// Client provides a means to access the OpenStack Compute Service.
 
61
type Client struct {
 
62
        client client.Client
 
63
}
 
64
 
 
65
// New creates a new Client.
 
66
func New(client client.Client) *Client {
 
67
        return &Client{client}
 
68
}
 
69
 
 
70
// ----------------------------------------------------------------------------
 
71
// Filtering helper.
 
72
//
 
73
// Filter builds filtering parameters to be used in an OpenStack query which supports
 
74
// filtering.  For example:
 
75
//
 
76
//     filter := NewFilter()
 
77
//     filter.Set(nova.FilterServer, "server_name")
 
78
//     filter.Set(nova.FilterStatus, nova.StatusBuild)
 
79
//     resp, err := nova.ListServers(filter)
 
80
//
 
81
type Filter struct {
 
82
        v url.Values
 
83
}
 
84
 
 
85
// NewFilter creates a new Filter.
 
86
func NewFilter() *Filter {
 
87
        return &Filter{make(url.Values)}
 
88
}
 
89
 
 
90
func (f *Filter) Set(filter, value string) {
 
91
        f.v.Set(filter, value)
 
92
}
 
93
 
 
94
// Link describes a link to a flavor or server.
 
95
type Link struct {
 
96
        Href string
 
97
        Rel  string
 
98
        Type string
 
99
}
 
100
 
 
101
// Entity describe a basic information about a flavor or server.
 
102
type Entity struct {
 
103
        Id    string `json:"-"`
 
104
        UUID  string `json:"uuid"`
 
105
        Links []Link `json:"links"`
 
106
        Name  string `json:"name"`
 
107
}
 
108
 
 
109
func stringValue(item interface{}, attr string) string {
 
110
        return reflect.ValueOf(item).FieldByName(attr).String()
 
111
}
 
112
 
 
113
// Allow Entity slices to be sorted by named attribute.
 
114
type EntitySortBy struct {
 
115
        Attr     string
 
116
        Entities []Entity
 
117
}
 
118
 
 
119
func (e EntitySortBy) Len() int {
 
120
        return len(e.Entities)
 
121
}
 
122
 
 
123
func (e EntitySortBy) Less(i, j int) bool {
 
124
        return stringValue(e.Entities[i], e.Attr) < stringValue(e.Entities[j], e.Attr)
 
125
}
 
126
 
 
127
func (e EntitySortBy) Swap(i, j int) {
 
128
        e.Entities[i], e.Entities[j] = e.Entities[j], e.Entities[i]
 
129
}
 
130
 
 
131
// ListFlavours lists IDs, names, and links for available flavors.
 
132
func (c *Client) ListFlavors() ([]Entity, error) {
 
133
        var resp struct {
 
134
                Flavors []Entity
 
135
        }
 
136
        requestData := goosehttp.RequestData{RespValue: &resp}
 
137
        err := c.client.SendRequest(client.GET, "compute", apiFlavors, &requestData)
 
138
        if err != nil {
 
139
                return nil, errors.Newf(err, "failed to get list of flavours")
 
140
        }
 
141
        return resp.Flavors, nil
 
142
}
 
143
 
 
144
// FlavorDetail describes detailed information about a flavor.
 
145
type FlavorDetail struct {
 
146
        Name  string
 
147
        RAM   int    // Available RAM, in MB
 
148
        VCPUs int    // Number of virtual CPU (cores)
 
149
        Disk  int    // Available root partition space, in GB
 
150
        Id    string `json:"-"`
 
151
        Links []Link
 
152
}
 
153
 
 
154
// Allow FlavorDetail slices to be sorted by named attribute.
 
155
type FlavorDetailSortBy struct {
 
156
        Attr          string
 
157
        FlavorDetails []FlavorDetail
 
158
}
 
159
 
 
160
func (e FlavorDetailSortBy) Len() int {
 
161
        return len(e.FlavorDetails)
 
162
}
 
163
 
 
164
func (e FlavorDetailSortBy) Less(i, j int) bool {
 
165
        return stringValue(e.FlavorDetails[i], e.Attr) < stringValue(e.FlavorDetails[j], e.Attr)
 
166
}
 
167
 
 
168
func (e FlavorDetailSortBy) Swap(i, j int) {
 
169
        e.FlavorDetails[i], e.FlavorDetails[j] = e.FlavorDetails[j], e.FlavorDetails[i]
 
170
}
 
171
 
 
172
// ListFlavorsDetail lists all details for available flavors.
 
173
func (c *Client) ListFlavorsDetail() ([]FlavorDetail, error) {
 
174
        var resp struct {
 
175
                Flavors []FlavorDetail
 
176
        }
 
177
        requestData := goosehttp.RequestData{RespValue: &resp}
 
178
        err := c.client.SendRequest(client.GET, "compute", apiFlavorsDetail, &requestData)
 
179
        if err != nil {
 
180
                return nil, errors.Newf(err, "failed to get list of flavour details")
 
181
        }
 
182
        return resp.Flavors, nil
 
183
}
 
184
 
 
185
// ListServers lists IDs, names, and links for all servers.
 
186
func (c *Client) ListServers(filter *Filter) ([]Entity, error) {
 
187
        var resp struct {
 
188
                Servers []Entity
 
189
        }
 
190
        var params *url.Values
 
191
        if filter != nil {
 
192
                params = &filter.v
 
193
        }
 
194
        requestData := goosehttp.RequestData{RespValue: &resp, Params: params, ExpectedStatus: []int{http.StatusOK}}
 
195
        err := c.client.SendRequest(client.GET, "compute", apiServers, &requestData)
 
196
        if err != nil {
 
197
                return nil, errors.Newf(err, "failed to get list of servers")
 
198
        }
 
199
        return resp.Servers, nil
 
200
}
 
201
 
 
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"`
 
206
}
 
207
 
 
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.
 
213
        AddressIPv4 string
 
214
        AddressIPv6 string
 
215
 
 
216
        // Addresses holds the list of all IP addresses assigned to this
 
217
        // server, grouped by "network" name ("public", "private" or a
 
218
        // custom name).
 
219
        Addresses map[string][]IPAddress
 
220
 
 
221
        // Created holds the creation timestamp of the server
 
222
        // in RFC3339 format.
 
223
        Created string
 
224
 
 
225
        Flavor   Entity
 
226
        HostId   string
 
227
        Id       string `json:"-"`
 
228
        UUID     string
 
229
        Image    Entity
 
230
        Links    []Link
 
231
        Name     string
 
232
        Metadata map[string]string
 
233
 
 
234
        // HP Cloud returns security groups in server details.
 
235
        Groups []Entity `json:"security_groups"`
 
236
 
 
237
        // Progress holds the completion percentage of
 
238
        // the current operation
 
239
        Progress int
 
240
 
 
241
        // Status holds the current status of the server,
 
242
        // one of the Status* constants.
 
243
        Status string
 
244
 
 
245
        TenantId string `json:"tenant_id"`
 
246
 
 
247
        // Updated holds the timestamp of the last update
 
248
        // to the server in RFC3339 format.
 
249
        Updated string
 
250
 
 
251
        UserId string `json:"user_id"`
 
252
 
 
253
        AvailabilityZone string `json:"OS-EXT-AZ:availability_zone"`
 
254
}
 
255
 
 
256
// ListServersDetail lists all details for available servers.
 
257
func (c *Client) ListServersDetail(filter *Filter) ([]ServerDetail, error) {
 
258
        var resp struct {
 
259
                Servers []ServerDetail
 
260
        }
 
261
        var params *url.Values
 
262
        if filter != nil {
 
263
                params = &filter.v
 
264
        }
 
265
        requestData := goosehttp.RequestData{RespValue: &resp, Params: params}
 
266
        err := c.client.SendRequest(client.GET, "compute", apiServersDetail, &requestData)
 
267
        if err != nil {
 
268
                return nil, errors.Newf(err, "failed to get list of server details")
 
269
        }
 
270
        return resp.Servers, nil
 
271
}
 
272
 
 
273
// GetServer lists details for the specified server.
 
274
func (c *Client) GetServer(serverId string) (*ServerDetail, error) {
 
275
        var resp struct {
 
276
                Server ServerDetail
 
277
        }
 
278
        url := fmt.Sprintf("%s/%s", apiServers, serverId)
 
279
        requestData := goosehttp.RequestData{RespValue: &resp}
 
280
        err := c.client.SendRequest(client.GET, "compute", url, &requestData)
 
281
        if err != nil {
 
282
                return nil, errors.Newf(err, "failed to get details for serverId: %s", serverId)
 
283
        }
 
284
        return &resp.Server, nil
 
285
}
 
286
 
 
287
// DeleteServer terminates the specified server.
 
288
func (c *Client) DeleteServer(serverId string) error {
 
289
        var resp struct {
 
290
                Server ServerDetail
 
291
        }
 
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)
 
295
        if err != nil {
 
296
                err = errors.Newf(err, "failed to delete server with serverId: %s", serverId)
 
297
        }
 
298
        return err
 
299
}
 
300
 
 
301
type SecurityGroupName struct {
 
302
        Name string `json:"name"`
 
303
}
 
304
 
 
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"`
 
312
}
 
313
 
 
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
 
325
}
 
326
 
 
327
// RunServer creates a new server, based on the given RunServerOpts.
 
328
func (c *Client) RunServer(opts RunServerOpts) (*Entity, error) {
 
329
        var req struct {
 
330
                Server RunServerOpts `json:"server"`
 
331
        }
 
332
        req.Server = opts
 
333
        // opts.UserData gets serialized to base64-encoded string automatically
 
334
        var resp struct {
 
335
                Server Entity `json:"server"`
 
336
        }
 
337
        requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusAccepted}}
 
338
        err := c.client.SendRequest(client.POST, "compute", apiServers, &requestData)
 
339
        if err != nil {
 
340
                return nil, errors.Newf(err, "failed to run a server with %#v", opts)
 
341
        }
 
342
        return &resp.Server, nil
 
343
}
 
344
 
 
345
type serverUpdateNameOpts struct {
 
346
        Name string `json:"name"`
 
347
}
 
348
 
 
349
// UpdateServerName updates the name of the given server.
 
350
func (c *Client) UpdateServerName(serverID, name string) (*Entity, error) {
 
351
        var req struct {
 
352
                Server serverUpdateNameOpts `json:"server"`
 
353
        }
 
354
        var resp struct {
 
355
                Server Entity `json:"server"`
 
356
        }
 
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)
 
361
        if err != nil {
 
362
                return nil, errors.Newf(err, "failed to update server name to %q", name)
 
363
        }
 
364
        return &resp.Server, nil
 
365
}
 
366
 
 
367
// SecurityGroupRef refers to an existing named security group
 
368
type SecurityGroupRef struct {
 
369
        TenantId string `json:"tenant_id"`
 
370
        Name     string `json:"name"`
 
371
}
 
372
 
 
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
 
381
        Id            string            `json:"-"`
 
382
        Group         SecurityGroupRef
 
383
}
 
384
 
 
385
// SecurityGroup describes a single security group in OpenStack.
 
386
type SecurityGroup struct {
 
387
        Rules       []SecurityGroupRule
 
388
        TenantId    string `json:"tenant_id"`
 
389
        Id          string `json:"-"`
 
390
        Name        string
 
391
        Description string
 
392
}
 
393
 
 
394
// ListSecurityGroups lists IDs, names, and other details for all security groups.
 
395
func (c *Client) ListSecurityGroups() ([]SecurityGroup, error) {
 
396
        var resp struct {
 
397
                Groups []SecurityGroup `json:"security_groups"`
 
398
        }
 
399
        requestData := goosehttp.RequestData{RespValue: &resp}
 
400
        err := c.client.SendRequest(client.GET, "compute", apiSecurityGroups, &requestData)
 
401
        if err != nil {
 
402
                return nil, errors.Newf(err, "failed to list security groups")
 
403
        }
 
404
        return resp.Groups, nil
 
405
}
 
406
 
 
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()
 
413
        if err != nil {
 
414
                return nil, err
 
415
        }
 
416
        for _, group := range groups {
 
417
                if group.Name == name {
 
418
                        return &group, nil
 
419
                }
 
420
        }
 
421
        return nil, errors.NewNotFoundf(nil, "", "Security group %s not found.", name)
 
422
}
 
423
 
 
424
// GetServerSecurityGroups list security groups for a specific server.
 
425
func (c *Client) GetServerSecurityGroups(serverId string) ([]SecurityGroup, error) {
 
426
 
 
427
        var resp struct {
 
428
                Groups []SecurityGroup `json:"security_groups"`
 
429
        }
 
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)
 
433
        if err != nil {
 
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)
 
438
                        if err == nil {
 
439
                                result := make([]SecurityGroup, len(serverDetails.Groups))
 
440
                                for i, e := range serverDetails.Groups {
 
441
                                        result[i] = SecurityGroup{
 
442
                                                Id:   e.Id,
 
443
                                                Name: e.Name,
 
444
                                        }
 
445
                                }
 
446
                                return result, nil
 
447
                        }
 
448
                }
 
449
                return nil, errors.Newf(err, "failed to list server (%s) security groups", serverId)
 
450
        }
 
451
        return resp.Groups, nil
 
452
}
 
453
 
 
454
// CreateSecurityGroup creates a new security group.
 
455
func (c *Client) CreateSecurityGroup(name, description string) (*SecurityGroup, error) {
 
456
        var req struct {
 
457
                SecurityGroup struct {
 
458
                        Name        string `json:"name"`
 
459
                        Description string `json:"description"`
 
460
                } `json:"security_group"`
 
461
        }
 
462
        req.SecurityGroup.Name = name
 
463
        req.SecurityGroup.Description = description
 
464
 
 
465
        var resp struct {
 
466
                SecurityGroup SecurityGroup `json:"security_group"`
 
467
        }
 
468
        requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp, ExpectedStatus: []int{http.StatusOK}}
 
469
        err := c.client.SendRequest(client.POST, "compute", apiSecurityGroups, &requestData)
 
470
        if err != nil {
 
471
                return nil, errors.Newf(err, "failed to create a security group with name: %s", name)
 
472
        }
 
473
        return &resp.SecurityGroup, nil
 
474
}
 
475
 
 
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)
 
481
        if err != nil {
 
482
                err = errors.Newf(err, "failed to delete security group with id: %s", groupId)
 
483
        }
 
484
        return err
 
485
}
 
486
 
 
487
// UpdateSecurityGroup updates the name and description of the given group.
 
488
func (c *Client) UpdateSecurityGroup(groupId, name, description string) (*SecurityGroup, error) {
 
489
        var req struct {
 
490
                SecurityGroup struct {
 
491
                        Name        string `json:"name"`
 
492
                        Description string `json:"description"`
 
493
                } `json:"security_group"`
 
494
        }
 
495
        req.SecurityGroup.Name = name
 
496
        req.SecurityGroup.Description = description
 
497
        var resp struct {
 
498
                SecurityGroup SecurityGroup `json:"security_group"`
 
499
        }
 
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)
 
503
        if err != nil {
 
504
                return nil, errors.Newf(err, "failed to update security group with Id %s to name: %s", groupId, name)
 
505
        }
 
506
        return &resp.SecurityGroup, nil
 
507
}
 
508
 
 
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.
 
512
// It can be either:
 
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).
 
517
//
 
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.
 
525
//
 
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"`
 
533
 
 
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"`
 
539
 
 
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).
 
544
        // need Cidr, while
 
545
        Cidr    string  `json:"cidr"`
 
546
        GroupId *string `json:"-"`
 
547
 
 
548
        // ParentGroupId is always required and specifies the group to which
 
549
        // the rule is added.
 
550
        ParentGroupId string `json:"-"`
 
551
}
 
552
 
 
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) {
 
557
        var req struct {
 
558
                SecurityGroupRule RuleInfo `json:"security_group_rule"`
 
559
        }
 
560
        req.SecurityGroupRule = ruleInfo
 
561
 
 
562
        var resp struct {
 
563
                SecurityGroupRule SecurityGroupRule `json:"security_group_rule"`
 
564
        }
 
565
 
 
566
        requestData := goosehttp.RequestData{ReqValue: req, RespValue: &resp}
 
567
        err := c.client.SendRequest(client.POST, "compute", apiSecurityGroupRules, &requestData)
 
568
        if err != nil {
 
569
                return nil, errors.Newf(err, "failed to create a rule for the security group with id: %v", ruleInfo.GroupId)
 
570
        }
 
571
        return &resp.SecurityGroupRule, nil
 
572
}
 
573
 
 
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)
 
579
        if err != nil {
 
580
                err = errors.Newf(err, "failed to delete security group rule with id: %s", ruleId)
 
581
        }
 
582
        return err
 
583
}
 
584
 
 
585
// AddServerSecurityGroup adds a security group to the specified server.
 
586
func (c *Client) AddServerSecurityGroup(serverId, groupName string) error {
 
587
        var req struct {
 
588
                AddSecurityGroup struct {
 
589
                        Name string `json:"name"`
 
590
                } `json:"addSecurityGroup"`
 
591
        }
 
592
        req.AddSecurityGroup.Name = groupName
 
593
 
 
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)
 
597
        if err != nil {
 
598
                err = errors.Newf(err, "failed to add security group '%s' to server with id: %s", groupName, serverId)
 
599
        }
 
600
        return err
 
601
}
 
602
 
 
603
// RemoveServerSecurityGroup removes a security group from the specified server.
 
604
func (c *Client) RemoveServerSecurityGroup(serverId, groupName string) error {
 
605
        var req struct {
 
606
                RemoveSecurityGroup struct {
 
607
                        Name string `json:"name"`
 
608
                } `json:"removeSecurityGroup"`
 
609
        }
 
610
        req.RemoveSecurityGroup.Name = groupName
 
611
 
 
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)
 
615
        if err != nil {
 
616
                err = errors.Newf(err, "failed to remove security group '%s' from server with id: %s", groupName, serverId)
 
617
        }
 
618
        return err
 
619
}
 
620
 
 
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"`
 
626
        Id      string  `json:"-"`
 
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"`
 
631
}
 
632
 
 
633
// ListFloatingIPs lists floating IP addresses associated with the tenant or account.
 
634
func (c *Client) ListFloatingIPs() ([]FloatingIP, error) {
 
635
        var resp struct {
 
636
                FloatingIPs []FloatingIP `json:"floating_ips"`
 
637
        }
 
638
 
 
639
        requestData := goosehttp.RequestData{RespValue: &resp}
 
640
        err := c.client.SendRequest(client.GET, "compute", apiFloatingIPs, &requestData)
 
641
        if err != nil {
 
642
                return nil, errors.Newf(err, "failed to list floating ips")
 
643
        }
 
644
        return resp.FloatingIPs, nil
 
645
}
 
646
 
 
647
// GetFloatingIP lists details of the floating IP address associated with specified id.
 
648
func (c *Client) GetFloatingIP(ipId string) (*FloatingIP, error) {
 
649
        var resp struct {
 
650
                FloatingIP FloatingIP `json:"floating_ip"`
 
651
        }
 
652
 
 
653
        url := fmt.Sprintf("%s/%s", apiFloatingIPs, ipId)
 
654
        requestData := goosehttp.RequestData{RespValue: &resp}
 
655
        err := c.client.SendRequest(client.GET, "compute", url, &requestData)
 
656
        if err != nil {
 
657
                return nil, errors.Newf(err, "failed to get floating ip %s details", ipId)
 
658
        }
 
659
        return &resp.FloatingIP, nil
 
660
}
 
661
 
 
662
// AllocateFloatingIP allocates a new floating IP address to a tenant or account.
 
663
func (c *Client) AllocateFloatingIP() (*FloatingIP, error) {
 
664
        var resp struct {
 
665
                FloatingIP FloatingIP `json:"floating_ip"`
 
666
        }
 
667
 
 
668
        requestData := goosehttp.RequestData{RespValue: &resp}
 
669
        err := c.client.SendRequest(client.POST, "compute", apiFloatingIPs, &requestData)
 
670
        if err != nil {
 
671
                return nil, errors.Newf(err, "failed to allocate a floating ip")
 
672
        }
 
673
        return &resp.FloatingIP, nil
 
674
}
 
675
 
 
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)
 
681
        if err != nil {
 
682
                err = errors.Newf(err, "failed to delete floating ip %s details", ipId)
 
683
        }
 
684
        return err
 
685
}
 
686
 
 
687
// AddServerFloatingIP assigns a floating IP address to the specified server.
 
688
func (c *Client) AddServerFloatingIP(serverId, address string) error {
 
689
        var req struct {
 
690
                AddFloatingIP struct {
 
691
                        Address string `json:"address"`
 
692
                } `json:"addFloatingIp"`
 
693
        }
 
694
        req.AddFloatingIP.Address = address
 
695
 
 
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)
 
699
        if err != nil {
 
700
                err = errors.Newf(err, "failed to add floating ip %s to server with id: %s", address, serverId)
 
701
        }
 
702
        return err
 
703
}
 
704
 
 
705
// RemoveServerFloatingIP removes a floating IP address from the specified server.
 
706
func (c *Client) RemoveServerFloatingIP(serverId, address string) error {
 
707
        var req struct {
 
708
                RemoveFloatingIP struct {
 
709
                        Address string `json:"address"`
 
710
                } `json:"removeFloatingIp"`
 
711
        }
 
712
        req.RemoveFloatingIP.Address = address
 
713
 
 
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)
 
717
        if err != nil {
 
718
                err = errors.Newf(err, "failed to remove floating ip %s from server with id: %s", address, serverId)
 
719
        }
 
720
        return err
 
721
}
 
722
 
 
723
// AvailabilityZone identifies an availability zone, and describes its state.
 
724
type AvailabilityZone struct {
 
725
        Name  string                `json:"zoneName"`
 
726
        State AvailabilityZoneState `json:"zoneState"`
 
727
}
 
728
 
 
729
// AvailabilityZoneState describes an availability zone's state.
 
730
type AvailabilityZoneState struct {
 
731
        Available bool
 
732
}
 
733
 
 
734
// ListAvailabilityZones lists all availability zones.
 
735
//
 
736
// Availability zones are an OpenStack extension; if the server does not
 
737
// support them, then an error satisfying errors.IsNotImplemented will be
 
738
// returned.
 
739
func (c *Client) ListAvailabilityZones() ([]AvailabilityZone, error) {
 
740
        var resp struct {
 
741
                AvailabilityZoneInfo []AvailabilityZone
 
742
        }
 
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",
 
750
                )
 
751
        }
 
752
        if err != nil {
 
753
                return nil, errors.Newf(err, "failed to get list of availability zones")
 
754
        }
 
755
        return resp.AvailabilityZoneInfo, nil
 
756
}
 
757
 
 
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"`
 
765
}
 
766
 
 
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) {
 
772
 
 
773
        type volumeAttachment struct {
 
774
                VolumeAttachment VolumeAttachment `json:"volumeAttachment"`
 
775
        }
 
776
 
 
777
        var resp volumeAttachment
 
778
        requestData := goosehttp.RequestData{
 
779
                ReqValue: &volumeAttachment{VolumeAttachment{
 
780
                        ServerId: serverId,
 
781
                        VolumeId: volumeId,
 
782
                        Device:   device,
 
783
                }},
 
784
                RespValue: &resp,
 
785
        }
 
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",
 
791
                )
 
792
        }
 
793
        if err != nil {
 
794
                return nil, errors.Newf(err, "failed to attach volume")
 
795
        }
 
796
        return &resp.VolumeAttachment, nil
 
797
}
 
798
 
 
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},
 
804
        }
 
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",
 
810
                )
 
811
        }
 
812
        if err != nil {
 
813
                return errors.Newf(err, "failed to delete volume attachment")
 
814
        }
 
815
        return nil
 
816
}
 
817
 
 
818
// ListVolumeAttachments lists the volumes currently attached to the
 
819
// server with the given serverId.
 
820
func (c *Client) ListVolumeAttachments(serverId string) ([]VolumeAttachment, error) {
 
821
 
 
822
        var resp struct {
 
823
                VolumeAttachments []VolumeAttachment `json:"volumeAttachments"`
 
824
        }
 
825
        requestData := goosehttp.RequestData{
 
826
                RespValue: &resp,
 
827
        }
 
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",
 
833
                )
 
834
        }
 
835
        if err != nil {
 
836
                return nil, errors.Newf(err, "failed to list volume attachments")
 
837
        }
 
838
        return resp.VolumeAttachments, nil
 
839
}
 
840
 
 
841
// SetServerMetadata sets metadata on a server.
 
842
func (c *Client) SetServerMetadata(serverId string, metadata map[string]string) error {
 
843
        req := struct {
 
844
                Metadata map[string]string `json:"metadata"`
 
845
        }{metadata}
 
846
 
 
847
        url := fmt.Sprintf("%s/%s/metadata", apiServers, serverId)
 
848
        requestData := goosehttp.RequestData{
 
849
                ReqValue: req, ExpectedStatus: []int{http.StatusOK},
 
850
        }
 
851
        err := c.client.SendRequest(client.POST, "compute", url, &requestData)
 
852
        if err != nil {
 
853
                err = errors.Newf(err, "failed to set metadata %v on server with id: %s", metadata, serverId)
 
854
        }
 
855
        return err
 
856
}