Decouple OpenStack implementation from Rackspace provider
diff --git a/openstack/db/v1/instances/doc.go b/openstack/db/v1/instances/doc.go
new file mode 100644
index 0000000..98a1bb3
--- /dev/null
+++ b/openstack/db/v1/instances/doc.go
@@ -0,0 +1 @@
+package instances
diff --git a/openstack/db/v1/instances/fixtures.go b/openstack/db/v1/instances/fixtures.go
new file mode 100644
index 0000000..8f7d1b7
--- /dev/null
+++ b/openstack/db/v1/instances/fixtures.go
@@ -0,0 +1,92 @@
+package instances
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ th "github.com/rackspace/gophercloud/testhelper"
+ fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func HandleCreateInstanceSuccessfully(t *testing.T) {
+ th.Mux.HandleFunc("/instances", func(w http.ResponseWriter, r *http.Request) {
+ th.TestMethod(t, r, "POST")
+ th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+
+ th.TestJSONRequest(t, r, `
+{
+ "instance": {
+ "databases": [
+ {
+ "character_set": "utf8",
+ "collate": "utf8_general_ci",
+ "name": "sampledb"
+ },
+ {
+ "name": "nextround"
+ }
+ ],
+ "flavorRef": "1",
+ "name": "json_rack_instance",
+ "users": [
+ {
+ "databases": [
+ {
+ "name": "sampledb"
+ }
+ ],
+ "name": "demouser",
+ "password": "demopassword"
+ }
+ ],
+ "volume": {
+ "size": 2
+ }
+ }
+}
+`)
+
+ fmt.Fprintf(w, `
+{
+ "instance": {
+ "created": "2014-02-13T21:47:13",
+ "datastore": {
+ "type": "mysql",
+ "version": "5.6"
+ },
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "href": "https://my-openstack.com/v1.0/1234/flavors/1",
+ "rel": "self"
+ },
+ {
+ "href": "https://my-openstack.com/v1.0/1234/flavors/1",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "links": [
+ {
+ "href": "https://my-openstack.com/v1.0/1234/instances/1",
+ "rel": "self"
+ }
+ ],
+ "hostname": "e09ad9a3f73309469cf1f43d11e79549caf9acf2.my-openstack.com",
+ "id": "d4603f69-ec7e-4e9b-803f-600b9205576f",
+ "name": "json_rack_instance",
+ "status": "BUILD",
+ "updated": "2014-02-13T21:47:13",
+ "volume": {
+ "size": 2
+ }
+ }
+}
+`)
+ })
+}
diff --git a/openstack/db/v1/instances/pkg.go b/openstack/db/v1/instances/pkg.go
new file mode 100644
index 0000000..98a1bb3
--- /dev/null
+++ b/openstack/db/v1/instances/pkg.go
@@ -0,0 +1 @@
+package instances
diff --git a/openstack/db/v1/instances/requests.go b/openstack/db/v1/instances/requests.go
new file mode 100644
index 0000000..914c593
--- /dev/null
+++ b/openstack/db/v1/instances/requests.go
@@ -0,0 +1,190 @@
+package instances
+
+import (
+ "fmt"
+
+ "github.com/racker/perigee"
+ "github.com/rackspace/gophercloud"
+)
+
+// CreateOptsBuilder is the top-level interface for create options.
+type CreateOptsBuilder interface {
+ ToInstanceCreateMap() (map[string]interface{}, error)
+}
+
+// DatabaseOpts is the struct responsible for configuring a database; often in
+// the context of an instance.
+type DatabaseOpts struct {
+ // Specifies the name of the database. Optional.
+ Name string
+
+ // Set of symbols and encodings. Optional; the default character set is utf8.
+ CharSet string
+
+ // Set of rules for comparing characters in a character set. Optional; the
+ // default value for collate is utf8_general_ci.
+ Collate string
+}
+
+func (opts DatabaseOpts) ToMap() (map[string]string, error) {
+ db := map[string]string{}
+ if opts.Name != "" {
+ db["name"] = opts.Name
+ }
+ if opts.CharSet != "" {
+ db["character_set"] = opts.CharSet
+ }
+ if opts.Collate != "" {
+ db["collate"] = opts.Collate
+ }
+ return db, nil
+}
+
+type DatabasesOpts []DatabaseOpts
+
+func (opts DatabasesOpts) ToMap() ([]map[string]string, error) {
+ var dbs []map[string]string
+ for _, db := range opts {
+ dbMap, err := db.ToMap()
+ if err != nil {
+ return dbs, err
+ }
+ dbs = append(dbs, dbMap)
+ }
+ return dbs, nil
+}
+
+// UserOpts is the struct responsible for configuring a user; often in the
+// context of an instance.
+type UserOpts struct {
+ // Specifies a name for the user.
+ Name string
+
+ // Specifies a password for the user.
+ Password string
+
+ // An array of databases that this user will connect to. The `name` field is
+ // the only requirement for each option.
+ Databases []DatabaseOpts
+
+ // Specifies the host from which a user is allowed to connect to the database.
+ // Possible values are a string containing an IPv4 address or "%" to allow
+ // connecting from any host. Optional; the default is "%".
+ Host string
+}
+
+func (opts UserOpts) ToMap() (map[string]interface{}, error) {
+ user := map[string]interface{}{}
+
+ if opts.Name != "" {
+ user["name"] = opts.Name
+ }
+ if opts.Password != "" {
+ user["password"] = opts.Password
+ }
+ if opts.Host != "" {
+ user["host"] = opts.Host
+ }
+
+ var dbs []map[string]string
+ for _, db := range opts.Databases {
+ dbs = append(dbs, map[string]string{"name": db.Name})
+ }
+ if len(dbs) > 0 {
+ user["databases"] = dbs
+ }
+
+ return user, nil
+}
+
+type UsersOpts []UserOpts
+
+func (opts UsersOpts) ToMap() ([]map[string]interface{}, error) {
+ var users []map[string]interface{}
+ for _, opt := range opts {
+ user, err := opt.ToMap()
+ if err != nil {
+ return users, err
+ }
+ users = append(users, user)
+ }
+ return users, nil
+}
+
+// CreateOpts is the struct responsible for configuring a new database instance.
+type CreateOpts struct {
+ // Either the integer UUID (in string form) of the flavor, or its URI
+ // reference as specified in the response from the List() call. Required.
+ FlavorRef string
+
+ // Specifies the volume size in gigabytes (GB). The value must be between 1
+ // and 300. Required.
+ Size int
+
+ // Name of the instance to create. The length of the name is limited to
+ // 255 characters and any characters are permitted. Optional.
+ Name string
+
+ // A slice of database information options.
+ Databases DatabasesOpts
+
+ // A slice of user information options.
+ Users UsersOpts
+}
+
+func (opts CreateOpts) ToInstanceCreateMap() (map[string]interface{}, error) {
+ if opts.Size > 300 || opts.Size < 1 {
+ return nil, fmt.Errorf("Size (GB) must be between 1-300")
+ }
+ if opts.FlavorRef == "" {
+ return nil, fmt.Errorf("FlavorRef is a required field")
+ }
+
+ instance := map[string]interface{}{
+ "volume": map[string]int{"size": opts.Size},
+ "flavorRef": opts.FlavorRef,
+ }
+
+ if opts.Name != "" {
+ instance["name"] = opts.Name
+ }
+ if len(opts.Databases) > 0 {
+ dbs, err := opts.Databases.ToMap()
+ if err != nil {
+ return nil, err
+ }
+ instance["databases"] = dbs
+ }
+ if len(opts.Users) > 0 {
+ users, err := opts.Users.ToMap()
+ if err != nil {
+ return nil, err
+ }
+ instance["users"] = users
+ }
+
+ return map[string]interface{}{"instance": instance}, nil
+}
+
+// Create will provision a new Database instance.
+func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
+ var res CreateResult
+
+ reqBody, err := opts.ToInstanceCreateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
+ resp, err := perigee.Request("POST", createURL(client), perigee.Options{
+ MoreHeaders: client.AuthenticatedHeaders(),
+ ReqBody: &reqBody,
+ Results: &res.Body,
+ OkCodes: []int{200},
+ })
+
+ res.Header = resp.HttpResponse.Header
+ res.Err = err
+
+ return res
+}
diff --git a/openstack/db/v1/instances/requests_test.go b/openstack/db/v1/instances/requests_test.go
new file mode 100644
index 0000000..18ba928
--- /dev/null
+++ b/openstack/db/v1/instances/requests_test.go
@@ -0,0 +1,60 @@
+package instances
+
+import (
+ "testing"
+
+ "github.com/rackspace/gophercloud"
+ th "github.com/rackspace/gophercloud/testhelper"
+ fake "github.com/rackspace/gophercloud/testhelper/client"
+)
+
+func TestCreate(t *testing.T) {
+ th.SetupHTTP()
+ defer th.TeardownHTTP()
+
+ HandleCreateInstanceSuccessfully(t)
+
+ opts := CreateOpts{
+ Name: "json_rack_instance",
+ FlavorRef: "1",
+ Databases: DatabasesOpts{
+ DatabaseOpts{CharSet: "utf8", Collate: "utf8_general_ci", Name: "sampledb"},
+ DatabaseOpts{Name: "nextround"},
+ },
+ Users: UsersOpts{
+ UserOpts{
+ Name: "demouser",
+ Password: "demopassword",
+ Databases: DatabasesOpts{
+ DatabaseOpts{Name: "sampledb"},
+ },
+ },
+ },
+ Size: 2,
+ }
+
+ instance, err := Create(fake.ServiceClient(), opts).Extract()
+
+ expected := &Instance{
+ Created: "2014-02-13T21:47:13",
+ Updated: "2014-02-13T21:47:13",
+ Flavor: Flavor{
+ ID: "1",
+ Links: []gophercloud.Link{
+ gophercloud.Link{Href: "https://my-openstack.com/v1.0/1234/flavors/1", Rel: "self"},
+ gophercloud.Link{Href: "https://my-openstack.com/v1.0/1234/flavors/1", Rel: "bookmark"},
+ },
+ },
+ Hostname: "e09ad9a3f73309469cf1f43d11e79549caf9acf2.my-openstack.com",
+ ID: "d4603f69-ec7e-4e9b-803f-600b9205576f",
+ Links: []gophercloud.Link{
+ gophercloud.Link{Href: "https://my-openstack.com/v1.0/1234/instances/1", Rel: "self"},
+ },
+ Name: "json_rack_instance",
+ Status: "BUILD",
+ Volume: Volume{Size: 2},
+ }
+
+ th.AssertNoErr(t, err)
+ th.AssertDeepEquals(t, expected, instance)
+}
diff --git a/openstack/db/v1/instances/results.go b/openstack/db/v1/instances/results.go
new file mode 100644
index 0000000..c3319e0
--- /dev/null
+++ b/openstack/db/v1/instances/results.go
@@ -0,0 +1,46 @@
+package instances
+
+import (
+ "github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud"
+)
+
+type Flavor struct {
+ ID string
+ Links []gophercloud.Link
+}
+
+type Volume struct {
+ Size int
+}
+
+type Instance struct {
+ Created string //time.Time
+ Updated string //time.Time
+ Flavor Flavor
+ Hostname string
+ ID string
+ Links []gophercloud.Link
+ Name string
+ Status string
+ Volume Volume
+}
+
+// CreateResult represents the result of a Create operation.
+type CreateResult struct {
+ gophercloud.Result
+}
+
+func (r CreateResult) Extract() (*Instance, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var response struct {
+ Instance Instance `mapstructure:"instance"`
+ }
+
+ err := mapstructure.Decode(r.Body, &response)
+
+ return &response.Instance, err
+}
diff --git a/openstack/db/v1/instances/urls.go b/openstack/db/v1/instances/urls.go
new file mode 100644
index 0000000..bb6edf2
--- /dev/null
+++ b/openstack/db/v1/instances/urls.go
@@ -0,0 +1,11 @@
+package instances
+
+import "github.com/rackspace/gophercloud"
+
+func baseURL(c *gophercloud.ServiceClient) string {
+ return c.ServiceURL("instances")
+}
+
+func createURL(c *gophercloud.ServiceClient) string {
+ return baseURL(c)
+}
diff --git a/rackspace/db/v1/instances/delegate.go b/rackspace/db/v1/instances/delegate.go
index 3e5596f..c8551df 100644
--- a/rackspace/db/v1/instances/delegate.go
+++ b/rackspace/db/v1/instances/delegate.go
@@ -1,17 +1,10 @@
package instances
import (
- "fmt"
-
- "github.com/racker/perigee"
"github.com/rackspace/gophercloud"
+ os "github.com/rackspace/gophercloud/openstack/db/v1/instances"
)
-// CreateOptsBuilder is the top-level interface for create options.
-type CreateOptsBuilder interface {
- ToInstanceCreateMap() (map[string]interface{}, error)
-}
-
// DatastoreOpts represents the configuration for how an instance stores data.
type DatastoreOpts struct {
Version string
@@ -25,105 +18,6 @@
}, nil
}
-// DatabaseOpts is the struct responsible for configuring a database; often in
-// the context of an instance.
-type DatabaseOpts struct {
- // Specifies the name of the database. Optional.
- Name string
-
- // Set of symbols and encodings. Optional; the default character set is utf8.
- CharSet string
-
- // Set of rules for comparing characters in a character set. Optional; the
- // default value for collate is utf8_general_ci.
- Collate string
-}
-
-func (opts DatabaseOpts) ToMap() (map[string]string, error) {
- db := map[string]string{}
- if opts.Name != "" {
- db["name"] = opts.Name
- }
- if opts.CharSet != "" {
- db["character_set"] = opts.CharSet
- }
- if opts.Collate != "" {
- db["collate"] = opts.Collate
- }
- return db, nil
-}
-
-type DatabasesOpts []DatabaseOpts
-
-func (opts DatabasesOpts) ToMap() ([]map[string]string, error) {
- var dbs []map[string]string
- for _, db := range opts {
- dbMap, err := db.ToMap()
- if err != nil {
- return dbs, err
- }
- dbs = append(dbs, dbMap)
- }
- return dbs, nil
-}
-
-// UserOpts is the struct responsible for configuring a user; often in the
-// context of an instance.
-type UserOpts struct {
- // Specifies a name for the user.
- Name string
-
- // Specifies a password for the user.
- Password string
-
- // An array of databases that this user will connect to. The `name` field is
- // the only requirement for each option.
- Databases []DatabaseOpts
-
- // Specifies the host from which a user is allowed to connect to the database.
- // Possible values are a string containing an IPv4 address or "%" to allow
- // connecting from any host. Optional; the default is "%".
- Host string
-}
-
-func (opts UserOpts) ToMap() (map[string]interface{}, error) {
- user := map[string]interface{}{}
-
- if opts.Name != "" {
- user["name"] = opts.Name
- }
- if opts.Password != "" {
- user["password"] = opts.Password
- }
- if opts.Host != "" {
- user["host"] = opts.Host
- }
-
- var dbs []map[string]string
- for _, db := range opts.Databases {
- dbs = append(dbs, map[string]string{"name": db.Name})
- }
- if len(dbs) > 0 {
- user["databases"] = dbs
- }
-
- return user, nil
-}
-
-type UsersOpts []UserOpts
-
-func (opts UsersOpts) ToMap() ([]map[string]interface{}, error) {
- var users []map[string]interface{}
- for _, opt := range opts {
- user, err := opt.ToMap()
- if err != nil {
- return users, err
- }
- users = append(users, user)
- }
- return users, nil
-}
-
// CreateOpts is the struct responsible for configuring a new database instance.
type CreateOpts struct {
// Either the integer UUID (in string form) of the flavor, or its URI
@@ -138,6 +32,12 @@
// 255 characters and any characters are permitted. Optional.
Name string
+ // A slice of database information options.
+ Databases os.DatabasesOpts
+
+ // A slice of user information options.
+ Users os.UsersOpts
+
// ID of the configuration group to associate with the instance. Optional.
ConfigID string
@@ -145,12 +45,6 @@
// optional, and if excluded will default to MySQL.
Datastore *DatastoreOpts
- // A slice of database information options.
- Databases DatabasesOpts
-
- // A slice of user information options.
- Users UsersOpts
-
// Specifies the backup ID from which to restore the database instance. There
// are some things to be aware of before using this field. When you execute
// the Restore Backup operation, a new database instance is created to store
@@ -164,24 +58,24 @@
}
func (opts CreateOpts) ToInstanceCreateMap() (map[string]interface{}, error) {
- if opts.Size > 300 || opts.Size < 1 {
- return nil, fmt.Errorf("Size (GB) must be between 1-300")
- }
- if opts.FlavorRef == "" {
- return nil, fmt.Errorf("FlavorRef is a required field")
+ instance, err := os.CreateOpts{
+ FlavorRef: opts.FlavorRef,
+ Size: opts.Size,
+ Name: opts.Name,
+ Databases: opts.Databases,
+ Users: opts.Users,
+ }.ToInstanceCreateMap()
+
+ if err != nil {
+ return nil, err
}
- instance := map[string]interface{}{
- "volume": map[string]int{"size": opts.Size},
- "flavorRef": opts.FlavorRef,
- }
+ instance = instance["instance"].(map[string]interface{})
- if opts.Name != "" {
- instance["name"] = opts.Name
- }
if opts.ConfigID != "" {
instance["configuration"] = opts.ConfigID
}
+
if opts.Datastore != nil {
ds, err := opts.Datastore.ToMap()
if err != nil {
@@ -189,20 +83,7 @@
}
instance["datastore"] = ds
}
- if len(opts.Databases) > 0 {
- dbs, err := opts.Databases.ToMap()
- if err != nil {
- return nil, err
- }
- instance["databases"] = dbs
- }
- if len(opts.Users) > 0 {
- users, err := opts.Users.ToMap()
- if err != nil {
- return nil, err
- }
- instance["users"] = users
- }
+
if opts.RestorePoint != "" {
instance["restorePoint"] = opts.RestorePoint
}
@@ -211,24 +92,6 @@
}
// Create will provision a new Database instance.
-func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
- var res CreateResult
-
- reqBody, err := opts.ToInstanceCreateMap()
- if err != nil {
- res.Err = err
- return res
- }
-
- resp, err := perigee.Request("POST", createURL(client), perigee.Options{
- MoreHeaders: client.AuthenticatedHeaders(),
- ReqBody: &reqBody,
- Results: &res.Body,
- OkCodes: []int{200},
- })
-
- res.Header = resp.HttpResponse.Header
- res.Err = err
-
- return res
+func Create(client *gophercloud.ServiceClient, opts os.CreateOptsBuilder) CreateResult {
+ return CreateResult{os.Create(client, opts)}
}
diff --git a/rackspace/db/v1/instances/delegate_test.go b/rackspace/db/v1/instances/delegate_test.go
index d995365..03dd47a 100644
--- a/rackspace/db/v1/instances/delegate_test.go
+++ b/rackspace/db/v1/instances/delegate_test.go
@@ -4,6 +4,7 @@
"testing"
"github.com/rackspace/gophercloud"
+ os "github.com/rackspace/gophercloud/openstack/db/v1/instances"
th "github.com/rackspace/gophercloud/testhelper"
fake "github.com/rackspace/gophercloud/testhelper/client"
)
@@ -17,16 +18,16 @@
opts := CreateOpts{
Name: "json_rack_instance",
FlavorRef: "1",
- Databases: DatabasesOpts{
- DatabaseOpts{CharSet: "utf8", Collate: "utf8_general_ci", Name: "sampledb"},
- DatabaseOpts{Name: "nextround"},
+ Databases: os.DatabasesOpts{
+ os.DatabaseOpts{CharSet: "utf8", Collate: "utf8_general_ci", Name: "sampledb"},
+ os.DatabaseOpts{Name: "nextround"},
},
- Users: UsersOpts{
- UserOpts{
+ Users: os.UsersOpts{
+ os.UserOpts{
Name: "demouser",
Password: "demopassword",
- Databases: DatabasesOpts{
- DatabaseOpts{Name: "sampledb"},
+ Databases: os.DatabasesOpts{
+ os.DatabaseOpts{Name: "sampledb"},
},
},
},
@@ -40,7 +41,7 @@
Created: "2014-02-13T21:47:13",
Updated: "2014-02-13T21:47:13",
Datastore: Datastore{Type: "mysql", Version: "5.6"},
- Flavor: Flavor{
+ Flavor: os.Flavor{
ID: "1",
Links: []gophercloud.Link{
gophercloud.Link{Href: "https://ord.databases.api.rackspacecloud.com/v1.0/1234/flavors/1", Rel: "self"},
@@ -54,7 +55,7 @@
},
Name: "json_rack_instance",
Status: "BUILD",
- Volume: Volume{Size: 2},
+ Volume: os.Volume{Size: 2},
}
th.AssertNoErr(t, err)
diff --git a/rackspace/db/v1/instances/results.go b/rackspace/db/v1/instances/results.go
index 66b3ce1..df13aed 100644
--- a/rackspace/db/v1/instances/results.go
+++ b/rackspace/db/v1/instances/results.go
@@ -3,6 +3,7 @@
import (
"github.com/mitchellh/mapstructure"
"github.com/rackspace/gophercloud"
+ os "github.com/rackspace/gophercloud/openstack/db/v1/instances"
)
type Datastore struct {
@@ -10,40 +11,24 @@
Version string
}
-type Flavor struct {
- ID string
- Links []gophercloud.Link
-}
-
-type Volume struct {
- Size int
-}
-
type Instance struct {
Created string //time.Time
Updated string //time.Time
Datastore Datastore
- Flavor Flavor
+ Flavor os.Flavor
Hostname string
ID string
Links []gophercloud.Link
Name string
Status string
- Volume Volume
+ Volume os.Volume
}
// CreateResult represents the result of a Create operation.
type CreateResult struct {
- gophercloud.Result
+ os.CreateResult
}
-// func handleInstanceConversion(from reflect.Kind, to reflect.Kind, data interface{}) (interface{}, error) {
-// if (from == reflect.String) && (to == reflect.Map) {
-// return map[string]interface{}{}, nil
-// }
-// return data, nil
-// }
-
func (r CreateResult) Extract() (*Instance, error) {
if r.Err != nil {
return nil, r.Err