Adding update port operation
diff --git a/openstack/networking/v2/ports/errors.go b/openstack/networking/v2/ports/errors.go
index 1f9e7dd..55c01e9 100644
--- a/openstack/networking/v2/ports/errors.go
+++ b/openstack/networking/v2/ports/errors.go
@@ -6,4 +6,6 @@
 	return fmt.Errorf("%s", str)
 }
 
-var ()
+var (
+	ErrNetworkIDRequired = err("A Network ID is required")
+)
diff --git a/openstack/networking/v2/ports/requests.go b/openstack/networking/v2/ports/requests.go
index 672b904..919e6de 100644
--- a/openstack/networking/v2/ports/requests.go
+++ b/openstack/networking/v2/ports/requests.go
@@ -103,3 +103,133 @@
 	}
 	return &p, nil
 }
+
+type PortOpts struct {
+	NetworkID      string
+	Status         string
+	Name           string
+	AdminStateUp   *bool
+	TenantID       string
+	MACAddress     string
+	FixedIPs       interface{}
+	SecurityGroups []string
+}
+
+func maybeString(original string) *string {
+	if original != "" {
+		return &original
+	}
+	return nil
+}
+
+func Create(c *gophercloud.ServiceClient, opts PortOpts) (*Port, error) {
+	type port struct {
+		NetworkID      string      `json:"network_id,omitempty"`
+		Status         *string     `json:"status,omitempty"`
+		Name           *string     `json:"name,omitempty"`
+		AdminStateUp   *bool       `json:"admin_state_up,omitempty"`
+		TenantID       *string     `json:"tenant_id,omitempty"`
+		MACAddress     *string     `json:"mac_address,omitempty"`
+		FixedIPs       interface{} `json:"fixed_ips,omitempty"`
+		SecurityGroups []string    `json:"security_groups,omitempty"`
+	}
+	type request struct {
+		Port port `json:"port"`
+	}
+
+	// Validate
+	if opts.NetworkID == "" {
+		return nil, ErrNetworkIDRequired
+	}
+
+	// Populate request body
+	reqBody := request{Port: port{
+		NetworkID:    opts.NetworkID,
+		Status:       maybeString(opts.Status),
+		Name:         maybeString(opts.Name),
+		AdminStateUp: opts.AdminStateUp,
+		TenantID:     maybeString(opts.TenantID),
+		MACAddress:   maybeString(opts.MACAddress),
+	}}
+
+	if opts.FixedIPs != nil {
+		reqBody.Port.FixedIPs = opts.FixedIPs
+	}
+
+	if opts.SecurityGroups != nil {
+		reqBody.Port.SecurityGroups = opts.SecurityGroups
+	}
+
+	// Response
+	type response struct {
+		Port *Port `json:"port"`
+	}
+	var res response
+	_, err := perigee.Request("POST", CreateURL(c), perigee.Options{
+		MoreHeaders: c.Provider.AuthenticatedHeaders(),
+		ReqBody:     &reqBody,
+		Results:     &res,
+		OkCodes:     []int{201},
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return res.Port, nil
+}
+
+func Update(c *gophercloud.ServiceClient, id string, opts PortOpts) (*Port, error) {
+	type port struct {
+		NetworkID      string      `json:"network_id,omitempty"`
+		Status         *string     `json:"status,omitempty"`
+		Name           *string     `json:"name,omitempty"`
+		AdminStateUp   *bool       `json:"admin_state_up,omitempty"`
+		TenantID       *string     `json:"tenant_id,omitempty"`
+		MACAddress     *string     `json:"mac_address,omitempty"`
+		FixedIPs       interface{} `json:"fixed_ips,omitempty"`
+		SecurityGroups []string    `json:"security_groups,omitempty"`
+	}
+	type request struct {
+		Port port `json:"port"`
+	}
+
+	// Validate
+	if opts.NetworkID == "" {
+		return nil, ErrNetworkIDRequired
+	}
+
+	// Populate request body
+	reqBody := request{Port: port{
+		NetworkID:    opts.NetworkID,
+		Status:       maybeString(opts.Status),
+		Name:         maybeString(opts.Name),
+		AdminStateUp: opts.AdminStateUp,
+		TenantID:     maybeString(opts.TenantID),
+		MACAddress:   maybeString(opts.MACAddress),
+	}}
+
+	if opts.FixedIPs != nil {
+		reqBody.Port.FixedIPs = opts.FixedIPs
+	}
+
+	if opts.SecurityGroups != nil {
+		reqBody.Port.SecurityGroups = opts.SecurityGroups
+	}
+
+	// Response
+	type response struct {
+		Port *Port `json:"port"`
+	}
+	var res response
+	_, err := perigee.Request("PUT", UpdateURL(c, id), perigee.Options{
+		MoreHeaders: c.Provider.AuthenticatedHeaders(),
+		ReqBody:     &reqBody,
+		Results:     &res,
+		OkCodes:     []int{200, 201},
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return res.Port, nil
+}
diff --git a/openstack/networking/v2/ports/requests_test.go b/openstack/networking/v2/ports/requests_test.go
index be52f6f..ab5e14c 100644
--- a/openstack/networking/v2/ports/requests_test.go
+++ b/openstack/networking/v2/ports/requests_test.go
@@ -185,3 +185,164 @@
 	th.AssertEquals(t, n.Status, "ACTIVE")
 	th.AssertEquals(t, n.DeviceID, "5e3898d7-11be-483e-9732-b2f5eccd2b2e")
 }
+
+func TestCreate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", TokenID)
+		th.TestHeader(t, r, "Content-Type", "application/json")
+		th.TestHeader(t, r, "Accept", "application/json")
+		th.TestJSONRequest(t, r, `
+{
+    "port": {
+        "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
+        "name": "private-port",
+        "admin_state_up": true
+    }
+}
+			`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusCreated)
+
+		fmt.Fprintf(w, `
+{
+    "port": {
+        "status": "DOWN",
+        "binding:host_id": "",
+        "name": "private-port",
+        "allowed_address_pairs": [],
+        "admin_state_up": true,
+        "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
+        "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
+        "binding:vif_details": {},
+        "binding:vnic_type": "normal",
+        "binding:vif_type": "unbound",
+        "device_owner": "",
+        "mac_address": "fa:16:3e:c9:cb:f0",
+        "binding:profile": {},
+        "fixed_ips": [
+            {
+                "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
+                "ip_address": "10.0.0.2"
+            }
+        ],
+        "id": "65c0ee9f-d634-4522-8954-51021b570b0d",
+        "security_groups": [
+            "f0ac4394-7e4a-4409-9701-ba8be283dbc3"
+        ],
+        "device_id": ""
+    }
+}
+		`)
+	})
+
+	asu := true
+	options := PortOpts{Name: "private-port", AdminStateUp: &asu, NetworkID: "a87cc70a-3e15-4acf-8205-9b711a3531b7"}
+	n, err := Create(ServiceClient(), options)
+	th.AssertNoErr(t, err)
+
+	th.AssertEquals(t, n.Status, "DOWN")
+	th.AssertEquals(t, n.BindingHostID, "")
+	th.AssertEquals(t, n.Name, "private-port")
+	th.AssertDeepEquals(t, n.AllowedAddressPairs, []interface{}(nil))
+	th.AssertEquals(t, n.AdminStateUp, true)
+	th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7")
+	th.AssertEquals(t, n.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa")
+	th.AssertDeepEquals(t, n.BindingVIFDetails, map[string]interface{}{})
+	th.AssertEquals(t, n.DeviceOwner, "")
+	th.AssertEquals(t, n.MACAddress, "fa:16:3e:c9:cb:f0")
+	th.AssertDeepEquals(t, n.BindingProfile, map[string]interface{}{})
+	th.AssertEquals(t, n.BindingVNICType, "normal")
+	th.AssertEquals(t, n.BindingVIFType, "unbound")
+	th.AssertDeepEquals(t, n.FixedIPs, []IP{
+		IP{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
+	})
+	th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d")
+	th.AssertDeepEquals(t, n.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
+	th.AssertEquals(t, n.DeviceID, "")
+}
+
+func TestUpdate(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+
+	th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "PUT")
+		th.TestHeader(t, r, "X-Auth-Token", TokenID)
+		th.TestHeader(t, r, "Content-Type", "application/json")
+		th.TestHeader(t, r, "Accept", "application/json")
+		th.TestJSONRequest(t, r, `
+{
+		"port": {
+				"network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
+				"name": "new_port_name",
+				"fixed_ips": [
+            {
+                "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
+                "ip_address": "10.0.0.3"
+            }
+        ],
+				"security_groups": [
+            "f0ac4394-7e4a-4409-9701-ba8be283dbc3"
+        ]
+		}
+}
+			`)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+
+		fmt.Fprintf(w, `
+{
+    "port": {
+        "status": "DOWN",
+        "binding:host_id": "",
+        "name": "new_port_name",
+        "allowed_address_pairs": [],
+        "admin_state_up": true,
+        "network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
+        "tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
+        "binding:vif_details": {},
+        "binding:vnic_type": "normal",
+        "binding:vif_type": "unbound",
+        "device_owner": "",
+        "mac_address": "fa:16:3e:c9:cb:f0",
+        "binding:profile": {},
+        "fixed_ips": [
+            {
+                "subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
+                "ip_address": "10.0.0.3"
+            }
+        ],
+        "id": "65c0ee9f-d634-4522-8954-51021b570b0d",
+        "security_groups": [
+            "f0ac4394-7e4a-4409-9701-ba8be283dbc3"
+        ],
+        "device_id": ""
+    }
+}
+		`)
+	})
+
+	options := PortOpts{
+		NetworkID: "a87cc70a-3e15-4acf-8205-9b711a3531b7",
+		Name:      "new_port_name",
+		FixedIPs: []IP{
+			IP{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
+		},
+		SecurityGroups: []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"},
+	}
+
+	s, err := Update(ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options)
+	th.AssertNoErr(t, err)
+
+	th.AssertEquals(t, s.Name, "new_port_name")
+	th.AssertDeepEquals(t, s.FixedIPs, []IP{
+		IP{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
+	})
+	th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
+}