blob: d1b41c4d80b981d0c51eebe14f374e2680b6a894 [file] [log] [blame]
Jon Perritta065da12015-02-06 10:20:16 -07001package stackresources
2
3import (
4 "fmt"
5 "net/http"
6 "testing"
7 "time"
8
9 "github.com/rackspace/gophercloud"
10 th "github.com/rackspace/gophercloud/testhelper"
11 fake "github.com/rackspace/gophercloud/testhelper/client"
12)
13
14var FindExpected = []Resource{
15 Resource{
16 Name: "hello_world",
17 Links: []gophercloud.Link{
18 gophercloud.Link{
19 Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
20 Rel: "self",
21 },
22 gophercloud.Link{
23 Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
24 Rel: "stack",
25 },
26 },
27 LogicalID: "hello_world",
28 StatusReason: "state changed",
29 UpdatedTime: time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC),
30 RequiredBy: []interface{}{},
31 Status: "CREATE_IN_PROGRESS",
32 PhysicalID: "49181cd6-169a-4130-9455-31185bbfc5bf",
33 Type: "OS::Nova::Server",
34 },
35}
36
37const FindOutput = `
38{
39 "resources": [
40 {
41 "resource_name": "hello_world",
42 "links": [
43 {
44 "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
45 "rel": "self"
46 },
47 {
48 "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
49 "rel": "stack"
50 }
51 ],
52 "logical_resource_id": "hello_world",
53 "resource_status_reason": "state changed",
54 "updated_time": "2015-02-05T21:33:11Z",
55 "required_by": [],
56 "resource_status": "CREATE_IN_PROGRESS",
57 "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
58 "resource_type": "OS::Nova::Server"
59 }
60 ]
61}`
62
63// HandleFindSuccessfully creates an HTTP handler at `/stacks/hello_world/resources`
64// on the test handler mux that responds with a `Find` response.
65func HandleFindSuccessfully(t *testing.T, output string) {
66 th.Mux.HandleFunc("/stacks/hello_world/resources", func(w http.ResponseWriter, r *http.Request) {
67 th.TestMethod(t, r, "GET")
68 th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
69 th.TestHeader(t, r, "Accept", "application/json")
70
71 w.Header().Set("Content-Type", "application/json")
72 w.WriteHeader(http.StatusOK)
73 fmt.Fprintf(w, output)
74 })
75}
76
77var ListExpected = []Resource{
78 Resource{
79 Name: "hello_world",
80 Links: []gophercloud.Link{
81 gophercloud.Link{
82 Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
83 Rel: "self",
84 },
85 gophercloud.Link{
86 Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
87 Rel: "stack",
88 },
89 },
90 LogicalID: "hello_world",
91 StatusReason: "state changed",
92 UpdatedTime: time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC),
93 RequiredBy: []interface{}{},
94 Status: "CREATE_IN_PROGRESS",
95 PhysicalID: "49181cd6-169a-4130-9455-31185bbfc5bf",
96 Type: "OS::Nova::Server",
97 },
98}
99
100const ListOutput = `{
101 "resources": [
102 {
103 "resource_name": "hello_world",
104 "links": [
105 {
106 "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b/resources/hello_world",
107 "rel": "self"
108 },
109 {
110 "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/5f57cff9-93fc-424e-9f78-df0515e7f48b",
111 "rel": "stack"
112 }
113 ],
114 "logical_resource_id": "hello_world",
115 "resource_status_reason": "state changed",
116 "updated_time": "2015-02-05T21:33:11Z",
117 "required_by": [],
118 "resource_status": "CREATE_IN_PROGRESS",
119 "physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
120 "resource_type": "OS::Nova::Server"
121 }
122]
123}`
124
125// HandleListSuccessfully creates an HTTP handler at `/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources`
126// on the test handler mux that responds with a `List` response.
127func HandleListSuccessfully(t *testing.T, output string) {
128 th.Mux.HandleFunc("/stacks/hello_world/49181cd6-169a-4130-9455-31185bbfc5bf/resources", func(w http.ResponseWriter, r *http.Request) {
129 th.TestMethod(t, r, "GET")
130 th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
131 th.TestHeader(t, r, "Accept", "application/json")
132
133 w.Header().Set("Content-Type", "application/json")
134 r.ParseForm()
135 marker := r.Form.Get("marker")
136 switch marker {
137 case "":
138 fmt.Fprintf(w, output)
139 default:
140 t.Fatalf("Unexpected marker: [%s]", marker)
141 }
142 })
143}
144
145var GetExpected = &Resource{
146 Name: "wordpress_instance",
147 Links: []gophercloud.Link{
148 gophercloud.Link{
149 Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance",
150 Rel: "self",
151 },
152 gophercloud.Link{
153 Href: "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e",
154 Rel: "stack",
155 },
156 },
157 LogicalID: "wordpress_instance",
158 StatusReason: "state changed",
159 UpdatedTime: time.Date(2014, 12, 10, 18, 34, 35, 0, time.UTC),
160 RequiredBy: []interface{}{},
161 Status: "CREATE_COMPLETE",
162 PhysicalID: "00e3a2fe-c65d-403c-9483-4db9930dd194",
163 Type: "OS::Nova::Server",
164}
165
166const GetOutput = `
167{
168 "resource": {
169 "resource_name": "wordpress_instance",
170 "description": "",
171 "links": [
172 {
173 "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance",
174 "rel": "self"
175 },
176 {
177 "href": "http://166.78.160.107:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e",
178 "rel": "stack"
179 }
180 ],
181 "logical_resource_id": "wordpress_instance",
182 "resource_status": "CREATE_COMPLETE",
183 "updated_time": "2014-12-10T18:34:35Z",
184 "required_by": [],
185 "resource_status_reason": "state changed",
186 "physical_resource_id": "00e3a2fe-c65d-403c-9483-4db9930dd194",
187 "resource_type": "OS::Nova::Server"
188 }
189}`
190
191// HandleGetSuccessfully creates an HTTP handler at `/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance`
192// on the test handler mux that responds with a `Get` response.
193func HandleGetSuccessfully(t *testing.T, output string) {
194 th.Mux.HandleFunc("/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance", func(w http.ResponseWriter, r *http.Request) {
195 th.TestMethod(t, r, "GET")
196 th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
197 th.TestHeader(t, r, "Accept", "application/json")
198
199 w.Header().Set("Content-Type", "application/json")
200 w.WriteHeader(http.StatusOK)
201 fmt.Fprintf(w, output)
202 })
203}
204
205var MetadataExpected = map[string]string{
206 "number": "7",
207 "animal": "auk",
208}
209
210const MetadataOutput = `
211{
212 "metadata": {
213 "number": "7",
214 "animal": "auk"
215 }
216}`
217
218// HandleMetadataSuccessfully creates an HTTP handler at `/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance/metadata`
219// on the test handler mux that responds with a `Metadata` response.
220func HandleMetadataSuccessfully(t *testing.T, output string) {
221 th.Mux.HandleFunc("/stacks/teststack/0b1771bd-9336-4f2b-ae86-a80f971faf1e/resources/wordpress_instance/metadata", func(w http.ResponseWriter, r *http.Request) {
222 th.TestMethod(t, r, "GET")
223 th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
224 th.TestHeader(t, r, "Accept", "application/json")
225
226 w.Header().Set("Content-Type", "application/json")
227 w.WriteHeader(http.StatusOK)
228 fmt.Fprintf(w, output)
229 })
230}
231
232var ListTypesExpected = []string{
233 "OS::Nova::Server",
234 "OS::Heat::RandomString",
235 "OS::Swift::Container",
236 "OS::Trove::Instance",
237 "OS::Nova::FloatingIPAssociation",
238 "OS::Cinder::VolumeAttachment",
239 "OS::Nova::FloatingIP",
240 "OS::Nova::KeyPair",
241}
242
243const ListTypesOutput = `
244{
245 "resource_types": [
246 "OS::Nova::Server",
247 "OS::Heat::RandomString",
248 "OS::Swift::Container",
249 "OS::Trove::Instance",
250 "OS::Nova::FloatingIPAssociation",
251 "OS::Cinder::VolumeAttachment",
252 "OS::Nova::FloatingIP",
253 "OS::Nova::KeyPair"
254 ]
255}`
256
257// HandleListTypesSuccessfully creates an HTTP handler at `/resource_types`
258// on the test handler mux that responds with a `ListTypes` response.
259func HandleListTypesSuccessfully(t *testing.T, output string) {
260 th.Mux.HandleFunc("/resource_types", func(w http.ResponseWriter, r *http.Request) {
261 th.TestMethod(t, r, "GET")
262 th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
263 th.TestHeader(t, r, "Accept", "application/json")
264
265 w.Header().Set("Content-Type", "application/json")
266 w.WriteHeader(http.StatusOK)
267 fmt.Fprintf(w, output)
268 })
269}
Jon Perritt1d4aca02015-02-06 12:29:16 -0700270
271var GetSchemaExpected = &TypeSchema{
272 Attributes: map[string]interface{}{
273 "an_attribute": map[string]interface{}{
274 "description": "An attribute description .",
275 },
276 },
277 Properties: map[string]interface{}{
278 "a_property": map[string]interface{}{
279 "update_allowed": false,
280 "required": true,
281 "type": "string",
282 "description": "A resource description.",
283 },
284 },
285 ResourceType: "OS::Heat::AResourceName",
286}
287
288const GetSchemaOutput = `
289{
290 "attributes": {
291 "an_attribute": {
292 "description": "An attribute description ."
293 }
294 },
295 "properties": {
296 "a_property": {
297 "update_allowed": false,
298 "required": true,
299 "type": "string",
300 "description": "A resource description."
301 }
302 },
303 "resource_type": "OS::Heat::AResourceName"
304}`
305
306// HandleGetSchemaSuccessfully creates an HTTP handler at `/resource_types/OS::Heat::AResourceName`
307// on the test handler mux that responds with a `Schema` response.
308func HandleGetSchemaSuccessfully(t *testing.T, output string) {
309 th.Mux.HandleFunc("/resource_types/OS::Heat::AResourceName", func(w http.ResponseWriter, r *http.Request) {
310 th.TestMethod(t, r, "GET")
311 th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
312 th.TestHeader(t, r, "Accept", "application/json")
313
314 w.Header().Set("Content-Type", "application/json")
315 w.WriteHeader(http.StatusOK)
316 fmt.Fprintf(w, output)
317 })
318}
319
320const GetTemplateOutput = `
321{
322 "Outputs": {
323 "addresses": {
324 "Description": "A dict of all network addresses with correspondingport_id.",
325 "Value": "{\"Fn::GetAtt\": [\"Server\", \"addresses\"]}"
326 },
327 "first_address": {
328 "Description": "Convenience attribute to fetch the first assigned network address, or an empty string if nothing has been assigned at this time. Result may not be predictable if the server has addresses from more than one network.",
329 "Value": "{\"Fn::GetAtt\": [\"Server\", \"first_address\"]}"
330 },
331 "show": {
332 "Description": "A dict of all server details as returned by the API.",
333 "Value": "{\"Fn::GetAtt\": [\"Server\", \"show\"]}"
334 },
335 "instance_name": {
336 "Description": "AWS compatible instance name.",
337 "Value": "{\"Fn::GetAtt\": [\"Server\", \"instance_name\"]}"
338 },
339 "accessIPv4": {
340 "Description": "The manually assigned alternative public IPv4 address of the server.",
341 "Value": "{\"Fn::GetAtt\": [\"Server\", \"accessIPv4\"]}"
342 },
343 "accessIPv6": {
344 "Description": "The manually assigned alternative public IPv6 address of the server.",
345 "Value": "{\"Fn::GetAtt\": [\"Server\", \"accessIPv6\"]}"
346 },
347 "networks": {
348 "Description": "A dict of assigned network addresses of the form: {\"public\": [ip1, ip2...], \"private\": [ip3, ip4]}.",
349 "Value": "{\"Fn::GetAtt\": [\"Server\", \"networks\"]}"
350 }
351 },
352 "HeatTemplateFormatVersion": "2012-12-12",
353 "Parameters": {
354 "scheduler_hints": {
355 "Type": "Json",
356 "Description": "Arbitrary key-value pairs specified by the client to help boot a server."
357 },
358 "admin_pass": {
359 "Type": "String",
360 "Description": "The administrator password for the server."
361 },
362 "user_data_format": {
363 "Default": "HEAT_CFNTOOLS",
364 "Type": "String",
365 "Description": "How the user_data should be formatted for the server. For HEAT_CFNTOOLS, the user_data is bundled as part of the heat-cfntools cloud-init boot configuration data. For RAW the user_data is passed to Nova unmodified. For SOFTWARE_CONFIG user_data is bundled as part of the software config data, and metadata is derived from any associated SoftwareDeployment resources.",
366 "AllowedValues": [
367 "HEAT_CFNTOOLS",
368 "RAW",
369 "SOFTWARE_CONFIG"
370 ]
371 },
372 "admin_user": {
373 "Type": "String",
374 "Description": "Name of the administrative user to use on the server. This property will be removed from Juno in favor of the default cloud-init user set up for each image (e.g. \"ubuntu\" for Ubuntu 12.04+, \"fedora\" for Fedora 19+ and \"cloud-user\" for CentOS/RHEL 6.5)."
375 },
376 "name": {
377 "Type": "String",
378 "Description": "Server name."
379 },
380 "block_device_mapping": {
381 "Type": "CommaDelimitedList",
382 "Description": "Block device mappings for this server."
383 },
384 "key_name": {
385 "Type": "String",
386 "Description": "Name of keypair to inject into the server."
387 },
388 "image": {
389 "Type": "String",
390 "Description": "The ID or name of the image to boot with."
391 },
392 "availability_zone": {
393 "Type": "String",
394 "Description": "Name of the availability zone for server placement."
395 },
396 "image_update_policy": {
397 "Default": "REPLACE",
398 "Type": "String",
399 "Description": "Policy on how to apply an image-id update; either by requesting a server rebuild or by replacing the entire server",
400 "AllowedValues": [
401 "REBUILD",
402 "REPLACE",
403 "REBUILD_PRESERVE_EPHEMERAL"
404 ]
405 },
406 "software_config_transport": {
407 "Default": "POLL_SERVER_CFN",
408 "Type": "String",
409 "Description": "How the server should receive the metadata required for software configuration. POLL_SERVER_CFN will allow calls to the cfn API action DescribeStackResource authenticated with the provided keypair. POLL_SERVER_HEAT will allow calls to the Heat API resource-show using the provided keystone credentials.",
410 "AllowedValues": [
411 "POLL_SERVER_CFN",
412 "POLL_SERVER_HEAT"
413 ]
414 },
415 "metadata": {
416 "Type": "Json",
417 "Description": "Arbitrary key/value metadata to store for this server. Both keys and values must be 255 characters or less. Non-string values will be serialized to JSON (and the serialized string must be 255 characters or less)."
418 },
419 "personality": {
420 "Default": {},
421 "Type": "Json",
422 "Description": "A map of files to create/overwrite on the server upon boot. Keys are file names and values are the file contents."
423 },
424 "user_data": {
425 "Default": "",
426 "Type": "String",
427 "Description": "User data script to be executed by cloud-init."
428 },
429 "flavor_update_policy": {
430 "Default": "RESIZE",
431 "Type": "String",
432 "Description": "Policy on how to apply a flavor update; either by requesting a server resize or by replacing the entire server.",
433 "AllowedValues": [
434 "RESIZE",
435 "REPLACE"
436 ]
437 },
438 "flavor": {
439 "Type": "String",
440 "Description": "The ID or name of the flavor to boot onto."
441 },
442 "diskConfig": {
443 "Type": "String",
444 "Description": "Control how the disk is partitioned when the server is created.",
445 "AllowedValues": [
446 "AUTO",
447 "MANUAL"
448 ]
449 },
450 "reservation_id": {
451 "Type": "String",
452 "Description": "A UUID for the set of servers being requested."
453 },
454 "networks": {
455 "Type": "CommaDelimitedList",
456 "Description": "An ordered list of nics to be added to this server, with information about connected networks, fixed ips, port etc."
457 },
458 "security_groups": {
459 "Default": [],
460 "Type": "CommaDelimitedList",
461 "Description": "List of security group names or IDs. Cannot be used if neutron ports are associated with this server; assign security groups to the ports instead."
462 },
463 "config_drive": {
464 "Type": "String",
465 "Description": "value for config drive either boolean, or volume-id."
466 }
467 },
468 "Resources": {
469 "Server": {
470 "Type": "OS::Nova::Server",
471 "Properties": {
472 "scheduler_hints": {
473 "Ref": "scheduler_hints"
474 },
475 "admin_pass": {
476 "Ref": "admin_pass"
477 },
478 "user_data_format": {
479 "Ref": "user_data_format"
480 },
481 "admin_user": {
482 "Ref": "admin_user"
483 },
484 "name": {
485 "Ref": "name"
486 },
487 "block_device_mapping": {
488 "Fn::Split": [
489 ",",
490 {
491 "Ref": "block_device_mapping"
492 }
493 ]
494 },
495 "key_name": {
496 "Ref": "key_name"
497 },
498 "image": {
499 "Ref": "image"
500 },
501 "availability_zone": {
502 "Ref": "availability_zone"
503 },
504 "image_update_policy": {
505 "Ref": "image_update_policy"
506 },
507 "software_config_transport": {
508 "Ref": "software_config_transport"
509 },
510 "metadata": {
511 "Ref": "metadata"
512 },
513 "personality": {
514 "Ref": "personality"
515 },
516 "user_data": {
517 "Ref": "user_data"
518 },
519 "flavor_update_policy": {
520 "Ref": "flavor_update_policy"
521 },
522 "flavor": {
523 "Ref": "flavor"
524 },
525 "diskConfig": {
526 "Ref": "diskConfig"
527 },
528 "reservation_id": {
529 "Ref": "reservation_id"
530 },
531 "networks": {
532 "Fn::Split": [
533 ",",
534 {
535 "Ref": "networks"
536 }
537 ]
538 },
539 "security_groups": {
540 "Fn::Split": [
541 ",",
542 {
543 "Ref": "security_groups"
544 }
545 ]
546 },
547 "config_drive": {
548 "Ref": "config_drive"
549 }
550 }
551 }
552 }
553}`