github.com/gophercloud/gophercloud@v1.11.0/internal/acceptance/openstack/clustering/v1/clustering.go (about) 1 package v1 2 3 import ( 4 "fmt" 5 "net/http" 6 "strings" 7 "testing" 8 9 "github.com/gophercloud/gophercloud" 10 "github.com/gophercloud/gophercloud/internal/acceptance/clients" 11 "github.com/gophercloud/gophercloud/internal/acceptance/tools" 12 "github.com/gophercloud/gophercloud/openstack/clustering/v1/actions" 13 "github.com/gophercloud/gophercloud/openstack/clustering/v1/clusters" 14 "github.com/gophercloud/gophercloud/openstack/clustering/v1/nodes" 15 "github.com/gophercloud/gophercloud/openstack/clustering/v1/policies" 16 "github.com/gophercloud/gophercloud/openstack/clustering/v1/profiles" 17 "github.com/gophercloud/gophercloud/openstack/clustering/v1/receivers" 18 th "github.com/gophercloud/gophercloud/testhelper" 19 ) 20 21 var TestPolicySpec = policies.Spec{ 22 Description: "new policy description", 23 Properties: map[string]interface{}{ 24 "destroy_after_deletion": true, 25 "grace_period": 60, 26 "reduce_desired_capacity": false, 27 "criteria": "OLDEST_FIRST", 28 }, 29 Type: "senlin.policy.deletion", 30 Version: "1.1", 31 } 32 33 // CreateCluster creates a random cluster. An error will be returned if 34 // the cluster could not be created. 35 func CreateCluster(t *testing.T, client *gophercloud.ServiceClient, profileID string) (*clusters.Cluster, error) { 36 name := tools.RandomString("TESTACC-", 8) 37 t.Logf("Attempting to create cluster: %s", name) 38 39 createOpts := clusters.CreateOpts{ 40 Name: name, 41 DesiredCapacity: 1, 42 ProfileID: profileID, 43 MinSize: new(int), 44 MaxSize: 20, 45 Timeout: 3600, 46 Metadata: map[string]interface{}{ 47 "foo": "bar", 48 "test": map[string]interface{}{ 49 "nil_interface": interface{}(nil), 50 "float_value": float64(123.3), 51 "string_value": "test_string", 52 "bool_value": false, 53 }, 54 }, 55 Config: map[string]interface{}{}, 56 } 57 58 res := clusters.Create(client, createOpts) 59 if res.Err != nil { 60 return nil, res.Err 61 } 62 63 requestID := res.Header.Get("X-OpenStack-Request-Id") 64 th.AssertEquals(t, true, requestID != "") 65 t.Logf("Cluster %s request ID: %s", name, requestID) 66 67 actionID, err := GetActionID(res.Header) 68 th.AssertNoErr(t, err) 69 th.AssertEquals(t, true, actionID != "") 70 t.Logf("Cluster %s action ID: %s", name, actionID) 71 72 err = WaitForAction(client, actionID) 73 if err != nil { 74 return nil, err 75 } 76 77 cluster, err := res.Extract() 78 if err != nil { 79 return nil, err 80 } 81 82 t.Logf("Successfully created cluster: %s", cluster.ID) 83 84 tools.PrintResource(t, cluster) 85 tools.PrintResource(t, cluster.CreatedAt) 86 87 th.AssertEquals(t, name, cluster.Name) 88 th.AssertEquals(t, profileID, cluster.ProfileID) 89 90 return cluster, nil 91 } 92 93 // CreateNode creates a random node. An error will be returned if 94 // the node could not be created. 95 func CreateNode(t *testing.T, client *gophercloud.ServiceClient, clusterID, profileID string) (*nodes.Node, error) { 96 name := tools.RandomString("TESTACC-", 8) 97 t.Logf("Attempting to create node: %s", name) 98 99 createOpts := nodes.CreateOpts{ 100 ClusterID: clusterID, 101 Metadata: map[string]interface{}{ 102 "foo": "bar", 103 "test": map[string]interface{}{ 104 "nil_interface": interface{}(nil), 105 "float_value": float64(123.3), 106 "string_value": "test_string", 107 "bool_value": false, 108 }, 109 }, 110 Name: name, 111 ProfileID: profileID, 112 Role: "", 113 } 114 115 res := nodes.Create(client, createOpts) 116 if res.Err != nil { 117 return nil, res.Err 118 } 119 120 requestID := res.Header.Get("X-OpenStack-Request-Id") 121 th.AssertEquals(t, true, requestID != "") 122 t.Logf("Node %s request ID: %s", name, requestID) 123 124 actionID, err := GetActionID(res.Header) 125 th.AssertNoErr(t, err) 126 th.AssertEquals(t, true, actionID != "") 127 t.Logf("Node %s action ID: %s", name, actionID) 128 129 err = WaitForAction(client, actionID) 130 if err != nil { 131 return nil, err 132 } 133 134 node, err := res.Extract() 135 if err != nil { 136 return nil, err 137 } 138 139 err = WaitForNodeStatus(client, node.ID, "ACTIVE") 140 if err != nil { 141 return nil, err 142 } 143 144 t.Logf("Successfully created node: %s", node.ID) 145 146 node, err = nodes.Get(client, node.ID).Extract() 147 if err != nil { 148 return nil, err 149 } 150 151 tools.PrintResource(t, node) 152 tools.PrintResource(t, node.CreatedAt) 153 154 th.AssertEquals(t, profileID, node.ProfileID) 155 th.AssertEquals(t, clusterID, node.ClusterID) 156 th.AssertDeepEquals(t, createOpts.Metadata, node.Metadata) 157 158 return node, nil 159 } 160 161 // CreatePolicy creates a random policy. An error will be returned if the 162 // policy could not be created. 163 func CreatePolicy(t *testing.T, client *gophercloud.ServiceClient) (*policies.Policy, error) { 164 name := tools.RandomString("TESTACC-", 8) 165 t.Logf("Attempting to create policy: %s", name) 166 167 createOpts := policies.CreateOpts{ 168 Name: name, 169 Spec: TestPolicySpec, 170 } 171 172 res := policies.Create(client, createOpts) 173 if res.Err != nil { 174 return nil, res.Err 175 } 176 177 requestID := res.Header.Get("X-OpenStack-Request-Id") 178 th.AssertEquals(t, true, requestID != "") 179 180 t.Logf("Policy %s request ID: %s", name, requestID) 181 182 policy, err := res.Extract() 183 if err != nil { 184 return nil, err 185 } 186 187 t.Logf("Successfully created policy: %s", policy.ID) 188 189 tools.PrintResource(t, policy) 190 tools.PrintResource(t, policy.CreatedAt) 191 192 th.AssertEquals(t, name, policy.Name) 193 194 return policy, nil 195 } 196 197 // CreateProfile will create a random profile. An error will be returned if the 198 // profile could not be created. 199 func CreateProfile(t *testing.T, client *gophercloud.ServiceClient) (*profiles.Profile, error) { 200 choices, err := clients.AcceptanceTestChoicesFromEnv() 201 if err != nil { 202 return nil, err 203 } 204 205 name := tools.RandomString("TESTACC-", 8) 206 t.Logf("Attempting to create profile: %s", name) 207 208 networks := []map[string]interface{}{ 209 {"network": choices.NetworkName}, 210 } 211 212 props := map[string]interface{}{ 213 "name": name, 214 "flavor": choices.FlavorID, 215 "image": choices.ImageID, 216 "networks": networks, 217 "security_groups": "", 218 } 219 220 createOpts := profiles.CreateOpts{ 221 Name: name, 222 Spec: profiles.Spec{ 223 Type: "os.nova.server", 224 Version: "1.0", 225 Properties: props, 226 }, 227 } 228 229 res := profiles.Create(client, createOpts) 230 if res.Err != nil { 231 return nil, res.Err 232 } 233 234 requestID := res.Header.Get("X-OpenStack-Request-Id") 235 th.AssertEquals(t, true, requestID != "") 236 237 t.Logf("Profile %s request ID: %s", name, requestID) 238 239 profile, err := res.Extract() 240 if err != nil { 241 return nil, err 242 } 243 244 t.Logf("Successfully created profile: %s", profile.ID) 245 246 tools.PrintResource(t, profile) 247 tools.PrintResource(t, profile.CreatedAt) 248 249 th.AssertEquals(t, name, profile.Name) 250 th.AssertEquals(t, profile.Spec.Type, "os.nova.server") 251 th.AssertEquals(t, profile.Spec.Version, "1.0") 252 253 return profile, nil 254 } 255 256 // CreateWebhookReceiver will create a random webhook receiver. An error will be returned if the 257 // receiver could not be created. 258 func CreateWebhookReceiver(t *testing.T, client *gophercloud.ServiceClient, clusterID string) (*receivers.Receiver, error) { 259 name := tools.RandomString("TESTACC-", 8) 260 t.Logf("Attempting to create receiver: %s", name) 261 262 createOpts := receivers.CreateOpts{ 263 Name: name, 264 ClusterID: clusterID, 265 Type: receivers.WebhookReceiver, 266 Action: "CLUSTER_SCALE_OUT", 267 } 268 269 res := receivers.Create(client, createOpts) 270 if res.Err != nil { 271 return nil, res.Err 272 } 273 274 receiver, err := res.Extract() 275 if err != nil { 276 return nil, err 277 } 278 279 t.Logf("Successfully created webhook receiver: %s", receiver.ID) 280 281 tools.PrintResource(t, receiver) 282 tools.PrintResource(t, receiver.CreatedAt) 283 284 th.AssertEquals(t, name, receiver.Name) 285 th.AssertEquals(t, createOpts.Action, receiver.Action) 286 287 return receiver, nil 288 } 289 290 // CreateMessageReceiver will create a message receiver with a random name. An error will be returned if the 291 // receiver could not be created. 292 func CreateMessageReceiver(t *testing.T, client *gophercloud.ServiceClient, clusterID string) (*receivers.Receiver, error) { 293 name := tools.RandomString("TESTACC-", 8) 294 t.Logf("Attempting to create receiver: %s", name) 295 296 createOpts := receivers.CreateOpts{ 297 Name: name, 298 ClusterID: clusterID, 299 Type: receivers.MessageReceiver, 300 } 301 302 res := receivers.Create(client, createOpts) 303 if res.Err != nil { 304 return nil, res.Err 305 } 306 307 receiver, err := res.Extract() 308 if err != nil { 309 return nil, err 310 } 311 312 t.Logf("Successfully created message receiver: %s", receiver.ID) 313 314 tools.PrintResource(t, receiver) 315 tools.PrintResource(t, receiver.CreatedAt) 316 317 th.AssertEquals(t, name, receiver.Name) 318 th.AssertEquals(t, createOpts.Action, receiver.Action) 319 320 return receiver, nil 321 } 322 323 // DeleteCluster will delete a given policy. A fatal error will occur if the 324 // cluster could not be deleted. This works best as a deferred function. 325 func DeleteCluster(t *testing.T, client *gophercloud.ServiceClient, id string) { 326 t.Logf("Attempting to delete cluster: %s", id) 327 328 res := clusters.Delete(client, id) 329 if res.Err != nil { 330 t.Fatalf("Error deleting cluster %s: %s:", id, res.Err) 331 } 332 333 actionID, err := GetActionID(res.Header) 334 if err != nil { 335 t.Fatalf("Error deleting cluster %s: %s:", id, res.Err) 336 } 337 338 err = WaitForAction(client, actionID) 339 if err != nil { 340 t.Fatalf("Error deleting cluster %s: %s:", id, res.Err) 341 } 342 343 t.Logf("Successfully deleted cluster: %s", id) 344 345 return 346 } 347 348 // DeleteNode will delete a given node. A fatal error will occur if the 349 // node could not be deleted. This works best as a deferred function. 350 func DeleteNode(t *testing.T, client *gophercloud.ServiceClient, id string) { 351 t.Logf("Attempting to delete node: %s", id) 352 353 res := nodes.Delete(client, id) 354 if res.Err != nil { 355 t.Fatalf("Error deleting node %s: %s:", id, res.Err) 356 } 357 358 actionID, err := GetActionID(res.Header) 359 if err != nil { 360 t.Fatalf("Error getting actionID %s: %s:", id, err) 361 } 362 363 err = WaitForAction(client, actionID) 364 365 if err != nil { 366 t.Fatalf("Error deleting node %s: %s", id, err) 367 } 368 369 t.Logf("Successfully deleted node: %s", id) 370 371 return 372 } 373 374 // DeletePolicy will delete a given policy. A fatal error will occur if the 375 // policy could not be deleted. This works best as a deferred function. 376 func DeletePolicy(t *testing.T, client *gophercloud.ServiceClient, id string) { 377 t.Logf("Attempting to delete policy: %s", id) 378 379 err := policies.Delete(client, id).ExtractErr() 380 if err != nil { 381 t.Fatalf("Error deleting policy %s: %s:", id, err) 382 } 383 384 t.Logf("Successfully deleted policy: %s", id) 385 386 return 387 } 388 389 // DeleteProfile will delete a given profile. A fatal error will occur if the 390 // profile could not be deleted. This works best as a deferred function. 391 func DeleteProfile(t *testing.T, client *gophercloud.ServiceClient, id string) { 392 t.Logf("Attempting to delete profile: %s", id) 393 394 err := profiles.Delete(client, id).ExtractErr() 395 if err != nil { 396 t.Fatalf("Error deleting profile %s: %s:", id, err) 397 } 398 399 t.Logf("Successfully deleted profile: %s", id) 400 401 return 402 } 403 404 // DeleteReceiver will delete a given receiver. A fatal error will occur if the 405 // receiver could not be deleted. This works best as a deferred function. 406 func DeleteReceiver(t *testing.T, client *gophercloud.ServiceClient, id string) { 407 t.Logf("Attempting to delete Receiver: %s", id) 408 409 res := receivers.Delete(client, id) 410 if res.Err != nil { 411 t.Fatalf("Error deleting receiver %s: %s:", id, res.Err) 412 } 413 414 t.Logf("Successfully deleted receiver: %s", id) 415 416 return 417 } 418 419 // GetActionID parses an HTTP header and returns the action ID. 420 func GetActionID(headers http.Header) (string, error) { 421 location := headers.Get("Location") 422 v := strings.Split(location, "actions/") 423 if len(v) < 2 { 424 return "", fmt.Errorf("unable to determine action ID") 425 } 426 427 actionID := v[1] 428 429 return actionID, nil 430 } 431 432 func WaitForAction(client *gophercloud.ServiceClient, actionID string) error { 433 return tools.WaitFor(func() (bool, error) { 434 action, err := actions.Get(client, actionID).Extract() 435 if err != nil { 436 return false, err 437 } 438 439 if action.Status == "SUCCEEDED" { 440 return true, nil 441 } 442 443 if action.Status == "FAILED" { 444 return false, fmt.Errorf("Action %s in FAILED state", actionID) 445 } 446 447 return false, nil 448 }) 449 } 450 451 func WaitForNodeStatus(client *gophercloud.ServiceClient, id string, status string) error { 452 return tools.WaitFor(func() (bool, error) { 453 latest, err := nodes.Get(client, id).Extract() 454 if err != nil { 455 if _, ok := err.(gophercloud.ErrDefault404); ok && status == "DELETED" { 456 return true, nil 457 } 458 459 return false, err 460 } 461 462 if latest.Status == status { 463 return true, nil 464 } 465 466 if latest.Status == "ERROR" { 467 return false, fmt.Errorf("Node %s in ERROR state", id) 468 } 469 470 return false, nil 471 }) 472 }