Bring partially https://github.com/gophercloud/gophercloud/pull/1221
Change-Id: I185e1991b68a22b387d043bc32bef79cd4c3dc4e
Related-To: PRODX-8893
diff --git a/openstack/orchestration/v1/stacks/doc.go b/openstack/orchestration/v1/stacks/doc.go
index 19231b5..dbacb03 100644
--- a/openstack/orchestration/v1/stacks/doc.go
+++ b/openstack/orchestration/v1/stacks/doc.go
@@ -1,8 +1,227 @@
-// Package stacks provides operation for working with Heat stacks. A stack is a
-// group of resources (servers, load balancers, databases, and so forth)
-// combined to fulfill a useful purpose. Based on a template, Heat orchestration
-// engine creates an instantiated set of resources (a stack) to run the
-// application framework or component specified (in the template). A stack is a
-// running instance of a template. The result of creating a stack is a deployment
-// of the application framework or component.
+/*
+Package stacks provides operation for working with Heat stacks. A stack is a
+group of resources (servers, load balancers, databases, and so forth)
+combined to fulfill a useful purpose. Based on a template, Heat orchestration
+engine creates an instantiated set of resources (a stack) to run the
+application framework or component specified (in the template). A stack is a
+running instance of a template. The result of creating a stack is a deployment
+of the application framework or component.
+
+Prepare required import packages
+
+import (
+ "fmt"
+ "gerrit.mcp.mirantis.net/debian/gophercloud.git"
+ "gerrit.mcp.mirantis.net/debian/gophercloud.git/openstack"
+ "gerrit.mcp.mirantis.net/debian/gophercloud.git/openstack/orchestration/v1/stacks"
+)
+
+Example of Preparing Orchestration client:
+
+ client, err := openstack.NewOrchestrationV1(provider, gophercloud.EndpointOpts{Region: "RegionOne"})
+
+Example of List Stack:
+ all_stack_pages, err := stacks.List(client, nil).AllPages()
+ if err != nil {
+ panic(err)
+ }
+
+ all_stacks, err := stacks.ExtractStacks(all_stack_pages)
+ if err != nil {
+ panic(err)
+ }
+
+ for _, stack := range all_stacks {
+ fmt.Printf("%+v\n", stack)
+ }
+
+
+Example to Create an Stack
+
+ // Create Template
+ t := make(map[string]interface{})
+ f, err := ioutil.ReadFile("template.yaml")
+ if err != nil {
+ panic(err)
+ }
+ err = yaml.Unmarshal(f, t)
+ if err != nil {
+ panic(err)
+ }
+
+ template := &stacks.Template{}
+ template.TE = stacks.TE{
+ Bin: f,
+ }
+ // Create Environment if needed
+ t_env := make(map[string]interface{})
+ f_env, err := ioutil.ReadFile("env.yaml")
+ if err != nil {
+ panic(err)
+ }
+ err = yaml.Unmarshal(f_env, t_env)
+ if err != nil {
+ panic(err)
+ }
+
+ env := &stacks.Environment{}
+ env.TE = stacks.TE{
+ Bin: f_env,
+ }
+
+ // Remember, the priority of parameters you given through
+ // Parameters is higher than the parameters you provided in EnvironmentOpts.
+ params := make(map[string]string)
+ params["number_of_nodes"] = 1
+ tags := []string{"example-stack"}
+ createOpts := &stacks.CreateOpts{
+ // The name of the stack. It must start with an alphabetic character.
+ Name: "testing_group",
+ // A structure that contains either the template file or url. Call the
+ // associated methods to extract the information relevant to send in a create request.
+ TemplateOpts: template,
+ // A structure that contains details for the environment of the stack.
+ EnvironmentOpts: env,
+ // User-defined parameters to pass to the template.
+ Parameters: params,
+ // A list of tags to assosciate with the Stack
+ Tags: tags,
+ }
+
+ r := stacks.Create(client, createOpts)
+ //dcreated_stack := stacks.CreatedStack()
+ if r.Err != nil {
+ panic(r.Err)
+ }
+ created_stack, err := r.Extract()
+ if err != nil {
+ panic(err)
+ }
+ fmt.Printf("Created Stack: %v", created_stack.ID)
+
+Example for Get Stack
+
+ get_result := stacks.Get(client, stackName, created_stack.ID)
+ if get_result.Err != nil {
+ panic(get_result.Err)
+ }
+ stack, err := get_result.Extract()
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println("Get Stack: Name: ", stack.Name, ", ID: ", stack.ID, ", Status: ", stack.Status)
+
+Example for Delete Stack
+
+ del_r := stacks.Delete(client, stackName, created_stack.ID)
+ if del_r.Err != nil {
+ panic(del_r.Err)
+ }
+ fmt.Println("Deleted Stack: ", stackName)
+
+Summary of Behavior Between Stack Update and UpdatePatch Methods :
+
+Function | Test Case | Result
+
+Update() | Template AND Parameters WITH Conflict | Parameter takes priority, parameters are set in raw_template.environment overlay
+Update() | Template ONLY | Template updates, raw_template.environment overlay is removed
+Update() | Parameters ONLY | No update, template is required
+
+UpdatePatch() | Template AND Parameters WITH Conflict | Parameter takes priority, parameters are set in raw_template.environment overlay
+UpdatePatch() | Template ONLY | Template updates, but raw_template.environment overlay is not removed, existing parameter values will remain
+UpdatePatch() | Parameters ONLY | Parameters (raw_template.environment) is updated, excluded values are unchanged
+
+The PUT Update() function will remove parameters from the raw_template.environment overlay
+if they are excluded from the operation, whereas PATCH Update() will never be destructive to the
+raw_template.environment overlay. It is not possible to expose the raw_template values with a
+patch update once they have been added to the environment overlay with the PATCH verb, but
+newly added values that do not have a corresponding key in the overlay will display the
+raw_template value.
+
+Example to Update a Stack Using the Update (PUT) Method
+
+ t := make(map[string]interface{})
+ f, err := ioutil.ReadFile("template.yaml")
+ if err != nil {
+ panic(err)
+ }
+ err = yaml.Unmarshal(f, t)
+ if err != nil {
+ panic(err)
+ }
+
+ template := stacks.Template{}
+ template.TE = stacks.TE{
+ Bin: f,
+ }
+
+ var params = make(map[string]interface{})
+ params["number_of_nodes"] = 2
+
+ stackName := "my_stack"
+ stackId := "d68cc349-ccc5-4b44-a17d-07f068c01e5a"
+
+ stackOpts := &stacks.UpdateOpts{
+ Parameters: params,
+ TemplateOpts: &template,
+ }
+
+ res := stacks.Update(orchestrationClient, stackName, stackId, stackOpts)
+ if res.Err != nil {
+ panic(res.Err)
+ }
+
+Example to Update a Stack Using the UpdatePatch (PATCH) Method
+
+ var params = make(map[string]interface{})
+ params["number_of_nodes"] = 2
+
+ stackName := "my_stack"
+ stackId := "d68cc349-ccc5-4b44-a17d-07f068c01e5a"
+
+ stackOpts := &stacks.UpdateOpts{
+ Parameters: params,
+ }
+
+ res := stacks.UpdatePatch(orchestrationClient, stackName, stackId, stackOpts)
+ if res.Err != nil {
+ panic(res.Err)
+ }
+
+Example YAML Template Containing a Heat::ResourceGroup With Three Nodes
+
+ heat_template_version: 2016-04-08
+
+ parameters:
+ number_of_nodes:
+ type: number
+ default: 3
+ description: the number of nodes
+ node_flavor:
+ type: string
+ default: m1.small
+ description: node flavor
+ node_image:
+ type: string
+ default: centos7.5-latest
+ description: node os image
+ node_network:
+ type: string
+ default: my-node-network
+ description: node network name
+
+ resources:
+ resource_group:
+ type: OS::Heat::ResourceGroup
+ properties:
+ count: { get_param: number_of_nodes }
+ resource_def:
+ type: OS::Nova::Server
+ properties:
+ name: my_nova_server_%index%
+ image: { get_param: node_image }
+ flavor: { get_param: node_flavor }
+ networks:
+ - network: {get_param: node_network}
+*/
package stacks
diff --git a/openstack/orchestration/v1/stacks/environment_test.go b/openstack/orchestration/v1/stacks/environment_test.go
index e7fe61d..e0c29c1 100644
--- a/openstack/orchestration/v1/stacks/environment_test.go
+++ b/openstack/orchestration/v1/stacks/environment_test.go
@@ -138,7 +138,7 @@
// handler for my_env.yaml
th.Mux.HandleFunc(urlparsed.Path, func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
- w.Header().Set("Content-Type", "application/jason")
+ w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, environmentContent)
})
@@ -150,7 +150,7 @@
// handler for my_db.yaml
th.Mux.HandleFunc(urlparsed.Path, func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
- w.Header().Set("Content-Type", "application/jason")
+ w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, dbContent)
})
@@ -170,13 +170,20 @@
th.AssertEquals(t, expectedEnvFilesContent, env.Files[fakeEnvURL])
th.AssertEquals(t, expectedDBFilesContent, env.Files[fakeDBURL])
+ // Update env's fileMaps to replace relative filenames by absolute URLs.
+ env.fileMaps = map[string]string{
+ "my_env.yaml": fakeEnvURL,
+ "my_db.yaml": fakeDBURL,
+ }
env.fixFileRefs()
+
expectedParsed := map[string]interface{}{
- "resource_registry": "2015-04-30",
- "My::WP::Server": fakeEnvURL,
- "resources": map[string]interface{}{
- "my_db_server": map[string]interface{}{
- "OS::DBInstance": fakeDBURL,
+ "resource_registry": map[string]interface{}{
+ "My::WP::Server": fakeEnvURL,
+ "resources": map[string]interface{}{
+ "my_db_server": map[string]interface{}{
+ "OS::DBInstance": fakeDBURL,
+ },
},
},
}
diff --git a/openstack/orchestration/v1/stacks/errors.go b/openstack/orchestration/v1/stacks/errors.go
index 01f4f94..aa8cee6 100644
--- a/openstack/orchestration/v1/stacks/errors.go
+++ b/openstack/orchestration/v1/stacks/errors.go
@@ -31,3 +31,11 @@
func (e ErrInvalidTemplateFormatVersion) Error() string {
return fmt.Sprintf("Template format version not found.")
}
+
+type ErrTemplateRequired struct {
+ gophercloud.BaseError
+}
+
+func (e ErrTemplateRequired) Error() string {
+ return fmt.Sprintf("Template required for this function.")
+}
diff --git a/openstack/orchestration/v1/stacks/fixtures.go b/openstack/orchestration/v1/stacks/fixtures.go
index d6fd075..58987d4 100644
--- a/openstack/orchestration/v1/stacks/fixtures.go
+++ b/openstack/orchestration/v1/stacks/fixtures.go
@@ -6,7 +6,7 @@
"heat_template_version": "2014-10-16",
"parameters": {
"flavor": {
- "default": 4353,
+ "default": "debian2G",
"description": "Flavor for the server to be created",
"hidden": true,
"type": "string"
@@ -32,7 +32,7 @@
flavor:
type: string
description: Flavor for the server to be created
- default: 4353
+ default: debian2G
hidden: true
resources:
test_server:
@@ -49,7 +49,7 @@
flavor:
type: string
description: Flavor for the server to be created
- default: 4353
+ default: debian2G
hidden: true
resources:
test_server:
@@ -128,7 +128,7 @@
flavor:
type: string
description: Flavor for the server to be created
- default: 4353
+ default: debian2G
hidden: true
resources:
test_server:
@@ -180,7 +180,7 @@
"heat_template_version": "2014-10-16",
"parameters": map[string]interface{}{
"flavor": map[string]interface{}{
- "default": 4353,
+ "default": "debian2G",
"description": "Flavor for the server to be created",
"hidden": true,
"type": "string",
diff --git a/openstack/orchestration/v1/stacks/requests.go b/openstack/orchestration/v1/stacks/requests.go
index 9eb6018..bc1c838 100644
--- a/openstack/orchestration/v1/stacks/requests.go
+++ b/openstack/orchestration/v1/stacks/requests.go
@@ -30,7 +30,7 @@
// A structure that contains details for the environment of the stack.
EnvironmentOpts *Environment `json:"-"`
// User-defined parameters to pass to the template.
- Parameters map[string]string `json:"parameters,omitempty"`
+ Parameters map[string]interface{} `json:"parameters,omitempty"`
// The timeout for stack creation in minutes.
Timeout int `json:"timeout_mins,omitempty"`
// A list of tags to assosciate with the Stack
@@ -127,7 +127,7 @@
// A structure that contains details for the environment of the stack.
EnvironmentOpts *Environment `json:"-"`
// User-defined parameters to pass to the template.
- Parameters map[string]string `json:"parameters,omitempty"`
+ Parameters map[string]interface{} `json:"parameters,omitempty"`
}
// ToStackAdoptMap casts a CreateOpts struct to a map.
@@ -265,42 +265,66 @@
ToStackUpdateMap() (map[string]interface{}, error)
}
+// UpdatePatchOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the UpdatePatch operation in this package
+type UpdatePatchOptsBuilder interface {
+ ToStackUpdatePatchMap() (map[string]interface{}, error)
+}
+
// UpdateOpts contains the common options struct used in this package's Update
-// operation.
+// and UpdatePatch operations.
type UpdateOpts struct {
// A structure that contains either the template file or url. Call the
// associated methods to extract the information relevant to send in a create request.
- TemplateOpts *Template `json:"-" required:"true"`
+ TemplateOpts *Template `json:"-"`
// A structure that contains details for the environment of the stack.
EnvironmentOpts *Environment `json:"-"`
// User-defined parameters to pass to the template.
- Parameters map[string]string `json:"parameters,omitempty"`
+ Parameters map[string]interface{} `json:"parameters,omitempty"`
// The timeout for stack creation in minutes.
Timeout int `json:"timeout_mins,omitempty"`
- // A list of tags to assosciate with the Stack
+ // A list of tags to associate with the Stack
Tags []string `json:"-"`
}
-// ToStackUpdateMap casts a CreateOpts struct to a map.
+// ToStackUpdateMap validates that a template was supplied and calls
+// the toStackUpdateMap private function.
func (opts UpdateOpts) ToStackUpdateMap() (map[string]interface{}, error) {
+ if opts.TemplateOpts == nil {
+ return nil, ErrTemplateRequired{}
+ }
+ return toStackUpdateMap(opts)
+}
+
+// ToStackUpdatePatchMap calls the private function toStackUpdateMap
+// directly.
+func (opts UpdateOpts) ToStackUpdatePatchMap() (map[string]interface{}, error) {
+ return toStackUpdateMap(opts)
+}
+
+// ToStackUpdateMap casts a CreateOpts struct to a map.
+func toStackUpdateMap(opts UpdateOpts) (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "")
if err != nil {
return nil, err
}
- if err := opts.TemplateOpts.Parse(); err != nil {
- return nil, err
- }
-
- if err := opts.TemplateOpts.getFileContents(opts.TemplateOpts.Parsed, ignoreIfTemplate, true); err != nil {
- return nil, err
- }
- opts.TemplateOpts.fixFileRefs()
- b["template"] = string(opts.TemplateOpts.Bin)
-
files := make(map[string]string)
- for k, v := range opts.TemplateOpts.Files {
- files[k] = v
+
+ if opts.TemplateOpts != nil {
+ if err := opts.TemplateOpts.Parse(); err != nil {
+ return nil, err
+ }
+
+ if err := opts.TemplateOpts.getFileContents(opts.TemplateOpts.Parsed, ignoreIfTemplate, true); err != nil {
+ return nil, err
+ }
+ opts.TemplateOpts.fixFileRefs()
+ b["template"] = string(opts.TemplateOpts.Bin)
+
+ for k, v := range opts.TemplateOpts.Files {
+ files[k] = v
+ }
}
if opts.EnvironmentOpts != nil {
@@ -328,8 +352,8 @@
return b, nil
}
-// Update accepts an UpdateOpts struct and updates an existing stack using the values
-// provided.
+// Update accepts an UpdateOpts struct and updates an existing stack using the
+// http PUT verb with the values provided. opts.TemplateOpts is required.
func Update(c *gophercloud.ServiceClient, stackName, stackID string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToStackUpdateMap()
if err != nil {
@@ -340,6 +364,18 @@
return
}
+// Update accepts an UpdateOpts struct and updates an existing stack using the
+// http PATCH verb with the values provided. opts.TemplateOpts is not required.
+func UpdatePatch(c *gophercloud.ServiceClient, stackName, stackID string, opts UpdatePatchOptsBuilder) (r UpdateResult) {
+ b, err := opts.ToStackUpdatePatchMap()
+ if err != nil {
+ r.Err = err
+ return
+ }
+ _, r.Err = c.Patch(updateURL(c, stackName, stackID), b, nil, nil)
+ return
+}
+
// Delete deletes a stack based on the stack name and stack ID.
func Delete(c *gophercloud.ServiceClient, stackName, stackID string) (r DeleteResult) {
_, r.Err = c.Delete(deleteURL(c, stackName, stackID), nil)
@@ -369,7 +405,7 @@
// A structure that contains details for the environment of the stack.
EnvironmentOpts *Environment `json:"-"`
// User-defined parameters to pass to the template.
- Parameters map[string]string `json:"parameters,omitempty"`
+ Parameters map[string]interface{} `json:"parameters,omitempty"`
}
// ToStackPreviewMap casts a PreviewOpts struct to a map.
diff --git a/openstack/orchestration/v1/stacks/results.go b/openstack/orchestration/v1/stacks/results.go
index d4fc967..e00eff2 100644
--- a/openstack/orchestration/v1/stacks/results.go
+++ b/openstack/orchestration/v1/stacks/results.go
@@ -63,17 +63,38 @@
type tmp ListedStack
var s struct {
tmp
- CreationTime gophercloud.JSONRFC3339NoZ `json:"creation_time"`
- UpdatedTime gophercloud.JSONRFC3339NoZ `json:"updated_time"`
+ CreationTime string `json:"creation_time"`
+ UpdatedTime string `json:"updated_time"`
}
+
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
+
*r = ListedStack(s.tmp)
- r.CreationTime = time.Time(s.CreationTime)
- r.UpdatedTime = time.Time(s.UpdatedTime)
+ if s.CreationTime != "" {
+ t, err := time.Parse(time.RFC3339, s.CreationTime)
+ if err != nil {
+ t, err = time.Parse(gophercloud.RFC3339NoZ, s.CreationTime)
+ if err != nil {
+ return err
+ }
+ }
+ r.CreationTime = t
+ }
+
+ if s.UpdatedTime != "" {
+ t, err := time.Parse(time.RFC3339, s.UpdatedTime)
+ if err != nil {
+ t, err = time.Parse(gophercloud.RFC3339NoZ, s.UpdatedTime)
+ if err != nil {
+ return err
+ }
+ }
+ r.UpdatedTime = t
+ }
return nil
}
@@ -112,17 +133,38 @@
type tmp RetrievedStack
var s struct {
tmp
- CreationTime gophercloud.JSONRFC3339NoZ `json:"creation_time"`
- UpdatedTime gophercloud.JSONRFC3339NoZ `json:"updated_time"`
+ CreationTime string `json:"creation_time"`
+ UpdatedTime string `json:"updated_time"`
}
+
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
+
*r = RetrievedStack(s.tmp)
- r.CreationTime = time.Time(s.CreationTime)
- r.UpdatedTime = time.Time(s.UpdatedTime)
+ if s.CreationTime != "" {
+ t, err := time.Parse(time.RFC3339, s.CreationTime)
+ if err != nil {
+ t, err = time.Parse(gophercloud.RFC3339NoZ, s.CreationTime)
+ if err != nil {
+ return err
+ }
+ }
+ r.CreationTime = t
+ }
+
+ if s.UpdatedTime != "" {
+ t, err := time.Parse(time.RFC3339, s.UpdatedTime)
+ if err != nil {
+ t, err = time.Parse(gophercloud.RFC3339NoZ, s.UpdatedTime)
+ if err != nil {
+ return err
+ }
+ }
+ r.UpdatedTime = t
+ }
return nil
}
@@ -173,17 +215,38 @@
type tmp PreviewedStack
var s struct {
tmp
- CreationTime gophercloud.JSONRFC3339NoZ `json:"creation_time"`
- UpdatedTime gophercloud.JSONRFC3339NoZ `json:"updated_time"`
+ CreationTime string `json:"creation_time"`
+ UpdatedTime string `json:"updated_time"`
}
+
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
+
*r = PreviewedStack(s.tmp)
- r.CreationTime = time.Time(s.CreationTime)
- r.UpdatedTime = time.Time(s.UpdatedTime)
+ if s.CreationTime != "" {
+ t, err := time.Parse(time.RFC3339, s.CreationTime)
+ if err != nil {
+ t, err = time.Parse(gophercloud.RFC3339NoZ, s.CreationTime)
+ if err != nil {
+ return err
+ }
+ }
+ r.CreationTime = t
+ }
+
+ if s.UpdatedTime != "" {
+ t, err := time.Parse(time.RFC3339, s.UpdatedTime)
+ if err != nil {
+ t, err = time.Parse(gophercloud.RFC3339NoZ, s.UpdatedTime)
+ if err != nil {
+ return err
+ }
+ }
+ r.UpdatedTime = t
+ }
return nil
}
diff --git a/openstack/orchestration/v1/stacks/template_test.go b/openstack/orchestration/v1/stacks/template_test.go
index 72368c3..2e94d25 100644
--- a/openstack/orchestration/v1/stacks/template_test.go
+++ b/openstack/orchestration/v1/stacks/template_test.go
@@ -99,7 +99,7 @@
- {uuid: 11111111-1111-1111-1111-111111111111}`
th.Mux.HandleFunc(urlparsed.Path, func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
- w.Header().Set("Content-Type", "application/jason")
+ w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, myNovaContent)
})
diff --git a/openstack/orchestration/v1/stacks/testing/fixtures.go b/openstack/orchestration/v1/stacks/testing/fixtures.go
index 11e9e70..c22b93e 100644
--- a/openstack/orchestration/v1/stacks/testing/fixtures.go
+++ b/openstack/orchestration/v1/stacks/testing/fixtures.go
@@ -12,6 +12,9 @@
fake "gerrit.mcp.mirantis.net/debian/gophercloud.git/testhelper/client"
)
+var Create_time, _ = time.Parse(time.RFC3339, "2018-06-26T07:58:17Z")
+var Updated_time, _ = time.Parse(time.RFC3339, "2018-06-26T07:59:17Z")
+
// CreateExpected represents the expected object from a Create request.
var CreateExpected = &stacks.CreatedStack{
ID: "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
@@ -61,7 +64,7 @@
},
StatusReason: "Stack CREATE completed successfully",
Name: "postman_stack",
- CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC),
+ CreationTime: Create_time,
Status: "CREATE_COMPLETE",
ID: "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
Tags: []string{"rackspace", "atx"},
@@ -76,8 +79,8 @@
},
StatusReason: "Stack successfully updated",
Name: "gophercloud-test-stack-2",
- CreationTime: time.Date(2014, 12, 11, 17, 39, 16, 0, time.UTC),
- UpdatedTime: time.Date(2014, 12, 11, 17, 40, 37, 0, time.UTC),
+ CreationTime: Create_time,
+ UpdatedTime: Updated_time,
Status: "UPDATE_COMPLETE",
ID: "db6977b2-27aa-4775-9ae7-6213212d4ada",
Tags: []string{"sfo", "satx"},
@@ -98,7 +101,7 @@
],
"stack_status_reason": "Stack CREATE completed successfully",
"stack_name": "postman_stack",
- "creation_time": "2015-02-03T20:07:39",
+ "creation_time": "2018-06-26T07:58:17Z",
"updated_time": null,
"stack_status": "CREATE_COMPLETE",
"id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
@@ -114,8 +117,8 @@
],
"stack_status_reason": "Stack successfully updated",
"stack_name": "gophercloud-test-stack-2",
- "creation_time": "2014-12-11T17:39:16",
- "updated_time": "2014-12-11T17:40:37",
+ "creation_time": "2018-06-26T07:58:17Z",
+ "updated_time": "2018-06-26T07:59:17Z",
"stack_status": "UPDATE_COMPLETE",
"id": "db6977b2-27aa-4775-9ae7-6213212d4ada",
"tags": ["sfo", "satx"]
@@ -158,7 +161,7 @@
StatusReason: "Stack CREATE completed successfully",
Name: "postman_stack",
Outputs: []map[string]interface{}{},
- CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC),
+ CreationTime: Create_time,
Links: []gophercloud.Link{
{
Href: "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87",
@@ -187,7 +190,7 @@
"stack_status_reason": "Stack CREATE completed successfully",
"stack_name": "postman_stack",
"outputs": [],
- "creation_time": "2015-02-03T20:07:39",
+ "creation_time": "2018-06-26T07:58:17Z",
"links": [
{
"href": "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87",
@@ -233,6 +236,19 @@
})
}
+// HandleUpdatePatchSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87`
+// on the test handler mux that responds with an `Update` response.
+func HandleUpdatePatchSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/stacks/gophercloud-test-stack-2/db6977b2-27aa-4775-9ae7-6213212d4ada", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "PATCH")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+ th.TestHeader(t, r, "Accept", "application/json")
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusAccepted)
+ })
+}
+
// HandleDeleteSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87`
// on the test handler mux that responds with a `Delete` response.
func HandleDeleteSuccessfully(t *testing.T) {
@@ -256,7 +272,7 @@
"OS::stack_id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
},
Name: "postman_stack",
- CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC),
+ CreationTime: Create_time,
Links: []gophercloud.Link{
{
Href: "http://166.76.160.117:8004/v1/98606384f58d4ad0b3db7d0d779549ac/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87",
diff --git a/openstack/orchestration/v1/stacks/testing/requests_test.go b/openstack/orchestration/v1/stacks/testing/requests_test.go
index 2846811..de5f9ec 100644
--- a/openstack/orchestration/v1/stacks/testing/requests_test.go
+++ b/openstack/orchestration/v1/stacks/testing/requests_test.go
@@ -39,6 +39,29 @@
th.AssertDeepEquals(t, expected, actual)
}
+func TestCreateStackMissingRequiredInOpts(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleCreateSuccessfully(t, CreateOutput)
+ template := new(stacks.Template)
+ template.Bin = []byte(`
+ {
+ "heat_template_version": "2013-05-23",
+ "description": "Simple template to test heat commands",
+ "parameters": {
+ "flavor": {
+ "default": "m1.tiny",
+ "type": "string"
+ }
+ }
+ }`)
+ createOpts := stacks.CreateOpts{
+ DisableRollback: gophercloud.Disabled,
+ }
+ r := stacks.Create(fake.ServiceClient(), createOpts)
+ th.AssertEquals(t, "Missing input for argument [Name]", r.Err.Error())
+}
+
func TestAdoptStack(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
@@ -133,13 +156,45 @@
}
}
}`)
- updateOpts := stacks.UpdateOpts{
+ updateOpts := &stacks.UpdateOpts{
TemplateOpts: template,
}
err := stacks.Update(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr()
th.AssertNoErr(t, err)
}
+func TestUpdateStackNoTemplate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleUpdateSuccessfully(t)
+
+ parameters := make(map[string]interface{})
+ parameters["flavor"] = "m1.tiny"
+
+ updateOpts := &stacks.UpdateOpts{
+ Parameters: parameters,
+ }
+ expected := stacks.ErrTemplateRequired{}
+
+ err := stacks.Update(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr()
+ th.AssertEquals(t, expected, err)
+}
+
+func TestUpdatePatchStack(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+ HandleUpdatePatchSuccessfully(t)
+
+ parameters := make(map[string]interface{})
+ parameters["flavor"] = "m1.tiny"
+
+ updateOpts := &stacks.UpdateOpts{
+ Parameters: parameters,
+ }
+ err := stacks.UpdatePatch(fake.ServiceClient(), "gophercloud-test-stack-2", "db6977b2-27aa-4775-9ae7-6213212d4ada", updateOpts).ExtractErr()
+ th.AssertNoErr(t, err)
+}
+
func TestDeleteStack(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
diff --git a/openstack/orchestration/v1/stacks/utils_test.go b/openstack/orchestration/v1/stacks/utils_test.go
index a20ca1b..591edf8 100644
--- a/openstack/orchestration/v1/stacks/utils_test.go
+++ b/openstack/orchestration/v1/stacks/utils_test.go
@@ -21,7 +21,7 @@
th.AssertEquals(t, string(te.Bin), `london bridge is falling down: my fair lady`)
}
-func TesttoStringKeys(t *testing.T) {
+func TestToStringKeys(t *testing.T) {
var test1 interface{} = map[interface{}]interface{}{
"Adam": "Smith",
"Isaac": "Newton",