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  }