github.com/gophercloud/gophercloud@v1.11.0/openstack/clustering/v1/clusters/requests.go (about) 1 package clusters 2 3 import ( 4 "fmt" 5 6 "github.com/gophercloud/gophercloud" 7 "github.com/gophercloud/gophercloud/pagination" 8 ) 9 10 // AdjustmentType represents valid values for resizing a cluster. 11 type AdjustmentType string 12 13 const ( 14 ExactCapacityAdjustment AdjustmentType = "EXACT_CAPACITY" 15 ChangeInCapacityAdjustment AdjustmentType = "CHANGE_IN_CAPACITY" 16 ChangeInPercentageAdjustment AdjustmentType = "CHANGE_IN_PERCENTAGE" 17 ) 18 19 // RecoveryAction represents valid values for recovering a cluster. 20 type RecoveryAction string 21 22 const ( 23 RebootRecovery RecoveryAction = "REBOOT" 24 RebuildRecovery RecoveryAction = "REBUILD" 25 RecreateRecovery RecoveryAction = "RECREATE" 26 ) 27 28 // CreateOptsBuilder allows extensions to add additional parameters 29 // to the Create request. 30 type CreateOptsBuilder interface { 31 ToClusterCreateMap() (map[string]interface{}, error) 32 } 33 34 // CreateOpts represents options used to create a cluster. 35 type CreateOpts struct { 36 Name string `json:"name" required:"true"` 37 DesiredCapacity int `json:"desired_capacity"` 38 ProfileID string `json:"profile_id" required:"true"` 39 MinSize *int `json:"min_size,omitempty"` 40 Timeout int `json:"timeout,omitempty"` 41 MaxSize int `json:"max_size,omitempty"` 42 Metadata map[string]interface{} `json:"metadata,omitempty"` 43 Config map[string]interface{} `json:"config,omitempty"` 44 } 45 46 // ToClusterCreateMap constructs a request body from CreateOpts. 47 func (opts CreateOpts) ToClusterCreateMap() (map[string]interface{}, error) { 48 return gophercloud.BuildRequestBody(opts, "cluster") 49 } 50 51 // Create requests the creation of a new cluster. 52 func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { 53 b, err := opts.ToClusterCreateMap() 54 if err != nil { 55 r.Err = err 56 return 57 } 58 resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ 59 OkCodes: []int{200, 201, 202}, 60 }) 61 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 62 return 63 } 64 65 // Get retrieves details of a single cluster. 66 func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { 67 resp, err := client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{ 68 OkCodes: []int{200}, 69 }) 70 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 71 return 72 } 73 74 // ListOptsBuilder allows extensions to add additional parameters to 75 // the List request. 76 type ListOptsBuilder interface { 77 ToClusterListQuery() (string, error) 78 } 79 80 // ListOpts represents options to list clusters. 81 type ListOpts struct { 82 Limit int `q:"limit"` 83 Marker string `q:"marker"` 84 Sort string `q:"sort"` 85 GlobalProject *bool `q:"global_project"` 86 Name string `q:"name,omitempty"` 87 Status string `q:"status,omitempty"` 88 } 89 90 // ToClusterListQuery formats a ListOpts into a query string. 91 func (opts ListOpts) ToClusterListQuery() (string, error) { 92 q, err := gophercloud.BuildQueryString(opts) 93 return q.String(), err 94 } 95 96 // List instructs OpenStack to provide a list of clusters. 97 func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { 98 url := listURL(client) 99 if opts != nil { 100 query, err := opts.ToClusterListQuery() 101 if err != nil { 102 return pagination.Pager{Err: err} 103 } 104 url += query 105 } 106 107 return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { 108 return ClusterPage{pagination.LinkedPageBase{PageResult: r}} 109 }) 110 } 111 112 // UpdateOptsBuilder allows extensions to add additional parameters to the 113 // Update request. 114 type UpdateOptsBuilder interface { 115 ToClusterUpdateMap() (map[string]interface{}, error) 116 } 117 118 // UpdateOpts represents options to update a cluster. 119 type UpdateOpts struct { 120 Config string `json:"config,omitempty"` 121 Name string `json:"name,omitempty"` 122 ProfileID string `json:"profile_id,omitempty"` 123 Timeout *int `json:"timeout,omitempty"` 124 Metadata map[string]interface{} `json:"metadata,omitempty"` 125 ProfileOnly *bool `json:"profile_only,omitempty"` 126 } 127 128 // ToClusterUpdateMap assembles a request body based on the contents of 129 // UpdateOpts. 130 func (opts UpdateOpts) ToClusterUpdateMap() (map[string]interface{}, error) { 131 b, err := gophercloud.BuildRequestBody(opts, "cluster") 132 if err != nil { 133 return nil, err 134 } 135 return b, nil 136 } 137 138 // Update will update an existing cluster. 139 func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { 140 b, err := opts.ToClusterUpdateMap() 141 if err != nil { 142 r.Err = err 143 return r 144 } 145 resp, err := client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ 146 OkCodes: []int{200, 202}, 147 }) 148 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 149 return 150 } 151 152 // Delete deletes the specified cluster ID. 153 func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { 154 resp, err := client.Delete(deleteURL(client, id), nil) 155 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 156 return 157 } 158 159 // ResizeOptsBuilder allows extensions to add additional parameters to the 160 // resize request. 161 type ResizeOptsBuilder interface { 162 ToClusterResizeMap() (map[string]interface{}, error) 163 } 164 165 // ResizeOpts represents options for resizing a cluster. 166 type ResizeOpts struct { 167 AdjustmentType AdjustmentType `json:"adjustment_type,omitempty"` 168 Number interface{} `json:"number,omitempty"` 169 MinSize *int `json:"min_size,omitempty"` 170 MaxSize *int `json:"max_size,omitempty"` 171 MinStep *int `json:"min_step,omitempty"` 172 Strict *bool `json:"strict,omitempty"` 173 } 174 175 // ToClusterResizeMap constructs a request body from ResizeOpts. 176 func (opts ResizeOpts) ToClusterResizeMap() (map[string]interface{}, error) { 177 if opts.AdjustmentType != "" && opts.Number == nil { 178 return nil, fmt.Errorf("Number field MUST NOT be empty when AdjustmentType field used") 179 } 180 181 switch opts.Number.(type) { 182 case nil, int, int32, int64: 183 // Valid type. Always allow 184 case float32, float64: 185 if opts.AdjustmentType != ChangeInPercentageAdjustment { 186 return nil, fmt.Errorf("Only ChangeInPercentageAdjustment allows float value for Number field") 187 } 188 default: 189 return nil, fmt.Errorf("Number field must be either int, float, or omitted") 190 } 191 192 return gophercloud.BuildRequestBody(opts, "resize") 193 } 194 195 func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) { 196 b, err := opts.ToClusterResizeMap() 197 if err != nil { 198 r.Err = err 199 return 200 } 201 resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ 202 OkCodes: []int{200, 201, 202}, 203 }) 204 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 205 return 206 } 207 208 // ScaleInOptsBuilder allows extensions to add additional parameters to the 209 // ScaleIn request. 210 type ScaleInOptsBuilder interface { 211 ToClusterScaleInMap() (map[string]interface{}, error) 212 } 213 214 // ScaleInOpts represents options used to scale-in a cluster. 215 type ScaleInOpts struct { 216 Count *int `json:"count,omitempty"` 217 } 218 219 // ToClusterScaleInMap constructs a request body from ScaleInOpts. 220 func (opts ScaleInOpts) ToClusterScaleInMap() (map[string]interface{}, error) { 221 return gophercloud.BuildRequestBody(opts, "scale_in") 222 } 223 224 // ScaleIn will reduce the capacity of a cluster. 225 func ScaleIn(client *gophercloud.ServiceClient, id string, opts ScaleInOptsBuilder) (r ActionResult) { 226 b, err := opts.ToClusterScaleInMap() 227 if err != nil { 228 r.Err = err 229 return 230 } 231 resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ 232 OkCodes: []int{200, 201, 202}, 233 }) 234 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 235 return 236 } 237 238 // ScaleOutOptsBuilder allows extensions to add additional parameters to the 239 // ScaleOut request. 240 type ScaleOutOptsBuilder interface { 241 ToClusterScaleOutMap() (map[string]interface{}, error) 242 } 243 244 // ScaleOutOpts represents options used to scale-out a cluster. 245 type ScaleOutOpts struct { 246 Count int `json:"count,omitempty"` 247 } 248 249 // ToClusterScaleOutMap constructs a request body from ScaleOutOpts. 250 func (opts ScaleOutOpts) ToClusterScaleOutMap() (map[string]interface{}, error) { 251 return gophercloud.BuildRequestBody(opts, "scale_out") 252 } 253 254 // ScaleOut will increase the capacity of a cluster. 255 func ScaleOut(client *gophercloud.ServiceClient, id string, opts ScaleOutOptsBuilder) (r ActionResult) { 256 b, err := opts.ToClusterScaleOutMap() 257 if err != nil { 258 r.Err = err 259 return 260 } 261 resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ 262 OkCodes: []int{200, 201, 202}, 263 }) 264 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 265 return 266 } 267 268 // AttachPolicyOptsBuilder allows extensions to add additional parameters to the 269 // AttachPolicy request. 270 type AttachPolicyOptsBuilder interface { 271 ToClusterAttachPolicyMap() (map[string]interface{}, error) 272 } 273 274 // PolicyOpts params 275 type AttachPolicyOpts struct { 276 PolicyID string `json:"policy_id" required:"true"` 277 Enabled *bool `json:"enabled,omitempty"` 278 } 279 280 // ToClusterAttachPolicyMap constructs a request body from AttachPolicyOpts. 281 func (opts AttachPolicyOpts) ToClusterAttachPolicyMap() (map[string]interface{}, error) { 282 return gophercloud.BuildRequestBody(opts, "policy_attach") 283 } 284 285 // Attach Policy will attach a policy to a cluster. 286 func AttachPolicy(client *gophercloud.ServiceClient, id string, opts AttachPolicyOptsBuilder) (r ActionResult) { 287 b, err := opts.ToClusterAttachPolicyMap() 288 if err != nil { 289 r.Err = err 290 return 291 } 292 resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ 293 OkCodes: []int{200, 201, 202}, 294 }) 295 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 296 return 297 } 298 299 // UpdatePolicyOptsBuilder allows extensions to add additional parameters to the 300 // UpdatePolicy request. 301 type UpdatePolicyOptsBuilder interface { 302 ToClusterUpdatePolicyMap() (map[string]interface{}, error) 303 } 304 305 // UpdatePolicyOpts represents options used to update a cluster policy. 306 type UpdatePolicyOpts struct { 307 PolicyID string `json:"policy_id" required:"true"` 308 Enabled *bool `json:"enabled,omitempty" required:"true"` 309 } 310 311 // ToClusterUpdatePolicyMap constructs a request body from UpdatePolicyOpts. 312 func (opts UpdatePolicyOpts) ToClusterUpdatePolicyMap() (map[string]interface{}, error) { 313 return gophercloud.BuildRequestBody(opts, "policy_update") 314 } 315 316 // UpdatePolicy will update a cluster's policy. 317 func UpdatePolicy(client *gophercloud.ServiceClient, id string, opts UpdatePolicyOptsBuilder) (r ActionResult) { 318 b, err := opts.ToClusterUpdatePolicyMap() 319 if err != nil { 320 r.Err = err 321 return 322 } 323 resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ 324 OkCodes: []int{200, 201, 202}, 325 }) 326 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 327 return 328 } 329 330 // DetachPolicyOptsBuilder allows extensions to add additional parameters to the 331 // DetachPolicy request. 332 type DetachPolicyOptsBuilder interface { 333 ToClusterDetachPolicyMap() (map[string]interface{}, error) 334 } 335 336 // DetachPolicyOpts represents options used to detach a policy from a cluster. 337 type DetachPolicyOpts struct { 338 PolicyID string `json:"policy_id" required:"true"` 339 } 340 341 // ToClusterDetachPolicyMap constructs a request body from DetachPolicyOpts. 342 func (opts DetachPolicyOpts) ToClusterDetachPolicyMap() (map[string]interface{}, error) { 343 return gophercloud.BuildRequestBody(opts, "policy_detach") 344 } 345 346 // DetachPolicy will detach a policy from a cluster. 347 func DetachPolicy(client *gophercloud.ServiceClient, id string, opts DetachPolicyOptsBuilder) (r ActionResult) { 348 b, err := opts.ToClusterDetachPolicyMap() 349 if err != nil { 350 r.Err = err 351 return 352 } 353 resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ 354 OkCodes: []int{200, 201, 202}, 355 }) 356 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 357 return 358 } 359 360 // ListPolicyOptsBuilder allows extensions to add additional parameters to the 361 // ListPolicies request. 362 type ListPoliciesOptsBuilder interface { 363 ToClusterPoliciesListQuery() (string, error) 364 } 365 366 // ListPoliciesOpts represents options to list a cluster's policies. 367 type ListPoliciesOpts struct { 368 Enabled *bool `q:"enabled"` 369 Name string `q:"policy_name"` 370 Type string `q:"policy_type"` 371 Sort string `q:"sort"` 372 } 373 374 // ToClusterPoliciesListQuery formats a ListOpts into a query string. 375 func (opts ListPoliciesOpts) ToClusterPoliciesListQuery() (string, error) { 376 q, err := gophercloud.BuildQueryString(opts) 377 return q.String(), err 378 } 379 380 // ListPolicies instructs OpenStack to provide a list of policies for a cluster. 381 func ListPolicies(client *gophercloud.ServiceClient, clusterID string, opts ListPoliciesOptsBuilder) pagination.Pager { 382 url := listPoliciesURL(client, clusterID) 383 if opts != nil { 384 query, err := opts.ToClusterPoliciesListQuery() 385 if err != nil { 386 return pagination.Pager{Err: err} 387 } 388 url += query 389 } 390 391 return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { 392 return ClusterPolicyPage{pagination.SinglePageBase(r)} 393 }) 394 } 395 396 // GetPolicy retrieves details of a cluster policy. 397 func GetPolicy(client *gophercloud.ServiceClient, clusterID string, policyID string) (r GetPolicyResult) { 398 resp, err := client.Get(getPolicyURL(client, clusterID, policyID), &r.Body, nil) 399 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 400 return 401 } 402 403 // RecoverOptsBuilder allows extensions to add additional parameters to the 404 // Recover request. 405 type RecoverOptsBuilder interface { 406 ToClusterRecoverMap() (map[string]interface{}, error) 407 } 408 409 // RecoverOpts represents options used to recover a cluster. 410 type RecoverOpts struct { 411 Operation RecoveryAction `json:"operation,omitempty"` 412 Check *bool `json:"check,omitempty"` 413 CheckCapacity *bool `json:"check_capacity,omitempty"` 414 } 415 416 // ToClusterRecovermap constructs a request body from RecoverOpts. 417 func (opts RecoverOpts) ToClusterRecoverMap() (map[string]interface{}, error) { 418 return gophercloud.BuildRequestBody(opts, "recover") 419 } 420 421 // Recover implements cluster recover request. 422 func Recover(client *gophercloud.ServiceClient, id string, opts RecoverOptsBuilder) (r ActionResult) { 423 b, err := opts.ToClusterRecoverMap() 424 if err != nil { 425 r.Err = err 426 return 427 } 428 resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ 429 OkCodes: []int{200, 201, 202}, 430 }) 431 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 432 return 433 } 434 435 // Check will perform a health check on a cluster. 436 func Check(client *gophercloud.ServiceClient, id string) (r ActionResult) { 437 b := map[string]interface{}{ 438 "check": map[string]interface{}{}, 439 } 440 441 resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ 442 OkCodes: []int{200, 201, 202}, 443 }) 444 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 445 return 446 } 447 448 // ToClusterCompleteLifecycleMap constructs a request body from CompleteLifecycleOpts. 449 func (opts CompleteLifecycleOpts) ToClusterCompleteLifecycleMap() (map[string]interface{}, error) { 450 return gophercloud.BuildRequestBody(opts, "complete_lifecycle") 451 } 452 453 type CompleteLifecycleOpts struct { 454 LifecycleActionTokenID string `json:"lifecycle_action_token" required:"true"` 455 } 456 457 func CompleteLifecycle(client *gophercloud.ServiceClient, id string, opts CompleteLifecycleOpts) (r ActionResult) { 458 b, err := opts.ToClusterCompleteLifecycleMap() 459 if err != nil { 460 r.Err = err 461 return 462 } 463 464 resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ 465 OkCodes: []int{202}, 466 }) 467 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 468 return 469 } 470 471 func (opts AddNodesOpts) ToClusterAddNodeMap() (map[string]interface{}, error) { 472 return gophercloud.BuildRequestBody(opts, "add_nodes") 473 } 474 475 type AddNodesOpts struct { 476 Nodes []string `json:"nodes" required:"true"` 477 } 478 479 func AddNodes(client *gophercloud.ServiceClient, id string, opts AddNodesOpts) (r ActionResult) { 480 b, err := opts.ToClusterAddNodeMap() 481 if err != nil { 482 r.Err = err 483 return 484 } 485 resp, err := client.Post(nodeURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ 486 OkCodes: []int{202}, 487 }) 488 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 489 return 490 } 491 492 func (opts RemoveNodesOpts) ToClusterRemoveNodeMap() (map[string]interface{}, error) { 493 return gophercloud.BuildRequestBody(opts, "del_nodes") 494 } 495 496 type RemoveNodesOpts struct { 497 Nodes []string `json:"nodes" required:"true"` 498 } 499 500 func RemoveNodes(client *gophercloud.ServiceClient, clusterID string, opts RemoveNodesOpts) (r ActionResult) { 501 b, err := opts.ToClusterRemoveNodeMap() 502 if err != nil { 503 r.Err = err 504 return 505 } 506 resp, err := client.Post(nodeURL(client, clusterID), b, &r.Body, &gophercloud.RequestOpts{ 507 OkCodes: []int{202}, 508 }) 509 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 510 return 511 } 512 513 func (opts ReplaceNodesOpts) ToClusterReplaceNodeMap() (map[string]interface{}, error) { 514 return gophercloud.BuildRequestBody(opts, "replace_nodes") 515 } 516 517 type ReplaceNodesOpts struct { 518 Nodes map[string]string `json:"nodes" required:"true"` 519 } 520 521 func ReplaceNodes(client *gophercloud.ServiceClient, id string, opts ReplaceNodesOpts) (r ActionResult) { 522 b, err := opts.ToClusterReplaceNodeMap() 523 if err != nil { 524 r.Err = err 525 return 526 } 527 resp, err := client.Post(nodeURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ 528 OkCodes: []int{202}, 529 }) 530 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 531 return 532 } 533 534 type CollectOptsBuilder interface { 535 ToClusterCollectMap() (string, error) 536 } 537 538 // CollectOpts represents options to collect attribute values across a cluster 539 type CollectOpts struct { 540 Path string `q:"path" required:"true"` 541 } 542 543 func (opts CollectOpts) ToClusterCollectMap() (string, error) { 544 return opts.Path, nil 545 } 546 547 // Collect instructs OpenStack to aggregate attribute values across a cluster 548 func Collect(client *gophercloud.ServiceClient, id string, opts CollectOptsBuilder) (r CollectResult) { 549 query, err := opts.ToClusterCollectMap() 550 if err != nil { 551 r.Err = err 552 return 553 } 554 resp, err := client.Get(collectURL(client, id, query), &r.Body, &gophercloud.RequestOpts{ 555 OkCodes: []int{200}, 556 }) 557 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 558 return 559 } 560 561 // OperationName represents valid values for cluster operation 562 type OperationName string 563 564 const ( 565 // Nova Profile Op Names 566 RebootOperation OperationName = "reboot" 567 RebuildOperation OperationName = "rebuild" 568 ChangePasswordOperation OperationName = "change_password" 569 PauseOperation OperationName = "pause" 570 UnpauseOperation OperationName = "unpause" 571 SuspendOperation OperationName = "suspend" 572 ResumeOperation OperationName = "resume" 573 LockOperation OperationName = "lock" 574 UnlockOperation OperationName = "unlock" 575 StartOperation OperationName = "start" 576 StopOperation OperationName = "stop" 577 RescueOperation OperationName = "rescue" 578 UnrescueOperation OperationName = "unrescue" 579 EvacuateOperation OperationName = "evacuate" 580 581 // Heat Pofile Op Names 582 AbandonOperation OperationName = "abandon" 583 ) 584 585 // ToClusterOperationMap constructs a request body from OperationOpts. 586 func (opts OperationOpts) ToClusterOperationMap() (map[string]interface{}, error) { 587 operationArg := struct { 588 Filters OperationFilters `json:"filters,omitempty"` 589 Params OperationParams `json:"params,omitempty"` 590 }{ 591 Filters: opts.Filters, 592 Params: opts.Params, 593 } 594 595 return gophercloud.BuildRequestBody(operationArg, string(opts.Operation)) 596 } 597 598 // OperationOptsBuilder allows extensions to add additional parameters to the 599 // Op request. 600 type OperationOptsBuilder interface { 601 ToClusterOperationMap() (map[string]interface{}, error) 602 } 603 type OperationFilters map[string]interface{} 604 type OperationParams map[string]interface{} 605 606 // OperationOpts represents options used to perform an operation on a cluster 607 type OperationOpts struct { 608 Operation OperationName `json:"operation" required:"true"` 609 Filters OperationFilters `json:"filters,omitempty"` 610 Params OperationParams `json:"params,omitempty"` 611 } 612 613 func Ops(client *gophercloud.ServiceClient, id string, opts OperationOptsBuilder) (r ActionResult) { 614 b, err := opts.ToClusterOperationMap() 615 if err != nil { 616 r.Err = err 617 return 618 } 619 resp, err := client.Post(opsURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ 620 OkCodes: []int{202}, 621 }) 622 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 623 return 624 }