| Samuel A. Falvo II | bc0d54a | 2013-07-08 14:45:21 -0700 | [diff] [blame] | 1 | // TODO(sfalvo): Remove Rackspace-specific Server structure fields and refactor them into a provider-specific access method. | 
|  | 2 | // Be sure to update godocs accordingly. | 
|  | 3 |  | 
| Samuel A. Falvo II | 2e2b877 | 2013-07-04 15:40:15 -0700 | [diff] [blame] | 4 | package gophercloud | 
|  | 5 |  | 
| Samuel A. Falvo II | bc0d54a | 2013-07-08 14:45:21 -0700 | [diff] [blame] | 6 | import ( | 
| Samuel A. Falvo II | 5c305e1 | 2013-07-25 19:19:43 -0700 | [diff] [blame] | 7 | "fmt" | 
| Samuel A. Falvo II | 20f1aa4 | 2013-07-31 14:32:03 -0700 | [diff] [blame] | 8 | "github.com/racker/perigee" | 
| Mark Peek | 6b57c23 | 2013-08-24 19:03:26 -0700 | [diff] [blame] | 9 | "strings" | 
| Samuel A. Falvo II | bc0d54a | 2013-07-08 14:45:21 -0700 | [diff] [blame] | 10 | ) | 
|  | 11 |  | 
| Samuel A. Falvo II | 1dd740a | 2013-07-08 15:48:40 -0700 | [diff] [blame] | 12 | // genericServersProvider structures provide the implementation for generic OpenStack-compatible | 
|  | 13 | // CloudServersProvider interfaces. | 
|  | 14 | type genericServersProvider struct { | 
| Samuel A. Falvo II | 2e2b877 | 2013-07-04 15:40:15 -0700 | [diff] [blame] | 15 | // endpoint refers to the provider's API endpoint base URL.  This will be used to construct | 
|  | 16 | // and issue queries. | 
|  | 17 | endpoint string | 
|  | 18 |  | 
|  | 19 | // Test context (if any) in which to issue requests. | 
|  | 20 | context *Context | 
| Samuel A. Falvo II | bc0d54a | 2013-07-08 14:45:21 -0700 | [diff] [blame] | 21 |  | 
|  | 22 | // access associates this API provider with a set of credentials, | 
|  | 23 | // which may be automatically renewed if they near expiration. | 
|  | 24 | access AccessProvider | 
| Samuel A. Falvo II | 2e2b877 | 2013-07-04 15:40:15 -0700 | [diff] [blame] | 25 | } | 
|  | 26 |  | 
| Samuel A. Falvo II | 1dd740a | 2013-07-08 15:48:40 -0700 | [diff] [blame] | 27 | // See the CloudServersProvider interface for details. | 
| Samuel A. Falvo II | a0a5584 | 2013-07-24 13:14:17 -0700 | [diff] [blame] | 28 | func (gcp *genericServersProvider) ListServersLinksOnly() ([]Server, error) { | 
| Samuel A. Falvo II | bc0d54a | 2013-07-08 14:45:21 -0700 | [diff] [blame] | 29 | var ss []Server | 
|  | 30 |  | 
| Samuel A. Falvo II | 7bd1fba | 2013-07-16 17:30:43 -0700 | [diff] [blame] | 31 | err := gcp.context.WithReauth(gcp.access, func() error { | 
|  | 32 | url := gcp.endpoint + "/servers" | 
|  | 33 | return perigee.Get(url, perigee.Options{ | 
|  | 34 | CustomClient: gcp.context.httpClient, | 
|  | 35 | Results:      &struct{ Servers *[]Server }{&ss}, | 
|  | 36 | MoreHeaders: map[string]string{ | 
|  | 37 | "X-Auth-Token": gcp.access.AuthToken(), | 
|  | 38 | }, | 
|  | 39 | }) | 
| Samuel A. Falvo II | bc0d54a | 2013-07-08 14:45:21 -0700 | [diff] [blame] | 40 | }) | 
|  | 41 | return ss, err | 
| Samuel A. Falvo II | 2e2b877 | 2013-07-04 15:40:15 -0700 | [diff] [blame] | 42 | } | 
|  | 43 |  | 
| Samuel A. Falvo II | 02f5e83 | 2013-07-10 13:52:27 -0700 | [diff] [blame] | 44 | // See the CloudServersProvider interface for details. | 
| Samuel A. Falvo II | a0a5584 | 2013-07-24 13:14:17 -0700 | [diff] [blame] | 45 | func (gcp *genericServersProvider) ListServers() ([]Server, error) { | 
|  | 46 | var ss []Server | 
|  | 47 |  | 
|  | 48 | err := gcp.context.WithReauth(gcp.access, func() error { | 
|  | 49 | url := gcp.endpoint + "/servers/detail" | 
|  | 50 | return perigee.Get(url, perigee.Options{ | 
|  | 51 | CustomClient: gcp.context.httpClient, | 
|  | 52 | Results:      &struct{ Servers *[]Server }{&ss}, | 
|  | 53 | MoreHeaders: map[string]string{ | 
|  | 54 | "X-Auth-Token": gcp.access.AuthToken(), | 
|  | 55 | }, | 
|  | 56 | }) | 
|  | 57 | }) | 
|  | 58 | return ss, err | 
|  | 59 | } | 
|  | 60 |  | 
|  | 61 | // See the CloudServersProvider interface for details. | 
| Samuel A. Falvo II | 02f5e83 | 2013-07-10 13:52:27 -0700 | [diff] [blame] | 62 | func (gsp *genericServersProvider) ServerById(id string) (*Server, error) { | 
|  | 63 | var s *Server | 
|  | 64 |  | 
| Samuel A. Falvo II | 7bd1fba | 2013-07-16 17:30:43 -0700 | [diff] [blame] | 65 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 66 | url := gsp.endpoint + "/servers/" + id | 
|  | 67 | return perigee.Get(url, perigee.Options{ | 
|  | 68 | Results: &struct{ Server **Server }{&s}, | 
|  | 69 | MoreHeaders: map[string]string{ | 
|  | 70 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 71 | }, | 
|  | 72 | }) | 
| Samuel A. Falvo II | 02f5e83 | 2013-07-10 13:52:27 -0700 | [diff] [blame] | 73 | }) | 
|  | 74 | return s, err | 
|  | 75 | } | 
|  | 76 |  | 
| Samuel A. Falvo II | e91ff6d | 2013-07-11 15:46:10 -0700 | [diff] [blame] | 77 | // See the CloudServersProvider interface for details. | 
|  | 78 | func (gsp *genericServersProvider) CreateServer(ns NewServer) (*NewServer, error) { | 
|  | 79 | var s *NewServer | 
|  | 80 |  | 
| Samuel A. Falvo II | 7bd1fba | 2013-07-16 17:30:43 -0700 | [diff] [blame] | 81 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 82 | ep := gsp.endpoint + "/servers" | 
|  | 83 | return perigee.Post(ep, perigee.Options{ | 
|  | 84 | ReqBody: &struct { | 
|  | 85 | Server *NewServer `json:"server"` | 
|  | 86 | }{&ns}, | 
|  | 87 | Results: &struct{ Server **NewServer }{&s}, | 
|  | 88 | MoreHeaders: map[string]string{ | 
|  | 89 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 90 | }, | 
|  | 91 | OkCodes: []int{202}, | 
|  | 92 | }) | 
| Samuel A. Falvo II | e91ff6d | 2013-07-11 15:46:10 -0700 | [diff] [blame] | 93 | }) | 
| Samuel A. Falvo II | 7bd1fba | 2013-07-16 17:30:43 -0700 | [diff] [blame] | 94 |  | 
| Samuel A. Falvo II | e91ff6d | 2013-07-11 15:46:10 -0700 | [diff] [blame] | 95 | return s, err | 
|  | 96 | } | 
|  | 97 |  | 
| Samuel A. Falvo II | 286e4de | 2013-07-12 11:33:31 -0700 | [diff] [blame] | 98 | // See the CloudServersProvider interface for details. | 
|  | 99 | func (gsp *genericServersProvider) DeleteServerById(id string) error { | 
| Samuel A. Falvo II | 7bd1fba | 2013-07-16 17:30:43 -0700 | [diff] [blame] | 100 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 101 | url := gsp.endpoint + "/servers/" + id | 
|  | 102 | return perigee.Delete(url, perigee.Options{ | 
|  | 103 | MoreHeaders: map[string]string{ | 
|  | 104 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 105 | }, | 
|  | 106 | OkCodes: []int{204}, | 
|  | 107 | }) | 
| Samuel A. Falvo II | 286e4de | 2013-07-12 11:33:31 -0700 | [diff] [blame] | 108 | }) | 
|  | 109 | return err | 
|  | 110 | } | 
|  | 111 |  | 
| Samuel A. Falvo II | 5c305e1 | 2013-07-25 19:19:43 -0700 | [diff] [blame] | 112 | // See the CloudServersProvider interface for details. | 
|  | 113 | func (gsp *genericServersProvider) SetAdminPassword(id, pw string) error { | 
|  | 114 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 115 | url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id) | 
|  | 116 | return perigee.Post(url, perigee.Options{ | 
|  | 117 | ReqBody: &struct { | 
|  | 118 | ChangePassword struct { | 
|  | 119 | AdminPass string `json:"adminPass"` | 
|  | 120 | } `json:"changePassword"` | 
|  | 121 | }{ | 
|  | 122 | struct { | 
|  | 123 | AdminPass string `json:"adminPass"` | 
|  | 124 | }{pw}, | 
|  | 125 | }, | 
|  | 126 | OkCodes: []int{202}, | 
|  | 127 | MoreHeaders: map[string]string{ | 
|  | 128 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 129 | }, | 
|  | 130 | }) | 
|  | 131 | }) | 
|  | 132 | return err | 
|  | 133 | } | 
|  | 134 |  | 
| Samuel A. Falvo II | 8512e9a | 2013-07-26 22:53:29 -0700 | [diff] [blame] | 135 | // See the CloudServersProvider interface for details. | 
|  | 136 | func (gsp *genericServersProvider) ResizeServer(id, newName, newFlavor, newDiskConfig string) error { | 
|  | 137 | err := gsp.context.WithReauth(gsp.access, func() error { | 
| Samuel A. Falvo II | 20f1aa4 | 2013-07-31 14:32:03 -0700 | [diff] [blame] | 138 | url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id) | 
|  | 139 | rr := ResizeRequest{ | 
|  | 140 | Name:       newName, | 
|  | 141 | FlavorRef:  newFlavor, | 
|  | 142 | DiskConfig: newDiskConfig, | 
|  | 143 | } | 
|  | 144 | return perigee.Post(url, perigee.Options{ | 
|  | 145 | ReqBody: &struct { | 
|  | 146 | Resize ResizeRequest `json:"resize"` | 
|  | 147 | }{rr}, | 
|  | 148 | OkCodes: []int{202}, | 
|  | 149 | MoreHeaders: map[string]string{ | 
|  | 150 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 151 | }, | 
|  | 152 | }) | 
| Samuel A. Falvo II | 8512e9a | 2013-07-26 22:53:29 -0700 | [diff] [blame] | 153 | }) | 
|  | 154 | return err | 
|  | 155 | } | 
|  | 156 |  | 
|  | 157 | // See the CloudServersProvider interface for details. | 
|  | 158 | func (gsp *genericServersProvider) RevertResize(id string) error { | 
|  | 159 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 160 | url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id) | 
|  | 161 | return perigee.Post(url, perigee.Options{ | 
|  | 162 | ReqBody: &struct { | 
|  | 163 | RevertResize *int `json:"revertResize"` | 
|  | 164 | }{nil}, | 
|  | 165 | OkCodes: []int{202}, | 
|  | 166 | MoreHeaders: map[string]string{ | 
|  | 167 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 168 | }, | 
|  | 169 | }) | 
|  | 170 | }) | 
|  | 171 | return err | 
|  | 172 | } | 
|  | 173 |  | 
|  | 174 | // See the CloudServersProvider interface for details. | 
|  | 175 | func (gsp *genericServersProvider) ConfirmResize(id string) error { | 
|  | 176 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 177 | url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id) | 
|  | 178 | return perigee.Post(url, perigee.Options{ | 
|  | 179 | ReqBody: &struct { | 
|  | 180 | ConfirmResize *int `json:"confirmResize"` | 
|  | 181 | }{nil}, | 
|  | 182 | OkCodes: []int{204}, | 
|  | 183 | MoreHeaders: map[string]string{ | 
|  | 184 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 185 | }, | 
|  | 186 | }) | 
|  | 187 | }) | 
|  | 188 | return err | 
|  | 189 | } | 
|  | 190 |  | 
| Samuel A. Falvo II | adbecf9 | 2013-07-30 13:13:59 -0700 | [diff] [blame] | 191 | // See the CloudServersProvider interface for details | 
|  | 192 | func (gsp *genericServersProvider) RebootServer(id string, hard bool) error { | 
|  | 193 | return gsp.context.WithReauth(gsp.access, func() error { | 
|  | 194 | url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id) | 
|  | 195 | types := map[bool]string{false: "SOFT", true: "HARD"} | 
|  | 196 | return perigee.Post(url, perigee.Options{ | 
| Samuel A. Falvo II | 20f1aa4 | 2013-07-31 14:32:03 -0700 | [diff] [blame] | 197 | ReqBody: &struct { | 
| Samuel A. Falvo II | adbecf9 | 2013-07-30 13:13:59 -0700 | [diff] [blame] | 198 | Reboot struct { | 
|  | 199 | Type string `json:"type"` | 
|  | 200 | } `json:"reboot"` | 
|  | 201 | }{ | 
|  | 202 | struct { | 
|  | 203 | Type string `json:"type"` | 
|  | 204 | }{types[hard]}, | 
|  | 205 | }, | 
|  | 206 | OkCodes: []int{202}, | 
|  | 207 | MoreHeaders: map[string]string{ | 
|  | 208 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 209 | }, | 
|  | 210 | }) | 
|  | 211 | }) | 
|  | 212 | } | 
|  | 213 |  | 
| Samuel A. Falvo II | 15da6ab | 2013-07-30 14:02:11 -0700 | [diff] [blame] | 214 | // See the CloudServersProvider interface for details | 
|  | 215 | func (gsp *genericServersProvider) RescueServer(id string) (string, error) { | 
|  | 216 | var pw *string | 
|  | 217 |  | 
|  | 218 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 219 | url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id) | 
|  | 220 | return perigee.Post(url, perigee.Options{ | 
| Samuel A. Falvo II | 20f1aa4 | 2013-07-31 14:32:03 -0700 | [diff] [blame] | 221 | ReqBody: &struct { | 
| Samuel A. Falvo II | 15da6ab | 2013-07-30 14:02:11 -0700 | [diff] [blame] | 222 | Rescue string `json:"rescue"` | 
|  | 223 | }{"none"}, | 
|  | 224 | MoreHeaders: map[string]string{ | 
|  | 225 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 226 | }, | 
| Samuel A. Falvo II | 20f1aa4 | 2013-07-31 14:32:03 -0700 | [diff] [blame] | 227 | Results: &struct { | 
| Samuel A. Falvo II | 15da6ab | 2013-07-30 14:02:11 -0700 | [diff] [blame] | 228 | AdminPass **string `json:"adminPass"` | 
|  | 229 | }{&pw}, | 
|  | 230 | }) | 
|  | 231 | }) | 
|  | 232 | return *pw, err | 
|  | 233 | } | 
|  | 234 |  | 
|  | 235 | // See the CloudServersProvider interface for details | 
|  | 236 | func (gsp *genericServersProvider) UnrescueServer(id string) error { | 
|  | 237 | return gsp.context.WithReauth(gsp.access, func() error { | 
|  | 238 | url := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id) | 
|  | 239 | return perigee.Post(url, perigee.Options{ | 
| Samuel A. Falvo II | 20f1aa4 | 2013-07-31 14:32:03 -0700 | [diff] [blame] | 240 | ReqBody: &struct { | 
| Samuel A. Falvo II | 15da6ab | 2013-07-30 14:02:11 -0700 | [diff] [blame] | 241 | Unrescue *int `json:"unrescue"` | 
|  | 242 | }{nil}, | 
|  | 243 | MoreHeaders: map[string]string{ | 
|  | 244 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 245 | }, | 
|  | 246 | OkCodes: []int{202}, | 
|  | 247 | }) | 
|  | 248 | }) | 
|  | 249 | } | 
|  | 250 |  | 
| Samuel A. Falvo II | 72ac2dd | 2013-07-31 13:45:05 -0700 | [diff] [blame] | 251 | // See the CloudServersProvider interface for details | 
|  | 252 | func (gsp *genericServersProvider) UpdateServer(id string, changes NewServerSettings) (*Server, error) { | 
|  | 253 | var svr *Server | 
|  | 254 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 255 | url := fmt.Sprintf("%s/servers/%s", gsp.endpoint, id) | 
|  | 256 | return perigee.Put(url, perigee.Options{ | 
| Samuel A. Falvo II | 20f1aa4 | 2013-07-31 14:32:03 -0700 | [diff] [blame] | 257 | ReqBody: &struct { | 
| Samuel A. Falvo II | 72ac2dd | 2013-07-31 13:45:05 -0700 | [diff] [blame] | 258 | Server NewServerSettings `json:"server"` | 
|  | 259 | }{changes}, | 
|  | 260 | MoreHeaders: map[string]string{ | 
|  | 261 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 262 | }, | 
| Samuel A. Falvo II | 20f1aa4 | 2013-07-31 14:32:03 -0700 | [diff] [blame] | 263 | Results: &struct { | 
| Samuel A. Falvo II | 72ac2dd | 2013-07-31 13:45:05 -0700 | [diff] [blame] | 264 | Server **Server `json:"server"` | 
|  | 265 | }{&svr}, | 
|  | 266 | }) | 
|  | 267 | }) | 
|  | 268 | return svr, err | 
|  | 269 | } | 
|  | 270 |  | 
| Samuel A. Falvo II | 414c15c | 2013-08-01 15:16:46 -0700 | [diff] [blame] | 271 | // See the CloudServersProvider interface for details. | 
| Samuel A. Falvo II | f339160 | 2013-08-14 14:53:32 -0700 | [diff] [blame] | 272 | func (gsp *genericServersProvider) RebuildServer(id string, ns NewServer) (*Server, error) { | 
| Samuel A. Falvo II | 414c15c | 2013-08-01 15:16:46 -0700 | [diff] [blame] | 273 | var s *Server | 
|  | 274 |  | 
|  | 275 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 276 | ep := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id) | 
|  | 277 | return perigee.Post(ep, perigee.Options{ | 
|  | 278 | ReqBody: &struct { | 
|  | 279 | Rebuild *NewServer `json:"rebuild"` | 
|  | 280 | }{&ns}, | 
|  | 281 | Results: &struct{ Server **Server }{&s}, | 
|  | 282 | MoreHeaders: map[string]string{ | 
|  | 283 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 284 | }, | 
|  | 285 | OkCodes: []int{202}, | 
|  | 286 | }) | 
|  | 287 | }) | 
|  | 288 |  | 
|  | 289 | return s, err | 
|  | 290 | } | 
|  | 291 |  | 
| Samuel A. Falvo II | e21808f | 2013-08-14 14:48:09 -0700 | [diff] [blame] | 292 | // See the CloudServersProvider interface for details. | 
|  | 293 | func (gsp *genericServersProvider) ListAddresses(id string) (AddressSet, error) { | 
|  | 294 | var pas *AddressSet | 
|  | 295 | var statusCode int | 
|  | 296 |  | 
|  | 297 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 298 | ep := fmt.Sprintf("%s/servers/%s/ips", gsp.endpoint, id) | 
|  | 299 | return perigee.Get(ep, perigee.Options{ | 
|  | 300 | Results: &struct{ Addresses **AddressSet }{&pas}, | 
|  | 301 | MoreHeaders: map[string]string{ | 
|  | 302 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 303 | }, | 
| Samuel A. Falvo II | f339160 | 2013-08-14 14:53:32 -0700 | [diff] [blame] | 304 | OkCodes:    []int{200, 203}, | 
| Samuel A. Falvo II | e21808f | 2013-08-14 14:48:09 -0700 | [diff] [blame] | 305 | StatusCode: &statusCode, | 
|  | 306 | }) | 
|  | 307 | }) | 
|  | 308 |  | 
|  | 309 | if err != nil { | 
|  | 310 | if statusCode == 203 { | 
|  | 311 | err = WarnUnauthoritative | 
|  | 312 | } | 
|  | 313 | } | 
|  | 314 |  | 
|  | 315 | return *pas, err | 
|  | 316 | } | 
|  | 317 |  | 
| Mark Peek | 6b57c23 | 2013-08-24 19:03:26 -0700 | [diff] [blame] | 318 | // See the CloudServersProvider interface for details. | 
| Jon Perritt | 0c1629d | 2013-12-06 19:51:36 -0600 | [diff] [blame] | 319 | func (gsp *genericServersProvider) ListAddressesByNetwork(id, networkLabel string) (NetworkAddress, error) { | 
| Jon Perritt | b1ead74 | 2013-10-29 16:03:40 -0500 | [diff] [blame] | 320 | pas := make(NetworkAddress) | 
| Jon Perritt | 499dce1 | 2013-10-29 15:41:14 -0500 | [diff] [blame] | 321 | var statusCode int | 
|  | 322 |  | 
|  | 323 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 324 | ep := fmt.Sprintf("%s/servers/%s/ips/%s", gsp.endpoint, id, networkLabel) | 
|  | 325 | return perigee.Get(ep, perigee.Options{ | 
|  | 326 | Results: &pas, | 
|  | 327 | MoreHeaders: map[string]string{ | 
|  | 328 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 329 | }, | 
| Jon Perritt | 0c1629d | 2013-12-06 19:51:36 -0600 | [diff] [blame] | 330 | OkCodes:    []int{200, 203}, | 
| Jon Perritt | 499dce1 | 2013-10-29 15:41:14 -0500 | [diff] [blame] | 331 | StatusCode: &statusCode, | 
|  | 332 | }) | 
|  | 333 | }) | 
|  | 334 |  | 
|  | 335 | if err != nil { | 
|  | 336 | if statusCode == 203 { | 
|  | 337 | err = WarnUnauthoritative | 
|  | 338 | } | 
|  | 339 | } | 
|  | 340 |  | 
|  | 341 | return pas, err | 
|  | 342 | } | 
|  | 343 |  | 
|  | 344 | // See the CloudServersProvider interface for details. | 
| Mark Peek | 6b57c23 | 2013-08-24 19:03:26 -0700 | [diff] [blame] | 345 | func (gsp *genericServersProvider) CreateImage(id string, ci CreateImage) (string, error) { | 
|  | 346 | response, err := gsp.context.ResponseWithReauth(gsp.access, func() (*perigee.Response, error) { | 
|  | 347 | ep := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id) | 
|  | 348 | return perigee.Request("POST", ep, perigee.Options{ | 
|  | 349 | ReqBody: &struct { | 
|  | 350 | CreateImage *CreateImage `json:"createImage"` | 
|  | 351 | }{&ci}, | 
|  | 352 | MoreHeaders: map[string]string{ | 
|  | 353 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 354 | }, | 
| Mark Peek | 7d3e09d | 2013-08-27 07:57:18 -0700 | [diff] [blame] | 355 | OkCodes: []int{200, 202}, | 
| Mark Peek | 6b57c23 | 2013-08-24 19:03:26 -0700 | [diff] [blame] | 356 | }) | 
|  | 357 | }) | 
|  | 358 |  | 
|  | 359 | if err != nil { | 
|  | 360 | return "", err | 
|  | 361 | } | 
|  | 362 | location, err := response.HttpResponse.Location() | 
|  | 363 | if err != nil { | 
|  | 364 | return "", err | 
|  | 365 | } | 
|  | 366 |  | 
|  | 367 | // Return the last element of the location which is the image id | 
|  | 368 | locationArr := strings.Split(location.Path, "/") | 
|  | 369 | return locationArr[len(locationArr)-1], err | 
|  | 370 | } | 
|  | 371 |  | 
| Jon Perritt | 816d2a0 | 2014-03-11 20:49:46 -0500 | [diff] [blame] | 372 | // See the CloudServersProvider interface for details. | 
|  | 373 | func (gsp *genericServersProvider) ListSecurityGroups() ([]SecurityGroup, error) { | 
|  | 374 | var sgs []SecurityGroup | 
|  | 375 |  | 
|  | 376 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 377 | ep := fmt.Sprintf("%s/os-security-groups", gsp.endpoint) | 
|  | 378 | return perigee.Get(ep, perigee.Options{ | 
|  | 379 | MoreHeaders: map[string]string{ | 
|  | 380 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 381 | }, | 
|  | 382 | Results: &struct { | 
|  | 383 | SecurityGroups *[]SecurityGroup `json:"security_groups"` | 
|  | 384 | }{&sgs}, | 
|  | 385 | }) | 
|  | 386 | }) | 
|  | 387 | return sgs, err | 
|  | 388 | } | 
|  | 389 |  | 
|  | 390 | // See the CloudServersProvider interface for details. | 
|  | 391 | func (gsp *genericServersProvider) CreateSecurityGroup(desired SecurityGroup) (*SecurityGroup, error) { | 
|  | 392 | var actual *SecurityGroup | 
|  | 393 |  | 
|  | 394 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 395 | ep := fmt.Sprintf("%s/os-security-groups", gsp.endpoint) | 
|  | 396 | return perigee.Post(ep, perigee.Options{ | 
|  | 397 | ReqBody: struct { | 
|  | 398 | AddSecurityGroup SecurityGroup `json:"security_group"` | 
|  | 399 | }{desired}, | 
|  | 400 | MoreHeaders: map[string]string{ | 
|  | 401 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 402 | }, | 
|  | 403 | Results: &struct { | 
|  | 404 | SecurityGroup **SecurityGroup `json:"security_group"` | 
|  | 405 | }{&actual}, | 
|  | 406 | }) | 
|  | 407 | }) | 
|  | 408 | return actual, err | 
|  | 409 | } | 
|  | 410 |  | 
|  | 411 | // See the CloudServersProvider interface for details. | 
|  | 412 | func (gsp *genericServersProvider) ListSecurityGroupsByServerId(id string) ([]SecurityGroup, error) { | 
|  | 413 | var sgs []SecurityGroup | 
|  | 414 |  | 
|  | 415 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 416 | ep := fmt.Sprintf("%s/servers/%s/os-security-groups", gsp.endpoint, id) | 
|  | 417 | return perigee.Get(ep, perigee.Options{ | 
|  | 418 | MoreHeaders: map[string]string{ | 
|  | 419 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 420 | }, | 
|  | 421 | Results: &struct { | 
|  | 422 | SecurityGroups *[]SecurityGroup `json:"security_groups"` | 
|  | 423 | }{&sgs}, | 
|  | 424 | }) | 
|  | 425 | }) | 
|  | 426 | return sgs, err | 
|  | 427 | } | 
|  | 428 |  | 
|  | 429 | // See the CloudServersProvider interface for details. | 
|  | 430 | func (gsp *genericServersProvider) SecurityGroupById(id int) (*SecurityGroup, error) { | 
|  | 431 | var actual *SecurityGroup | 
|  | 432 |  | 
|  | 433 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 434 | ep := fmt.Sprintf("%s/os-security-groups/%d", gsp.endpoint, id) | 
|  | 435 | return perigee.Get(ep, perigee.Options{ | 
|  | 436 | MoreHeaders: map[string]string{ | 
|  | 437 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 438 | }, | 
|  | 439 | Results: &struct { | 
|  | 440 | SecurityGroup **SecurityGroup `json:"security_group"` | 
|  | 441 | }{&actual}, | 
|  | 442 | }) | 
|  | 443 | }) | 
|  | 444 | return actual, err | 
|  | 445 | } | 
|  | 446 |  | 
|  | 447 | // See the CloudServersProvider interface for details. | 
|  | 448 | func (gsp *genericServersProvider) DeleteSecurityGroupById(id int) error { | 
|  | 449 | err := gsp.context.WithReauth(gsp.access, func() error { | 
|  | 450 | ep := fmt.Sprintf("%s/os-security-groups/%d", gsp.endpoint, id) | 
|  | 451 | return perigee.Delete(ep, perigee.Options{ | 
|  | 452 | MoreHeaders: map[string]string{ | 
|  | 453 | "X-Auth-Token": gsp.access.AuthToken(), | 
|  | 454 | }, | 
|  | 455 | OkCodes: []int{202}, | 
|  | 456 | }) | 
|  | 457 | }) | 
|  | 458 | return err | 
|  | 459 | } | 
|  | 460 |  | 
|  | 461 | // SecurityGroup provides a description of a security group, including all its rules. | 
|  | 462 | type SecurityGroup struct { | 
|  | 463 | Description string   `json:"description,omitempty"` | 
|  | 464 | Id          int      `json:"id,omitempty"` | 
|  | 465 | Name        string   `json:"name,omitempty"` | 
|  | 466 | Rules       []SGRule `json:"rules,omitempty"` | 
|  | 467 | TenantId    string   `json:"tenant_id,omitempty"` | 
|  | 468 | } | 
|  | 469 |  | 
|  | 470 | // SGRule encapsulates a single rule which applies to a security group. | 
|  | 471 | // This definition is just a guess, based on the documentation found in another extension here: http://docs.openstack.org/api/openstack-compute/2/content/GET_os-security-group-default-rules-v2_listSecGroupDefaultRules_v2__tenant_id__os-security-group-rules_ext-os-security-group-default-rules.html | 
|  | 472 | type SGRule struct { | 
|  | 473 | FromPort   int                    `json:"from_port,omitempty"` | 
|  | 474 | Id         int                    `json:"id,omitempty"` | 
|  | 475 | IpProtocol string                 `json:"ip_protocol,omitempty"` | 
|  | 476 | IpRange    map[string]interface{} `json:"ip_range,omitempty"` | 
|  | 477 | ToPort     int                    `json:"to_port,omitempty"` | 
|  | 478 | } | 
|  | 479 |  | 
| Samuel A. Falvo II | bc0d54a | 2013-07-08 14:45:21 -0700 | [diff] [blame] | 480 | // RaxBandwidth provides measurement of server bandwidth consumed over a given audit interval. | 
|  | 481 | type RaxBandwidth struct { | 
|  | 482 | AuditPeriodEnd    string `json:"audit_period_end"` | 
|  | 483 | AuditPeriodStart  string `json:"audit_period_start"` | 
|  | 484 | BandwidthInbound  int64  `json:"bandwidth_inbound"` | 
|  | 485 | BandwidthOutbound int64  `json:"bandwidth_outbound"` | 
|  | 486 | Interface         string `json:"interface"` | 
|  | 487 | } | 
|  | 488 |  | 
|  | 489 | // A VersionedAddress denotes either an IPv4 or IPv6 (depending on version indicated) | 
|  | 490 | // address. | 
|  | 491 | type VersionedAddress struct { | 
|  | 492 | Addr    string `json:"addr"` | 
|  | 493 | Version int    `json:"version"` | 
|  | 494 | } | 
|  | 495 |  | 
|  | 496 | // An AddressSet provides a set of public and private IP addresses for a resource. | 
|  | 497 | // Each address has a version to identify if IPv4 or IPv6. | 
|  | 498 | type AddressSet struct { | 
|  | 499 | Public  []VersionedAddress `json:"public"` | 
|  | 500 | Private []VersionedAddress `json:"private"` | 
|  | 501 | } | 
|  | 502 |  | 
| Jon Perritt | 499dce1 | 2013-10-29 15:41:14 -0500 | [diff] [blame] | 503 | type NetworkAddress map[string][]VersionedAddress | 
|  | 504 |  | 
| Samuel A. Falvo II | bc0d54a | 2013-07-08 14:45:21 -0700 | [diff] [blame] | 505 | // Server records represent (virtual) hardware instances (not configurations) accessible by the user. | 
|  | 506 | // | 
|  | 507 | // The AccessIPv4 / AccessIPv6 fields provides IP addresses for the server in the IPv4 or IPv6 format, respectively. | 
|  | 508 | // | 
|  | 509 | // Addresses provides addresses for any attached isolated networks. | 
|  | 510 | // The version field indicates whether the IP address is version 4 or 6. | 
|  | 511 | // | 
|  | 512 | // Created tells when the server entity was created. | 
|  | 513 | // | 
|  | 514 | // The Flavor field includes the flavor ID and flavor links. | 
|  | 515 | // | 
|  | 516 | // The compute provisioning algorithm has an anti-affinity property that | 
|  | 517 | // attempts to spread customer VMs across hosts. | 
|  | 518 | // Under certain situations, | 
|  | 519 | // VMs from the same customer might be placed on the same host. | 
|  | 520 | // The HostId field represents the host your server runs on and | 
|  | 521 | // can be used to determine this scenario if it is relevant to your application. | 
|  | 522 | // Note that HostId is unique only per account; it is not globally unique. | 
| Mark Peek | a2818af | 2013-08-24 15:01:12 -0700 | [diff] [blame] | 523 | // | 
| Samuel A. Falvo II | bc0d54a | 2013-07-08 14:45:21 -0700 | [diff] [blame] | 524 | // Id provides the server's unique identifier. | 
|  | 525 | // This field must be treated opaquely. | 
|  | 526 | // | 
|  | 527 | // Image indicates which image is installed on the server. | 
|  | 528 | // | 
|  | 529 | // Links provides one or more means of accessing the server. | 
|  | 530 | // | 
|  | 531 | // Metadata provides a small key-value store for application-specific information. | 
|  | 532 | // | 
|  | 533 | // Name provides a human-readable name for the server. | 
|  | 534 | // | 
|  | 535 | // Progress indicates how far along it is towards being provisioned. | 
|  | 536 | // 100 represents complete, while 0 represents just beginning. | 
|  | 537 | // | 
|  | 538 | // Status provides an indication of what the server's doing at the moment. | 
|  | 539 | // A server will be in ACTIVE state if it's ready for use. | 
|  | 540 | // | 
|  | 541 | // OsDcfDiskConfig indicates the server's boot volume configuration. | 
|  | 542 | // Valid values are: | 
|  | 543 | //     AUTO | 
|  | 544 | //     ---- | 
|  | 545 | //     The server is built with a single partition the size of the target flavor disk. | 
|  | 546 | //     The file system is automatically adjusted to fit the entire partition. | 
|  | 547 | //     This keeps things simple and automated. | 
|  | 548 | //     AUTO is valid only for images and servers with a single partition that use the EXT3 file system. | 
|  | 549 | //     This is the default setting for applicable Rackspace base images. | 
|  | 550 | // | 
|  | 551 | //     MANUAL | 
|  | 552 | //     ------ | 
|  | 553 | //     The server is built using whatever partition scheme and file system is in the source image. | 
|  | 554 | //     If the target flavor disk is larger, | 
|  | 555 | //     the remaining disk space is left unpartitioned. | 
|  | 556 | //     This enables images to have non-EXT3 file systems, multiple partitions, and so on, | 
|  | 557 | //     and enables you to manage the disk configuration. | 
|  | 558 | // | 
|  | 559 | // RaxBandwidth provides measures of the server's inbound and outbound bandwidth per interface. | 
|  | 560 | // | 
|  | 561 | // OsExtStsPowerState provides an indication of the server's power. | 
|  | 562 | // This field appears to be a set of flag bits: | 
|  | 563 | // | 
|  | 564 | //           ... 4  3   2   1   0 | 
|  | 565 | //         +--//--+---+---+---+---+ | 
|  | 566 | //         | .... | 0 | S | 0 | I | | 
|  | 567 | //         +--//--+---+---+---+---+ | 
|  | 568 | //                      |       | | 
|  | 569 | //                      |       +---  0=Instance is down. | 
|  | 570 | //                      |             1=Instance is up. | 
|  | 571 | //                      | | 
|  | 572 | //                      +-----------  0=Server is switched ON. | 
|  | 573 | //                                    1=Server is switched OFF. | 
|  | 574 | //                                    (note reverse logic.) | 
|  | 575 | // | 
|  | 576 | // Unused bits should be ignored when read, and written as 0 for future compatibility. | 
|  | 577 | // | 
|  | 578 | // OsExtStsTaskState and OsExtStsVmState work together | 
|  | 579 | // to provide visibility in the provisioning process for the instance. | 
|  | 580 | // Consult Rackspace documentation at | 
|  | 581 | // http://docs.rackspace.com/servers/api/v2/cs-devguide/content/ch_extensions.html#ext_status | 
|  | 582 | // for more details.  It's too lengthy to include here. | 
| Samuel A. Falvo II | 2e2b877 | 2013-07-04 15:40:15 -0700 | [diff] [blame] | 583 | type Server struct { | 
| Samuel A. Falvo II | 5d20fbf | 2014-02-08 16:41:54 -0800 | [diff] [blame] | 584 | AccessIPv4         string            `json:"accessIPv4"` | 
|  | 585 | AccessIPv6         string            `json:"accessIPv6"` | 
|  | 586 | Addresses          AddressSet        `json:"addresses"` | 
| Mark Peek | 22efb6c | 2013-08-26 13:50:22 -0700 | [diff] [blame] | 587 | Created            string            `json:"created"` | 
|  | 588 | Flavor             FlavorLink        `json:"flavor"` | 
|  | 589 | HostId             string            `json:"hostId"` | 
|  | 590 | Id                 string            `json:"id"` | 
|  | 591 | Image              ImageLink         `json:"image"` | 
|  | 592 | Links              []Link            `json:"links"` | 
|  | 593 | Metadata           map[string]string `json:"metadata"` | 
|  | 594 | Name               string            `json:"name"` | 
|  | 595 | Progress           int               `json:"progress"` | 
|  | 596 | Status             string            `json:"status"` | 
|  | 597 | TenantId           string            `json:"tenant_id"` | 
|  | 598 | Updated            string            `json:"updated"` | 
|  | 599 | UserId             string            `json:"user_id"` | 
|  | 600 | OsDcfDiskConfig    string            `json:"OS-DCF:diskConfig"` | 
|  | 601 | RaxBandwidth       []RaxBandwidth    `json:"rax-bandwidth:bandwidth"` | 
|  | 602 | OsExtStsPowerState int               `json:"OS-EXT-STS:power_state"` | 
|  | 603 | OsExtStsTaskState  string            `json:"OS-EXT-STS:task_state"` | 
|  | 604 | OsExtStsVmState    string            `json:"OS-EXT-STS:vm_state"` | 
| Samuel A. Falvo II | 2e2b877 | 2013-07-04 15:40:15 -0700 | [diff] [blame] | 605 | } | 
| Samuel A. Falvo II | e91ff6d | 2013-07-11 15:46:10 -0700 | [diff] [blame] | 606 |  | 
| Samuel A. Falvo II | 72ac2dd | 2013-07-31 13:45:05 -0700 | [diff] [blame] | 607 | // NewServerSettings structures record those fields of the Server structure to change | 
|  | 608 | // when updating a server (see UpdateServer method). | 
|  | 609 | type NewServerSettings struct { | 
| Samuel A. Falvo II | 20f1aa4 | 2013-07-31 14:32:03 -0700 | [diff] [blame] | 610 | Name       string `json:"name,omitempty"` | 
| Samuel A. Falvo II | 72ac2dd | 2013-07-31 13:45:05 -0700 | [diff] [blame] | 611 | AccessIPv4 string `json:"accessIPv4,omitempty"` | 
|  | 612 | AccessIPv6 string `json:"accessIPv6,omitempty"` | 
|  | 613 | } | 
|  | 614 |  | 
| Samuel A. Falvo II | e91ff6d | 2013-07-11 15:46:10 -0700 | [diff] [blame] | 615 | // NewServer structures are used for both requests and responses. | 
|  | 616 | // The fields discussed below are relevent for server-creation purposes. | 
|  | 617 | // | 
|  | 618 | // The Name field contains the desired name of the server. | 
|  | 619 | // Note that (at present) Rackspace permits more than one server with the same name; | 
|  | 620 | // however, software should not depend on this. | 
|  | 621 | // Not only will Rackspace support thank you, so will your own devops engineers. | 
|  | 622 | // A name is required. | 
|  | 623 | // | 
|  | 624 | // The ImageRef field contains the ID of the desired software image to place on the server. | 
|  | 625 | // This ID must be found in the image slice returned by the Images() function. | 
|  | 626 | // This field is required. | 
|  | 627 | // | 
|  | 628 | // The FlavorRef field contains the ID of the server configuration desired for deployment. | 
|  | 629 | // This ID must be found in the flavor slice returned by the Flavors() function. | 
|  | 630 | // This field is required. | 
|  | 631 | // | 
|  | 632 | // For OsDcfDiskConfig, refer to the Image or Server structure documentation. | 
|  | 633 | // This field defaults to "AUTO" if not explicitly provided. | 
|  | 634 | // | 
|  | 635 | // Metadata contains a small key/value association of arbitrary data. | 
|  | 636 | // Neither Rackspace nor OpenStack places significance on this field in any way. | 
|  | 637 | // This field defaults to an empty map if not provided. | 
|  | 638 | // | 
|  | 639 | // Personality specifies the contents of certain files in the server's filesystem. | 
|  | 640 | // The files and their contents are mapped through a slice of FileConfig structures. | 
|  | 641 | // If not provided, all filesystem entities retain their image-specific configuration. | 
|  | 642 | // | 
|  | 643 | // Networks specifies an affinity for the server's various networks and interfaces. | 
|  | 644 | // Networks are identified through UUIDs; see NetworkConfig structure documentation for more details. | 
|  | 645 | // If not provided, network affinity is determined automatically. | 
|  | 646 | // | 
|  | 647 | // The AdminPass field may be used to provide a root- or administrator-password | 
|  | 648 | // during the server provisioning process. | 
|  | 649 | // If not provided, a random password will be automatically generated and returned in this field. | 
|  | 650 | // | 
|  | 651 | // The following fields are intended to be used to communicate certain results about the server being provisioned. | 
|  | 652 | // When attempting to create a new server, these fields MUST not be provided. | 
|  | 653 | // They'll be filled in by the response received from the Rackspace APIs. | 
|  | 654 | // | 
|  | 655 | // The Id field contains the server's unique identifier. | 
|  | 656 | // The identifier's scope is best assumed to be bound by the user's account, unless other arrangements have been made with Rackspace. | 
|  | 657 | // | 
|  | 658 | // Any Links provided are used to refer to the server specifically by URL. | 
|  | 659 | // These links are useful for making additional REST calls not explicitly supported by Gorax. | 
|  | 660 | type NewServer struct { | 
| Samuel A. Falvo II | 5d20fbf | 2014-02-08 16:41:54 -0800 | [diff] [blame] | 661 | Name            string            `json:"name,omitempty"` | 
|  | 662 | ImageRef        string            `json:"imageRef,omitempty"` | 
|  | 663 | FlavorRef       string            `json:"flavorRef,omitempty"` | 
|  | 664 | Metadata        map[string]string `json:"metadata,omitempty"` | 
|  | 665 | Personality     []FileConfig      `json:"personality,omitempty"` | 
|  | 666 | Networks        []NetworkConfig   `json:"networks,omitempty"` | 
|  | 667 | AdminPass       string            `json:"adminPass,omitempty"` | 
|  | 668 | KeyPairName     string            `json:"key_name,omitempty"` | 
|  | 669 | Id              string            `json:"id,omitempty"` | 
|  | 670 | Links           []Link            `json:"links,omitempty"` | 
|  | 671 | OsDcfDiskConfig string            `json:"OS-DCF:diskConfig,omitempty"` | 
| Samuel A. Falvo II | e91ff6d | 2013-07-11 15:46:10 -0700 | [diff] [blame] | 672 | } | 
| Samuel A. Falvo II | 8512e9a | 2013-07-26 22:53:29 -0700 | [diff] [blame] | 673 |  | 
|  | 674 | // ResizeRequest structures are used internally to encode to JSON the parameters required to resize a server instance. | 
|  | 675 | // Client applications will not use this structure (no API accepts an instance of this structure). | 
|  | 676 | // See the Region method ResizeServer() for more details on how to resize a server. | 
|  | 677 | type ResizeRequest struct { | 
| Samuel A. Falvo II | 20f1aa4 | 2013-07-31 14:32:03 -0700 | [diff] [blame] | 678 | Name       string `json:"name,omitempty"` | 
|  | 679 | FlavorRef  string `json:"flavorRef"` | 
|  | 680 | DiskConfig string `json:"OS-DCF:diskConfig,omitempty"` | 
| Samuel A. Falvo II | 8512e9a | 2013-07-26 22:53:29 -0700 | [diff] [blame] | 681 | } | 
| Mark Peek | 6b57c23 | 2013-08-24 19:03:26 -0700 | [diff] [blame] | 682 |  | 
|  | 683 | type CreateImage struct { | 
|  | 684 | Name     string            `json:"name"` | 
|  | 685 | Metadata map[string]string `json:"metadata,omitempty"` | 
|  | 686 | } |