1package v1
2
3import (
4	"fmt"
5	"math"
6	"strings"
7	"testing"
8	"time"
9
10	"github.com/gophercloud/gophercloud"
11	"github.com/gophercloud/gophercloud/acceptance/clients"
12	idv3 "github.com/gophercloud/gophercloud/acceptance/openstack/identity/v3"
13	"github.com/gophercloud/gophercloud/acceptance/tools"
14	"github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clusters"
15	"github.com/gophercloud/gophercloud/openstack/containerinfra/v1/clustertemplates"
16	"github.com/gophercloud/gophercloud/openstack/containerinfra/v1/quotas"
17	th "github.com/gophercloud/gophercloud/testhelper"
18)
19
20// CreateClusterTemplateCOE will create a random cluster template for the specified orchestration engine.
21// An error will be returned if the cluster template could not be created.
22func CreateClusterTemplateCOE(t *testing.T, client *gophercloud.ServiceClient, coe string) (*clustertemplates.ClusterTemplate, error) {
23	choices, err := clients.AcceptanceTestChoicesFromEnv()
24	if err != nil {
25		return nil, err
26	}
27
28	name := tools.RandomString("TESTACC-", 8)
29	t.Logf("Attempting to create %s cluster template: %s", coe, name)
30
31	boolFalse := false
32	createOpts := clustertemplates.CreateOpts{
33		COE:                 coe,
34		DNSNameServer:       "8.8.8.8",
35		DockerStorageDriver: "overlay2",
36		ExternalNetworkID:   choices.ExternalNetworkID,
37		FlavorID:            choices.FlavorID,
38		FloatingIPEnabled:   &boolFalse,
39		ImageID:             choices.MagnumImageID,
40		MasterFlavorID:      choices.FlavorID,
41		MasterLBEnabled:     &boolFalse,
42		Name:                name,
43		Public:              &boolFalse,
44		RegistryEnabled:     &boolFalse,
45		ServerType:          "vm",
46	}
47
48	res := clustertemplates.Create(client, createOpts)
49	if res.Err != nil {
50		return nil, res.Err
51	}
52
53	requestID := res.Header.Get("X-OpenStack-Request-Id")
54	th.AssertEquals(t, true, requestID != "")
55
56	t.Logf("Cluster Template %s request ID: %s", name, requestID)
57
58	clusterTemplate, err := res.Extract()
59	if err != nil {
60		return nil, err
61	}
62
63	t.Logf("Successfully created cluster template: %s", clusterTemplate.Name)
64
65	tools.PrintResource(t, clusterTemplate)
66	tools.PrintResource(t, clusterTemplate.CreatedAt)
67
68	th.AssertEquals(t, name, clusterTemplate.Name)
69	th.AssertEquals(t, choices.ExternalNetworkID, clusterTemplate.ExternalNetworkID)
70	th.AssertEquals(t, choices.MagnumImageID, clusterTemplate.ImageID)
71
72	return clusterTemplate, nil
73}
74
75// CreateClusterTemplate will create a random swarm cluster template.
76// An error will be returned if the cluster template could not be created.
77func CreateClusterTemplate(t *testing.T, client *gophercloud.ServiceClient) (*clustertemplates.ClusterTemplate, error) {
78	return CreateClusterTemplateCOE(t, client, "swarm")
79}
80
81// CreateKubernetesClusterTemplate will create a random kubernetes cluster template.
82// An error will be returned if the cluster template could not be created.
83func CreateKubernetesClusterTemplate(t *testing.T, client *gophercloud.ServiceClient) (*clustertemplates.ClusterTemplate, error) {
84	return CreateClusterTemplateCOE(t, client, "kubernetes")
85}
86
87// DeleteClusterTemplate will delete a given cluster-template. A fatal error will occur if the
88// cluster-template could not be deleted. This works best as a deferred function.
89func DeleteClusterTemplate(t *testing.T, client *gophercloud.ServiceClient, id string) {
90	t.Logf("Attempting to delete cluster-template: %s", id)
91
92	err := clustertemplates.Delete(client, id).ExtractErr()
93	if err != nil {
94		t.Fatalf("Error deleting cluster-template %s: %s:", id, err)
95	}
96
97	t.Logf("Successfully deleted cluster-template: %s", id)
98
99	return
100}
101
102// CreateClusterTimeout will create a random cluster and wait for it to reach CREATE_COMPLETE status
103// within the given timeout duration. An error will be returned if the cluster could not be created.
104func CreateClusterTimeout(t *testing.T, client *gophercloud.ServiceClient, clusterTemplateID string, timeout time.Duration) (string, error) {
105	clusterName := tools.RandomString("TESTACC-", 8)
106	t.Logf("Attempting to create cluster: %s using template %s", clusterName, clusterTemplateID)
107
108	choices, err := clients.AcceptanceTestChoicesFromEnv()
109	if err != nil {
110		return "", err
111	}
112
113	masterCount := 1
114	nodeCount := 1
115	// createTimeout is the creation timeout on the magnum side in minutes
116	createTimeout := int(math.Ceil(timeout.Minutes()))
117	createOpts := clusters.CreateOpts{
118		ClusterTemplateID: clusterTemplateID,
119		CreateTimeout:     &createTimeout,
120		FlavorID:          choices.FlavorID,
121		Keypair:           choices.MagnumKeypair,
122		Labels:            map[string]string{},
123		MasterCount:       &masterCount,
124		MasterFlavorID:    choices.FlavorID,
125		Name:              clusterName,
126		NodeCount:         &nodeCount,
127	}
128
129	createResult := clusters.Create(client, createOpts)
130	th.AssertNoErr(t, createResult.Err)
131	if len(createResult.Header["X-Openstack-Request-Id"]) > 0 {
132		t.Logf("Cluster Create Request ID: %s", createResult.Header["X-Openstack-Request-Id"][0])
133	}
134
135	clusterID, err := createResult.Extract()
136	if err != nil {
137		return "", err
138	}
139
140	t.Logf("Cluster created: %+v", clusterID)
141
142	err = WaitForCluster(client, clusterID, "CREATE_COMPLETE", timeout)
143	if err != nil {
144		return clusterID, err
145	}
146
147	t.Logf("Successfully created cluster: %s id: %s", clusterName, clusterID)
148	return clusterID, nil
149}
150
151// CreateCluster will create a random cluster. An error will be returned if the
152// cluster could not be created. Has a timeout of 300 seconds.
153func CreateCluster(t *testing.T, client *gophercloud.ServiceClient, clusterTemplateID string) (string, error) {
154	return CreateClusterTimeout(t, client, clusterTemplateID, 300*time.Second)
155}
156
157// CreateKubernetesCluster is the same as CreateCluster with a longer timeout necessary for creating a kubernetes cluster
158func CreateKubernetesCluster(t *testing.T, client *gophercloud.ServiceClient, clusterTemplateID string) (string, error) {
159	return CreateClusterTimeout(t, client, clusterTemplateID, 900*time.Second)
160}
161
162func DeleteCluster(t *testing.T, client *gophercloud.ServiceClient, id string) {
163	t.Logf("Attempting to delete cluster: %s", id)
164
165	r := clusters.Delete(client, id)
166	err := clusters.Delete(client, id).ExtractErr()
167	deleteRequestID := ""
168	idKey := "X-Openstack-Request-Id"
169	if len(r.Header[idKey]) > 0 {
170		deleteRequestID = r.Header[idKey][0]
171	}
172	if err != nil {
173		t.Fatalf("Error deleting cluster. requestID=%s clusterID=%s: err%s:", deleteRequestID, id, err)
174	}
175
176	err = WaitForCluster(client, id, "DELETE_COMPLETE", 300*time.Second)
177	if err != nil {
178		t.Fatalf("Error deleting cluster %s: %s:", id, err)
179	}
180
181	t.Logf("Successfully deleted cluster: %s", id)
182
183	return
184}
185
186func WaitForCluster(client *gophercloud.ServiceClient, clusterID string, status string, timeout time.Duration) error {
187	return tools.WaitForTimeout(func() (bool, error) {
188		cluster, err := clusters.Get(client, clusterID).Extract()
189		if err != nil {
190			if _, ok := err.(gophercloud.ErrDefault404); ok && status == "DELETE_COMPLETE" {
191				return true, nil
192			}
193
194			return false, err
195		}
196
197		if cluster.Status == status {
198			return true, nil
199		}
200
201		if strings.Contains(cluster.Status, "FAILED") {
202			return false, fmt.Errorf("Cluster %s FAILED. Status=%s StatusReason=%s", clusterID, cluster.Status, cluster.StatusReason)
203		}
204
205		return false, nil
206	}, timeout)
207}
208
209// CreateQuota will create a random quota. An error will be returned if the
210// quota could not be created.
211func CreateQuota(t *testing.T, client *gophercloud.ServiceClient) (*quotas.Quotas, error) {
212	name := tools.RandomString("TESTACC-", 8)
213	t.Logf("Attempting to create quota: %s", name)
214
215	idClient, err := clients.NewIdentityV3Client()
216	th.AssertNoErr(t, err)
217
218	project, err := idv3.CreateProject(t, idClient, nil)
219	th.AssertNoErr(t, err)
220	defer idv3.DeleteProject(t, idClient, project.ID)
221
222	createOpts := quotas.CreateOpts{
223		Resource:  "Cluster",
224		ProjectID: project.ID,
225		HardLimit: 10,
226	}
227
228	res := quotas.Create(client, createOpts)
229	if res.Err != nil {
230		return nil, res.Err
231	}
232
233	requestID := res.Header.Get("X-OpenStack-Request-Id")
234	th.AssertEquals(t, true, requestID != "")
235
236	t.Logf("Quota %s request ID: %s", name, requestID)
237
238	quota, err := res.Extract()
239	if err == nil {
240		t.Logf("Successfully created quota: %s", quota.ProjectID)
241
242		tools.PrintResource(t, quota)
243
244		th.AssertEquals(t, project.ID, quota.ProjectID)
245		th.AssertEquals(t, "Cluster", quota.Resource)
246		th.AssertEquals(t, 10, quota.HardLimit)
247	}
248
249	return quota, err
250}
251