1// Package v2 contains common functions for creating compute-based resources
2// for use in acceptance tests. See the `*_test.go` files for example usages.
3package v2
4
5import (
6	"crypto/rand"
7	"crypto/rsa"
8	"fmt"
9	"testing"
10
11	"github.com/gophercloud/gophercloud"
12	"github.com/gophercloud/gophercloud/acceptance/clients"
13	"github.com/gophercloud/gophercloud/acceptance/tools"
14	"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
15	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/aggregates"
16	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces"
17	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
18	dsr "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules"
19	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
20	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
21	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/networks"
22	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets"
23	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles"
24	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue"
25	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints"
26	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups"
27	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups"
28	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks"
29	"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
30	"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
31	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
32	neutron "github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
33	th "github.com/gophercloud/gophercloud/testhelper"
34
35	"golang.org/x/crypto/ssh"
36)
37
38// AssociateFloatingIP will associate a floating IP with an instance. An error
39// will be returned if the floating IP was unable to be associated.
40func AssociateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server) error {
41	associateOpts := floatingips.AssociateOpts{
42		FloatingIP: floatingIP.IP,
43	}
44
45	t.Logf("Attempting to associate floating IP %s to instance %s", floatingIP.IP, server.ID)
46	err := floatingips.AssociateInstance(client, server.ID, associateOpts).ExtractErr()
47	if err != nil {
48		return err
49	}
50
51	return nil
52}
53
54// AssociateFloatingIPWithFixedIP will associate a floating IP with an
55// instance's specific fixed IP. An error will be returend if the floating IP
56// was unable to be associated.
57func AssociateFloatingIPWithFixedIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server, fixedIP string) error {
58	associateOpts := floatingips.AssociateOpts{
59		FloatingIP: floatingIP.IP,
60		FixedIP:    fixedIP,
61	}
62
63	t.Logf("Attempting to associate floating IP %s to fixed IP %s on instance %s", floatingIP.IP, fixedIP, server.ID)
64	err := floatingips.AssociateInstance(client, server.ID, associateOpts).ExtractErr()
65	if err != nil {
66		return err
67	}
68
69	return nil
70}
71
72// AttachInterface will create and attach an interface on a given server.
73// An error will returned if the interface could not be created.
74func AttachInterface(t *testing.T, client *gophercloud.ServiceClient, serverID string) (*attachinterfaces.Interface, error) {
75	t.Logf("Attempting to attach interface to server %s", serverID)
76
77	choices, err := clients.AcceptanceTestChoicesFromEnv()
78	if err != nil {
79		t.Fatal(err)
80	}
81
82	networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName)
83	if err != nil {
84		return nil, err
85	}
86
87	createOpts := attachinterfaces.CreateOpts{
88		NetworkID: networkID,
89	}
90
91	iface, err := attachinterfaces.Create(client, serverID, createOpts).Extract()
92	if err != nil {
93		return nil, err
94	}
95
96	t.Logf("Successfully created interface %s on server %s", iface.PortID, serverID)
97
98	return iface, nil
99}
100
101// CreateAggregate will create an aggregate with random name and available zone.
102// An error will be returned if the aggregate could not be created.
103func CreateAggregate(t *testing.T, client *gophercloud.ServiceClient) (*aggregates.Aggregate, error) {
104	aggregateName := tools.RandomString("aggregate_", 5)
105	availabilityZone := tools.RandomString("zone_", 5)
106	t.Logf("Attempting to create aggregate %s", aggregateName)
107
108	createOpts := aggregates.CreateOpts{
109		Name:             aggregateName,
110		AvailabilityZone: availabilityZone,
111	}
112
113	aggregate, err := aggregates.Create(client, createOpts).Extract()
114	if err != nil {
115		return nil, err
116	}
117
118	t.Logf("Successfully created aggregate %d", aggregate.ID)
119
120	aggregate, err = aggregates.Get(client, aggregate.ID).Extract()
121	if err != nil {
122		return nil, err
123	}
124
125	th.AssertEquals(t, aggregate.Name, aggregateName)
126	th.AssertEquals(t, aggregate.AvailabilityZone, availabilityZone)
127
128	return aggregate, nil
129}
130
131// CreateBootableVolumeServer works like CreateServer but is configured with
132// one or more block devices defined by passing in []bootfromvolume.BlockDevice.
133// An error will be returned if a server was unable to be created.
134func CreateBootableVolumeServer(t *testing.T, client *gophercloud.ServiceClient, blockDevices []bootfromvolume.BlockDevice) (*servers.Server, error) {
135	var server *servers.Server
136
137	choices, err := clients.AcceptanceTestChoicesFromEnv()
138	if err != nil {
139		t.Fatal(err)
140	}
141
142	networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName)
143	if err != nil {
144		return server, err
145	}
146
147	name := tools.RandomString("ACPTTEST", 16)
148	t.Logf("Attempting to create bootable volume server: %s", name)
149
150	serverCreateOpts := servers.CreateOpts{
151		Name:      name,
152		FlavorRef: choices.FlavorID,
153		Networks: []servers.Network{
154			{UUID: networkID},
155		},
156	}
157
158	if blockDevices[0].SourceType == bootfromvolume.SourceImage && blockDevices[0].DestinationType == bootfromvolume.DestinationLocal {
159		serverCreateOpts.ImageRef = blockDevices[0].UUID
160	}
161
162	server, err = bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{
163		CreateOptsBuilder: serverCreateOpts,
164		BlockDevice:       blockDevices,
165	}).Extract()
166
167	if err != nil {
168		return server, err
169	}
170
171	if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
172		return server, err
173	}
174
175	newServer, err := servers.Get(client, server.ID).Extract()
176	if err != nil {
177		return nil, err
178	}
179
180	th.AssertEquals(t, newServer.Name, name)
181	th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID)
182
183	return newServer, nil
184}
185
186// CreateDefaultRule will create a default security group rule with a
187// random port range between 80 and 90. An error will be returned if
188// a default rule was unable to be created.
189func CreateDefaultRule(t *testing.T, client *gophercloud.ServiceClient) (dsr.DefaultRule, error) {
190	createOpts := dsr.CreateOpts{
191		FromPort:   tools.RandomInt(80, 89),
192		ToPort:     tools.RandomInt(90, 99),
193		IPProtocol: "TCP",
194		CIDR:       "0.0.0.0/0",
195	}
196
197	defaultRule, err := dsr.Create(client, createOpts).Extract()
198	if err != nil {
199		return *defaultRule, err
200	}
201
202	t.Logf("Created default rule: %s", defaultRule.ID)
203
204	return *defaultRule, nil
205}
206
207// CreateFlavor will create a flavor with a random name.
208// An error will be returned if the flavor could not be created.
209func CreateFlavor(t *testing.T, client *gophercloud.ServiceClient) (*flavors.Flavor, error) {
210	flavorName := tools.RandomString("flavor_", 5)
211	t.Logf("Attempting to create flavor %s", flavorName)
212
213	isPublic := true
214	createOpts := flavors.CreateOpts{
215		Name:     flavorName,
216		RAM:      1,
217		VCPUs:    1,
218		Disk:     gophercloud.IntToPointer(1),
219		IsPublic: &isPublic,
220	}
221
222	flavor, err := flavors.Create(client, createOpts).Extract()
223	if err != nil {
224		return nil, err
225	}
226
227	t.Logf("Successfully created flavor %s", flavor.ID)
228
229	th.AssertEquals(t, flavor.Name, flavorName)
230	th.AssertEquals(t, flavor.RAM, 1)
231	th.AssertEquals(t, flavor.Disk, 1)
232	th.AssertEquals(t, flavor.VCPUs, 1)
233	th.AssertEquals(t, flavor.IsPublic, true)
234
235	return flavor, nil
236}
237
238// CreateFloatingIP will allocate a floating IP.
239// An error will be returend if one was unable to be allocated.
240func CreateFloatingIP(t *testing.T, client *gophercloud.ServiceClient) (*floatingips.FloatingIP, error) {
241	choices, err := clients.AcceptanceTestChoicesFromEnv()
242	if err != nil {
243		t.Fatal(err)
244	}
245
246	createOpts := floatingips.CreateOpts{
247		Pool: choices.FloatingIPPoolName,
248	}
249	floatingIP, err := floatingips.Create(client, createOpts).Extract()
250	if err != nil {
251		return floatingIP, err
252	}
253
254	t.Logf("Created floating IP: %s", floatingIP.ID)
255	return floatingIP, nil
256}
257
258func createKey() (string, error) {
259	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
260	if err != nil {
261		return "", err
262	}
263
264	publicKey := privateKey.PublicKey
265	pub, err := ssh.NewPublicKey(&publicKey)
266	if err != nil {
267		return "", err
268	}
269
270	pubBytes := ssh.MarshalAuthorizedKey(pub)
271	pk := string(pubBytes)
272	return pk, nil
273}
274
275// CreateKeyPair will create a KeyPair with a random name. An error will occur
276// if the keypair failed to be created. An error will be returned if the
277// keypair was unable to be created.
278func CreateKeyPair(t *testing.T, client *gophercloud.ServiceClient) (*keypairs.KeyPair, error) {
279	keyPairName := tools.RandomString("keypair_", 5)
280
281	t.Logf("Attempting to create keypair: %s", keyPairName)
282	createOpts := keypairs.CreateOpts{
283		Name: keyPairName,
284	}
285	keyPair, err := keypairs.Create(client, createOpts).Extract()
286	if err != nil {
287		return keyPair, err
288	}
289
290	t.Logf("Created keypair: %s", keyPairName)
291
292	th.AssertEquals(t, keyPair.Name, keyPairName)
293
294	return keyPair, nil
295}
296
297// CreateMultiEphemeralServer works like CreateServer but is configured with
298// one or more block devices defined by passing in []bootfromvolume.BlockDevice.
299// These block devices act like block devices when booting from a volume but
300// are actually local ephemeral disks.
301// An error will be returned if a server was unable to be created.
302func CreateMultiEphemeralServer(t *testing.T, client *gophercloud.ServiceClient, blockDevices []bootfromvolume.BlockDevice) (*servers.Server, error) {
303	var server *servers.Server
304
305	choices, err := clients.AcceptanceTestChoicesFromEnv()
306	if err != nil {
307		t.Fatal(err)
308	}
309
310	networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName)
311	if err != nil {
312		return server, err
313	}
314
315	name := tools.RandomString("ACPTTEST", 16)
316	t.Logf("Attempting to create bootable volume server: %s", name)
317
318	serverCreateOpts := servers.CreateOpts{
319		Name:      name,
320		FlavorRef: choices.FlavorID,
321		ImageRef:  choices.ImageID,
322		Networks: []servers.Network{
323			{UUID: networkID},
324		},
325	}
326
327	server, err = bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{
328		CreateOptsBuilder: serverCreateOpts,
329		BlockDevice:       blockDevices,
330	}).Extract()
331
332	if err != nil {
333		return server, err
334	}
335
336	if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
337		return server, err
338	}
339
340	newServer, err := servers.Get(client, server.ID).Extract()
341	if err != nil {
342		return server, err
343	}
344	th.AssertEquals(t, newServer.Name, name)
345	th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID)
346	th.AssertEquals(t, newServer.Image["id"], choices.ImageID)
347
348	return newServer, nil
349}
350
351// CreatePrivateFlavor will create a private flavor with a random name.
352// An error will be returned if the flavor could not be created.
353func CreatePrivateFlavor(t *testing.T, client *gophercloud.ServiceClient) (*flavors.Flavor, error) {
354	flavorName := tools.RandomString("flavor_", 5)
355	t.Logf("Attempting to create flavor %s", flavorName)
356
357	isPublic := false
358	createOpts := flavors.CreateOpts{
359		Name:     flavorName,
360		RAM:      1,
361		VCPUs:    1,
362		Disk:     gophercloud.IntToPointer(1),
363		IsPublic: &isPublic,
364	}
365
366	flavor, err := flavors.Create(client, createOpts).Extract()
367	if err != nil {
368		return nil, err
369	}
370
371	t.Logf("Successfully created flavor %s", flavor.ID)
372
373	th.AssertEquals(t, flavor.Name, flavorName)
374	th.AssertEquals(t, flavor.RAM, 1)
375	th.AssertEquals(t, flavor.Disk, 1)
376	th.AssertEquals(t, flavor.VCPUs, 1)
377	th.AssertEquals(t, flavor.IsPublic, false)
378
379	return flavor, nil
380}
381
382// CreateSecurityGroup will create a security group with a random name.
383// An error will be returned if one was failed to be created.
384func CreateSecurityGroup(t *testing.T, client *gophercloud.ServiceClient) (*secgroups.SecurityGroup, error) {
385	name := tools.RandomString("secgroup_", 5)
386
387	createOpts := secgroups.CreateOpts{
388		Name:        name,
389		Description: "something",
390	}
391
392	securityGroup, err := secgroups.Create(client, createOpts).Extract()
393	if err != nil {
394		return nil, err
395	}
396
397	t.Logf("Created security group: %s", securityGroup.ID)
398
399	th.AssertEquals(t, securityGroup.Name, name)
400
401	return securityGroup, nil
402}
403
404// CreateSecurityGroupRule will create a security group rule with a random name
405// and a random TCP port range between port 80 and 99. An error will be
406// returned if the rule failed to be created.
407func CreateSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, securityGroupID string) (*secgroups.Rule, error) {
408	fromPort := tools.RandomInt(80, 89)
409	toPort := tools.RandomInt(90, 99)
410	createOpts := secgroups.CreateRuleOpts{
411		ParentGroupID: securityGroupID,
412		FromPort:      fromPort,
413		ToPort:        toPort,
414		IPProtocol:    "TCP",
415		CIDR:          "0.0.0.0/0",
416	}
417
418	rule, err := secgroups.CreateRule(client, createOpts).Extract()
419	if err != nil {
420		return nil, err
421	}
422
423	t.Logf("Created security group rule: %s", rule.ID)
424
425	th.AssertEquals(t, rule.FromPort, fromPort)
426	th.AssertEquals(t, rule.ToPort, toPort)
427	th.AssertEquals(t, rule.ParentGroupID, securityGroupID)
428
429	return rule, nil
430}
431
432// CreateServer creates a basic instance with a randomly generated name.
433// The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable.
434// The image will be the value of the OS_IMAGE_ID environment variable.
435// The instance will be launched on the network specified in OS_NETWORK_NAME.
436// An error will be returned if the instance was unable to be created.
437func CreateServer(t *testing.T, client *gophercloud.ServiceClient) (*servers.Server, error) {
438	choices, err := clients.AcceptanceTestChoicesFromEnv()
439	if err != nil {
440		t.Fatal(err)
441	}
442
443	networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName)
444	if err != nil {
445		return nil, err
446	}
447
448	name := tools.RandomString("ACPTTEST", 16)
449	t.Logf("Attempting to create server: %s", name)
450
451	pwd := tools.MakeNewPassword("")
452
453	server, err := servers.Create(client, servers.CreateOpts{
454		Name:      name,
455		FlavorRef: choices.FlavorID,
456		ImageRef:  choices.ImageID,
457		AdminPass: pwd,
458		Networks: []servers.Network{
459			{UUID: networkID},
460		},
461		Metadata: map[string]string{
462			"abc": "def",
463		},
464		Personality: servers.Personality{
465			&servers.File{
466				Path:     "/etc/test",
467				Contents: []byte("hello world"),
468			},
469		},
470	}).Extract()
471	if err != nil {
472		return server, err
473	}
474
475	if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
476		return nil, err
477	}
478
479	newServer, err := servers.Get(client, server.ID).Extract()
480	if err != nil {
481		return nil, err
482	}
483
484	th.AssertEquals(t, newServer.Name, name)
485	th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID)
486	th.AssertEquals(t, newServer.Image["id"], choices.ImageID)
487
488	return newServer, nil
489}
490
491// CreateMicroversionServer creates a basic instance compatible with
492// newer microversions with a randomly generated name.
493// The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable.
494// The image will be the value of the OS_IMAGE_ID environment variable.
495// The instance will be launched on the network specified in OS_NETWORK_NAME.
496// An error will be returned if the instance was unable to be created.
497func CreateMicroversionServer(t *testing.T, client *gophercloud.ServiceClient) (*servers.Server, error) {
498	choices, err := clients.AcceptanceTestChoicesFromEnv()
499	if err != nil {
500		t.Fatal(err)
501	}
502
503	networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName)
504	if err != nil {
505		return nil, err
506	}
507
508	name := tools.RandomString("ACPTTEST", 16)
509	t.Logf("Attempting to create server: %s", name)
510
511	pwd := tools.MakeNewPassword("")
512
513	server, err := servers.Create(client, servers.CreateOpts{
514		Name:      name,
515		FlavorRef: choices.FlavorID,
516		ImageRef:  choices.ImageID,
517		AdminPass: pwd,
518		Networks: []servers.Network{
519			{UUID: networkID},
520		},
521		Metadata: map[string]string{
522			"abc": "def",
523		},
524	}).Extract()
525	if err != nil {
526		return server, err
527	}
528
529	if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
530		return nil, err
531	}
532
533	newServer, err := servers.Get(client, server.ID).Extract()
534	if err != nil {
535		return nil, err
536	}
537
538	th.AssertEquals(t, newServer.Name, name)
539	th.AssertEquals(t, newServer.Image["id"], choices.ImageID)
540
541	return newServer, nil
542}
543
544// CreateServerWithoutImageRef creates a basic instance with a randomly generated name.
545// The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable.
546// The image is intentionally missing to trigger an error.
547// The instance will be launched on the network specified in OS_NETWORK_NAME.
548// An error will be returned if the instance was unable to be created.
549func CreateServerWithoutImageRef(t *testing.T, client *gophercloud.ServiceClient) (*servers.Server, error) {
550	choices, err := clients.AcceptanceTestChoicesFromEnv()
551	if err != nil {
552		t.Fatal(err)
553	}
554
555	networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName)
556	if err != nil {
557		return nil, err
558	}
559
560	name := tools.RandomString("ACPTTEST", 16)
561	t.Logf("Attempting to create server: %s", name)
562
563	pwd := tools.MakeNewPassword("")
564
565	server, err := servers.Create(client, servers.CreateOpts{
566		Name:      name,
567		FlavorRef: choices.FlavorID,
568		AdminPass: pwd,
569		Networks: []servers.Network{
570			{UUID: networkID},
571		},
572		Personality: servers.Personality{
573			&servers.File{
574				Path:     "/etc/test",
575				Contents: []byte("hello world"),
576			},
577		},
578	}).Extract()
579	if err != nil {
580		return nil, err
581	}
582
583	if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
584		return nil, err
585	}
586
587	return server, nil
588}
589
590// CreateServerWithTags creates a basic instance with a randomly generated name.
591// The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable.
592// The image will be the value of the OS_IMAGE_ID environment variable.
593// The instance will be launched on the network specified in OS_NETWORK_NAME.
594// Two tags will be assigned to the server.
595// An error will be returned if the instance was unable to be created.
596func CreateServerWithTags(t *testing.T, client *gophercloud.ServiceClient, networkID string) (*servers.Server, error) {
597	choices, err := clients.AcceptanceTestChoicesFromEnv()
598	if err != nil {
599		t.Fatal(err)
600	}
601
602	name := tools.RandomString("ACPTTEST", 16)
603	t.Logf("Attempting to create server: %s", name)
604
605	pwd := tools.MakeNewPassword("")
606
607	server, err := servers.Create(client, servers.CreateOpts{
608		Name:      name,
609		FlavorRef: choices.FlavorID,
610		ImageRef:  choices.ImageID,
611		AdminPass: pwd,
612		Networks: []servers.Network{
613			{UUID: networkID},
614		},
615		Metadata: map[string]string{
616			"abc": "def",
617		},
618		Personality: servers.Personality{
619			&servers.File{
620				Path:     "/etc/test",
621				Contents: []byte("hello world"),
622			},
623		},
624		Tags: []string{"tag1", "tag2"},
625	}).Extract()
626	if err != nil {
627		return server, err
628	}
629
630	if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
631		return nil, err
632	}
633
634	res := servers.Get(client, server.ID)
635	if res.Err != nil {
636		return nil, res.Err
637	}
638
639	newServer, err := res.Extract()
640	th.AssertNoErr(t, err)
641	th.AssertEquals(t, newServer.Name, name)
642	th.AssertDeepEquals(t, *newServer.Tags, []string{"tag1", "tag2"})
643
644	return newServer, nil
645}
646
647// CreateServerGroup will create a server with a random name. An error will be
648// returned if the server group failed to be created.
649func CreateServerGroup(t *testing.T, client *gophercloud.ServiceClient, policy string) (*servergroups.ServerGroup, error) {
650	name := tools.RandomString("ACPTTEST", 16)
651
652	t.Logf("Attempting to create server group %s", name)
653
654	sg, err := servergroups.Create(client, &servergroups.CreateOpts{
655		Name:     name,
656		Policies: []string{policy},
657	}).Extract()
658
659	if err != nil {
660		return nil, err
661	}
662
663	t.Logf("Successfully created server group %s", name)
664
665	th.AssertEquals(t, sg.Name, name)
666
667	return sg, nil
668}
669
670// CreateServerGroupMicroversion will create a server with a random name using 2.64 microversion. An error will be
671// returned if the server group failed to be created.
672func CreateServerGroupMicroversion(t *testing.T, client *gophercloud.ServiceClient) (*servergroups.ServerGroup, error) {
673	name := tools.RandomString("ACPTTEST", 16)
674	policy := "anti-affinity"
675	maxServerPerHost := 3
676
677	t.Logf("Attempting to create %s server group with max server per host = %d: %s", policy, maxServerPerHost, name)
678
679	sg, err := servergroups.Create(client, &servergroups.CreateOpts{
680		Name:   name,
681		Policy: policy,
682		Rules: &servergroups.Rules{
683			MaxServerPerHost: maxServerPerHost,
684		},
685	}).Extract()
686
687	if err != nil {
688		return nil, err
689	}
690
691	t.Logf("Successfully created server group %s", name)
692
693	th.AssertEquals(t, sg.Name, name)
694
695	return sg, nil
696}
697
698// CreateServerInServerGroup works like CreateServer but places the instance in
699// a specified Server Group.
700func CreateServerInServerGroup(t *testing.T, client *gophercloud.ServiceClient, serverGroup *servergroups.ServerGroup) (*servers.Server, error) {
701	choices, err := clients.AcceptanceTestChoicesFromEnv()
702	if err != nil {
703		t.Fatal(err)
704	}
705
706	networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName)
707	if err != nil {
708		return nil, err
709	}
710
711	name := tools.RandomString("ACPTTEST", 16)
712	t.Logf("Attempting to create server: %s", name)
713
714	pwd := tools.MakeNewPassword("")
715
716	serverCreateOpts := servers.CreateOpts{
717		Name:      name,
718		FlavorRef: choices.FlavorID,
719		ImageRef:  choices.ImageID,
720		AdminPass: pwd,
721		Networks: []servers.Network{
722			{UUID: networkID},
723		},
724	}
725
726	schedulerHintsOpts := schedulerhints.CreateOptsExt{
727		CreateOptsBuilder: serverCreateOpts,
728		SchedulerHints: schedulerhints.SchedulerHints{
729			Group: serverGroup.ID,
730		},
731	}
732	server, err := servers.Create(client, schedulerHintsOpts).Extract()
733	if err != nil {
734		return nil, err
735	}
736
737	if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
738		return nil, err
739	}
740
741	newServer, err := servers.Get(client, server.ID).Extract()
742	if err != nil {
743		return nil, err
744	}
745
746	th.AssertEquals(t, newServer.Name, name)
747	th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID)
748	th.AssertEquals(t, newServer.Image["id"], choices.ImageID)
749
750	return newServer, nil
751}
752
753// CreateServerWithPublicKey works the same as CreateServer, but additionally
754// configures the server with a specified Key Pair name.
755func CreateServerWithPublicKey(t *testing.T, client *gophercloud.ServiceClient, keyPairName string) (*servers.Server, error) {
756	choices, err := clients.AcceptanceTestChoicesFromEnv()
757	if err != nil {
758		t.Fatal(err)
759	}
760
761	networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName)
762	if err != nil {
763		return nil, err
764	}
765
766	name := tools.RandomString("ACPTTEST", 16)
767	t.Logf("Attempting to create server: %s", name)
768
769	serverCreateOpts := servers.CreateOpts{
770		Name:      name,
771		FlavorRef: choices.FlavorID,
772		ImageRef:  choices.ImageID,
773		Networks: []servers.Network{
774			{UUID: networkID},
775		},
776	}
777
778	server, err := servers.Create(client, keypairs.CreateOptsExt{
779		CreateOptsBuilder: serverCreateOpts,
780		KeyName:           keyPairName,
781	}).Extract()
782	if err != nil {
783		return nil, err
784	}
785
786	if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
787		return nil, err
788	}
789
790	newServer, err := servers.Get(client, server.ID).Extract()
791	if err != nil {
792		return nil, err
793	}
794
795	th.AssertEquals(t, newServer.Name, name)
796	th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID)
797	th.AssertEquals(t, newServer.Image["id"], choices.ImageID)
798
799	return newServer, nil
800}
801
802// CreateVolumeAttachment will attach a volume to a server. An error will be
803// returned if the volume failed to attach.
804func CreateVolumeAttachment(t *testing.T, client *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, server *servers.Server, volume *volumes.Volume) (*volumeattach.VolumeAttachment, error) {
805	tag := tools.RandomString("ACPTTEST", 16)
806	dot := false
807
808	volumeAttachOptions := volumeattach.CreateOpts{
809		VolumeID:            volume.ID,
810		Tag:                 tag,
811		DeleteOnTermination: dot,
812	}
813
814	t.Logf("Attempting to attach volume %s to server %s", volume.ID, server.ID)
815	volumeAttachment, err := volumeattach.Create(client, server.ID, volumeAttachOptions).Extract()
816	if err != nil {
817		return volumeAttachment, err
818	}
819
820	if err := volumes.WaitForStatus(blockClient, volume.ID, "in-use", 60); err != nil {
821		return volumeAttachment, err
822	}
823
824	return volumeAttachment, nil
825}
826
827// DeleteAggregate will delete a given host aggregate. A fatal error will occur if
828// the aggregate deleting is failed. This works best when using it as a
829// deferred function.
830func DeleteAggregate(t *testing.T, client *gophercloud.ServiceClient, aggregate *aggregates.Aggregate) {
831	err := aggregates.Delete(client, aggregate.ID).ExtractErr()
832	if err != nil {
833		t.Fatalf("Unable to delete aggregate %d", aggregate.ID)
834	}
835
836	t.Logf("Deleted aggregate: %d", aggregate.ID)
837}
838
839// DeleteDefaultRule deletes a default security group rule.
840// A fatal error will occur if the rule failed to delete. This works best when
841// using it as a deferred function.
842func DeleteDefaultRule(t *testing.T, client *gophercloud.ServiceClient, defaultRule dsr.DefaultRule) {
843	err := dsr.Delete(client, defaultRule.ID).ExtractErr()
844	if err != nil {
845		t.Fatalf("Unable to delete default rule %s: %v", defaultRule.ID, err)
846	}
847
848	t.Logf("Deleted default rule: %s", defaultRule.ID)
849}
850
851// DeleteFlavor will delete a flavor. A fatal error will occur if the flavor
852// could not be deleted. This works best when using it as a deferred function.
853func DeleteFlavor(t *testing.T, client *gophercloud.ServiceClient, flavor *flavors.Flavor) {
854	err := flavors.Delete(client, flavor.ID).ExtractErr()
855	if err != nil {
856		t.Fatalf("Unable to delete flavor %s", flavor.ID)
857	}
858
859	t.Logf("Deleted flavor: %s", flavor.ID)
860}
861
862// DeleteFloatingIP will de-allocate a floating IP. A fatal error will occur if
863// the floating IP failed to de-allocate. This works best when using it as a
864// deferred function.
865func DeleteFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP) {
866	err := floatingips.Delete(client, floatingIP.ID).ExtractErr()
867	if err != nil {
868		t.Fatalf("Unable to delete floating IP %s: %v", floatingIP.ID, err)
869	}
870
871	t.Logf("Deleted floating IP: %s", floatingIP.ID)
872}
873
874// DeleteKeyPair will delete a specified keypair. A fatal error will occur if
875// the keypair failed to be deleted. This works best when used as a deferred
876// function.
877func DeleteKeyPair(t *testing.T, client *gophercloud.ServiceClient, keyPair *keypairs.KeyPair) {
878	err := keypairs.Delete(client, keyPair.Name, nil).ExtractErr()
879	if err != nil {
880		t.Fatalf("Unable to delete keypair %s: %v", keyPair.Name, err)
881	}
882
883	t.Logf("Deleted keypair: %s", keyPair.Name)
884}
885
886// DeleteSecurityGroup will delete a security group. A fatal error will occur
887// if the group failed to be deleted. This works best as a deferred function.
888func DeleteSecurityGroup(t *testing.T, client *gophercloud.ServiceClient, securityGroupID string) {
889	err := secgroups.Delete(client, securityGroupID).ExtractErr()
890	if err != nil {
891		t.Fatalf("Unable to delete security group %s: %s", securityGroupID, err)
892	}
893
894	t.Logf("Deleted security group: %s", securityGroupID)
895}
896
897// DeleteSecurityGroupRule will delete a security group rule. A fatal error
898// will occur if the rule failed to be deleted. This works best when used
899// as a deferred function.
900func DeleteSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, ruleID string) {
901	err := secgroups.DeleteRule(client, ruleID).ExtractErr()
902	if err != nil {
903		t.Fatalf("Unable to delete rule: %v", err)
904	}
905
906	t.Logf("Deleted security group rule: %s", ruleID)
907}
908
909// DeleteServer deletes an instance via its UUID.
910// A fatal error will occur if the instance failed to be destroyed. This works
911// best when using it as a deferred function.
912func DeleteServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) {
913	err := servers.Delete(client, server.ID).ExtractErr()
914	if err != nil {
915		t.Fatalf("Unable to delete server %s: %s", server.ID, err)
916	}
917
918	if err := WaitForComputeStatus(client, server, "DELETED"); err != nil {
919		if _, ok := err.(gophercloud.ErrDefault404); ok {
920			t.Logf("Deleted server: %s", server.ID)
921			return
922		}
923		t.Fatalf("Error deleting server %s: %s", server.ID, err)
924	}
925
926	// If we reach this point, the API returned an actual DELETED status
927	// which is a very short window of time, but happens occasionally.
928	t.Logf("Deleted server: %s", server.ID)
929}
930
931// DeleteServerGroup will delete a server group. A fatal error will occur if
932// the server group failed to be deleted. This works best when used as a
933// deferred function.
934func DeleteServerGroup(t *testing.T, client *gophercloud.ServiceClient, serverGroup *servergroups.ServerGroup) {
935	err := servergroups.Delete(client, serverGroup.ID).ExtractErr()
936	if err != nil {
937		t.Fatalf("Unable to delete server group %s: %v", serverGroup.ID, err)
938	}
939
940	t.Logf("Deleted server group %s", serverGroup.ID)
941}
942
943// DeleteVolumeAttachment will disconnect a volume from an instance. A fatal
944// error will occur if the volume failed to detach. This works best when used
945// as a deferred function.
946func DeleteVolumeAttachment(t *testing.T, client *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, server *servers.Server, volumeAttachment *volumeattach.VolumeAttachment) {
947
948	err := volumeattach.Delete(client, server.ID, volumeAttachment.VolumeID).ExtractErr()
949	if err != nil {
950		t.Fatalf("Unable to detach volume: %v", err)
951	}
952
953	if err := volumes.WaitForStatus(blockClient, volumeAttachment.ID, "available", 60); err != nil {
954		t.Fatalf("Unable to wait for volume: %v", err)
955	}
956	t.Logf("Deleted volume: %s", volumeAttachment.VolumeID)
957}
958
959// DetachInterface will detach an interface from a server. A fatal
960// error will occur if the interface could not be detached. This works best
961// when used as a deferred function.
962func DetachInterface(t *testing.T, client *gophercloud.ServiceClient, serverID, portID string) {
963	t.Logf("Attempting to detach interface %s from server %s", portID, serverID)
964
965	err := attachinterfaces.Delete(client, serverID, portID).ExtractErr()
966	if err != nil {
967		t.Fatalf("Unable to detach interface %s from server %s", portID, serverID)
968	}
969
970	t.Logf("Detached interface %s from server %s", portID, serverID)
971}
972
973// DisassociateFloatingIP will disassociate a floating IP from an instance. A
974// fatal error will occur if the floating IP failed to disassociate. This works
975// best when using it as a deferred function.
976func DisassociateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server) {
977	disassociateOpts := floatingips.DisassociateOpts{
978		FloatingIP: floatingIP.IP,
979	}
980
981	err := floatingips.DisassociateInstance(client, server.ID, disassociateOpts).ExtractErr()
982	if err != nil {
983		t.Fatalf("Unable to disassociate floating IP %s from server %s: %v", floatingIP.IP, server.ID, err)
984	}
985
986	t.Logf("Disassociated floating IP %s from server %s", floatingIP.IP, server.ID)
987}
988
989// GetNetworkIDFromOSNetworks will return the network ID from a specified network
990// UUID using the os-networks API extension. An error will be returned if the
991// network could not be retrieved.
992func GetNetworkIDFromOSNetworks(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) {
993	allPages, err := networks.List(client).AllPages()
994	if err != nil {
995		t.Fatalf("Unable to list networks: %v", err)
996	}
997
998	networkList, err := networks.ExtractNetworks(allPages)
999	if err != nil {
1000		t.Fatalf("Unable to list networks: %v", err)
1001	}
1002
1003	networkID := ""
1004	for _, network := range networkList {
1005		t.Logf("Network: %v", network)
1006		if network.Label == networkName {
1007			networkID = network.ID
1008		}
1009	}
1010
1011	t.Logf("Found network ID for %s: %s", networkName, networkID)
1012
1013	return networkID, nil
1014}
1015
1016// GetNetworkIDFromTenantNetworks will return the network UUID for a given
1017// network name using the os-tenant-networks API extension. An error will be
1018// returned if the network could not be retrieved.
1019func GetNetworkIDFromTenantNetworks(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) {
1020	allPages, err := tenantnetworks.List(client).AllPages()
1021	if err != nil {
1022		return "", err
1023	}
1024
1025	allTenantNetworks, err := tenantnetworks.ExtractNetworks(allPages)
1026	if err != nil {
1027		return "", err
1028	}
1029
1030	for _, network := range allTenantNetworks {
1031		if network.Name == networkName {
1032			return network.ID, nil
1033		}
1034	}
1035
1036	return "", fmt.Errorf("Failed to obtain network ID for network %s", networkName)
1037}
1038
1039// GetNetworkIDFromNetworks will return the network UUID for a given network
1040// name using either the os-tenant-networks API extension or Neutron API.
1041// An error will be returned if the network could not be retrieved.
1042func GetNetworkIDFromNetworks(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) {
1043	allPages, err := tenantnetworks.List(client).AllPages()
1044	if err == nil {
1045		allTenantNetworks, err := tenantnetworks.ExtractNetworks(allPages)
1046		if err != nil {
1047			return "", err
1048		}
1049
1050		for _, network := range allTenantNetworks {
1051			if network.Name == networkName {
1052				return network.ID, nil
1053			}
1054		}
1055	}
1056
1057	networkClient, err := clients.NewNetworkV2Client()
1058	th.AssertNoErr(t, err)
1059
1060	allPages2, err := neutron.List(networkClient, nil).AllPages()
1061	th.AssertNoErr(t, err)
1062
1063	allNetworks, err := neutron.ExtractNetworks(allPages2)
1064	th.AssertNoErr(t, err)
1065
1066	for _, network := range allNetworks {
1067		if network.Name == networkName {
1068			return network.ID, nil
1069		}
1070	}
1071
1072	return "", fmt.Errorf("Failed to obtain network ID for network %s", networkName)
1073}
1074
1075// ImportPublicKey will create a KeyPair with a random name and a specified
1076// public key. An error will be returned if the keypair failed to be created.
1077func ImportPublicKey(t *testing.T, client *gophercloud.ServiceClient, publicKey string) (*keypairs.KeyPair, error) {
1078	keyPairName := tools.RandomString("keypair_", 5)
1079
1080	t.Logf("Attempting to create keypair: %s", keyPairName)
1081	createOpts := keypairs.CreateOpts{
1082		Name:      keyPairName,
1083		PublicKey: publicKey,
1084	}
1085	keyPair, err := keypairs.Create(client, createOpts).Extract()
1086	if err != nil {
1087		return keyPair, err
1088	}
1089
1090	t.Logf("Created keypair: %s", keyPairName)
1091
1092	th.AssertEquals(t, keyPair.Name, keyPairName)
1093	th.AssertEquals(t, keyPair.PublicKey, publicKey)
1094
1095	return keyPair, nil
1096}
1097
1098// ResizeServer performs a resize action on an instance. An error will be
1099// returned if the instance failed to resize.
1100// The new flavor that the instance will be resized to is specified in OS_FLAVOR_ID_RESIZE.
1101func ResizeServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) error {
1102	choices, err := clients.AcceptanceTestChoicesFromEnv()
1103	if err != nil {
1104		t.Fatal(err)
1105	}
1106
1107	opts := &servers.ResizeOpts{
1108		FlavorRef: choices.FlavorIDResize,
1109	}
1110	if res := servers.Resize(client, server.ID, opts); res.Err != nil {
1111		return res.Err
1112	}
1113
1114	if err := WaitForComputeStatus(client, server, "VERIFY_RESIZE"); err != nil {
1115		return err
1116	}
1117
1118	return nil
1119}
1120
1121// WaitForComputeStatus will poll an instance's status until it either matches
1122// the specified status or the status becomes ERROR.
1123func WaitForComputeStatus(client *gophercloud.ServiceClient, server *servers.Server, status string) error {
1124	return tools.WaitFor(func() (bool, error) {
1125		latest, err := servers.Get(client, server.ID).Extract()
1126		if err != nil {
1127			return false, err
1128		}
1129
1130		if latest.Status == status {
1131			// Success!
1132			return true, nil
1133		}
1134
1135		if latest.Status == "ERROR" {
1136			return false, fmt.Errorf("Instance in ERROR state")
1137		}
1138
1139		return false, nil
1140	})
1141}
1142
1143//Convenience method to fill an QuotaSet-UpdateOpts-struct from a QuotaSet-struct
1144func FillUpdateOptsFromQuotaSet(src quotasets.QuotaSet, dest *quotasets.UpdateOpts) {
1145	dest.FixedIPs = &src.FixedIPs
1146	dest.FloatingIPs = &src.FloatingIPs
1147	dest.InjectedFileContentBytes = &src.InjectedFileContentBytes
1148	dest.InjectedFilePathBytes = &src.InjectedFilePathBytes
1149	dest.InjectedFiles = &src.InjectedFiles
1150	dest.KeyPairs = &src.KeyPairs
1151	dest.RAM = &src.RAM
1152	dest.SecurityGroupRules = &src.SecurityGroupRules
1153	dest.SecurityGroups = &src.SecurityGroups
1154	dest.Cores = &src.Cores
1155	dest.Instances = &src.Instances
1156	dest.ServerGroups = &src.ServerGroups
1157	dest.ServerGroupMembers = &src.ServerGroupMembers
1158	dest.MetadataItems = &src.MetadataItems
1159}
1160
1161// RescueServer will place the specified server into rescue mode.
1162func RescueServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) error {
1163	t.Logf("Attempting to put server %s into rescue mode", server.ID)
1164	_, err := rescueunrescue.Rescue(client, server.ID, rescueunrescue.RescueOpts{}).Extract()
1165	if err != nil {
1166		return err
1167	}
1168
1169	if err := WaitForComputeStatus(client, server, "RESCUE"); err != nil {
1170		return err
1171	}
1172
1173	return nil
1174}
1175
1176// UnrescueServer will return server from rescue mode.
1177func UnrescueServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) error {
1178	t.Logf("Attempting to return server %s from rescue mode", server.ID)
1179	if err := rescueunrescue.Unrescue(client, server.ID).ExtractErr(); err != nil {
1180		return err
1181	}
1182
1183	if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
1184		return err
1185	}
1186
1187	return nil
1188}
1189
1190// CreateRemoteConsole will create a remote noVNC console for the specified server.
1191func CreateRemoteConsole(t *testing.T, client *gophercloud.ServiceClient, serverID string) (*remoteconsoles.RemoteConsole, error) {
1192	createOpts := remoteconsoles.CreateOpts{
1193		Protocol: remoteconsoles.ConsoleProtocolVNC,
1194		Type:     remoteconsoles.ConsoleTypeNoVNC,
1195	}
1196
1197	t.Logf("Attempting to create a %s console for the server %s", createOpts.Type, serverID)
1198	remoteConsole, err := remoteconsoles.Create(client, serverID, createOpts).Extract()
1199	if err != nil {
1200		return nil, err
1201	}
1202
1203	t.Logf("Successfully created console: %s", remoteConsole.URL)
1204	return remoteConsole, nil
1205}
1206
1207// CreateNoNetworkServer creates a basic instance with a randomly generated name.
1208// The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable.
1209// The image will be the value of the OS_IMAGE_ID environment variable.
1210// The instance will be launched without network interfaces attached.
1211// An error will be returned if the instance was unable to be created.
1212func CreateServerNoNetwork(t *testing.T, client *gophercloud.ServiceClient) (*servers.Server, error) {
1213	choices, err := clients.AcceptanceTestChoicesFromEnv()
1214	if err != nil {
1215		t.Fatal(err)
1216	}
1217
1218	name := tools.RandomString("ACPTTEST", 16)
1219	t.Logf("Attempting to create server: %s", name)
1220
1221	pwd := tools.MakeNewPassword("")
1222
1223	server, err := servers.Create(client, servers.CreateOpts{
1224		Name:      name,
1225		FlavorRef: choices.FlavorID,
1226		ImageRef:  choices.ImageID,
1227		AdminPass: pwd,
1228		Networks:  "none",
1229		Metadata: map[string]string{
1230			"abc": "def",
1231		},
1232		Personality: servers.Personality{
1233			&servers.File{
1234				Path:     "/etc/test",
1235				Contents: []byte("hello world"),
1236			},
1237		},
1238	}).Extract()
1239	if err != nil {
1240		return server, err
1241	}
1242
1243	if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
1244		return nil, err
1245	}
1246
1247	newServer, err := servers.Get(client, server.ID).Extract()
1248	if err != nil {
1249		return nil, err
1250	}
1251
1252	th.AssertEquals(t, newServer.Name, name)
1253	th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID)
1254	th.AssertEquals(t, newServer.Image["id"], choices.ImageID)
1255
1256	return newServer, nil
1257}
1258