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",