Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 56 additions & 12 deletions openstack/db/v1/instances/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ 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
// ID or name of an existing instance to replicate from.
ReplicaOf string `json:"replica_of,omitempty"`
// Specifies the volume size in gigabytes (GB). The value must be between 1
// and 300. Required.
Size int
Size int `json:",omitempty"`
// Specifies the volume type.
VolumeType string
VolumeType string `json:",omitempty"`
// Name of the instance to create. The length of the name is limited to
// 255 characters and any characters are permitted. Optional.
Name string
Expand All @@ -73,22 +75,44 @@ type CreateOpts struct {

// ToInstanceCreateMap will render a JSON map.
func (opts CreateOpts) ToInstanceCreateMap() (map[string]interface{}, error) {
if opts.Size > 300 || opts.Size < 1 {
instance := map[string]interface{}{}

if opts.ReplicaOf != "" {
instance["replica_of"] = opts.ReplicaOf
}

// Volume Size cannot be specified when creating replicas of another instance.
if opts.Size > 0 && opts.ReplicaOf != "" {
err := gophercloud.ErrInvalidInput{}
err.Argument = "instances.CreateOpts.Size"
err.Value = opts.Size
err.Info = "Volume Size cannot be specified when ReplicaOf is provided"
return nil, err
} else if (opts.Size > 300 || opts.Size < 1) && opts.ReplicaOf == "" {
err := gophercloud.ErrInvalidInput{}
err.Argument = "instances.CreateOpts.Size"
err.Value = opts.Size
err.Info = "Size (GB) must be between 1-300"
return nil, err
}

if opts.FlavorRef == "" {
return nil, gophercloud.ErrMissingInput{Argument: "instances.CreateOpts.FlavorRef"}
// FlavorRef cannot be specified when creating replicas of another instance.
if opts.FlavorRef == "" && opts.ReplicaOf == "" {
err := gophercloud.ErrMissingInput{}
err.Argument = "instances.CreateOpts.FlavorRef or instances.CreateOpts.ReplicaOf"
err.Info = "ReplicaOf or FlavorRef should be provided"
return nil, err
}

instance := map[string]interface{}{
"flavorRef": opts.FlavorRef,
if opts.FlavorRef != "" && opts.ReplicaOf != "" {
err := gophercloud.ErrInvalidInput{}
err.Argument = "instances.CreateOpts.FlavorRef"
err.Value = opts.Size
err.Info = "FlavorRef cannot be specified when ReplicaOf is provided"
return nil, err
}

instance["flavorRef"] = opts.FlavorRef

if opts.AvailabilityZone != "" {
instance["availability_zone"] = opts.AvailabilityZone
}
Expand All @@ -110,6 +134,15 @@ func (opts CreateOpts) ToInstanceCreateMap() (map[string]interface{}, error) {
}
instance["users"] = users["users"]
}

// Datastore cannot be specified when creating replicas of another instance.
if opts.Datastore != nil && opts.ReplicaOf != "" {
err := gophercloud.ErrInvalidInput{}
err.Argument = "instances.CreateOpts.Datastore"
err.Value = opts.Size
err.Info = "Datastore cannot be specified when ReplicaOf is provided"
return nil, err
}
if opts.Datastore != nil {
datastore, err := opts.Datastore.ToMap()
if err != nil {
Expand All @@ -130,15 +163,26 @@ func (opts CreateOpts) ToInstanceCreateMap() (map[string]interface{}, error) {
instance["nics"] = networks
}

volume := map[string]interface{}{
"size": opts.Size,
volume := map[string]interface{}{}

if opts.ReplicaOf == "" {
volume["size"] = opts.Size
}

if opts.VolumeType != "" {
if opts.VolumeType != "" && opts.ReplicaOf != "" {
err := gophercloud.ErrInvalidInput{}
err.Argument = "instances.CreateOpts.VolumeType"
err.Value = opts.Size
err.Info = "Volume Type cannot be specified when ReplicaOf is provided"
return nil, err
}
if opts.VolumeType != "" && opts.ReplicaOf == "" {
volume["type"] = opts.VolumeType
}

instance["volume"] = volume
if opts.ReplicaOf == "" {
instance["volume"] = volume
}

return map[string]interface{}{"instance": instance}, nil
}
Expand Down
45 changes: 45 additions & 0 deletions openstack/db/v1/instances/testing/fixtures_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,24 @@ var instance = `
}
`

// https://github.com/openstack/trove/blob/40fdb7b44fb33e022b77ba8df63fde2565c70dea/api-ref/source/parameters.yaml#L799
var instanceReplicaOf = `
{
"created": "` + timestamp + `",
"links": [
{
"href": "https://openstack.example.com/v1.0/1234/instances/1",
"rel": "self"
}
],
"hostname": "e09ad9a3f73309469cf1f43d11e79549caf9acf2.openstack.example.com",
"id": "{instanceID}",
"name": "json_rack_instance",
"status": "BUILD",
"updated": "` + timestamp + `"
}
`

var instanceGet = `
{
"created": "` + timestamp + `",
Expand Down Expand Up @@ -136,6 +154,16 @@ var createReq = `
}
`

var createReplicaOfReq = `
{
"instance": {
"availability_zone": "us-east1",
"replica_of": "master-server-to-replicate-of",
"name": "json_rack_instance",
}
}
`

var instanceWithFault = `
{
"created": "` + timestamp + `",
Expand Down Expand Up @@ -197,6 +225,7 @@ var (

var (
createResp = fmt.Sprintf(`{"instance": %s}`, instance)
createReplicaOfResp = fmt.Sprintf(`{"instance": %s}`, instanceReplicaOf)
createWithFaultResp = fmt.Sprintf(`{"instance": %s}`, instanceWithFault)
listInstancesResp = fmt.Sprintf(`{"instances":[%s]}`, instance)
getInstanceResp = fmt.Sprintf(`{"instance": %s}`, instanceGet)
Expand Down Expand Up @@ -228,6 +257,18 @@ var expectedInstance = instances.Instance{
},
}

var expectedInstanceReplicaOf = instances.Instance{
Created: timeVal,
Updated: timeVal,
Hostname: "e09ad9a3f73309469cf1f43d11e79549caf9acf2.openstack.example.com",
ID: instanceID,
Links: []gophercloud.Link{
{Href: "https://openstack.example.com/v1.0/1234/instances/1", Rel: "self"},
},
Name: "json_rack_instance",
Status: "BUILD",
}

var expectedGetInstance = instances.Instance{
Created: timeVal,
Updated: timeVal,
Expand Down Expand Up @@ -288,6 +329,10 @@ func HandleCreate(t *testing.T) {
fixture.SetupHandler(t, rootURL, "POST", createReq, createResp, 200)
}

func HandleCreateWithReplicaOf(t *testing.T) {
fixture.SetupHandler(t, rootURL, "POST", createReplicaOfReq, createReplicaOfResp, 200)
}

func HandleCreateWithFault(t *testing.T) {
fixture.SetupHandler(t, rootURL, "POST", createReq, createWithFaultResp, 200)
}
Expand Down
18 changes: 18 additions & 0 deletions openstack/db/v1/instances/testing/requests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,24 @@ func TestCreate(t *testing.T) {
th.AssertDeepEquals(t, &expectedInstance, instance)
}

func TestCreateWithReplicaOf(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
HandleCreateWithReplicaOf(t)

// Subtest 1: ReplicaOf Only - No FlavorRef specified
t.Run("It creates a replica without specifying FlavorRef", func(t *testing.T) {
opts := instances.CreateOpts{
AvailabilityZone: "us-east1",
Name: "json_rack_instance",
ReplicaOf: "master-server-to-replicate-of",
}
instance, err := instances.Create(fake.ServiceClient(), opts).Extract()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, &expectedInstance, instance)
})
}

func TestCreateWithFault(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()
Expand Down