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

Commit d97fe9b

Browse files
committed
Adds Fixed IP support to os-floating-ips
This commit enables the ability to specify a fixed IP when associating a floating IP to an instance. If a fixed IP is not specified, Nova will attempt to associate the floating IP to the first detected fixed IP, as it did prior to this patch.
1 parent f928634 commit d97fe9b

File tree

4 files changed

+205
-3
lines changed

4 files changed

+205
-3
lines changed

acceptance/openstack/compute/v2/floatingip_test.go

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ func createFloatingIP(t *testing.T, client *gophercloud.ServiceClient) (*floatin
4949
return fip, err
5050
}
5151

52-
func associateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, serverId string, fip *floatingip.FloatingIP) {
52+
func associateFloatingIPDeprecated(t *testing.T, client *gophercloud.ServiceClient, serverId string, fip *floatingip.FloatingIP) {
53+
// This form works, but is considered deprecated.
54+
// See associateFloatingIP or associateFloatingIPFixed
5355
err := floatingip.Associate(client, serverId, fip.IP).ExtractErr()
5456
th.AssertNoErr(t, err)
5557
t.Logf("Associated floating IP %v from instance %v", fip.IP, serverId)
@@ -63,6 +65,63 @@ func associateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, server
6365
t.Logf("Floating IP %v is associated with Fixed IP %v", fip.IP, floatingIp.FixedIP)
6466
}
6567

68+
func associateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, serverId string, fip *floatingip.FloatingIP) {
69+
associateOpts := floatingip.AssociateOpts{
70+
ServerID: serverId,
71+
FloatingIP: fip.IP,
72+
}
73+
74+
err := floatingip.AssociateFloatingIP(client, associateOpts).ExtractErr()
75+
th.AssertNoErr(t, err)
76+
t.Logf("Associated floating IP %v from instance %v", fip.IP, serverId)
77+
defer func() {
78+
err = floatingip.DisassociateFloatingIP(client, associateOpts).ExtractErr()
79+
th.AssertNoErr(t, err)
80+
t.Logf("Disassociated floating IP %v from instance %v", fip.IP, serverId)
81+
}()
82+
floatingIp, err := floatingip.Get(client, fip.ID).Extract()
83+
th.AssertNoErr(t, err)
84+
t.Logf("Floating IP %v is associated with Fixed IP %v", fip.IP, floatingIp.FixedIP)
85+
}
86+
87+
func associateFloatingIPFixed(t *testing.T, client *gophercloud.ServiceClient, serverId string, fip *floatingip.FloatingIP) {
88+
89+
network := os.Getenv("OS_NETWORK_NAME")
90+
server, err := servers.Get(client, serverId).Extract()
91+
if err != nil {
92+
t.Fatalf("%s", err)
93+
}
94+
95+
var fixedIP string
96+
for _, networkAddresses := range server.Addresses[network].([]interface{}) {
97+
address := networkAddresses.(map[string]interface{})
98+
if address["OS-EXT-IPS:type"] == "fixed" {
99+
if address["version"].(float64) == 4 {
100+
fixedIP = address["addr"].(string)
101+
}
102+
}
103+
}
104+
105+
associateOpts := floatingip.AssociateOpts{
106+
ServerID: serverId,
107+
FloatingIP: fip.IP,
108+
FixedIP: fixedIP,
109+
}
110+
111+
err = floatingip.AssociateFloatingIP(client, associateOpts).ExtractErr()
112+
th.AssertNoErr(t, err)
113+
t.Logf("Associated floating IP %v from instance %v with Fixed IP %v", fip.IP, serverId, fixedIP)
114+
defer func() {
115+
err = floatingip.DisassociateFloatingIP(client, associateOpts).ExtractErr()
116+
th.AssertNoErr(t, err)
117+
t.Logf("Disassociated floating IP %v from instance %v with Fixed IP %v", fip.IP, serverId, fixedIP)
118+
}()
119+
floatingIp, err := floatingip.Get(client, fip.ID).Extract()
120+
th.AssertNoErr(t, err)
121+
th.AssertEquals(t, floatingIp.FixedIP, fixedIP)
122+
t.Logf("Floating IP %v is associated with Fixed IP %v", fip.IP, floatingIp.FixedIP)
123+
}
124+
66125
func TestFloatingIP(t *testing.T) {
67126
pool := os.Getenv("OS_POOL_NAME")
68127
if pool == "" {
@@ -102,6 +161,8 @@ func TestFloatingIP(t *testing.T) {
102161
t.Logf("Floating IP deleted.")
103162
}()
104163

164+
associateFloatingIPDeprecated(t, client, server.ID, fip)
105165
associateFloatingIP(t, client, server.ID, fip)
166+
associateFloatingIPFixed(t, client, server.ID, fip)
106167

107168
}

openstack/compute/v2/extensions/floatingip/fixtures.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,25 @@ func HandleAssociateSuccessfully(t *testing.T) {
155155
})
156156
}
157157

158+
// HandleFixedAssociateSucessfully configures the test server to respond to a Post request
159+
// to associate an allocated floating IP with a specific fixed IP address
160+
func HandleAssociateFixedSuccessfully(t *testing.T) {
161+
th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/action", func(w http.ResponseWriter, r *http.Request) {
162+
th.TestMethod(t, r, "POST")
163+
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
164+
th.TestJSONRequest(t, r, `
165+
{
166+
"addFloatingIp": {
167+
"address": "10.10.10.2",
168+
"fixed_address": "166.78.185.201"
169+
}
170+
}
171+
`)
172+
173+
w.WriteHeader(http.StatusAccepted)
174+
})
175+
}
176+
158177
// HandleDisassociateSuccessfully configures the test server to respond to a Post request
159178
// to disassociate an allocated floating IP
160179
func HandleDisassociateSuccessfully(t *testing.T) {

openstack/compute/v2/extensions/floatingip/requests.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ type CreateOpts struct {
2626
Pool string
2727
}
2828

29+
// AssociateOpts specifies the required information to associate or disassociate a floating IP to an instance
30+
type AssociateOpts struct {
31+
// ServerID is the UUID of the server
32+
ServerID string
33+
34+
// FixedIP is an optional fixed IP address of the server
35+
FixedIP string
36+
37+
// FloatingIP is the floating IP to associate with an instance
38+
FloatingIP string
39+
}
40+
2941
// ToFloatingIPCreateMap constructs a request body from CreateOpts.
3042
func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
3143
if opts.Pool == "" {
@@ -35,6 +47,26 @@ func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
3547
return map[string]interface{}{"pool": opts.Pool}, nil
3648
}
3749

50+
// ToAssociateMap constructs a request body from AssociateOpts.
51+
func (opts AssociateOpts) ToAssociateMap() (map[string]interface{}, error) {
52+
if opts.ServerID == "" {
53+
return nil, errors.New("Required field missing for floating IP association: ServerID")
54+
}
55+
56+
if opts.FloatingIP == "" {
57+
return nil, errors.New("Required field missing for floating IP association: FloatingIP")
58+
}
59+
60+
associateInfo := map[string]interface{}{
61+
"serverId": opts.ServerID,
62+
"floatingIp": opts.FloatingIP,
63+
"fixedIp": opts.FixedIP,
64+
}
65+
66+
return associateInfo, nil
67+
68+
}
69+
3870
// Create requests the creation of a new floating IP
3971
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
4072
var res CreateResult
@@ -68,6 +100,7 @@ func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
68100
// association / disassociation
69101

70102
// Associate pairs an allocated floating IP with an instance
103+
// Deprecated. Use AssociateFloatingIP.
71104
func Associate(client *gophercloud.ServiceClient, serverId, fip string) AssociateResult {
72105
var res AssociateResult
73106

@@ -79,7 +112,33 @@ func Associate(client *gophercloud.ServiceClient, serverId, fip string) Associat
79112
return res
80113
}
81114

115+
// AssociateFloatingIP pairs an allocated floating IP with an instance.
116+
func AssociateFloatingIP(client *gophercloud.ServiceClient, opts AssociateOpts) AssociateResult {
117+
var res AssociateResult
118+
119+
associateInfo, err := opts.ToAssociateMap()
120+
if err != nil {
121+
res.Err = err
122+
return res
123+
}
124+
125+
addFloatingIp := make(map[string]interface{})
126+
addFloatingIp["address"] = associateInfo["floatingIp"].(string)
127+
128+
// fixedIp is not required
129+
if associateInfo["fixedIp"] != "" {
130+
addFloatingIp["fixed_address"] = associateInfo["fixedIp"].(string)
131+
}
132+
133+
serverId := associateInfo["serverId"].(string)
134+
135+
reqBody := map[string]interface{}{"addFloatingIp": addFloatingIp}
136+
_, res.Err = client.Post(associateURL(client, serverId), reqBody, nil, nil)
137+
return res
138+
}
139+
82140
// Disassociate decouples an allocated floating IP from an instance
141+
// Deprecated. Use DisassociateFloatingIP.
83142
func Disassociate(client *gophercloud.ServiceClient, serverId, fip string) DisassociateResult {
84143
var res DisassociateResult
85144

@@ -90,3 +149,23 @@ func Disassociate(client *gophercloud.ServiceClient, serverId, fip string) Disas
90149
_, res.Err = client.Post(disassociateURL(client, serverId), reqBody, nil, nil)
91150
return res
92151
}
152+
153+
// DisassociateFloatingIP decouples an allocated floating IP from an instance
154+
func DisassociateFloatingIP(client *gophercloud.ServiceClient, opts AssociateOpts) DisassociateResult {
155+
var res DisassociateResult
156+
157+
associateInfo, err := opts.ToAssociateMap()
158+
if err != nil {
159+
res.Err = err
160+
return res
161+
}
162+
163+
removeFloatingIp := make(map[string]interface{})
164+
removeFloatingIp["address"] = associateInfo["floatingIp"].(string)
165+
reqBody := map[string]interface{}{"removeFloatingIp": removeFloatingIp}
166+
167+
serverId := associateInfo["serverId"].(string)
168+
169+
_, res.Err = client.Post(disassociateURL(client, serverId), reqBody, nil, nil)
170+
return res
171+
}

openstack/compute/v2/extensions/floatingip/requests_test.go

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func TestDelete(t *testing.T) {
5757
th.AssertNoErr(t, err)
5858
}
5959

60-
func TestAssociate(t *testing.T) {
60+
func TestAssociateDeprecated(t *testing.T) {
6161
th.SetupHTTP()
6262
defer th.TeardownHTTP()
6363
HandleAssociateSuccessfully(t)
@@ -68,7 +68,36 @@ func TestAssociate(t *testing.T) {
6868
th.AssertNoErr(t, err)
6969
}
7070

71-
func TestDisassociate(t *testing.T) {
71+
func TestAssociate(t *testing.T) {
72+
th.SetupHTTP()
73+
defer th.TeardownHTTP()
74+
HandleAssociateSuccessfully(t)
75+
76+
associateOpts := AssociateOpts{
77+
ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
78+
FloatingIP: "10.10.10.2",
79+
}
80+
81+
err := AssociateFloatingIP(client.ServiceClient(), associateOpts).ExtractErr()
82+
th.AssertNoErr(t, err)
83+
}
84+
85+
func TestAssociateFixed(t *testing.T) {
86+
th.SetupHTTP()
87+
defer th.TeardownHTTP()
88+
HandleAssociateFixedSuccessfully(t)
89+
90+
associateOpts := AssociateOpts{
91+
ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
92+
FloatingIP: "10.10.10.2",
93+
FixedIP: "166.78.185.201",
94+
}
95+
96+
err := AssociateFloatingIP(client.ServiceClient(), associateOpts).ExtractErr()
97+
th.AssertNoErr(t, err)
98+
}
99+
100+
func TestDisassociateDeprecated(t *testing.T) {
72101
th.SetupHTTP()
73102
defer th.TeardownHTTP()
74103
HandleDisassociateSuccessfully(t)
@@ -78,3 +107,17 @@ func TestDisassociate(t *testing.T) {
78107
err := Disassociate(client.ServiceClient(), serverId, fip).ExtractErr()
79108
th.AssertNoErr(t, err)
80109
}
110+
111+
func TestDisassociateFloatingIP(t *testing.T) {
112+
th.SetupHTTP()
113+
defer th.TeardownHTTP()
114+
HandleDisassociateSuccessfully(t)
115+
116+
associateOpts := AssociateOpts{
117+
ServerID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
118+
FloatingIP: "10.10.10.2",
119+
}
120+
121+
err := DisassociateFloatingIP(client.ServiceClient(), associateOpts).ExtractErr()
122+
th.AssertNoErr(t, err)
123+
}

0 commit comments

Comments
 (0)