Skip to content
This repository was archived by the owner on Aug 1, 2023. It is now read-only.

Commit f606159

Browse files
committed
Allow to specify hostid when creating/updating a port
1 parent 116a4d7 commit f606159

File tree

6 files changed

+507
-0
lines changed

6 files changed

+507
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Package portsbinding provides information and interaction with the port
2+
// binding extension for the OpenStack Networking service.
3+
package portsbinding
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package portsbinding
2+
3+
import (
4+
"github.com/rackspace/gophercloud"
5+
"github.com/rackspace/gophercloud/openstack/networking/v2/ports"
6+
)
7+
8+
// CreateOpts represents the attributes used when creating a new
9+
// port with extended attributes.
10+
type CreateOpts struct {
11+
// CreateOptsBuilder is the interface options structs have to satisfy in order
12+
// to be used in the main Create operation in this package.
13+
ports.CreateOptsBuilder
14+
// The ID of the host where the port is allocated
15+
HostID string
16+
// The virtual network interface card (vNIC) type that is bound to the
17+
// neutron port
18+
VNICType string
19+
// A dictionary that enables the application running on the specified
20+
// host to pass and receive virtual network interface (VIF) port-specific
21+
// information to the plug-in
22+
Profile map[string]string
23+
}
24+
25+
// Get retrieves a specific port based on its unique ID.
26+
func Get(c *gophercloud.ServiceClient, id string) GetResult {
27+
var res GetResult
28+
_, res.Err = c.Get(getURL(c, id), &res.Body, nil)
29+
return res
30+
}
31+
32+
// ToPortCreateMap casts a CreateOpts struct to a map.
33+
func (opts CreateOpts) ToPortCreateMap() (map[string]interface{}, error) {
34+
p, err := opts.CreateOptsBuilder.ToPortCreateMap()
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
port := p["port"].(map[string]interface{})
40+
41+
if opts.HostID != "" {
42+
port["binding:host_id"] = opts.HostID
43+
}
44+
if opts.VNICType != "" {
45+
port["binding:vnic_type"] = opts.VNICType
46+
}
47+
if opts.Profile != nil {
48+
port["binding:profile"] = opts.Profile
49+
}
50+
51+
return map[string]interface{}{"port": port}, nil
52+
}
53+
54+
// Create accepts a CreateOpts struct and creates a new port with extended attributes.
55+
// You must remember to provide a NetworkID value.
56+
func Create(c *gophercloud.ServiceClient, opts ports.CreateOptsBuilder) CreateResult {
57+
var res CreateResult
58+
59+
reqBody, err := opts.ToPortCreateMap()
60+
if err != nil {
61+
res.Err = err
62+
return res
63+
}
64+
65+
_, res.Err = c.Post(createURL(c), reqBody, &res.Body, nil)
66+
return res
67+
}
68+
69+
// UpdateOpts represents the attributes used when updating an existing port.
70+
type UpdateOpts struct {
71+
ports.UpdateOptsBuilder
72+
HostID string
73+
VNICType string
74+
Profile map[string]string
75+
}
76+
77+
// ToPortUpdateMap casts an UpdateOpts struct to a map.
78+
func (opts UpdateOpts) ToPortUpdateMap() (map[string]interface{}, error) {
79+
p, err := opts.UpdateOptsBuilder.ToPortUpdateMap()
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
port := p["port"].(map[string]interface{})
85+
86+
if opts.HostID != "" {
87+
port["binding:host_id"] = opts.HostID
88+
}
89+
if opts.VNICType != "" {
90+
port["binding:vnic_type"] = opts.VNICType
91+
}
92+
if opts.Profile != nil {
93+
port["binding:profile"] = opts.Profile
94+
}
95+
96+
return map[string]interface{}{"port": port}, nil
97+
}
98+
99+
// Update accepts a UpdateOpts struct and updates an existing port using the
100+
// values provided.
101+
func Update(c *gophercloud.ServiceClient, id string, opts ports.UpdateOptsBuilder) UpdateResult {
102+
var res UpdateResult
103+
104+
reqBody, err := opts.ToPortUpdateMap()
105+
if err != nil {
106+
res.Err = err
107+
return res
108+
}
109+
110+
_, res.Err = c.Put(updateURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
111+
OkCodes: []int{200, 201},
112+
})
113+
return res
114+
}
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package portsbinding
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"testing"
7+
8+
fake "github.com/rackspace/gophercloud/openstack/networking/v2/common"
9+
"github.com/rackspace/gophercloud/openstack/networking/v2/ports"
10+
th "github.com/rackspace/gophercloud/testhelper"
11+
)
12+
13+
func TestGet(t *testing.T) {
14+
th.SetupHTTP()
15+
defer th.TeardownHTTP()
16+
17+
th.Mux.HandleFunc("/v2.0/ports/46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2", func(w http.ResponseWriter, r *http.Request) {
18+
th.TestMethod(t, r, "GET")
19+
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
20+
21+
w.Header().Add("Content-Type", "application/json")
22+
w.WriteHeader(http.StatusOK)
23+
24+
fmt.Fprintf(w, `
25+
{
26+
"port": {
27+
"status": "ACTIVE",
28+
"name": "",
29+
"admin_state_up": true,
30+
"network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
31+
"tenant_id": "7e02058126cc4950b75f9970368ba177",
32+
"device_owner": "network:router_interface",
33+
"mac_address": "fa:16:3e:23:fd:d7",
34+
"fixed_ips": [
35+
{
36+
"subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
37+
"ip_address": "10.0.0.1"
38+
}
39+
],
40+
"id": "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2",
41+
"security_groups": [],
42+
"device_id": "5e3898d7-11be-483e-9732-b2f5eccd2b2e",
43+
"binding:host_id": "HOST1",
44+
"binding:vnic_type": "normal"
45+
}
46+
}
47+
`)
48+
})
49+
50+
n, err := Get(fake.ServiceClient(), "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2").Extract()
51+
th.AssertNoErr(t, err)
52+
53+
th.AssertEquals(t, n.Status, "ACTIVE")
54+
th.AssertEquals(t, n.Name, "")
55+
th.AssertEquals(t, n.AdminStateUp, true)
56+
th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7")
57+
th.AssertEquals(t, n.TenantID, "7e02058126cc4950b75f9970368ba177")
58+
th.AssertEquals(t, n.DeviceOwner, "network:router_interface")
59+
th.AssertEquals(t, n.MACAddress, "fa:16:3e:23:fd:d7")
60+
th.AssertDeepEquals(t, n.FixedIPs, []IP{
61+
{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.1"},
62+
})
63+
th.AssertEquals(t, n.ID, "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2")
64+
th.AssertDeepEquals(t, n.SecurityGroups, []string{})
65+
th.AssertEquals(t, n.DeviceID, "5e3898d7-11be-483e-9732-b2f5eccd2b2e")
66+
th.AssertEquals(t, n.HostID, "HOST1")
67+
th.AssertEquals(t, n.VNICType, "normal")
68+
}
69+
70+
func TestCreate(t *testing.T) {
71+
th.SetupHTTP()
72+
defer th.TeardownHTTP()
73+
74+
th.Mux.HandleFunc("/v2.0/ports", func(w http.ResponseWriter, r *http.Request) {
75+
th.TestMethod(t, r, "POST")
76+
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
77+
th.TestHeader(t, r, "Content-Type", "application/json")
78+
th.TestHeader(t, r, "Accept", "application/json")
79+
th.TestJSONRequest(t, r, `
80+
{
81+
"port": {
82+
"network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
83+
"name": "private-port",
84+
"admin_state_up": true,
85+
"fixed_ips": [
86+
{
87+
"subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
88+
"ip_address": "10.0.0.2"
89+
}
90+
],
91+
"security_groups": ["foo"],
92+
"binding:host_id": "HOST1",
93+
"binding:vnic_type": "normal"
94+
}
95+
}
96+
`)
97+
98+
w.Header().Add("Content-Type", "application/json")
99+
w.WriteHeader(http.StatusCreated)
100+
101+
fmt.Fprintf(w, `
102+
{
103+
"port": {
104+
"status": "DOWN",
105+
"name": "private-port",
106+
"allowed_address_pairs": [],
107+
"admin_state_up": true,
108+
"network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
109+
"tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
110+
"device_owner": "",
111+
"mac_address": "fa:16:3e:c9:cb:f0",
112+
"fixed_ips": [
113+
{
114+
"subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
115+
"ip_address": "10.0.0.2"
116+
}
117+
],
118+
"binding:host_id": "HOST1",
119+
"binding:vnic_type": "normal",
120+
"id": "65c0ee9f-d634-4522-8954-51021b570b0d",
121+
"security_groups": [
122+
"f0ac4394-7e4a-4409-9701-ba8be283dbc3"
123+
],
124+
"device_id": ""
125+
}
126+
}
127+
`)
128+
})
129+
130+
asu := true
131+
options := CreateOpts{
132+
CreateOptsBuilder: ports.CreateOpts{
133+
Name: "private-port",
134+
AdminStateUp: &asu,
135+
NetworkID: "a87cc70a-3e15-4acf-8205-9b711a3531b7",
136+
FixedIPs: []IP{
137+
{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
138+
},
139+
SecurityGroups: []string{"foo"},
140+
},
141+
HostID: "HOST1",
142+
VNICType: "normal",
143+
}
144+
n, err := Create(fake.ServiceClient(), options).Extract()
145+
th.AssertNoErr(t, err)
146+
147+
th.AssertEquals(t, n.Status, "DOWN")
148+
th.AssertEquals(t, n.Name, "private-port")
149+
th.AssertEquals(t, n.AdminStateUp, true)
150+
th.AssertEquals(t, n.NetworkID, "a87cc70a-3e15-4acf-8205-9b711a3531b7")
151+
th.AssertEquals(t, n.TenantID, "d6700c0c9ffa4f1cb322cd4a1f3906fa")
152+
th.AssertEquals(t, n.DeviceOwner, "")
153+
th.AssertEquals(t, n.MACAddress, "fa:16:3e:c9:cb:f0")
154+
th.AssertDeepEquals(t, n.FixedIPs, []IP{
155+
{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.2"},
156+
})
157+
th.AssertEquals(t, n.ID, "65c0ee9f-d634-4522-8954-51021b570b0d")
158+
th.AssertDeepEquals(t, n.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
159+
th.AssertEquals(t, n.HostID, "HOST1")
160+
th.AssertEquals(t, n.VNICType, "normal")
161+
}
162+
163+
func TestRequiredCreateOpts(t *testing.T) {
164+
res := Create(fake.ServiceClient(), CreateOpts{CreateOptsBuilder: ports.CreateOpts{}})
165+
if res.Err == nil {
166+
t.Fatalf("Expected error, got none")
167+
}
168+
}
169+
170+
func TestUpdate(t *testing.T) {
171+
th.SetupHTTP()
172+
defer th.TeardownHTTP()
173+
174+
th.Mux.HandleFunc("/v2.0/ports/65c0ee9f-d634-4522-8954-51021b570b0d", func(w http.ResponseWriter, r *http.Request) {
175+
th.TestMethod(t, r, "PUT")
176+
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
177+
th.TestHeader(t, r, "Content-Type", "application/json")
178+
th.TestHeader(t, r, "Accept", "application/json")
179+
th.TestJSONRequest(t, r, `
180+
{
181+
"port": {
182+
"name": "new_port_name",
183+
"fixed_ips": [
184+
{
185+
"subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
186+
"ip_address": "10.0.0.3"
187+
}
188+
],
189+
"security_groups": [
190+
"f0ac4394-7e4a-4409-9701-ba8be283dbc3"
191+
],
192+
"binding:host_id": "HOST1",
193+
"binding:vnic_type": "normal"
194+
}
195+
}
196+
`)
197+
198+
w.Header().Add("Content-Type", "application/json")
199+
w.WriteHeader(http.StatusOK)
200+
201+
fmt.Fprintf(w, `
202+
{
203+
"port": {
204+
"status": "DOWN",
205+
"name": "new_port_name",
206+
"admin_state_up": true,
207+
"network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
208+
"tenant_id": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
209+
"device_owner": "",
210+
"mac_address": "fa:16:3e:c9:cb:f0",
211+
"fixed_ips": [
212+
{
213+
"subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2",
214+
"ip_address": "10.0.0.3"
215+
}
216+
],
217+
"id": "65c0ee9f-d634-4522-8954-51021b570b0d",
218+
"security_groups": [
219+
"f0ac4394-7e4a-4409-9701-ba8be283dbc3"
220+
],
221+
"device_id": "",
222+
"binding:host_id": "HOST1",
223+
"binding:vnic_type": "normal"
224+
}
225+
}
226+
`)
227+
})
228+
229+
options := UpdateOpts{
230+
UpdateOptsBuilder: ports.UpdateOpts{
231+
Name: "new_port_name",
232+
FixedIPs: []IP{
233+
{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
234+
},
235+
SecurityGroups: []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"},
236+
},
237+
HostID: "HOST1",
238+
VNICType: "normal",
239+
}
240+
241+
s, err := Update(fake.ServiceClient(), "65c0ee9f-d634-4522-8954-51021b570b0d", options).Extract()
242+
th.AssertNoErr(t, err)
243+
244+
th.AssertEquals(t, s.Name, "new_port_name")
245+
th.AssertDeepEquals(t, s.FixedIPs, []IP{
246+
{SubnetID: "a0304c3a-4f08-4c43-88af-d796509c97d2", IPAddress: "10.0.0.3"},
247+
})
248+
th.AssertDeepEquals(t, s.SecurityGroups, []string{"f0ac4394-7e4a-4409-9701-ba8be283dbc3"})
249+
th.AssertEquals(t, s.HostID, "HOST1")
250+
th.AssertEquals(t, s.VNICType, "normal")
251+
}

0 commit comments

Comments
 (0)