Fix Floating IP Disassociation (#103)
This commit fixes floating IP disassociation by changing the PortID to a
string pointer rather than a string. This allows a value of "null" to be
passed which is what the Networking API is looking for.
diff --git a/acceptance/openstack/networking/v2/extensions/layer3/floatingips_test.go b/acceptance/openstack/networking/v2/extensions/layer3/floatingips_test.go
index cac8983..c20b0d1 100644
--- a/acceptance/openstack/networking/v2/extensions/layer3/floatingips_test.go
+++ b/acceptance/openstack/networking/v2/extensions/layer3/floatingips_test.go
@@ -8,7 +8,6 @@
"github.com/gophercloud/gophercloud/acceptance/clients"
networking "github.com/gophercloud/gophercloud/acceptance/openstack/networking/v2"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
- "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
)
func TestLayer3FloatingIPsList(t *testing.T) {
@@ -56,28 +55,22 @@
}
defer DeleteRouter(t, client, router.ID)
- aiOpts := routers.AddInterfaceOpts{
- SubnetID: subnet.ID,
- }
-
- iface, err := routers.AddInterface(client, router.ID, aiOpts).Extract()
- if err != nil {
- t.Fatalf("Unable to add interface to router: %v", err)
- }
-
- PrintRouter(t, router)
- PrintRouterInterface(t, iface)
-
port, err := networking.CreatePort(t, client, choices.ExternalNetworkID, subnet.ID)
if err != nil {
t.Fatalf("Unable to create port: %v", err)
}
- defer networking.DeletePort(t, client, port.ID)
+
+ _, err = CreateRouterInterface(t, client, port.ID, router.ID)
+ if err != nil {
+ t.Fatalf("Unable to create router interface: %v", err)
+ }
+ defer DeleteRouterInterface(t, client, port.ID, router.ID)
fip, err := CreateFloatingIP(t, client, choices.ExternalNetworkID, port.ID)
if err != nil {
t.Fatalf("Unable to create floating IP: %v", err)
}
+ defer DeleteFloatingIP(t, client, fip.ID)
newFip, err := floatingips.Get(client, fip.ID).Extract()
if err != nil {
@@ -86,14 +79,13 @@
PrintFloatingIP(t, newFip)
- DeleteFloatingIP(t, client, fip.ID)
-
- riOpts := routers.RemoveInterfaceOpts{
- SubnetID: subnet.ID,
+ // Disassociate the floating IP
+ updateOpts := floatingips.UpdateOpts{
+ PortID: nil,
}
- _, err = routers.RemoveInterface(client, router.ID, riOpts).Extract()
+ newFip, err = floatingips.Update(client, fip.ID, updateOpts).Extract()
if err != nil {
- t.Fatalf("Failed to remove interface from router: %v", err)
+ t.Fatalf("Unable to disassociate floating IP: %v", err)
}
}
diff --git a/acceptance/openstack/networking/v2/extensions/layer3/layer3.go b/acceptance/openstack/networking/v2/extensions/layer3/layer3.go
index 5c6031e..3d2d88f 100644
--- a/acceptance/openstack/networking/v2/extensions/layer3/layer3.go
+++ b/acceptance/openstack/networking/v2/extensions/layer3/layer3.go
@@ -8,6 +8,7 @@
"github.com/gophercloud/gophercloud/acceptance/tools"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
+ "github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
)
// CreateFloatingIP creates a floating IP on a given network and port. An error
@@ -42,7 +43,7 @@
routerName := tools.RandomString("TESTACC-", 8)
- t.Logf("Attempting to create router: %s", routerName)
+ t.Logf("Attempting to create external router: %s", routerName)
adminStateUp := true
gatewayInfo := routers.GatewayInfo{
@@ -60,6 +61,10 @@
return router, err
}
+ if err := WaitForRouterToCreate(client, router.ID, 60); err != nil {
+ return router, err
+ }
+
t.Logf("Created router: %s", routerName)
return router, nil
@@ -88,11 +93,37 @@
return router, err
}
+ if err := WaitForRouterToCreate(client, router.ID, 60); err != nil {
+ return router, err
+ }
+
t.Logf("Created router: %s", routerName)
return router, nil
}
+// CreateRouterInterface will attach a subnet to a router. An error will be
+// returned if the operation fails.
+func CreateRouterInterface(t *testing.T, client *gophercloud.ServiceClient, portID, routerID string) (*routers.InterfaceInfo, error) {
+ t.Logf("Attempting to add port %s to router %s", portID, routerID)
+
+ aiOpts := routers.AddInterfaceOpts{
+ PortID: portID,
+ }
+
+ iface, err := routers.AddInterface(client, routerID, aiOpts).Extract()
+ if err != nil {
+ return iface, err
+ }
+
+ if err := WaitForRouterInterfaceToAttach(client, portID, 60); err != nil {
+ return iface, err
+ }
+
+ t.Logf("Successfully added port %s to router %s", portID, routerID)
+ return iface, nil
+}
+
// DeleteRouter deletes a router of a specified ID. A fatal error will occur
// if the deletion failed. This works best when used as a deferred function.
func DeleteRouter(t *testing.T, client *gophercloud.ServiceClient, routerID string) {
@@ -103,9 +134,35 @@
t.Fatalf("Error deleting router: %v", err)
}
+ if err := WaitForRouterToDelete(client, routerID, 60); err != nil {
+ t.Fatalf("Error waiting for router to delete: %v", err)
+ }
+
t.Logf("Deleted router: %s", routerID)
}
+// DeleteRouterInterface will detach a subnet to a router. A fatal error will
+// occur if the deletion failed. This works best when used as a deferred
+// function.
+func DeleteRouterInterface(t *testing.T, client *gophercloud.ServiceClient, portID, routerID string) {
+ t.Logf("Attempting to detach port %s from router %s", portID, routerID)
+
+ riOpts := routers.RemoveInterfaceOpts{
+ PortID: portID,
+ }
+
+ _, err := routers.RemoveInterface(client, routerID, riOpts).Extract()
+ if err != nil {
+ t.Fatalf("Failed to detach port %s from router %s", portID, routerID)
+ }
+
+ if err := WaitForRouterInterfaceToDetach(client, portID, 60); err != nil {
+ t.Fatalf("Failed to wait for port %s to detach from router %s", portID, routerID)
+ }
+
+ t.Logf("Successfully detached port %s from router %s", portID, routerID)
+}
+
// DeleteFloatingIP deletes a floatingIP of a specified ID. A fatal error will
// occur if the deletion failed. This works best when used as a deferred
// function.
@@ -155,3 +212,73 @@
t.Logf("\tDestinationCIDR: %s", route.DestinationCIDR)
}
}
+
+func WaitForRouterToCreate(client *gophercloud.ServiceClient, routerID string, secs int) error {
+ return gophercloud.WaitFor(secs, func() (bool, error) {
+ r, err := routers.Get(client, routerID).Extract()
+ if err != nil {
+ return false, err
+ }
+
+ if r.Status == "ACTIVE" {
+ return true, nil
+ }
+
+ return false, nil
+ })
+}
+
+func WaitForRouterToDelete(client *gophercloud.ServiceClient, routerID string, secs int) error {
+ return gophercloud.WaitFor(secs, func() (bool, error) {
+ _, err := routers.Get(client, routerID).Extract()
+ if err != nil {
+ if _, ok := err.(gophercloud.ErrDefault404); ok {
+ return true, nil
+ }
+
+ return false, err
+ }
+
+ return false, nil
+ })
+}
+
+func WaitForRouterInterfaceToAttach(client *gophercloud.ServiceClient, routerInterfaceID string, secs int) error {
+ return gophercloud.WaitFor(secs, func() (bool, error) {
+ r, err := ports.Get(client, routerInterfaceID).Extract()
+ if err != nil {
+ return false, err
+ }
+
+ if r.Status == "ACTIVE" {
+ return true, nil
+ }
+
+ return false, nil
+ })
+}
+
+func WaitForRouterInterfaceToDetach(client *gophercloud.ServiceClient, routerInterfaceID string, secs int) error {
+ return gophercloud.WaitFor(secs, func() (bool, error) {
+ r, err := ports.Get(client, routerInterfaceID).Extract()
+ if err != nil {
+ if _, ok := err.(gophercloud.ErrDefault404); ok {
+ return true, nil
+ }
+
+ if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
+ if errCode.Actual == 409 {
+ return false, nil
+ }
+ }
+
+ return false, err
+ }
+
+ if r.Status == "ACTIVE" {
+ return true, nil
+ }
+
+ return false, nil
+ })
+}
diff --git a/acceptance/openstack/networking/v2/networking.go b/acceptance/openstack/networking/v2/networking.go
index 86c3545..9432dda 100644
--- a/acceptance/openstack/networking/v2/networking.go
+++ b/acceptance/openstack/networking/v2/networking.go
@@ -42,7 +42,7 @@
createOpts := ports.CreateOpts{
NetworkID: networkID,
Name: portName,
- AdminStateUp: gophercloud.Disabled,
+ AdminStateUp: gophercloud.Enabled,
FixedIPs: []ports.IP{ports.IP{SubnetID: subnetID}},
}
@@ -51,9 +51,18 @@
return port, err
}
+ if err := WaitForPortToCreate(client, port.ID, 60); err != nil {
+ return port, err
+ }
+
+ newPort, err := ports.Get(client, port.ID).Extract()
+ if err != nil {
+ return newPort, err
+ }
+
t.Logf("Successfully created port: %s", portName)
- return port, nil
+ return newPort, nil
}
// CreateSubnet will create a subnet on the specified Network ID. An error
@@ -180,3 +189,18 @@
t.Logf("Name: %s", versionResource.Name)
t.Logf("Collection: %s", versionResource.Collection)
}
+
+func WaitForPortToCreate(client *gophercloud.ServiceClient, portID string, secs int) error {
+ return gophercloud.WaitFor(secs, func() (bool, error) {
+ p, err := ports.Get(client, portID).Extract()
+ if err != nil {
+ return false, err
+ }
+
+ if p.Status == "ACTIVE" || p.Status == "DOWN" {
+ return true, nil
+ }
+
+ return false, nil
+ })
+}
diff --git a/openstack/networking/v2/extensions/layer3/floatingips/requests.go b/openstack/networking/v2/extensions/layer3/floatingips/requests.go
index ed6b263..21a3b26 100644
--- a/openstack/networking/v2/extensions/layer3/floatingips/requests.go
+++ b/openstack/networking/v2/extensions/layer3/floatingips/requests.go
@@ -111,7 +111,7 @@
// linked to. To associate the floating IP with a new internal port, provide its
// ID. To disassociate the floating IP from all ports, provide an empty string.
type UpdateOpts struct {
- PortID string `json:"port_id"`
+ PortID *string `json:"port_id"`
}
// ToFloatingIPUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder
diff --git a/openstack/networking/v2/extensions/layer3/floatingips/testing/requests_test.go b/openstack/networking/v2/extensions/layer3/floatingips/testing/requests_test.go
index 45f27eb..d7bb043 100644
--- a/openstack/networking/v2/extensions/layer3/floatingips/testing/requests_test.go
+++ b/openstack/networking/v2/extensions/layer3/floatingips/testing/requests_test.go
@@ -293,10 +293,11 @@
`)
})
- ip, err := floatingips.Update(fake.ServiceClient(), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", floatingips.UpdateOpts{PortID: "423abc8d-2991-4a55-ba98-2aaea84cc72e"}).Extract()
+ portID := "423abc8d-2991-4a55-ba98-2aaea84cc72e"
+ ip, err := floatingips.Update(fake.ServiceClient(), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", floatingips.UpdateOpts{PortID: &portID}).Extract()
th.AssertNoErr(t, err)
- th.AssertDeepEquals(t, "423abc8d-2991-4a55-ba98-2aaea84cc72e", ip.PortID)
+ th.AssertDeepEquals(t, portID, ip.PortID)
}
func TestDisassociate(t *testing.T) {
@@ -311,7 +312,7 @@
th.TestJSONRequest(t, r, `
{
"floatingip": {
- "port_id": ""
+ "port_id": null
}
}
`)
@@ -334,7 +335,7 @@
`)
})
- ip, err := floatingips.Update(fake.ServiceClient(), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", floatingips.UpdateOpts{}).Extract()
+ ip, err := floatingips.Update(fake.ServiceClient(), "2f245a7b-796b-4f26-9cf9-9e82d248fda7", floatingips.UpdateOpts{PortID: nil}).Extract()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, "", ip.FixedIP)