Handle Unmarshaling Compute Floating IP IDs (#217)

diff --git a/openstack/compute/v2/extensions/floatingips/results.go b/openstack/compute/v2/extensions/floatingips/results.go
index 753f3af..2f5b338 100644
--- a/openstack/compute/v2/extensions/floatingips/results.go
+++ b/openstack/compute/v2/extensions/floatingips/results.go
@@ -1,6 +1,9 @@
 package floatingips
 
 import (
+	"encoding/json"
+	"strconv"
+
 	"github.com/gophercloud/gophercloud"
 	"github.com/gophercloud/gophercloud/pagination"
 )
@@ -8,7 +11,7 @@
 // A FloatingIP is an IP that can be associated with an instance
 type FloatingIP struct {
 	// ID is a unique ID of the Floating IP
-	ID string `json:"id"`
+	ID string `json:"-"`
 
 	// FixedIP is the IP of the instance related to the Floating IP
 	FixedIP string `json:"fixed_ip,omitempty"`
@@ -23,6 +26,29 @@
 	Pool string `json:"pool"`
 }
 
+func (r *FloatingIP) UnmarshalJSON(b []byte) error {
+	type tmp FloatingIP
+	var s struct {
+		tmp
+		ID interface{} `json:"id"`
+	}
+	err := json.Unmarshal(b, &s)
+	if err != nil {
+		return err
+	}
+
+	*r = FloatingIP(s.tmp)
+
+	switch t := s.ID.(type) {
+	case float64:
+		r.ID = strconv.FormatFloat(t, 'f', -1, 64)
+	case string:
+		r.ID = t
+	}
+
+	return err
+}
+
 // FloatingIPPage stores a single, only page of FloatingIPs
 // results from a List call.
 type FloatingIPPage struct {
diff --git a/openstack/compute/v2/extensions/floatingips/testing/fixtures.go b/openstack/compute/v2/extensions/floatingips/testing/fixtures.go
index e372606..6866e26 100644
--- a/openstack/compute/v2/extensions/floatingips/testing/fixtures.go
+++ b/openstack/compute/v2/extensions/floatingips/testing/fixtures.go
@@ -58,6 +58,20 @@
 }
 `
 
+// CreateOutputWithNumericID is a sample response to a Post call
+// with a legacy nova-network-based numeric ID.
+const CreateOutputWithNumericID = `
+{
+    "floating_ip": {
+        "fixed_ip": null,
+        "id": 1,
+        "instance_id": null,
+        "ip": "10.10.10.1",
+        "pool": "nova"
+    }
+}
+`
+
 // FirstFloatingIP is the first result in ListOutput.
 var FirstFloatingIP = floatingips.FloatingIP{
 	ID:   "1",
@@ -125,6 +139,23 @@
 	})
 }
 
+// HandleCreateWithNumericIDSuccessfully configures the test server to respond to a Create request
+// for a new floating ip
+func HandleCreateWithNumericIDSuccessfully(t *testing.T) {
+	th.Mux.HandleFunc("/os-floating-ips", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "POST")
+		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+		th.TestJSONRequest(t, r, `
+{
+	"pool": "nova"
+}
+`)
+
+		w.Header().Add("Content-Type", "application/json")
+		fmt.Fprintf(w, CreateOutputWithNumericID)
+	})
+}
+
 // HandleDeleteSuccessfully configures the test server to respond to a Delete request for a
 // an existing floating ip
 func HandleDeleteSuccessfully(t *testing.T) {
diff --git a/openstack/compute/v2/extensions/floatingips/testing/requests_test.go b/openstack/compute/v2/extensions/floatingips/testing/requests_test.go
index e53da91..2356671 100644
--- a/openstack/compute/v2/extensions/floatingips/testing/requests_test.go
+++ b/openstack/compute/v2/extensions/floatingips/testing/requests_test.go
@@ -39,6 +39,18 @@
 	th.CheckDeepEquals(t, &CreatedFloatingIP, actual)
 }
 
+func TestCreateWithNumericID(t *testing.T) {
+	th.SetupHTTP()
+	defer th.TeardownHTTP()
+	HandleCreateWithNumericIDSuccessfully(t)
+
+	actual, err := floatingips.Create(client.ServiceClient(), floatingips.CreateOpts{
+		Pool: "nova",
+	}).Extract()
+	th.AssertNoErr(t, err)
+	th.CheckDeepEquals(t, &CreatedFloatingIP, actual)
+}
+
 func TestGet(t *testing.T) {
 	th.SetupHTTP()
 	defer th.TeardownHTTP()