diff --git a/acceptance/openstack/cce/helpers.go b/acceptance/openstack/cce/helpers.go index 49c0b4e58..7314e476f 100644 --- a/acceptance/openstack/cce/helpers.go +++ b/acceptance/openstack/cce/helpers.go @@ -126,7 +126,7 @@ func CreateKeypair(t *testing.T) string { opts := keypairs.CreateOpts{ Name: tools.RandomString("cce-nodes-", 4), } - _, err = keypairs.Create(client, opts).Extract() + _, err = keypairs.Create(client, opts) th.AssertNoErr(t, err) return opts.Name } @@ -135,5 +135,5 @@ func DeleteKeypair(t *testing.T, kp string) { client, err := clients.NewComputeV2Client() th.AssertNoErr(t, err) - th.AssertNoErr(t, keypairs.Delete(client, kp).ExtractErr()) + th.AssertNoErr(t, keypairs.Delete(client, kp)) } diff --git a/acceptance/openstack/common.go b/acceptance/openstack/common.go index 422ec86ad..18add1b22 100644 --- a/acceptance/openstack/common.go +++ b/acceptance/openstack/common.go @@ -33,9 +33,7 @@ func DefaultSecurityGroup(t *testing.T) string { client, err := clients.NewComputeV2Client() th.AssertNoErr(t, err) - securityGroupPages, err := secgroups.List(client).AllPages() - th.AssertNoErr(t, err) - securityGroups, err := secgroups.ExtractSecurityGroups(securityGroupPages) + securityGroups, err := secgroups.List(client) th.AssertNoErr(t, err) var sgId string for _, val := range securityGroups { @@ -54,11 +52,11 @@ func CreateSecurityGroup(t *testing.T) string { client, err := clients.NewComputeV2Client() th.AssertNoErr(t, err) - createSGOpts := secgroups.CreateOpts{ + createSGOpts := secgroups.GroupOpts{ Name: tools.RandomString("acc-sg-", 3), Description: "security group for acceptance testing", } - secGroup, err := secgroups.Create(client, createSGOpts).Extract() + secGroup, err := secgroups.Create(client, createSGOpts) th.AssertNoErr(t, err) t.Logf("Security group %s was created", secGroup.ID) diff --git a/acceptance/openstack/compute/v2/servers_test.go b/acceptance/openstack/compute/v2/servers_test.go index 3c04bf50b..26727d405 100644 --- a/acceptance/openstack/compute/v2/servers_test.go +++ b/acceptance/openstack/compute/v2/servers_test.go @@ -68,29 +68,29 @@ func TestServerLifecycle(t *testing.T) { }, } - ecs, err := servers.Create(client, createOpts).Extract() + ecs, err := servers.Create(client, createOpts) th.AssertNoErr(t, err) err = servers.WaitForStatus(client, ecs.ID, "ACTIVE", 1200) th.AssertNoErr(t, err) t.Logf("Created ECSv2: %s", ecs.ID) - ecs, err = servers.Get(client, ecs.ID).Extract() + ecs, err = servers.Get(client, ecs.ID) th.AssertNoErr(t, err) th.AssertEquals(t, ecsName, ecs.Name) - nicInfo, err := servers.GetNICs(client, ecs.ID).Extract() + nicInfo, err := servers.GetNICs(client, ecs.ID) th.AssertNoErr(t, err) tools.PrintResource(t, nicInfo) defer func() { t.Logf("Attempting to delete ECSv2: %s", ecs.ID) - _, err := servers.Delete(client, ecs.ID).ExtractJobResponse() + err := servers.Delete(client, ecs.ID) th.AssertNoErr(t, err) err = golangsdk.WaitFor(1200, func() (bool, error) { - _, err := servers.Get(client, ecs.ID).Extract() + _, err := servers.Get(client, ecs.ID) if err != nil { if _, ok := err.(golangsdk.ErrDefault400); ok { time.Sleep(10 * time.Second) @@ -112,13 +112,13 @@ func TestServerLifecycle(t *testing.T) { Name: ecsName, } - _, err = servers.Update(client, ecs.ID, updateOpts).Extract() + _, err = servers.Update(client, ecs.ID, updateOpts) th.AssertNoErr(t, err) t.Logf("ECSv2 successfully updated: %s", ecs.ID) th.AssertNoErr(t, err) - newECS, err := servers.Get(client, ecs.ID).Extract() + newECS, err := servers.Get(client, ecs.ID) th.AssertNoErr(t, err) th.AssertEquals(t, ecsName, newECS.Name) } diff --git a/acceptance/openstack/csbs/v1/policies_test.go b/acceptance/openstack/csbs/v1/policies_test.go index ceecf0b75..06cb96f8a 100644 --- a/acceptance/openstack/csbs/v1/policies_test.go +++ b/acceptance/openstack/csbs/v1/policies_test.go @@ -206,7 +206,7 @@ func createComputeInstance(t *testing.T, subnetID string) *servers.Server { }, } - server, err := servers.Create(client, opts).Extract() + server, err := servers.Create(client, opts) th.AssertNoErr(t, err) err = waitForComputeInstanceAvailable(client, 600, server.ID) th.AssertNoErr(t, err) @@ -221,7 +221,7 @@ func deleteComputeInstance(t *testing.T, instanceId string) { client, err := clients.NewComputeV2Client() th.AssertNoErr(t, err) - err = servers.Delete(client, instanceId).ExtractErr() + err = servers.Delete(client, instanceId) th.AssertNoErr(t, err) err = waitForComputeInstanceDelete(client, 600, instanceId) th.AssertNoErr(t, err) @@ -229,7 +229,7 @@ func deleteComputeInstance(t *testing.T, instanceId string) { func waitForComputeInstanceAvailable(client *golangsdk.ServiceClient, secs int, instanceId string) error { return golangsdk.WaitFor(secs, func() (bool, error) { - server, err := servers.Get(client, instanceId).Extract() + server, err := servers.Get(client, instanceId) if err != nil { return false, err } @@ -242,7 +242,7 @@ func waitForComputeInstanceAvailable(client *golangsdk.ServiceClient, secs int, func waitForComputeInstanceDelete(client *golangsdk.ServiceClient, secs int, instanceId string) error { return golangsdk.WaitFor(secs, func() (bool, error) { - server, err := servers.Get(client, instanceId).Extract() + server, err := servers.Get(client, instanceId) if err != nil { if _, ok := err.(golangsdk.ErrDefault404); ok { return true, nil diff --git a/internal/build/backward_test.go b/internal/build/backward_test.go index 61a3aa997..a8482dd00 100644 --- a/internal/build/backward_test.go +++ b/internal/build/backward_test.go @@ -5,7 +5,6 @@ import ( "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" "github.com/opentelekomcloud/gophertelekomcloud/openstack/common/tags" - "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/servers" "github.com/opentelekomcloud/gophertelekomcloud/openstack/dns/v2/zones" "github.com/opentelekomcloud/gophertelekomcloud/openstack/objectstorage/v1/accounts" "github.com/stretchr/testify/require" @@ -18,24 +17,6 @@ import ( // // Those tests can be used as an example of replacing deprecated methods. -func TestCompareQueryOpts(t *testing.T) { - listOpts := &servers.ListOpts{ - ChangesSince: "2012-06-12", - Image: "some-random-image", - Flavor: "top-flavor", - Limit: 64, - AllTenants: true, - } - - expected, err := listOpts.ToServerListQuery() - require.NoError(t, err) - - actual, err := QueryString(listOpts) - require.NoError(t, err) - - require.EqualValues(t, expected, actual.String()) // inside ToServerListQuery `.String()` for URL is called -} - func TestCompareHeaderOpts(t *testing.T) { headerOpts := &accounts.UpdateOpts{ ContentType: "application/lolson", diff --git a/openstack/compute/v2/extensions/aggregates/add_host.go b/openstack/compute/v2/extensions/aggregates/add_host.go new file mode 100644 index 000000000..d7c81bead --- /dev/null +++ b/openstack/compute/v2/extensions/aggregates/add_host.go @@ -0,0 +1,26 @@ +package aggregates + +import ( + "strconv" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +type AddHostOpts struct { + // The name of the host. + Host string `json:"host" required:"true"` +} + +// AddHost makes a request against the API to add host to a specific aggregate. +func AddHost(client *golangsdk.ServiceClient, aggregateID int, opts AddHostOpts) (*Aggregate, error) { + b, err := build.RequestBody(opts, "add_host") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("os-aggregates", strconv.Itoa(aggregateID), "action"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/aggregates/create.go b/openstack/compute/v2/extensions/aggregates/create.go new file mode 100644 index 000000000..417758e7c --- /dev/null +++ b/openstack/compute/v2/extensions/aggregates/create.go @@ -0,0 +1,29 @@ +package aggregates + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +type CreateOpts struct { + // The name of the host aggregate. + Name string `json:"name" required:"true"` + // The availability zone of the host aggregate. + // You should use a custom availability zone rather than + // the default returned by the os-availability-zone API. + // The availability zone must not include ‘:’ in its name. + AvailabilityZone string `json:"availability_zone,omitempty"` +} + +// Create makes a request against the API to create an aggregate. +func Create(client *golangsdk.ServiceClient, opts CreateOpts) (*Aggregate, error) { + b, err := build.RequestBody(opts, "aggregate") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("os-aggregates"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/aggregates/delete.go b/openstack/compute/v2/extensions/aggregates/delete.go new file mode 100644 index 000000000..986ff806e --- /dev/null +++ b/openstack/compute/v2/extensions/aggregates/delete.go @@ -0,0 +1,15 @@ +package aggregates + +import ( + "strconv" + + "github.com/opentelekomcloud/gophertelekomcloud" +) + +// Delete makes a request against the API to delete an aggregate. +func Delete(client *golangsdk.ServiceClient, aggregateID int) (err error) { + _, err = client.Delete(client.ServiceURL("os-aggregates", strconv.Itoa(aggregateID)), &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/openstack/compute/v2/extensions/aggregates/get.go b/openstack/compute/v2/extensions/aggregates/get.go new file mode 100644 index 000000000..23ce7e741 --- /dev/null +++ b/openstack/compute/v2/extensions/aggregates/get.go @@ -0,0 +1,15 @@ +package aggregates + +import ( + "strconv" + + "github.com/opentelekomcloud/gophertelekomcloud" +) + +// Get makes a request against the API to get details for a specific aggregate. +func Get(client *golangsdk.ServiceClient, aggregateID int) (*Aggregate, error) { + raw, err := client.Get(client.ServiceURL("os-aggregates", strconv.Itoa(aggregateID)), nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/aggregates/list.go b/openstack/compute/v2/extensions/aggregates/list.go new file mode 100644 index 000000000..3d0e7cfdb --- /dev/null +++ b/openstack/compute/v2/extensions/aggregates/list.go @@ -0,0 +1,18 @@ +package aggregates + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// List makes a request against the API to list aggregates. +func List(client *golangsdk.ServiceClient) ([]Aggregate, error) { + raw, err := client.Get(client.ServiceURL("os-aggregates"), nil, nil) + if err != nil { + return nil, err + } + + var res []Aggregate + err = extract.IntoSlicePtr(raw.Body, &res, "aggregates") + return res, err +} diff --git a/openstack/compute/v2/extensions/aggregates/remove_host.go b/openstack/compute/v2/extensions/aggregates/remove_host.go new file mode 100644 index 000000000..0028538fb --- /dev/null +++ b/openstack/compute/v2/extensions/aggregates/remove_host.go @@ -0,0 +1,26 @@ +package aggregates + +import ( + "strconv" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +type RemoveHostOpts struct { + // The name of the host. + Host string `json:"host" required:"true"` +} + +// RemoveHost makes a request against the API to remove host from a specific aggregate. +func RemoveHost(client *golangsdk.ServiceClient, aggregateID int, opts RemoveHostOpts) (*Aggregate, error) { + b, err := build.RequestBody(opts, "remove_host") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("os-aggregates", strconv.Itoa(aggregateID), "action"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/aggregates/requests.go b/openstack/compute/v2/extensions/aggregates/requests.go deleted file mode 100644 index e5fe946fd..000000000 --- a/openstack/compute/v2/extensions/aggregates/requests.go +++ /dev/null @@ -1,162 +0,0 @@ -package aggregates - -import ( - "strconv" - - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// List makes a request against the API to list aggregates. -func List(client *golangsdk.ServiceClient) pagination.Pager { - return pagination.NewPager(client, aggregatesListURL(client), func(r pagination.PageResult) pagination.Page { - return AggregatesPage{pagination.SinglePageBase(r)} - }) -} - -type CreateOpts struct { - // The name of the host aggregate. - Name string `json:"name" required:"true"` - - // The availability zone of the host aggregate. - // You should use a custom availability zone rather than - // the default returned by the os-availability-zone API. - // The availability zone must not include ‘:’ in its name. - AvailabilityZone string `json:"availability_zone,omitempty"` -} - -func (opts CreateOpts) ToAggregatesCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "aggregate") -} - -// Create makes a request against the API to create an aggregate. -func Create(client *golangsdk.ServiceClient, opts CreateOpts) (r CreateResult) { - b, err := opts.ToAggregatesCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(aggregatesCreateURL(client), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Delete makes a request against the API to delete an aggregate. -func Delete(client *golangsdk.ServiceClient, aggregateID int) (r DeleteResult) { - v := strconv.Itoa(aggregateID) - _, r.Err = client.Delete(aggregatesDeleteURL(client, v), &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Get makes a request against the API to get details for a specific aggregate. -func Get(client *golangsdk.ServiceClient, aggregateID int) (r GetResult) { - v := strconv.Itoa(aggregateID) - _, r.Err = client.Get(aggregatesGetURL(client, v), &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -type UpdateOpts struct { - // The name of the host aggregate. - Name string `json:"name,omitempty"` - - // The availability zone of the host aggregate. - // You should use a custom availability zone rather than - // the default returned by the os-availability-zone API. - // The availability zone must not include ‘:’ in its name. - AvailabilityZone string `json:"availability_zone,omitempty"` -} - -func (opts UpdateOpts) ToAggregatesUpdateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "aggregate") -} - -// Update makes a request against the API to update a specific aggregate. -func Update(client *golangsdk.ServiceClient, aggregateID int, opts UpdateOpts) (r UpdateResult) { - v := strconv.Itoa(aggregateID) - - b, err := opts.ToAggregatesUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(aggregatesUpdateURL(client, v), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -type AddHostOpts struct { - // The name of the host. - Host string `json:"host" required:"true"` -} - -func (opts AddHostOpts) ToAggregatesAddHostMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "add_host") -} - -// AddHost makes a request against the API to add host to a specific aggregate. -func AddHost(client *golangsdk.ServiceClient, aggregateID int, opts AddHostOpts) (r ActionResult) { - v := strconv.Itoa(aggregateID) - - b, err := opts.ToAggregatesAddHostMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(aggregatesAddHostURL(client, v), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -type RemoveHostOpts struct { - // The name of the host. - Host string `json:"host" required:"true"` -} - -func (opts RemoveHostOpts) ToAggregatesRemoveHostMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "remove_host") -} - -// RemoveHost makes a request against the API to remove host from a specific aggregate. -func RemoveHost(client *golangsdk.ServiceClient, aggregateID int, opts RemoveHostOpts) (r ActionResult) { - v := strconv.Itoa(aggregateID) - - b, err := opts.ToAggregatesRemoveHostMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(aggregatesRemoveHostURL(client, v), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -type SetMetadataOpts struct { - Metadata map[string]interface{} `json:"metadata" required:"true"` -} - -func (opts SetMetadataOpts) ToSetMetadataMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "set_metadata") -} - -// SetMetadata makes a request against the API to set metadata to a specific aggregate. -func SetMetadata(client *golangsdk.ServiceClient, aggregateID int, opts SetMetadataOpts) (r ActionResult) { - v := strconv.Itoa(aggregateID) - - b, err := opts.ToSetMetadataMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(aggregatesSetMetadataURL(client, v), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} diff --git a/openstack/compute/v2/extensions/aggregates/results.go b/openstack/compute/v2/extensions/aggregates/results.go index 5a59a3971..6be929ecf 100644 --- a/openstack/compute/v2/extensions/aggregates/results.go +++ b/openstack/compute/v2/extensions/aggregates/results.go @@ -2,40 +2,33 @@ package aggregates import ( "encoding/json" + "net/http" "time" "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" ) // Aggregate represents a host aggregate in the OpenStack cloud. type Aggregate struct { // The availability zone of the host aggregate. AvailabilityZone string `json:"availability_zone"` - // A list of host ids in this aggregate. Hosts []string `json:"hosts"` - // The ID of the host aggregate. ID int `json:"id"` - // Metadata key and value pairs associate with the aggregate. Metadata map[string]string `json:"metadata"` - // Name of the aggregate. Name string `json:"name"` - // The date and time when the resource was created. CreatedAt time.Time `json:"-"` - // The date and time when the resource was updated, // if the resource has not been updated, this field will show as null. UpdatedAt time.Time `json:"-"` - // The date and time when the resource was deleted, // if the resource has not been deleted yet, this field will be null. DeletedAt time.Time `json:"-"` - // A boolean indicates whether this aggregate is deleted or not, // if it has not been deleted, false will appear. Deleted bool `json:"deleted"` @@ -63,55 +56,12 @@ func (r *Aggregate) UnmarshalJSON(b []byte) error { return nil } -// AggregatesPage represents a single page of all Aggregates from a List -// request. -type AggregatesPage struct { - pagination.SinglePageBase -} - -// IsEmpty determines whether or not a page of Aggregates contains any results. -func (page AggregatesPage) IsEmpty() (bool, error) { - aggregates, err := ExtractAggregates(page) - return len(aggregates) == 0, err -} - -// ExtractAggregates interprets a page of results as a slice of Aggregates. -func ExtractAggregates(p pagination.Page) ([]Aggregate, error) { - var a struct { - Aggregates []Aggregate `json:"aggregates"` - } - err := (p.(AggregatesPage)).ExtractInto(&a) - return a.Aggregates, err -} - -type aggregatesResult struct { - golangsdk.Result -} - -func (r aggregatesResult) Extract() (*Aggregate, error) { - var s struct { - Aggregate *Aggregate `json:"aggregate"` +func extra(err error, raw *http.Response) (*Aggregate, error) { + if err != nil { + return nil, err } - err := r.ExtractInto(&s) - return s.Aggregate, err -} - -type CreateResult struct { - aggregatesResult -} - -type GetResult struct { - aggregatesResult -} - -type DeleteResult struct { - golangsdk.ErrResult -} - -type UpdateResult struct { - aggregatesResult -} -type ActionResult struct { - aggregatesResult + var res Aggregate + err = extract.IntoStructPtr(raw.Body, &res, "aggregate") + return &res, err } diff --git a/openstack/compute/v2/extensions/aggregates/set_metadata.go b/openstack/compute/v2/extensions/aggregates/set_metadata.go new file mode 100644 index 000000000..6c24a70e4 --- /dev/null +++ b/openstack/compute/v2/extensions/aggregates/set_metadata.go @@ -0,0 +1,25 @@ +package aggregates + +import ( + "strconv" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +type SetMetadataOpts struct { + Metadata map[string]interface{} `json:"metadata" required:"true"` +} + +// SetMetadata makes a request against the API to set metadata to a specific aggregate. +func SetMetadata(client *golangsdk.ServiceClient, aggregateID int, opts SetMetadataOpts) (*Aggregate, error) { + b, err := build.RequestBody(opts, "set_metadata") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("os-aggregates", strconv.Itoa(aggregateID), "action"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/aggregates/testing/requests_test.go b/openstack/compute/v2/extensions/aggregates/testing/requests_test.go index 78d65bdee..5ddd9d5f8 100644 --- a/openstack/compute/v2/extensions/aggregates/testing/requests_test.go +++ b/openstack/compute/v2/extensions/aggregates/testing/requests_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/aggregates" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) @@ -14,29 +13,14 @@ func TestListAggregates(t *testing.T) { defer th.TeardownHTTP() HandleListSuccessfully(t) - pages := 0 - err := aggregates.List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { - pages++ - - actual, err := aggregates.ExtractAggregates(page) - if err != nil { - return false, err - } - - if len(actual) != 2 { - t.Fatalf("Expected 2 aggregates, got %d", len(actual)) - } - th.CheckDeepEquals(t, FirstFakeAggregate, actual[0]) - th.CheckDeepEquals(t, SecondFakeAggregate, actual[1]) - - return true, nil - }) - + actual, err := aggregates.List(client.ServiceClient()) th.AssertNoErr(t, err) - if pages != 1 { - t.Errorf("Expected 1 page, saw %d", pages) + if len(actual) != 2 { + t.Fatalf("Expected 2 aggregates, got %d", len(actual)) } + th.CheckDeepEquals(t, FirstFakeAggregate, actual[0]) + th.CheckDeepEquals(t, SecondFakeAggregate, actual[1]) } func TestCreateAggregates(t *testing.T) { @@ -51,7 +35,7 @@ func TestCreateAggregates(t *testing.T) { AvailabilityZone: "london", } - actual, err := aggregates.Create(client.ServiceClient(), opts).Extract() + actual, err := aggregates.Create(client.ServiceClient(), opts) th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expected, actual) @@ -62,7 +46,7 @@ func TestDeleteAggregates(t *testing.T) { defer th.TeardownHTTP() HandleDeleteSuccessfully(t) - err := aggregates.Delete(client.ServiceClient(), AggregateIDtoDelete).ExtractErr() + err := aggregates.Delete(client.ServiceClient(), AggregateIDtoDelete) th.AssertNoErr(t, err) } @@ -73,7 +57,7 @@ func TestGetAggregates(t *testing.T) { expected := SecondFakeAggregate - actual, err := aggregates.Get(client.ServiceClient(), AggregateIDtoGet).Extract() + actual, err := aggregates.Get(client.ServiceClient(), AggregateIDtoGet) th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expected, actual) @@ -91,7 +75,7 @@ func TestUpdateAggregate(t *testing.T) { AvailabilityZone: "nova2", } - actual, err := aggregates.Update(client.ServiceClient(), expected.ID, opts).Extract() + actual, err := aggregates.Update(client.ServiceClient(), expected.ID, opts) th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expected, actual) @@ -108,7 +92,7 @@ func TestAddHostAggregate(t *testing.T) { Host: "cmp1", } - actual, err := aggregates.AddHost(client.ServiceClient(), expected.ID, opts).Extract() + actual, err := aggregates.AddHost(client.ServiceClient(), expected.ID, opts) th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expected, actual) @@ -125,7 +109,7 @@ func TestRemoveHostAggregate(t *testing.T) { Host: "cmp1", } - actual, err := aggregates.RemoveHost(client.ServiceClient(), expected.ID, opts).Extract() + actual, err := aggregates.RemoveHost(client.ServiceClient(), expected.ID, opts) th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expected, actual) @@ -142,7 +126,7 @@ func TestSetMetadataAggregate(t *testing.T) { Metadata: map[string]interface{}{"key": "value"}, } - actual, err := aggregates.SetMetadata(client.ServiceClient(), expected.ID, opts).Extract() + actual, err := aggregates.SetMetadata(client.ServiceClient(), expected.ID, opts) th.AssertNoErr(t, err) th.AssertDeepEquals(t, &expected, actual) diff --git a/openstack/compute/v2/extensions/aggregates/update.go b/openstack/compute/v2/extensions/aggregates/update.go new file mode 100644 index 000000000..e1074d789 --- /dev/null +++ b/openstack/compute/v2/extensions/aggregates/update.go @@ -0,0 +1,31 @@ +package aggregates + +import ( + "strconv" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +type UpdateOpts struct { + // The name of the host aggregate. + Name string `json:"name,omitempty"` + // The availability zone of the host aggregate. + // You should use a custom availability zone rather than + // the default returned by the os-availability-zone API. + // The availability zone must not include ‘:’ in its name. + AvailabilityZone string `json:"availability_zone,omitempty"` +} + +// Update makes a request against the API to update a specific aggregate. +func Update(client *golangsdk.ServiceClient, aggregateID int, opts UpdateOpts) (*Aggregate, error) { + b, err := build.RequestBody(opts, "aggregate") + if err != nil { + return nil, err + } + + raw, err := client.Put(client.ServiceURL("os-aggregates", strconv.Itoa(aggregateID)), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/aggregates/urls.go b/openstack/compute/v2/extensions/aggregates/urls.go deleted file mode 100644 index 1e597ab59..000000000 --- a/openstack/compute/v2/extensions/aggregates/urls.go +++ /dev/null @@ -1,35 +0,0 @@ -package aggregates - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func aggregatesListURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("os-aggregates") -} - -func aggregatesCreateURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("os-aggregates") -} - -func aggregatesDeleteURL(c *golangsdk.ServiceClient, aggregateID string) string { - return c.ServiceURL("os-aggregates", aggregateID) -} - -func aggregatesGetURL(c *golangsdk.ServiceClient, aggregateID string) string { - return c.ServiceURL("os-aggregates", aggregateID) -} - -func aggregatesUpdateURL(c *golangsdk.ServiceClient, aggregateID string) string { - return c.ServiceURL("os-aggregates", aggregateID) -} - -func aggregatesAddHostURL(c *golangsdk.ServiceClient, aggregateID string) string { - return c.ServiceURL("os-aggregates", aggregateID, "action") -} - -func aggregatesRemoveHostURL(c *golangsdk.ServiceClient, aggregateID string) string { - return c.ServiceURL("os-aggregates", aggregateID, "action") -} - -func aggregatesSetMetadataURL(c *golangsdk.ServiceClient, aggregateID string) string { - return c.ServiceURL("os-aggregates", aggregateID, "action") -} diff --git a/openstack/compute/v2/extensions/attachinterfaces/create.go b/openstack/compute/v2/extensions/attachinterfaces/create.go new file mode 100644 index 000000000..6825e124a --- /dev/null +++ b/openstack/compute/v2/extensions/attachinterfaces/create.go @@ -0,0 +1,44 @@ +package attachinterfaces + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// CreateOpts specifies parameters of a new interface attachment. +type CreateOpts struct { + // PortID is the ID of the port for which you want to create an interface. + // The NetworkID and PortID parameters are mutually exclusive. + // If you do not specify the PortID parameter, the OpenStack Networking API + // v2.0 allocates a port and creates an interface for it on the network. + PortID string `json:"port_id,omitempty"` + // NetworkID is the ID of the network for which you want to create an interface. + // The NetworkID and PortID parameters are mutually exclusive. + // If you do not specify the NetworkID parameter, the OpenStack Networking + // API v2.0 uses the network information cache that is associated with the instance. + NetworkID string `json:"net_id,omitempty"` + // Slice of FixedIPs. If you request a specific FixedIP address without a + // NetworkID, the request returns a Bad Request (400) response code. + // Note: this uses the FixedIP struct, but only the IPAddress field can be used. + FixedIPs []FixedIP `json:"fixed_ips,omitempty"` +} + +// Create requests the creation of a new interface attachment on the server. +func Create(client *golangsdk.ServiceClient, serverID string, opts CreateOpts) (*Interface, error) { + b, err := build.RequestBody(opts, "interfaceAttachment") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("servers", serverID, "os-interface"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + if err != nil { + return nil, err + } + + var res Interface + err = extract.IntoStructPtr(raw.Body, &res, "interfaceAttachment") + return &res, err +} diff --git a/openstack/compute/v2/extensions/attachinterfaces/delete.go b/openstack/compute/v2/extensions/attachinterfaces/delete.go new file mode 100644 index 000000000..1bf9a436a --- /dev/null +++ b/openstack/compute/v2/extensions/attachinterfaces/delete.go @@ -0,0 +1,10 @@ +package attachinterfaces + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Delete makes a request against the nova API to detach a single interface from the server. +// It needs server and port IDs to make a such request. +func Delete(client *golangsdk.ServiceClient, serverID, portID string) (err error) { + _, err = client.Delete(client.ServiceURL("servers", serverID, "os-interface", portID), nil) + return +} diff --git a/openstack/compute/v2/extensions/attachinterfaces/get.go b/openstack/compute/v2/extensions/attachinterfaces/get.go new file mode 100644 index 000000000..c2bf93329 --- /dev/null +++ b/openstack/compute/v2/extensions/attachinterfaces/get.go @@ -0,0 +1,29 @@ +package attachinterfaces + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// Get requests details on a single interface attachment by the server and port IDs. +func Get(client *golangsdk.ServiceClient, serverID, portID string) (*Interface, error) { + raw, err := client.Get(client.ServiceURL("servers", serverID, "os-interface", portID), nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + if err != nil { + return nil, err + } + + var res Interface + err = extract.IntoStructPtr(raw.Body, &res, "interfaceAttachment") + return &res, err +} + +// Interface represents a network interface on a server. +type Interface struct { + PortState string `json:"port_state"` + FixedIPs []FixedIP `json:"fixed_ips"` + PortID string `json:"port_id"` + NetID string `json:"net_id"` + MACAddr string `json:"mac_addr"` +} diff --git a/openstack/compute/v2/extensions/attachinterfaces/list.go b/openstack/compute/v2/extensions/attachinterfaces/list.go new file mode 100644 index 000000000..44d338752 --- /dev/null +++ b/openstack/compute/v2/extensions/attachinterfaces/list.go @@ -0,0 +1,26 @@ +package attachinterfaces + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// List makes a request against the nova API to list the server's interfaces. +func List(client *golangsdk.ServiceClient, serverID string) ([]Interface, error) { + raw, err := client.Get(client.ServiceURL("servers", serverID, "os-interface"), nil, nil) + if err != nil { + return nil, err + } + + var res []Interface + err = extract.IntoSlicePtr(raw.Body, &res, "interfaceAttachments") + return res, err +} + +// FixedIP represents a Fixed IP Address. +// This struct is also used when creating an attachment, +// but it is not possible to specify a SubnetID. +type FixedIP struct { + SubnetID string `json:"subnet_id,omitempty"` + IPAddress string `json:"ip_address"` +} diff --git a/openstack/compute/v2/extensions/attachinterfaces/requests.go b/openstack/compute/v2/extensions/attachinterfaces/requests.go deleted file mode 100644 index 0cb33eedf..000000000 --- a/openstack/compute/v2/extensions/attachinterfaces/requests.go +++ /dev/null @@ -1,72 +0,0 @@ -package attachinterfaces - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// List makes a request against the nova API to list the server's interfaces. -func List(client *golangsdk.ServiceClient, serverID string) pagination.Pager { - return pagination.NewPager(client, listInterfaceURL(client, serverID), func(r pagination.PageResult) pagination.Page { - return InterfacePage{pagination.SinglePageBase(r)} - }) -} - -// Get requests details on a single interface attachment by the server and port IDs. -func Get(client *golangsdk.ServiceClient, serverID, portID string) (r GetResult) { - _, r.Err = client.Get(getInterfaceURL(client, serverID, portID), &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToAttachInterfacesCreateMap() (map[string]interface{}, error) -} - -// CreateOpts specifies parameters of a new interface attachment. -type CreateOpts struct { - // PortID is the ID of the port for which you want to create an interface. - // The NetworkID and PortID parameters are mutually exclusive. - // If you do not specify the PortID parameter, the OpenStack Networking API - // v2.0 allocates a port and creates an interface for it on the network. - PortID string `json:"port_id,omitempty"` - - // NetworkID is the ID of the network for which you want to create an interface. - // The NetworkID and PortID parameters are mutually exclusive. - // If you do not specify the NetworkID parameter, the OpenStack Networking - // API v2.0 uses the network information cache that is associated with the instance. - NetworkID string `json:"net_id,omitempty"` - - // Slice of FixedIPs. If you request a specific FixedIP address without a - // NetworkID, the request returns a Bad Request (400) response code. - // Note: this uses the FixedIP struct, but only the IPAddress field can be used. - FixedIPs []FixedIP `json:"fixed_ips,omitempty"` -} - -// ToAttachInterfacesCreateMap constructs a request body from CreateOpts. -func (opts CreateOpts) ToAttachInterfacesCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "interfaceAttachment") -} - -// Create requests the creation of a new interface attachment on the server. -func Create(client *golangsdk.ServiceClient, serverID string, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToAttachInterfacesCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createInterfaceURL(client, serverID), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Delete makes a request against the nova API to detach a single interface from the server. -// It needs server and port IDs to make a such request. -func Delete(client *golangsdk.ServiceClient, serverID, portID string) (r DeleteResult) { - _, r.Err = client.Delete(deleteInterfaceURL(client, serverID, portID), nil) - return -} diff --git a/openstack/compute/v2/extensions/attachinterfaces/results.go b/openstack/compute/v2/extensions/attachinterfaces/results.go deleted file mode 100644 index e74a82b7c..000000000 --- a/openstack/compute/v2/extensions/attachinterfaces/results.go +++ /dev/null @@ -1,80 +0,0 @@ -package attachinterfaces - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -type attachInterfaceResult struct { - golangsdk.Result -} - -// Extract interprets any attachInterfaceResult as an Interface, if possible. -func (r attachInterfaceResult) Extract() (*Interface, error) { - var s struct { - Interface *Interface `json:"interfaceAttachment"` - } - err := r.ExtractInto(&s) - return s.Interface, err -} - -// GetResult is the response from a Get operation. Call its Extract -// method to interpret it as an Interface. -type GetResult struct { - attachInterfaceResult -} - -// CreateResult is the response from a Create operation. Call its Extract -// method to interpret it as an Interface. -type CreateResult struct { - attachInterfaceResult -} - -// DeleteResult is the response from a Delete operation. Call its ExtractErr -// method to determine if the call succeeded or failed. -type DeleteResult struct { - golangsdk.ErrResult -} - -// FixedIP represents a Fixed IP Address. -// This struct is also used when creating an attachment, -// but it is not possible to specify a SubnetID. -type FixedIP struct { - SubnetID string `json:"subnet_id,omitempty"` - IPAddress string `json:"ip_address"` -} - -// Interface represents a network interface on a server. -type Interface struct { - PortState string `json:"port_state"` - FixedIPs []FixedIP `json:"fixed_ips"` - PortID string `json:"port_id"` - NetID string `json:"net_id"` - MACAddr string `json:"mac_addr"` -} - -// InterfacePage abstracts the raw results of making a List() request against -// the API. -// -// As OpenStack extensions may freely alter the response bodies of structures -// returned to the client, you may only safely access the data provided through -// the ExtractInterfaces call. -type InterfacePage struct { - pagination.SinglePageBase -} - -// IsEmpty returns true if an InterfacePage contains no interfaces. -func (r InterfacePage) IsEmpty() (bool, error) { - interfaces, err := ExtractInterfaces(r) - return len(interfaces) == 0, err -} - -// ExtractInterfaces interprets the results of a single page from a List() call, -// producing a slice of Interface structs. -func ExtractInterfaces(r pagination.Page) ([]Interface, error) { - var s struct { - Interfaces []Interface `json:"interfaceAttachments"` - } - err := (r.(InterfacePage)).ExtractInto(&s) - return s.Interfaces, err -} diff --git a/openstack/compute/v2/extensions/attachinterfaces/testing/doc.go b/openstack/compute/v2/extensions/attachinterfaces/testing/doc.go deleted file mode 100644 index cfc07ad55..000000000 --- a/openstack/compute/v2/extensions/attachinterfaces/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// attachinterfaces unit tests -package testing diff --git a/openstack/compute/v2/extensions/attachinterfaces/testing/requests_test.go b/openstack/compute/v2/extensions/attachinterfaces/testing/requests_test.go index dd1522cd2..2a4a5b2f4 100644 --- a/openstack/compute/v2/extensions/attachinterfaces/testing/requests_test.go +++ b/openstack/compute/v2/extensions/attachinterfaces/testing/requests_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/attachinterfaces" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) @@ -15,22 +14,13 @@ func TestListInterface(t *testing.T) { HandleInterfaceListSuccessfully(t) expected := ListInterfacesExpected - pages := 0 - err := attachinterfaces.List(client.ServiceClient(), "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f").EachPage(func(page pagination.Page) (bool, error) { - pages++ - - actual, err := attachinterfaces.ExtractInterfaces(page) - th.AssertNoErr(t, err) - - if len(actual) != 1 { - t.Fatalf("Expected 1 interface, got %d", len(actual)) - } - th.CheckDeepEquals(t, expected, actual) - - return true, nil - }) + actual, err := attachinterfaces.List(client.ServiceClient(), "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f") th.AssertNoErr(t, err) - th.CheckEquals(t, 1, pages) + + if len(actual) != 1 { + t.Fatalf("Expected 1 interface, got %d", len(actual)) + } + th.CheckDeepEquals(t, expected, actual) } func TestListInterfacesAllPages(t *testing.T) { @@ -38,9 +28,7 @@ func TestListInterfacesAllPages(t *testing.T) { defer th.TeardownHTTP() HandleInterfaceListSuccessfully(t) - allPages, err := attachinterfaces.List(client.ServiceClient(), "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f").AllPages() - th.AssertNoErr(t, err) - _, err = attachinterfaces.ExtractInterfaces(allPages) + _, err := attachinterfaces.List(client.ServiceClient(), "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f") th.AssertNoErr(t, err) } @@ -54,7 +42,7 @@ func TestGetInterface(t *testing.T) { serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f" interfaceID := "0dde1598-b374-474e-986f-5b8dd1df1d4e" - actual, err := attachinterfaces.Get(client.ServiceClient(), serverID, interfaceID).Extract() + actual, err := attachinterfaces.Get(client.ServiceClient(), serverID, interfaceID) th.AssertNoErr(t, err) th.CheckDeepEquals(t, &expected, actual) } @@ -71,7 +59,7 @@ func TestCreateInterface(t *testing.T) { actual, err := attachinterfaces.Create(client.ServiceClient(), serverID, attachinterfaces.CreateOpts{ NetworkID: networkID, - }).Extract() + }) th.AssertNoErr(t, err) th.CheckDeepEquals(t, &expected, actual) } @@ -84,6 +72,6 @@ func TestDeleteInterface(t *testing.T) { serverID := "b07e7a3b-d951-4efc-a4f9-ac9f001afb7f" portID := "0dde1598-b374-474e-986f-5b8dd1df1d4e" - err := attachinterfaces.Delete(client.ServiceClient(), serverID, portID).ExtractErr() + err := attachinterfaces.Delete(client.ServiceClient(), serverID, portID) th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/attachinterfaces/urls.go b/openstack/compute/v2/extensions/attachinterfaces/urls.go deleted file mode 100644 index 332cdad4a..000000000 --- a/openstack/compute/v2/extensions/attachinterfaces/urls.go +++ /dev/null @@ -1,18 +0,0 @@ -package attachinterfaces - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func listInterfaceURL(client *golangsdk.ServiceClient, serverID string) string { - return client.ServiceURL("servers", serverID, "os-interface") -} - -func getInterfaceURL(client *golangsdk.ServiceClient, serverID, portID string) string { - return client.ServiceURL("servers", serverID, "os-interface", portID) -} - -func createInterfaceURL(client *golangsdk.ServiceClient, serverID string) string { - return client.ServiceURL("servers", serverID, "os-interface") -} -func deleteInterfaceURL(client *golangsdk.ServiceClient, serverID, portID string) string { - return client.ServiceURL("servers", serverID, "os-interface", portID) -} diff --git a/openstack/compute/v2/extensions/availabilityzones/request.go b/openstack/compute/v2/extensions/availabilityzones/request.go new file mode 100644 index 000000000..52cacc50f --- /dev/null +++ b/openstack/compute/v2/extensions/availabilityzones/request.go @@ -0,0 +1,30 @@ +package availabilityzones + +import ( + "net/http" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// List will return the existing availability zones. +func List(client *golangsdk.ServiceClient) ([]AvailabilityZone, error) { + raw, err := client.Get(client.ServiceURL("os-availability-zone"), nil, nil) + return extra(err, raw) +} + +// ListDetail will return the existing availability zones with detailed information. +func ListDetail(client *golangsdk.ServiceClient) ([]AvailabilityZone, error) { + raw, err := client.Get(client.ServiceURL("os-availability-zone", "detail"), nil, nil) + return extra(err, raw) +} + +func extra(err error, raw *http.Response) ([]AvailabilityZone, error) { + if err != nil { + return nil, err + } + + var res []AvailabilityZone + err = extract.IntoSlicePtr(raw.Body, &res, "availabilityZoneInfo") + return res, err +} diff --git a/openstack/compute/v2/extensions/availabilityzones/requests.go b/openstack/compute/v2/extensions/availabilityzones/requests.go deleted file mode 100644 index 9bb9ebd92..000000000 --- a/openstack/compute/v2/extensions/availabilityzones/requests.go +++ /dev/null @@ -1,20 +0,0 @@ -package availabilityzones - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// List will return the existing availability zones. -func List(client *golangsdk.ServiceClient) pagination.Pager { - return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { - return AvailabilityZonePage{pagination.SinglePageBase(r)} - }) -} - -// ListDetail will return the existing availability zones with detailed information. -func ListDetail(client *golangsdk.ServiceClient) pagination.Pager { - return pagination.NewPager(client, listDetailURL(client), func(r pagination.PageResult) pagination.Page { - return AvailabilityZonePage{pagination.SinglePageBase(r)} - }) -} diff --git a/openstack/compute/v2/extensions/availabilityzones/results.go b/openstack/compute/v2/extensions/availabilityzones/results.go index 16c73bca2..68179b420 100644 --- a/openstack/compute/v2/extensions/availabilityzones/results.go +++ b/openstack/compute/v2/extensions/availabilityzones/results.go @@ -5,12 +5,11 @@ import ( "time" "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" ) // ServerAvailabilityZoneExt is an extension to the base Server object. type ServerAvailabilityZoneExt struct { - // AvailabilityZone is the availabilty zone the server is in. + // AvailabilityZone is the availability zone the server is in. AvailabilityZone string `json:"OS-EXT-AZ:availability_zone"` } @@ -52,25 +51,10 @@ type ZoneState struct { Available bool `json:"available"` } -// AvailabilityZone contains all the information associated with an OpenStack -// AvailabilityZone. +// AvailabilityZone contains all the information associated with an OpenStack AvailabilityZone. type AvailabilityZone struct { Hosts Hosts `json:"hosts"` // The availability zone name ZoneName string `json:"zoneName"` ZoneState ZoneState `json:"zoneState"` } - -type AvailabilityZonePage struct { - pagination.SinglePageBase -} - -// ExtractAvailabilityZones returns a slice of AvailabilityZones contained in a -// single page of results. -func ExtractAvailabilityZones(r pagination.Page) ([]AvailabilityZone, error) { - var s struct { - AvailabilityZoneInfo []AvailabilityZone `json:"availabilityZoneInfo"` - } - err := (r.(AvailabilityZonePage)).ExtractInto(&s) - return s.AvailabilityZoneInfo, err -} diff --git a/openstack/compute/v2/extensions/availabilityzones/testing/doc.go b/openstack/compute/v2/extensions/availabilityzones/testing/doc.go deleted file mode 100644 index a4408d7a0..000000000 --- a/openstack/compute/v2/extensions/availabilityzones/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// availabilityzones unittests -package testing diff --git a/openstack/compute/v2/extensions/availabilityzones/testing/requests_test.go b/openstack/compute/v2/extensions/availabilityzones/testing/requests_test.go index 2fd70fb8d..a67c1170e 100644 --- a/openstack/compute/v2/extensions/availabilityzones/testing/requests_test.go +++ b/openstack/compute/v2/extensions/availabilityzones/testing/requests_test.go @@ -15,10 +15,7 @@ func TestList(t *testing.T) { HandleGetSuccessfully(t) - allPages, err := az.List(client.ServiceClient()).AllPages() - th.AssertNoErr(t, err) - - actual, err := az.ExtractAvailabilityZones(allPages) + actual, err := az.List(client.ServiceClient()) th.AssertNoErr(t, err) th.CheckDeepEquals(t, AZResult, actual) @@ -31,10 +28,7 @@ func TestListDetail(t *testing.T) { HandleGetDetailSuccessfully(t) - allPages, err := az.ListDetail(client.ServiceClient()).AllPages() - th.AssertNoErr(t, err) - - actual, err := az.ExtractAvailabilityZones(allPages) + actual, err := az.ListDetail(client.ServiceClient()) th.AssertNoErr(t, err) th.CheckDeepEquals(t, AZDetailResult, actual) diff --git a/openstack/compute/v2/extensions/availabilityzones/urls.go b/openstack/compute/v2/extensions/availabilityzones/urls.go deleted file mode 100644 index 8a201c775..000000000 --- a/openstack/compute/v2/extensions/availabilityzones/urls.go +++ /dev/null @@ -1,11 +0,0 @@ -package availabilityzones - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func listURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("os-availability-zone") -} - -func listDetailURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("os-availability-zone", "detail") -} diff --git a/openstack/compute/v2/extensions/bootfromvolume/requests.go b/openstack/compute/v2/extensions/bootfromvolume/create.go similarity index 86% rename from openstack/compute/v2/extensions/bootfromvolume/requests.go rename to openstack/compute/v2/extensions/bootfromvolume/create.go index b077a9c57..ac47a38e3 100644 --- a/openstack/compute/v2/extensions/bootfromvolume/requests.go +++ b/openstack/compute/v2/extensions/bootfromvolume/create.go @@ -9,32 +9,22 @@ type ( // DestinationType represents the type of medium being used as the // destination of the bootable device. DestinationType string - - // SourceType represents the type of medium being used as the source of the - // bootable device. + // SourceType represents the type of medium being used as the source of the bootable device. SourceType string ) const ( - // DestinationLocal DestinationType is for using an ephemeral disk as the - // destination. + // DestinationLocal DestinationType is for using an ephemeral disk as the destination. DestinationLocal DestinationType = "local" - // DestinationVolume DestinationType is for using a volume as the destination. DestinationVolume DestinationType = "volume" - // SourceBlank SourceType is for a "blank" or empty source. SourceBlank SourceType = "blank" - // SourceImage SourceType is for using images as the source of a block device. SourceImage SourceType = "image" - - // SourceSnapshot SourceType is for using a volume snapshot as the source of - // a block device. + // SourceSnapshot SourceType is for using a volume snapshot as the source of a block device. SourceSnapshot SourceType = "snapshot" - - // SourceVolume SourceType is for using a volume as the source of block - // device. + // SourceVolume SourceType is for using a volume as the source of block device. SourceVolume SourceType = "volume" ) @@ -45,53 +35,42 @@ const ( type BlockDevice struct { // SourceType must be one of: "volume", "snapshot", "image", or "blank". SourceType SourceType `json:"source_type" required:"true"` - // UUID is the unique identifier for the existing volume, snapshot, or // image (see above). UUID string `json:"uuid,omitempty"` - // BootIndex is the boot index. It defaults to 0. BootIndex int `json:"boot_index"` - - // DeleteOnTermination specifies whether or not to delete the attached volume + // DeleteOnTermination specifies whether to delete the attached volume // when the server is deleted. Defaults to `false`. DeleteOnTermination bool `json:"delete_on_termination"` - // DestinationType is the type that gets created. Possible values are "volume" // and "local". DestinationType DestinationType `json:"destination_type,omitempty"` - // GuestFormat specifies the format of the block device. GuestFormat string `json:"guest_format,omitempty"` - // VolumeSize is the size of the volume to create (in gigabytes). This can be // omitted for existing volumes. VolumeSize int `json:"volume_size,omitempty"` - // DeviceType specifies the device type of the block devices. // Examples of this are disk, cdrom, floppy, lun, etc. DeviceType string `json:"device_type,omitempty"` - // DiskBus is the bus type of the block devices. // Examples of this are ide, usb, virtio, scsi, etc. - DiskBus string `json:"disk_bus,omitempty"` - + DiskBus string `json:"disk_bus,omitempty"` DeviceName string `json:"device_name,omitempty"` - VolumeType string `json:"volume_type,omitempty"` } // CreateOptsExt is a structure that extends the server `CreateOpts` structure // by allowing for a block device mapping. type CreateOptsExt struct { - servers.CreateOptsBuilder + servers.CreateOpts BlockDevice []BlockDevice `json:"block_device_mapping_v2,omitempty"` } -// ToServerCreateMap adds the block device mapping option to the base server -// creation options. +// ToServerCreateMap adds the block device mapping option to the base server creation options. func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) { - base, err := opts.CreateOptsBuilder.ToServerCreateMap() + base, err := opts.CreateOpts.ToServerCreateMap() if err != nil { return nil, err } @@ -119,14 +98,14 @@ func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) { } // Create requests the creation of a server from the given block device mapping. -func Create(client *golangsdk.ServiceClient, opts servers.CreateOptsBuilder) (r servers.CreateResult) { +func Create(client *golangsdk.ServiceClient, opts CreateOptsExt) (*servers.Server, error) { b, err := opts.ToServerCreateMap() if err != nil { - r.Err = err - return + return nil, err } - _, r.Err = client.Post(createURL(client), b, &r.Body, &golangsdk.RequestOpts{ + + raw, err := client.Post(client.ServiceURL("os-volumes_boot"), b, nil, &golangsdk.RequestOpts{ OkCodes: []int{200, 202}, }) - return + return servers.ExtractSer(err, raw) } diff --git a/openstack/compute/v2/extensions/bootfromvolume/results.go b/openstack/compute/v2/extensions/bootfromvolume/results.go deleted file mode 100644 index 2448b03cc..000000000 --- a/openstack/compute/v2/extensions/bootfromvolume/results.go +++ /dev/null @@ -1,12 +0,0 @@ -package bootfromvolume - -import ( - os "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/servers" -) - -// CreateResult temporarily contains the response from a Create call. -// It embeds the standard servers.CreateResults type and so can be used the -// same way as a standard server request result. -type CreateResult struct { - os.CreateResult -} diff --git a/openstack/compute/v2/extensions/bootfromvolume/testing/doc.go b/openstack/compute/v2/extensions/bootfromvolume/testing/doc.go deleted file mode 100644 index cf5048acb..000000000 --- a/openstack/compute/v2/extensions/bootfromvolume/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// bootfromvolume unit tests -package testing diff --git a/openstack/compute/v2/extensions/bootfromvolume/testing/fixtures.go b/openstack/compute/v2/extensions/bootfromvolume/testing/fixtures.go index e6b1a6633..40a605e82 100644 --- a/openstack/compute/v2/extensions/bootfromvolume/testing/fixtures.go +++ b/openstack/compute/v2/extensions/bootfromvolume/testing/fixtures.go @@ -37,7 +37,7 @@ const ExpectedNewVolumeRequest = ` ` var NewVolumeRequest = bootfromvolume.CreateOptsExt{ - CreateOptsBuilder: BaseCreateOpts, + CreateOpts: BaseCreateOpts, BlockDevice: []bootfromvolume.BlockDevice{ { UUID: "123456", @@ -69,7 +69,7 @@ const ExpectedExistingVolumeRequest = ` ` var ExistingVolumeRequest = bootfromvolume.CreateOptsExt{ - CreateOptsBuilder: BaseCreateOpts, + CreateOpts: BaseCreateOpts, BlockDevice: []bootfromvolume.BlockDevice{ { UUID: "123456", @@ -100,7 +100,7 @@ const ExpectedImageRequest = ` ` var ImageRequest = bootfromvolume.CreateOptsExt{ - CreateOptsBuilder: BaseCreateOptsWithImageRef, + CreateOpts: BaseCreateOptsWithImageRef, BlockDevice: []bootfromvolume.BlockDevice{ { BootIndex: 0, @@ -148,7 +148,7 @@ const ExpectedMultiEphemeralRequest = ` ` var MultiEphemeralRequest = bootfromvolume.CreateOptsExt{ - CreateOptsBuilder: BaseCreateOptsWithImageRef, + CreateOpts: BaseCreateOptsWithImageRef, BlockDevice: []bootfromvolume.BlockDevice{ { BootIndex: 0, @@ -205,7 +205,7 @@ const ExpectedImageAndNewVolumeRequest = ` ` var ImageAndNewVolumeRequest = bootfromvolume.CreateOptsExt{ - CreateOptsBuilder: BaseCreateOptsWithImageRef, + CreateOpts: BaseCreateOptsWithImageRef, BlockDevice: []bootfromvolume.BlockDevice{ { BootIndex: 0, @@ -254,7 +254,7 @@ const ExpectedImageAndExistingVolumeRequest = ` ` var ImageAndExistingVolumeRequest = bootfromvolume.CreateOptsExt{ - CreateOptsBuilder: BaseCreateOptsWithImageRef, + CreateOpts: BaseCreateOptsWithImageRef, BlockDevice: []bootfromvolume.BlockDevice{ { BootIndex: 0, diff --git a/openstack/compute/v2/extensions/bootfromvolume/urls.go b/openstack/compute/v2/extensions/bootfromvolume/urls.go deleted file mode 100644 index 54427759b..000000000 --- a/openstack/compute/v2/extensions/bootfromvolume/urls.go +++ /dev/null @@ -1,7 +0,0 @@ -package bootfromvolume - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func createURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("os-volumes_boot") -} diff --git a/openstack/compute/v2/extensions/defsecrules/requests.go b/openstack/compute/v2/extensions/defsecrules/create.go similarity index 55% rename from openstack/compute/v2/extensions/defsecrules/requests.go rename to openstack/compute/v2/extensions/defsecrules/create.go index 76eacf702..6539c3475 100644 --- a/openstack/compute/v2/extensions/defsecrules/requests.go +++ b/openstack/compute/v2/extensions/defsecrules/create.go @@ -4,27 +4,16 @@ import ( "strings" "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" ) -// List will return a collection of default rules. -func List(client *golangsdk.ServiceClient) pagination.Pager { - return pagination.NewPager(client, rootURL(client), func(r pagination.PageResult) pagination.Page { - return DefaultRulePage{pagination.SinglePageBase(r)} - }) -} - // CreateOpts represents the configuration for adding a new default rule. type CreateOpts struct { // The lower bound of the port range that will be opened. FromPort int `json:"from_port"` - // The upper bound of the port range that will be opened. ToPort int `json:"to_port"` - // The protocol type that will be allowed, e.g. TCP. IPProtocol string `json:"ip_protocol" required:"true"` - // ONLY required if FromGroupID is blank. This represents the IP range that // will be the source of network traffic to your security group. // @@ -33,11 +22,6 @@ type CreateOpts struct { CIDR string `json:"cidr,omitempty"` } -// CreateOptsBuilder builds the create rule options into a serializable format. -type CreateOptsBuilder interface { - ToRuleCreateMap() (map[string]interface{}, error) -} - // ToRuleCreateMap builds the create rule options into a serializable format. func (opts CreateOpts) ToRuleCreateMap() (map[string]interface{}, error) { if opts.FromPort == 0 && strings.ToUpper(opts.IPProtocol) != "ICMP" { @@ -50,26 +34,14 @@ func (opts CreateOpts) ToRuleCreateMap() (map[string]interface{}, error) { } // Create is the operation responsible for creating a new default rule. -func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { +func Create(client *golangsdk.ServiceClient, opts CreateOpts) (*DefaultRule, error) { b, err := opts.ToRuleCreateMap() if err != nil { - r.Err = err - return + return nil, err } - _, r.Err = client.Post(rootURL(client), b, &r.Body, &golangsdk.RequestOpts{ + + raw, err := client.Post(client.ServiceURL("os-security-group-default-rules"), b, nil, &golangsdk.RequestOpts{ OkCodes: []int{200}, }) - return -} - -// Get will return details for a particular default rule. -func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(resourceURL(client, id), &r.Body, nil) - return -} - -// Delete will permanently delete a rule the project's default security group. -func Delete(client *golangsdk.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(resourceURL(client, id), nil) - return + return extra(err, raw) } diff --git a/openstack/compute/v2/extensions/defsecrules/delete.go b/openstack/compute/v2/extensions/defsecrules/delete.go new file mode 100644 index 000000000..5d4dc6d54 --- /dev/null +++ b/openstack/compute/v2/extensions/defsecrules/delete.go @@ -0,0 +1,9 @@ +package defsecrules + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Delete will permanently delete a rule the project's default security group. +func Delete(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Delete(client.ServiceURL("os-security-group-default-rules", id), nil) + return +} diff --git a/openstack/compute/v2/extensions/defsecrules/get.go b/openstack/compute/v2/extensions/defsecrules/get.go new file mode 100644 index 000000000..db9728964 --- /dev/null +++ b/openstack/compute/v2/extensions/defsecrules/get.go @@ -0,0 +1,9 @@ +package defsecrules + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Get will return details for a particular default rule. +func Get(client *golangsdk.ServiceClient, id string) (*DefaultRule, error) { + raw, err := client.Get(client.ServiceURL("os-security-group-default-rules", id), nil, nil) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/defsecrules/list.go b/openstack/compute/v2/extensions/defsecrules/list.go new file mode 100644 index 000000000..8e2ab63ce --- /dev/null +++ b/openstack/compute/v2/extensions/defsecrules/list.go @@ -0,0 +1,18 @@ +package defsecrules + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// List will return a collection of default rules. +func List(client *golangsdk.ServiceClient) ([]DefaultRule, error) { + raw, err := client.Get(client.ServiceURL("os-security-group-default-rules"), nil, nil) + if err != nil { + return nil, err + } + + var res []DefaultRule + err = extract.IntoSlicePtr(raw.Body, &res, "security_group_default_rules") + return res, err +} diff --git a/openstack/compute/v2/extensions/defsecrules/results.go b/openstack/compute/v2/extensions/defsecrules/results.go index e7efbe360..d87fa81cf 100644 --- a/openstack/compute/v2/extensions/defsecrules/results.go +++ b/openstack/compute/v2/extensions/defsecrules/results.go @@ -2,10 +2,10 @@ package defsecrules import ( "encoding/json" + "net/http" - "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/secgroups" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" ) // DefaultRule represents a rule belonging to the "default" security group. @@ -22,52 +22,12 @@ func (r *DefaultRule) UnmarshalJSON(b []byte) error { return nil } -// DefaultRulePage is a single page of a DefaultRule collection. -type DefaultRulePage struct { - pagination.SinglePageBase -} - -// IsEmpty determines whether or not a page of default rules contains any results. -func (page DefaultRulePage) IsEmpty() (bool, error) { - users, err := ExtractDefaultRules(page) - return len(users) == 0, err -} - -// ExtractDefaultRules returns a slice of DefaultRules contained in a single -// page of results. -func ExtractDefaultRules(r pagination.Page) ([]DefaultRule, error) { - var s struct { - DefaultRules []DefaultRule `json:"security_group_default_rules"` - } - err := (r.(DefaultRulePage)).ExtractInto(&s) - return s.DefaultRules, err -} - -type commonResult struct { - golangsdk.Result -} - -// CreateResult represents the result of a create operation. -type CreateResult struct { - commonResult -} - -// GetResult represents the result of a get operation. -type GetResult struct { - commonResult -} - -// Extract will extract a DefaultRule struct from a Create or Get response. -func (r commonResult) Extract() (*DefaultRule, error) { - var s struct { - DefaultRule DefaultRule `json:"security_group_default_rule"` +func extra(err error, raw *http.Response) (*DefaultRule, error) { + if err != nil { + return nil, err } - err := r.ExtractInto(&s) - return &s.DefaultRule, err -} -// DeleteResult is the response from a delete operation. Call its ExtractErr -// method to determine if the request succeeded or failed. -type DeleteResult struct { - golangsdk.ErrResult + var res DefaultRule + err = extract.IntoStructPtr(raw.Body, &res, "security_group_default_rule") + return &res, err } diff --git a/openstack/compute/v2/extensions/defsecrules/testing/doc.go b/openstack/compute/v2/extensions/defsecrules/testing/doc.go deleted file mode 100644 index 6eeb60f05..000000000 --- a/openstack/compute/v2/extensions/defsecrules/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// defsecrules unit tests -package testing diff --git a/openstack/compute/v2/extensions/defsecrules/testing/requests_test.go b/openstack/compute/v2/extensions/defsecrules/testing/requests_test.go index 4140b3bf7..a3bd5675a 100644 --- a/openstack/compute/v2/extensions/defsecrules/testing/requests_test.go +++ b/openstack/compute/v2/extensions/defsecrules/testing/requests_test.go @@ -5,7 +5,6 @@ import ( "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/defsecrules" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/secgroups" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) @@ -15,33 +14,22 @@ const ruleID = "{ruleID}" func TestList(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() - mockListRulesResponse(t) - count := 0 - - err := defsecrules.List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { - count++ - actual, err := defsecrules.ExtractDefaultRules(page) - th.AssertNoErr(t, err) - - expected := []defsecrules.DefaultRule{ - { - FromPort: 80, - ID: ruleID, - IPProtocol: "TCP", - IPRange: secgroups.IPRange{CIDR: "10.10.10.0/24"}, - ToPort: 80, - }, - } - - th.CheckDeepEquals(t, expected, actual) + actual, err := defsecrules.List(client.ServiceClient()) + th.AssertNoErr(t, err) - return true, nil - }) + expected := []defsecrules.DefaultRule{ + { + FromPort: 80, + ID: ruleID, + IPProtocol: "TCP", + IPRange: secgroups.IPRange{CIDR: "10.10.10.0/24"}, + ToPort: 80, + }, + } - th.AssertNoErr(t, err) - th.AssertEquals(t, 1, count) + th.CheckDeepEquals(t, expected, actual) } func TestCreate(t *testing.T) { @@ -57,7 +45,7 @@ func TestCreate(t *testing.T) { CIDR: "10.10.12.0/24", } - group, err := defsecrules.Create(client.ServiceClient(), opts).Extract() + group, err := defsecrules.Create(client.ServiceClient(), opts) th.AssertNoErr(t, err) expected := &defsecrules.DefaultRule{ @@ -83,7 +71,7 @@ func TestCreateICMPZero(t *testing.T) { CIDR: "10.10.12.0/24", } - group, err := defsecrules.Create(client.ServiceClient(), opts).Extract() + group, err := defsecrules.Create(client.ServiceClient(), opts) th.AssertNoErr(t, err) expected := &defsecrules.DefaultRule{ @@ -102,7 +90,7 @@ func TestGet(t *testing.T) { mockGetRuleResponse(t, ruleID) - group, err := defsecrules.Get(client.ServiceClient(), ruleID).Extract() + group, err := defsecrules.Get(client.ServiceClient(), ruleID) th.AssertNoErr(t, err) expected := &defsecrules.DefaultRule{ @@ -122,6 +110,6 @@ func TestDelete(t *testing.T) { mockDeleteRuleResponse(t, ruleID) - err := defsecrules.Delete(client.ServiceClient(), ruleID).ExtractErr() + err := defsecrules.Delete(client.ServiceClient(), ruleID) th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/defsecrules/urls.go b/openstack/compute/v2/extensions/defsecrules/urls.go deleted file mode 100644 index bd0d4e9af..000000000 --- a/openstack/compute/v2/extensions/defsecrules/urls.go +++ /dev/null @@ -1,13 +0,0 @@ -package defsecrules - -import "github.com/opentelekomcloud/gophertelekomcloud" - -const rulepath = "os-security-group-default-rules" - -func resourceURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL(rulepath, id) -} - -func rootURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL(rulepath) -} diff --git a/openstack/compute/v2/extensions/delegate.go b/openstack/compute/v2/extensions/delegate.go index 7aabf3c1f..7b5f011ce 100644 --- a/openstack/compute/v2/extensions/delegate.go +++ b/openstack/compute/v2/extensions/delegate.go @@ -12,12 +12,12 @@ func ExtractExtensions(page pagination.Page) ([]common.Extension, error) { } // Get retrieves information for a specific extension using its alias. -func Get(c *golangsdk.ServiceClient, alias string) common.GetResult { - return common.Get(c, alias) +func Get(client *golangsdk.ServiceClient, alias string) common.GetResult { + return common.Get(client, alias) } // List returns a Pager which allows you to iterate over the full collection of extensions. // It does not accept query parameters. -func List(c *golangsdk.ServiceClient) pagination.Pager { - return common.List(c) +func List(client *golangsdk.ServiceClient) pagination.Pager { + return common.List(client) } diff --git a/openstack/compute/v2/extensions/diskconfig/requests.go b/openstack/compute/v2/extensions/diskconfig/requests.go index 03f538d10..bc914cec5 100644 --- a/openstack/compute/v2/extensions/diskconfig/requests.go +++ b/openstack/compute/v2/extensions/diskconfig/requests.go @@ -15,7 +15,6 @@ const ( // Auto may only be used with images and servers that use a single EXT3 // partition. Auto DiskConfig = "AUTO" - // Manual builds a server using whatever partition scheme and filesystem are // present in the source image. If the target flavor disk is larger, the // remaining space is left unpartitioned. This enables images to have non-EXT3 @@ -26,15 +25,14 @@ const ( // CreateOptsExt adds a DiskConfig option to the base CreateOpts. type CreateOptsExt struct { - servers.CreateOptsBuilder - + servers.CreateOpts // DiskConfig [optional] controls how the created server's disk is partitioned. DiskConfig DiskConfig `json:"OS-DCF:diskConfig,omitempty"` } // ToServerCreateMap adds the diskconfig option to the base server creation options. func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) { - base, err := opts.CreateOptsBuilder.ToServerCreateMap() + base, err := opts.CreateOpts.ToServerCreateMap() if err != nil { return nil, err } @@ -51,8 +49,7 @@ func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) { // RebuildOptsExt adds a DiskConfig option to the base RebuildOpts. type RebuildOptsExt struct { - servers.RebuildOptsBuilder - + servers.RebuildOpts // DiskConfig controls how the rebuilt server's disk is partitioned. DiskConfig DiskConfig `json:"OS-DCF:diskConfig,omitempty"` } @@ -66,7 +63,7 @@ func (opts RebuildOptsExt) ToServerRebuildMap() (map[string]interface{}, error) return nil, err } - base, err := opts.RebuildOptsBuilder.ToServerRebuildMap() + base, err := opts.RebuildOpts.ToServerRebuildMap() if err != nil { return nil, err } @@ -79,8 +76,7 @@ func (opts RebuildOptsExt) ToServerRebuildMap() (map[string]interface{}, error) // ResizeOptsExt adds a DiskConfig option to the base server resize options. type ResizeOptsExt struct { - servers.ResizeOptsBuilder - + servers.ResizeOpts // DiskConfig [optional] controls how the resized server's disk is partitioned. DiskConfig DiskConfig } @@ -94,7 +90,7 @@ func (opts ResizeOptsExt) ToServerResizeMap() (map[string]interface{}, error) { return nil, err } - base, err := opts.ResizeOptsBuilder.ToServerResizeMap() + base, err := golangsdk.BuildRequestBody(opts.ResizeOpts, "resize") if err != nil { return nil, err } diff --git a/openstack/compute/v2/extensions/diskconfig/testing/doc.go b/openstack/compute/v2/extensions/diskconfig/testing/doc.go deleted file mode 100644 index 52ab24756..000000000 --- a/openstack/compute/v2/extensions/diskconfig/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// diskconfig unit tests -package testing diff --git a/openstack/compute/v2/extensions/diskconfig/testing/requests_test.go b/openstack/compute/v2/extensions/diskconfig/testing/requests_test.go index c5cea5bfe..082c82c60 100644 --- a/openstack/compute/v2/extensions/diskconfig/testing/requests_test.go +++ b/openstack/compute/v2/extensions/diskconfig/testing/requests_test.go @@ -16,8 +16,8 @@ func TestCreateOpts(t *testing.T) { } ext := diskconfig.CreateOptsExt{ - CreateOptsBuilder: base, - DiskConfig: diskconfig.Manual, + CreateOpts: base, + DiskConfig: diskconfig.Manual, } expected := ` @@ -43,8 +43,8 @@ func TestRebuildOpts(t *testing.T) { } ext := diskconfig.RebuildOptsExt{ - RebuildOptsBuilder: base, - DiskConfig: diskconfig.Auto, + RebuildOpts: base, + DiskConfig: diskconfig.Auto, } actual, err := ext.ToServerRebuildMap() @@ -69,8 +69,8 @@ func TestResizeOpts(t *testing.T) { } ext := diskconfig.ResizeOptsExt{ - ResizeOptsBuilder: base, - DiskConfig: diskconfig.Auto, + ResizeOpts: base, + DiskConfig: diskconfig.Auto, } actual, err := ext.ToServerResizeMap() diff --git a/openstack/compute/v2/extensions/evacuate/requests.go b/openstack/compute/v2/extensions/evacuate/evacuate.go similarity index 52% rename from openstack/compute/v2/extensions/evacuate/requests.go rename to openstack/compute/v2/extensions/evacuate/evacuate.go index ed9b920e3..7bea263ca 100644 --- a/openstack/compute/v2/extensions/evacuate/requests.go +++ b/openstack/compute/v2/extensions/evacuate/evacuate.go @@ -2,40 +2,37 @@ package evacuate import ( "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" ) -// EvacuateOptsBuilder allows extensions to add additional parameters to the -// the Evacuate request. -type EvacuateOptsBuilder interface { - ToEvacuateMap() (map[string]interface{}, error) -} - // EvacuateOpts specifies Evacuate action parameters. type EvacuateOpts struct { // The name of the host to which the server is evacuated Host string `json:"host,omitempty"` - // Indicates whether server is on shared storage OnSharedStorage bool `json:"onSharedStorage"` - // An administrative password to access the evacuated server AdminPass string `json:"adminPass,omitempty"` } -// ToServerGroupCreateMap constructs a request body from CreateOpts. -func (opts EvacuateOpts) ToEvacuateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "evacuate") -} - // Evacuate will Evacuate a failed instance to another host. -func Evacuate(client *golangsdk.ServiceClient, id string, opts EvacuateOptsBuilder) (r EvacuateResult) { - b, err := opts.ToEvacuateMap() +func Evacuate(client *golangsdk.ServiceClient, id string, opts EvacuateOpts) (string, error) { + b, err := build.RequestBody(opts, "evacuate") if err != nil { - r.Err = err - return + return "", err } - _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ + + raw, err := client.Post(client.ServiceURL("servers", id, "action"), b, nil, &golangsdk.RequestOpts{ OkCodes: []int{200}, }) - return + if err != nil { + return "", err + } + + var res struct { + AdminPass string `json:"adminPass"` + } + err = extract.Into(raw.Body, &res) + return res.AdminPass, err } diff --git a/openstack/compute/v2/extensions/evacuate/results.go b/openstack/compute/v2/extensions/evacuate/results.go deleted file mode 100644 index 7879d6171..000000000 --- a/openstack/compute/v2/extensions/evacuate/results.go +++ /dev/null @@ -1,23 +0,0 @@ -package evacuate - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -// EvacuateResult is the response from an Evacuate operation. -// Call its ExtractAdminPass method to retrieve the admin password of the instance. -// The admin password will be an empty string if the cloud is not configured to inject admin passwords.. -type EvacuateResult struct { - golangsdk.Result -} - -func (r EvacuateResult) ExtractAdminPass() (string, error) { - var s struct { - AdminPass string `json:"adminPass"` - } - err := r.ExtractInto(&s) - if err != nil && err.Error() == "EOF" { - return "", nil - } - return s.AdminPass, err -} diff --git a/openstack/compute/v2/extensions/evacuate/testing/doc.go b/openstack/compute/v2/extensions/evacuate/testing/doc.go deleted file mode 100644 index 613ac1d4b..000000000 --- a/openstack/compute/v2/extensions/evacuate/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// compute_extensions_evacuate_v2 -package testing diff --git a/openstack/compute/v2/extensions/evacuate/testing/requests_test.go b/openstack/compute/v2/extensions/evacuate/testing/requests_test.go index 2fc4f2f8d..6f55365a2 100644 --- a/openstack/compute/v2/extensions/evacuate/testing/requests_test.go +++ b/openstack/compute/v2/extensions/evacuate/testing/requests_test.go @@ -19,7 +19,7 @@ func TestEvacuate(t *testing.T) { Host: "derp", AdminPass: "MySecretPass", OnSharedStorage: false, - }).ExtractAdminPass() + }) th.AssertNoErr(t, err) } @@ -32,7 +32,7 @@ func TestEvacuateWithHost(t *testing.T) { _, err := evacuate.Evacuate(client.ServiceClient(), serverID, evacuate.EvacuateOpts{ Host: "derp", - }).ExtractAdminPass() + }) th.AssertNoErr(t, err) } @@ -43,7 +43,7 @@ func TestEvacuateWithNoOpts(t *testing.T) { mockEvacuateResponseWithNoOpts(t, serverID) - _, err := evacuate.Evacuate(client.ServiceClient(), serverID, evacuate.EvacuateOpts{}).ExtractAdminPass() + _, err := evacuate.Evacuate(client.ServiceClient(), serverID, evacuate.EvacuateOpts{}) th.AssertNoErr(t, err) } @@ -54,7 +54,7 @@ func TestEvacuateAdminpassResponse(t *testing.T) { mockEvacuateAdminpassResponse(t, serverID) - actual, err := evacuate.Evacuate(client.ServiceClient(), serverID, evacuate.EvacuateOpts{}).ExtractAdminPass() + actual, err := evacuate.Evacuate(client.ServiceClient(), serverID, evacuate.EvacuateOpts{}) th.CheckEquals(t, "MySecretPass", actual) th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/evacuate/urls.go b/openstack/compute/v2/extensions/evacuate/urls.go deleted file mode 100644 index 2da15a3fc..000000000 --- a/openstack/compute/v2/extensions/evacuate/urls.go +++ /dev/null @@ -1,9 +0,0 @@ -package evacuate - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -func actionURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL("servers", id, "action") -} diff --git a/openstack/compute/v2/extensions/extendedserverattributes/testing/doc.go b/openstack/compute/v2/extensions/extendedserverattributes/testing/doc.go deleted file mode 100644 index 7603f836a..000000000 --- a/openstack/compute/v2/extensions/extendedserverattributes/testing/doc.go +++ /dev/null @@ -1 +0,0 @@ -package testing diff --git a/openstack/compute/v2/extensions/extendedserverattributes/testing/requests_test.go b/openstack/compute/v2/extensions/extendedserverattributes/testing/requests_test.go index a29cc00cb..50646643e 100644 --- a/openstack/compute/v2/extensions/extendedserverattributes/testing/requests_test.go +++ b/openstack/compute/v2/extensions/extendedserverattributes/testing/requests_test.go @@ -23,12 +23,12 @@ func TestServerWithUsageExt(t *testing.T) { _, _ = fmt.Fprint(w, ServerWithAttributesExtResult) }) - type serverAttributesExt struct { + var serverWithAttributesExt struct { servers.Server extendedserverattributes.ServerAttributesExt } - var serverWithAttributesExt serverAttributesExt - err := servers.Get(fake.ServiceClient(), "d650a0ce-17c3-497d-961a-43c4af80998a").ExtractInto(&serverWithAttributesExt) + + err := servers.GetInto(fake.ServiceClient(), "d650a0ce-17c3-497d-961a-43c4af80998a", &serverWithAttributesExt) th.AssertNoErr(t, err) th.AssertEquals(t, serverWithAttributesExt.Host, "compute01") diff --git a/openstack/compute/v2/extensions/floatingips/associate_instance.go b/openstack/compute/v2/extensions/floatingips/associate_instance.go new file mode 100644 index 000000000..94a9d8240 --- /dev/null +++ b/openstack/compute/v2/extensions/floatingips/associate_instance.go @@ -0,0 +1,25 @@ +package floatingips + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// AssociateOpts specifies the required information to associate a Floating IP with an instance +type AssociateOpts struct { + // FloatingIP is the Floating IP to associate with an instance. + FloatingIP string `json:"address" required:"true"` + // FixedIP is an optional fixed IP address of the server. + FixedIP string `json:"fixed_address,omitempty"` +} + +// AssociateInstance pairs an allocated Floating IP with a server. +func AssociateInstance(client *golangsdk.ServiceClient, serverID string, opts AssociateOpts) (err error) { + b, err := build.RequestBody(opts, "addFloatingIp") + if err != nil { + return + } + + _, err = client.Post(client.ServiceURL("servers/"+serverID+"/action"), b, nil, nil) + return +} diff --git a/openstack/compute/v2/extensions/floatingips/create.go b/openstack/compute/v2/extensions/floatingips/create.go new file mode 100644 index 000000000..ad958c10c --- /dev/null +++ b/openstack/compute/v2/extensions/floatingips/create.go @@ -0,0 +1,25 @@ +package floatingips + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// CreateOpts specifies a Floating IP allocation request. +type CreateOpts struct { + // Pool is the pool of Floating IPs to allocate one from. + Pool string `json:"pool" required:"true"` +} + +// Create requests the creation of a new Floating IP. +func Create(client *golangsdk.ServiceClient, opts CreateOpts) (*FloatingIP, error) { + b, err := build.RequestBody(opts, "") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("os-floating-ips"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/floatingips/delete.go b/openstack/compute/v2/extensions/floatingips/delete.go new file mode 100644 index 000000000..940302814 --- /dev/null +++ b/openstack/compute/v2/extensions/floatingips/delete.go @@ -0,0 +1,9 @@ +package floatingips + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Delete requests the deletion of a previous allocated Floating IP. +func Delete(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Delete(client.ServiceURL("os-floating-ips", id), nil) + return +} diff --git a/openstack/compute/v2/extensions/floatingips/disassociate_instance.go b/openstack/compute/v2/extensions/floatingips/disassociate_instance.go new file mode 100644 index 000000000..e4d779aeb --- /dev/null +++ b/openstack/compute/v2/extensions/floatingips/disassociate_instance.go @@ -0,0 +1,23 @@ +package floatingips + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// DisassociateOpts specifies the required information to disassociate a +// Floating IP with a server. +type DisassociateOpts struct { + FloatingIP string `json:"address" required:"true"` +} + +// DisassociateInstance decouples an allocated Floating IP from an instance +func DisassociateInstance(client *golangsdk.ServiceClient, serverID string, opts DisassociateOpts) (err error) { + b, err := build.RequestBody(opts, "removeFloatingIp") + if err != nil { + return + } + + _, err = client.Post(client.ServiceURL("servers/"+serverID+"/action"), b, nil, nil) + return +} diff --git a/openstack/compute/v2/extensions/floatingips/get.go b/openstack/compute/v2/extensions/floatingips/get.go new file mode 100644 index 000000000..4bac7c9f0 --- /dev/null +++ b/openstack/compute/v2/extensions/floatingips/get.go @@ -0,0 +1,9 @@ +package floatingips + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Get returns data about a previously created Floating IP. +func Get(client *golangsdk.ServiceClient, id string) (*FloatingIP, error) { + raw, err := client.Get(client.ServiceURL("os-floating-ips", id), nil, nil) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/floatingips/list.go b/openstack/compute/v2/extensions/floatingips/list.go new file mode 100644 index 000000000..ecf008864 --- /dev/null +++ b/openstack/compute/v2/extensions/floatingips/list.go @@ -0,0 +1,18 @@ +package floatingips + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// List returns a Pager that allows you to iterate over a collection of FloatingIPs. +func List(client *golangsdk.ServiceClient) ([]FloatingIP, error) { + raw, err := client.Get(client.ServiceURL("os-floating-ips"), nil, nil) + if err != nil { + return nil, err + } + + var res []FloatingIP + err = extract.IntoSlicePtr(raw.Body, &res, "floating_ips") + return res, err +} diff --git a/openstack/compute/v2/extensions/floatingips/requests.go b/openstack/compute/v2/extensions/floatingips/requests.go deleted file mode 100644 index 2186fca7e..000000000 --- a/openstack/compute/v2/extensions/floatingips/requests.go +++ /dev/null @@ -1,114 +0,0 @@ -package floatingips - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// List returns a Pager that allows you to iterate over a collection of FloatingIPs. -func List(client *golangsdk.ServiceClient) pagination.Pager { - return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { - return FloatingIPPage{pagination.SinglePageBase(r)} - }) -} - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToFloatingIPCreateMap() (map[string]interface{}, error) -} - -// CreateOpts specifies a Floating IP allocation request. -type CreateOpts struct { - // Pool is the pool of Floating IPs to allocate one from. - Pool string `json:"pool" required:"true"` -} - -// ToFloatingIPCreateMap constructs a request body from CreateOpts. -func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "") -} - -// Create requests the creation of a new Floating IP. -func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToFloatingIPCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Get returns data about a previously created Floating IP. -func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) - return -} - -// Delete requests the deletion of a previous allocated Floating IP. -func Delete(client *golangsdk.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) - return -} - -// AssociateOptsBuilder allows extensions to add additional parameters to the -// Associate request. -type AssociateOptsBuilder interface { - ToFloatingIPAssociateMap() (map[string]interface{}, error) -} - -// AssociateOpts specifies the required information to associate a Floating IP with an instance -type AssociateOpts struct { - // FloatingIP is the Floating IP to associate with an instance. - FloatingIP string `json:"address" required:"true"` - - // FixedIP is an optional fixed IP address of the server. - FixedIP string `json:"fixed_address,omitempty"` -} - -// ToFloatingIPAssociateMap constructs a request body from AssociateOpts. -func (opts AssociateOpts) ToFloatingIPAssociateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "addFloatingIp") -} - -// AssociateInstance pairs an allocated Floating IP with a server. -func AssociateInstance(client *golangsdk.ServiceClient, serverID string, opts AssociateOptsBuilder) (r AssociateResult) { - b, err := opts.ToFloatingIPAssociateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(associateURL(client, serverID), b, nil, nil) - return -} - -// DisassociateOptsBuilder allows extensions to add additional parameters to -// the Disassociate request. -type DisassociateOptsBuilder interface { - ToFloatingIPDisassociateMap() (map[string]interface{}, error) -} - -// DisassociateOpts specifies the required information to disassociate a -// Floating IP with a server. -type DisassociateOpts struct { - FloatingIP string `json:"address" required:"true"` -} - -// ToFloatingIPDisassociateMap constructs a request body from DisassociateOpts. -func (opts DisassociateOpts) ToFloatingIPDisassociateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "removeFloatingIp") -} - -// DisassociateInstance decouples an allocated Floating IP from an instance -func DisassociateInstance(client *golangsdk.ServiceClient, serverID string, opts DisassociateOptsBuilder) (r DisassociateResult) { - b, err := opts.ToFloatingIPDisassociateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(disassociateURL(client, serverID), b, nil, nil) - return -} diff --git a/openstack/compute/v2/extensions/floatingips/results.go b/openstack/compute/v2/extensions/floatingips/results.go index d03efa15e..10fe886a4 100644 --- a/openstack/compute/v2/extensions/floatingips/results.go +++ b/openstack/compute/v2/extensions/floatingips/results.go @@ -2,26 +2,22 @@ package floatingips import ( "encoding/json" + "net/http" "strconv" - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" ) // A FloatingIP is an IP that can be associated with a server. type FloatingIP struct { // ID is a unique ID of the Floating IP ID string `json:"-"` - // FixedIP is a specific IP on the server to pair the Floating IP with. FixedIP string `json:"fixed_ip,omitempty"` - // InstanceID is the ID of the server that is using the Floating IP. InstanceID string `json:"instance_id"` - // IP is the actual Floating IP. IP string `json:"ip"` - // Pool is the pool of Floating IPs that this Floating IP belongs to. Pool string `json:"pool"` } @@ -49,67 +45,12 @@ func (r *FloatingIP) UnmarshalJSON(b []byte) error { return err } -// FloatingIPPage stores a single page of FloatingIPs from a List call. -type FloatingIPPage struct { - pagination.SinglePageBase -} - -// IsEmpty determines whether or not a FloatingIPsPage is empty. -func (page FloatingIPPage) IsEmpty() (bool, error) { - va, err := ExtractFloatingIPs(page) - return len(va) == 0, err -} - -// ExtractFloatingIPs interprets a page of results as a slice of FloatingIPs. -func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) { - var s struct { - FloatingIPs []FloatingIP `json:"floating_ips"` - } - err := (r.(FloatingIPPage)).ExtractInto(&s) - return s.FloatingIPs, err -} - -// FloatingIPResult is the raw result from a FloatingIP request. -type FloatingIPResult struct { - golangsdk.Result -} - -// Extract is a method that attempts to interpret any FloatingIP resource -// response as a FloatingIP struct. -func (r FloatingIPResult) Extract() (*FloatingIP, error) { - var s struct { - FloatingIP *FloatingIP `json:"floating_ip"` +func extra(err error, raw *http.Response) (*FloatingIP, error) { + if err != nil { + return nil, err } - err := r.ExtractInto(&s) - return s.FloatingIP, err -} - -// CreateResult is the response from a Create operation. Call its Extract method -// to interpret it as a FloatingIP. -type CreateResult struct { - FloatingIPResult -} - -// GetResult is the response from a Get operation. Call its Extract method to -// interpret it as a FloatingIP. -type GetResult struct { - FloatingIPResult -} - -// DeleteResult is the response from a Delete operation. Call its ExtractErr -// method to determine if the call succeeded or failed. -type DeleteResult struct { - golangsdk.ErrResult -} - -// AssociateResult is the response from a Delete operation. Call its ExtractErr -// method to determine if the call succeeded or failed. -type AssociateResult struct { - golangsdk.ErrResult -} -// DisassociateResult is the response from a Delete operation. Call its -// ExtractErr method to determine if the call succeeded or failed. -type DisassociateResult struct { - golangsdk.ErrResult + var res FloatingIP + err = extract.IntoStructPtr(raw.Body, &res, "floating_ip") + return &res, err } diff --git a/openstack/compute/v2/extensions/floatingips/testing/doc.go b/openstack/compute/v2/extensions/floatingips/testing/doc.go deleted file mode 100644 index 82dfbe7fe..000000000 --- a/openstack/compute/v2/extensions/floatingips/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// floatingips unit tests -package testing diff --git a/openstack/compute/v2/extensions/floatingips/testing/requests_test.go b/openstack/compute/v2/extensions/floatingips/testing/requests_test.go index 09057a883..5575ccedf 100644 --- a/openstack/compute/v2/extensions/floatingips/testing/requests_test.go +++ b/openstack/compute/v2/extensions/floatingips/testing/requests_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/floatingips" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) @@ -14,17 +13,9 @@ func TestList(t *testing.T) { defer th.TeardownHTTP() HandleListSuccessfully(t) - count := 0 - err := floatingips.List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { - count++ - actual, err := floatingips.ExtractFloatingIPs(page) - th.AssertNoErr(t, err) - th.CheckDeepEquals(t, ExpectedFloatingIPsSlice, actual) - - return true, nil - }) + actual, err := floatingips.List(client.ServiceClient()) th.AssertNoErr(t, err) - th.CheckEquals(t, 1, count) + th.CheckDeepEquals(t, ExpectedFloatingIPsSlice, actual) } func TestCreate(t *testing.T) { @@ -34,7 +25,7 @@ func TestCreate(t *testing.T) { actual, err := floatingips.Create(client.ServiceClient(), floatingips.CreateOpts{ Pool: "nova", - }).Extract() + }) th.AssertNoErr(t, err) th.CheckDeepEquals(t, &CreatedFloatingIP, actual) } @@ -46,7 +37,7 @@ func TestCreateWithNumericID(t *testing.T) { actual, err := floatingips.Create(client.ServiceClient(), floatingips.CreateOpts{ Pool: "nova", - }).Extract() + }) th.AssertNoErr(t, err) th.CheckDeepEquals(t, &CreatedFloatingIP, actual) } @@ -56,7 +47,7 @@ func TestGet(t *testing.T) { defer th.TeardownHTTP() HandleGetSuccessfully(t) - actual, err := floatingips.Get(client.ServiceClient(), "2").Extract() + actual, err := floatingips.Get(client.ServiceClient(), "2") th.AssertNoErr(t, err) th.CheckDeepEquals(t, &SecondFloatingIP, actual) } @@ -66,7 +57,7 @@ func TestDelete(t *testing.T) { defer th.TeardownHTTP() HandleDeleteSuccessfully(t) - err := floatingips.Delete(client.ServiceClient(), "1").ExtractErr() + err := floatingips.Delete(client.ServiceClient(), "1") th.AssertNoErr(t, err) } @@ -79,7 +70,7 @@ func TestAssociate(t *testing.T) { FloatingIP: "10.10.10.2", } - err := floatingips.AssociateInstance(client.ServiceClient(), "4d8c3732-a248-40ed-bebc-539a6ffd25c0", associateOpts).ExtractErr() + err := floatingips.AssociateInstance(client.ServiceClient(), "4d8c3732-a248-40ed-bebc-539a6ffd25c0", associateOpts) th.AssertNoErr(t, err) } @@ -93,7 +84,7 @@ func TestAssociateFixed(t *testing.T) { FixedIP: "166.78.185.201", } - err := floatingips.AssociateInstance(client.ServiceClient(), "4d8c3732-a248-40ed-bebc-539a6ffd25c0", associateOpts).ExtractErr() + err := floatingips.AssociateInstance(client.ServiceClient(), "4d8c3732-a248-40ed-bebc-539a6ffd25c0", associateOpts) th.AssertNoErr(t, err) } @@ -106,6 +97,6 @@ func TestDisassociateInstance(t *testing.T) { FloatingIP: "10.10.10.2", } - err := floatingips.DisassociateInstance(client.ServiceClient(), "4d8c3732-a248-40ed-bebc-539a6ffd25c0", disassociateOpts).ExtractErr() + err := floatingips.DisassociateInstance(client.ServiceClient(), "4d8c3732-a248-40ed-bebc-539a6ffd25c0", disassociateOpts) th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/floatingips/urls.go b/openstack/compute/v2/extensions/floatingips/urls.go deleted file mode 100644 index e20a88c5f..000000000 --- a/openstack/compute/v2/extensions/floatingips/urls.go +++ /dev/null @@ -1,37 +0,0 @@ -package floatingips - -import "github.com/opentelekomcloud/gophertelekomcloud" - -const resourcePath = "os-floating-ips" - -func resourceURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL(resourcePath) -} - -func listURL(c *golangsdk.ServiceClient) string { - return resourceURL(c) -} - -func createURL(c *golangsdk.ServiceClient) string { - return resourceURL(c) -} - -func getURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL(resourcePath, id) -} - -func deleteURL(c *golangsdk.ServiceClient, id string) string { - return getURL(c, id) -} - -func serverURL(c *golangsdk.ServiceClient, serverID string) string { - return c.ServiceURL("servers/" + serverID + "/action") -} - -func associateURL(c *golangsdk.ServiceClient, serverID string) string { - return serverURL(c, serverID) -} - -func disassociateURL(c *golangsdk.ServiceClient, serverID string) string { - return serverURL(c, serverID) -} diff --git a/openstack/compute/v2/extensions/hypervisors/get.go b/openstack/compute/v2/extensions/hypervisors/get.go new file mode 100644 index 000000000..95bc7201e --- /dev/null +++ b/openstack/compute/v2/extensions/hypervisors/get.go @@ -0,0 +1,94 @@ +package hypervisors + +import ( + "strconv" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// Get makes a request against the API to get details for specific hypervisor. +func Get(client *golangsdk.ServiceClient, hypervisorID int) (*Hypervisor, error) { + raw, err := client.Get(client.ServiceURL("os-hypervisors", strconv.Itoa(hypervisorID)), nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + if err != nil { + return nil, err + } + + var res Hypervisor + err = extract.IntoStructPtr(raw.Body, &res, "hypervisor") + return &res, err +} + +// Hypervisor represents a hypervisor in the OpenStack cloud. +type Hypervisor struct { + // A structure that contains cpu information like arch, model, vendor, + // features and topology. + CPUInfo CPUInfo `json:"-"` + // The current_workload is the number of tasks the hypervisor is responsible + // for. This will be equal or greater than the number of active VMs on the + // system (it can be greater when VMs are being deleted and the hypervisor is + // still cleaning up). + CurrentWorkload int `json:"current_workload"` + // Status of the hypervisor, either "enabled" or "disabled". + Status string `json:"status"` + // State of the hypervisor, either "up" or "down". + State string `json:"state"` + // DiskAvailableLeast is the actual free disk on this hypervisor, + // measured in GB. + DiskAvailableLeast int `json:"disk_available_least"` + // HostIP is the hypervisor's IP address. + HostIP string `json:"host_ip"` + // FreeDiskGB is the free disk remaining on the hypervisor, measured in GB. + FreeDiskGB int `json:"-"` + // FreeRAMMB is the free RAM in the hypervisor, measured in MB. + FreeRamMB int `json:"free_ram_mb"` + // HypervisorHostname is the hostname of the hypervisor. + HypervisorHostname string `json:"hypervisor_hostname"` + // HypervisorType is the type of hypervisor. + HypervisorType string `json:"hypervisor_type"` + // HypervisorVersion is the version of the hypervisor. + HypervisorVersion int `json:"-"` + // ID is the unique ID of the hypervisor. + ID int `json:"id"` + // LocalGB is the disk space in the hypervisor, measured in GB. + LocalGB int `json:"-"` + // LocalGBUsed is the used disk space of the hypervisor, measured in GB. + LocalGBUsed int `json:"local_gb_used"` + // MemoryMB is the total memory of the hypervisor, measured in MB. + MemoryMB int `json:"memory_mb"` + // MemoryMBUsed is the used memory of the hypervisor, measured in MB. + MemoryMBUsed int `json:"memory_mb_used"` + // RunningVMs is the number of running vms on the hypervisor. + RunningVMs int `json:"running_vms"` + // Service is the service this hypervisor represents. + Service Service `json:"service"` + // VCPUs is the total number of vcpus on the hypervisor. + VCPUs int `json:"vcpus"` + // VCPUsUsed is the number of used vcpus on the hypervisor. + VCPUsUsed int `json:"vcpus_used"` +} + +// Topology represents a CPU Topology. +type Topology struct { + Sockets int `json:"sockets"` + Cores int `json:"cores"` + Threads int `json:"threads"` +} + +// CPUInfo represents CPU information of the hypervisor. +type CPUInfo struct { + Vendor string `json:"vendor"` + Arch string `json:"arch"` + Model string `json:"model"` + Features []string `json:"features"` + Topology Topology `json:"topology"` +} + +// Service represents a Compute service running on the hypervisor. +type Service struct { + Host string `json:"host"` + ID int `json:"id"` + DisabledReason string `json:"disabled_reason"` +} diff --git a/openstack/compute/v2/extensions/hypervisors/get_statistics.go b/openstack/compute/v2/extensions/hypervisors/get_statistics.go new file mode 100644 index 000000000..34735d552 --- /dev/null +++ b/openstack/compute/v2/extensions/hypervisors/get_statistics.go @@ -0,0 +1,49 @@ +package hypervisors + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// GetStatistics makes a request against the API to get hypervisors statistics. +func GetStatistics(client *golangsdk.ServiceClient) (*Statistics, error) { + raw, err := client.Get(client.ServiceURL("os-hypervisors", "statistics"), nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + if err != nil { + return nil, err + } + + var res Statistics + err = extract.IntoStructPtr(raw.Body, &res, "hypervisor_statistics") + return &res, err +} + +// Statistics represents a summary statistics for all enabled +// hypervisors over all compute nodes in the OpenStack cloud. +type Statistics struct { + // The number of hypervisors. + Count int `json:"count"` + // The current_workload is the number of tasks the hypervisor is responsible for + CurrentWorkload int `json:"current_workload"` + // The actual free disk on this hypervisor(in GB). + DiskAvailableLeast int `json:"disk_available_least"` + // The free disk remaining on this hypervisor(in GB). + FreeDiskGB int `json:"free_disk_gb"` + // The free RAM in this hypervisor(in MB). + FreeRamMB int `json:"free_ram_mb"` + // The disk in this hypervisor(in GB). + LocalGB int `json:"local_gb"` + // The disk used in this hypervisor(in GB). + LocalGBUsed int `json:"local_gb_used"` + // The memory of this hypervisor(in MB). + MemoryMB int `json:"memory_mb"` + // The memory used in this hypervisor(in MB). + MemoryMBUsed int `json:"memory_mb_used"` + // The total number of running vms on all hypervisors. + RunningVMs int `json:"running_vms"` + // The number of vcpu in this hypervisor. + VCPUs int `json:"vcpus"` + // The number of vcpu used in this hypervisor. + VCPUsUsed int `json:"vcpus_used"` +} diff --git a/openstack/compute/v2/extensions/hypervisors/get_uptime.go b/openstack/compute/v2/extensions/hypervisors/get_uptime.go new file mode 100644 index 000000000..791b8e253 --- /dev/null +++ b/openstack/compute/v2/extensions/hypervisors/get_uptime.go @@ -0,0 +1,37 @@ +package hypervisors + +import ( + "strconv" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// GetUptime makes a request against the API to get uptime for specific hypervisor. +func GetUptime(client *golangsdk.ServiceClient, hypervisorID int) (*Uptime, error) { + raw, err := client.Get(client.ServiceURL("os-hypervisors", strconv.Itoa(hypervisorID), "uptime"), nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + if err != nil { + return nil, err + } + + var res Uptime + err = extract.IntoStructPtr(raw.Body, &res, "hypervisor") + return &res, err +} + +// Uptime represents uptime and additional info for a specific hypervisor. +type Uptime struct { + // The hypervisor host name provided by the Nova virt driver. + // For the Ironic driver, it is the Ironic node uuid. + HypervisorHostname string `json:"hypervisor_hostname"` + // The id of the hypervisor. + ID int `json:"id"` + // The state of the hypervisor. One of up or down. + State string `json:"state"` + // The status of the hypervisor. One of enabled or disabled. + Status string `json:"status"` + // The total uptime of the hypervisor and information about average load. + Uptime string `json:"uptime"` +} diff --git a/openstack/compute/v2/extensions/hypervisors/list.go b/openstack/compute/v2/extensions/hypervisors/list.go new file mode 100644 index 000000000..ac38bdd21 --- /dev/null +++ b/openstack/compute/v2/extensions/hypervisors/list.go @@ -0,0 +1,94 @@ +package hypervisors + +import ( + "encoding/json" + "fmt" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// List makes a request against the API to list hypervisors. +func List(client *golangsdk.ServiceClient) ([]Hypervisor, error) { + raw, err := client.Get(client.ServiceURL("os-hypervisors", "detail"), nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + if err != nil { + return nil, err + } + + var res []Hypervisor + err = extract.IntoSlicePtr(raw.Body, &res, "hypervisors") + return res, err +} + +func (r *Hypervisor) UnmarshalJSON(b []byte) error { + type tmp Hypervisor + var s struct { + tmp + CPUInfo interface{} `json:"cpu_info"` + HypervisorVersion interface{} `json:"hypervisor_version"` + FreeDiskGB interface{} `json:"free_disk_gb"` + LocalGB interface{} `json:"local_gb"` + } + + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Hypervisor(s.tmp) + + // Newer versions return the CPU info as the correct type. + // Older versions return the CPU info as a string and need to be + // unmarshalled by the json parser. + var tmpb []byte + + switch t := s.CPUInfo.(type) { + case string: + tmpb = []byte(t) + case map[string]interface{}: + tmpb, err = json.Marshal(t) + if err != nil { + return err + } + default: + return fmt.Errorf("CPUInfo has unexpected type: %T", t) + } + + err = json.Unmarshal(tmpb, &r.CPUInfo) + if err != nil { + return err + } + + // These fields may be returned as a scientific notation, so they need + // converted to int. + switch t := s.HypervisorVersion.(type) { + case int: + r.HypervisorVersion = t + case float64: + r.HypervisorVersion = int(t) + default: + return fmt.Errorf("Hypervisor version of unexpected type") + } + + switch t := s.FreeDiskGB.(type) { + case int: + r.FreeDiskGB = t + case float64: + r.FreeDiskGB = int(t) + default: + return fmt.Errorf("Free disk GB of unexpected type") + } + + switch t := s.LocalGB.(type) { + case int: + r.LocalGB = t + case float64: + r.LocalGB = int(t) + default: + return fmt.Errorf("Local GB of unexpected type") + } + + return nil +} diff --git a/openstack/compute/v2/extensions/hypervisors/requests.go b/openstack/compute/v2/extensions/hypervisors/requests.go deleted file mode 100644 index 712cf7cc5..000000000 --- a/openstack/compute/v2/extensions/hypervisors/requests.go +++ /dev/null @@ -1,41 +0,0 @@ -package hypervisors - -import ( - "strconv" - - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// List makes a request against the API to list hypervisors. -func List(client *golangsdk.ServiceClient) pagination.Pager { - return pagination.NewPager(client, hypervisorsListDetailURL(client), func(r pagination.PageResult) pagination.Page { - return HypervisorPage{pagination.SinglePageBase(r)} - }) -} - -// Statistics makes a request against the API to get hypervisors statistics. -func GetStatistics(client *golangsdk.ServiceClient) (r StatisticsResult) { - _, r.Err = client.Get(hypervisorsStatisticsURL(client), &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Get makes a request against the API to get details for specific hypervisor. -func Get(client *golangsdk.ServiceClient, hypervisorID int) (r HypervisorResult) { - v := strconv.Itoa(hypervisorID) - _, r.Err = client.Get(hypervisorsGetURL(client, v), &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// GetUptime makes a request against the API to get uptime for specific hypervisor. -func GetUptime(client *golangsdk.ServiceClient, hypervisorID int) (r UptimeResult) { - v := strconv.Itoa(hypervisorID) - _, r.Err = client.Get(hypervisorsUptimeURL(client, v), &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} diff --git a/openstack/compute/v2/extensions/hypervisors/results.go b/openstack/compute/v2/extensions/hypervisors/results.go deleted file mode 100644 index 536502e4f..000000000 --- a/openstack/compute/v2/extensions/hypervisors/results.go +++ /dev/null @@ -1,290 +0,0 @@ -package hypervisors - -import ( - "encoding/json" - "fmt" - - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// Topology represents a CPU Topology. -type Topology struct { - Sockets int `json:"sockets"` - Cores int `json:"cores"` - Threads int `json:"threads"` -} - -// CPUInfo represents CPU information of the hypervisor. -type CPUInfo struct { - Vendor string `json:"vendor"` - Arch string `json:"arch"` - Model string `json:"model"` - Features []string `json:"features"` - Topology Topology `json:"topology"` -} - -// Service represents a Compute service running on the hypervisor. -type Service struct { - Host string `json:"host"` - ID int `json:"id"` - DisabledReason string `json:"disabled_reason"` -} - -// Hypervisor represents a hypervisor in the OpenStack cloud. -type Hypervisor struct { - // A structure that contains cpu information like arch, model, vendor, - // features and topology. - CPUInfo CPUInfo `json:"-"` - - // The current_workload is the number of tasks the hypervisor is responsible - // for. This will be equal or greater than the number of active VMs on the - // system (it can be greater when VMs are being deleted and the hypervisor is - // still cleaning up). - CurrentWorkload int `json:"current_workload"` - - // Status of the hypervisor, either "enabled" or "disabled". - Status string `json:"status"` - - // State of the hypervisor, either "up" or "down". - State string `json:"state"` - - // DiskAvailableLeast is the actual free disk on this hypervisor, - // measured in GB. - DiskAvailableLeast int `json:"disk_available_least"` - - // HostIP is the hypervisor's IP address. - HostIP string `json:"host_ip"` - - // FreeDiskGB is the free disk remaining on the hypervisor, measured in GB. - FreeDiskGB int `json:"-"` - - // FreeRAMMB is the free RAM in the hypervisor, measured in MB. - FreeRamMB int `json:"free_ram_mb"` - - // HypervisorHostname is the hostname of the hypervisor. - HypervisorHostname string `json:"hypervisor_hostname"` - - // HypervisorType is the type of hypervisor. - HypervisorType string `json:"hypervisor_type"` - - // HypervisorVersion is the version of the hypervisor. - HypervisorVersion int `json:"-"` - - // ID is the unique ID of the hypervisor. - ID int `json:"id"` - - // LocalGB is the disk space in the hypervisor, measured in GB. - LocalGB int `json:"-"` - - // LocalGBUsed is the used disk space of the hypervisor, measured in GB. - LocalGBUsed int `json:"local_gb_used"` - - // MemoryMB is the total memory of the hypervisor, measured in MB. - MemoryMB int `json:"memory_mb"` - - // MemoryMBUsed is the used memory of the hypervisor, measured in MB. - MemoryMBUsed int `json:"memory_mb_used"` - - // RunningVMs is the The number of running vms on the hypervisor. - RunningVMs int `json:"running_vms"` - - // Service is the service this hypervisor represents. - Service Service `json:"service"` - - // VCPUs is the total number of vcpus on the hypervisor. - VCPUs int `json:"vcpus"` - - // VCPUsUsed is the number of used vcpus on the hypervisor. - VCPUsUsed int `json:"vcpus_used"` -} - -func (r *Hypervisor) UnmarshalJSON(b []byte) error { - type tmp Hypervisor - var s struct { - tmp - CPUInfo interface{} `json:"cpu_info"` - HypervisorVersion interface{} `json:"hypervisor_version"` - FreeDiskGB interface{} `json:"free_disk_gb"` - LocalGB interface{} `json:"local_gb"` - } - - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - - *r = Hypervisor(s.tmp) - - // Newer versions return the CPU info as the correct type. - // Older versions return the CPU info as a string and need to be - // unmarshalled by the json parser. - var tmpb []byte - - switch t := s.CPUInfo.(type) { - case string: - tmpb = []byte(t) - case map[string]interface{}: - tmpb, err = json.Marshal(t) - if err != nil { - return err - } - default: - return fmt.Errorf("CPUInfo has unexpected type: %T", t) - } - - err = json.Unmarshal(tmpb, &r.CPUInfo) - if err != nil { - return err - } - - // These fields may be returned as a scientific notation, so they need - // converted to int. - switch t := s.HypervisorVersion.(type) { - case int: - r.HypervisorVersion = t - case float64: - r.HypervisorVersion = int(t) - default: - return fmt.Errorf("Hypervisor version of unexpected type") - } - - switch t := s.FreeDiskGB.(type) { - case int: - r.FreeDiskGB = t - case float64: - r.FreeDiskGB = int(t) - default: - return fmt.Errorf("Free disk GB of unexpected type") - } - - switch t := s.LocalGB.(type) { - case int: - r.LocalGB = t - case float64: - r.LocalGB = int(t) - default: - return fmt.Errorf("Local GB of unexpected type") - } - - return nil -} - -// HypervisorPage represents a single page of all Hypervisors from a List -// request. -type HypervisorPage struct { - pagination.SinglePageBase -} - -// IsEmpty determines whether or not a HypervisorPage is empty. -func (page HypervisorPage) IsEmpty() (bool, error) { - va, err := ExtractHypervisors(page) - return len(va) == 0, err -} - -// ExtractHypervisors interprets a page of results as a slice of Hypervisors. -func ExtractHypervisors(p pagination.Page) ([]Hypervisor, error) { - var h struct { - Hypervisors []Hypervisor `json:"hypervisors"` - } - err := (p.(HypervisorPage)).ExtractInto(&h) - return h.Hypervisors, err -} - -type HypervisorResult struct { - golangsdk.Result -} - -// Extract interprets any HypervisorResult as a Hypervisor, if possible. -func (r HypervisorResult) Extract() (*Hypervisor, error) { - var s struct { - Hypervisor Hypervisor `json:"hypervisor"` - } - err := r.ExtractInto(&s) - return &s.Hypervisor, err -} - -// Statistics represents a summary statistics for all enabled -// hypervisors over all compute nodes in the OpenStack cloud. -type Statistics struct { - // The number of hypervisors. - Count int `json:"count"` - - // The current_workload is the number of tasks the hypervisor is responsible for - CurrentWorkload int `json:"current_workload"` - - // The actual free disk on this hypervisor(in GB). - DiskAvailableLeast int `json:"disk_available_least"` - - // The free disk remaining on this hypervisor(in GB). - FreeDiskGB int `json:"free_disk_gb"` - - // The free RAM in this hypervisor(in MB). - FreeRamMB int `json:"free_ram_mb"` - - // The disk in this hypervisor(in GB). - LocalGB int `json:"local_gb"` - - // The disk used in this hypervisor(in GB). - LocalGBUsed int `json:"local_gb_used"` - - // The memory of this hypervisor(in MB). - MemoryMB int `json:"memory_mb"` - - // The memory used in this hypervisor(in MB). - MemoryMBUsed int `json:"memory_mb_used"` - - // The total number of running vms on all hypervisors. - RunningVMs int `json:"running_vms"` - - // The number of vcpu in this hypervisor. - VCPUs int `json:"vcpus"` - - // The number of vcpu used in this hypervisor. - VCPUsUsed int `json:"vcpus_used"` -} - -type StatisticsResult struct { - golangsdk.Result -} - -// Extract interprets any StatisticsResult as a Statistics, if possible. -func (r StatisticsResult) Extract() (*Statistics, error) { - var s struct { - Stats Statistics `json:"hypervisor_statistics"` - } - err := r.ExtractInto(&s) - return &s.Stats, err -} - -// Uptime represents uptime and additional info for a specific hypervisor. -type Uptime struct { - // The hypervisor host name provided by the Nova virt driver. - // For the Ironic driver, it is the Ironic node uuid. - HypervisorHostname string `json:"hypervisor_hostname"` - - // The id of the hypervisor. - ID int `json:"id"` - - // The state of the hypervisor. One of up or down. - State string `json:"state"` - - // The status of the hypervisor. One of enabled or disabled. - Status string `json:"status"` - - // The total uptime of the hypervisor and information about average load. - Uptime string `json:"uptime"` -} - -type UptimeResult struct { - golangsdk.Result -} - -// Extract interprets any UptimeResult as a Uptime, if possible. -func (r UptimeResult) Extract() (*Uptime, error) { - var s struct { - Uptime Uptime `json:"hypervisor"` - } - err := r.ExtractInto(&s) - return &s.Uptime, err -} diff --git a/openstack/compute/v2/extensions/hypervisors/testing/requests_test.go b/openstack/compute/v2/extensions/hypervisors/testing/requests_test.go index 0515d16d9..66098d751 100644 --- a/openstack/compute/v2/extensions/hypervisors/testing/requests_test.go +++ b/openstack/compute/v2/extensions/hypervisors/testing/requests_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/hypervisors" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) @@ -14,29 +13,14 @@ func TestListHypervisors(t *testing.T) { defer testhelper.TeardownHTTP() HandleHypervisorListSuccessfully(t) - pages := 0 - err := hypervisors.List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { - pages++ - - actual, err := hypervisors.ExtractHypervisors(page) - if err != nil { - return false, err - } - - if len(actual) != 2 { - t.Fatalf("Expected 2 hypervisors, got %d", len(actual)) - } - testhelper.CheckDeepEquals(t, HypervisorFake, actual[0]) - testhelper.CheckDeepEquals(t, HypervisorFake, actual[1]) - - return true, nil - }) - + actual, err := hypervisors.List(client.ServiceClient()) testhelper.AssertNoErr(t, err) - if pages != 1 { - t.Errorf("Expected 1 page, saw %d", pages) + if len(actual) != 2 { + t.Fatalf("Expected 2 hypervisors, got %d", len(actual)) } + testhelper.CheckDeepEquals(t, HypervisorFake, actual[0]) + testhelper.CheckDeepEquals(t, HypervisorFake, actual[1]) } func TestListAllHypervisors(t *testing.T) { @@ -44,9 +28,7 @@ func TestListAllHypervisors(t *testing.T) { defer testhelper.TeardownHTTP() HandleHypervisorListSuccessfully(t) - allPages, err := hypervisors.List(client.ServiceClient()).AllPages() - testhelper.AssertNoErr(t, err) - actual, err := hypervisors.ExtractHypervisors(allPages) + actual, err := hypervisors.List(client.ServiceClient()) testhelper.AssertNoErr(t, err) testhelper.CheckDeepEquals(t, HypervisorFake, actual[0]) testhelper.CheckDeepEquals(t, HypervisorFake, actual[1]) @@ -59,7 +41,7 @@ func TestHypervisorsStatistics(t *testing.T) { expected := HypervisorsStatisticsExpected - actual, err := hypervisors.GetStatistics(client.ServiceClient()).Extract() + actual, err := hypervisors.GetStatistics(client.ServiceClient()) testhelper.AssertNoErr(t, err) testhelper.CheckDeepEquals(t, &expected, actual) } @@ -70,8 +52,7 @@ func TestGetHypervisor(t *testing.T) { HandleHypervisorGetSuccessfully(t) expected := HypervisorFake - - actual, err := hypervisors.Get(client.ServiceClient(), expected.ID).Extract() + actual, err := hypervisors.Get(client.ServiceClient(), expected.ID) testhelper.AssertNoErr(t, err) testhelper.CheckDeepEquals(t, &expected, actual) } @@ -83,7 +64,7 @@ func TestHypervisorsUptime(t *testing.T) { expected := HypervisorUptimeExpected - actual, err := hypervisors.GetUptime(client.ServiceClient(), HypervisorFake.ID).Extract() + actual, err := hypervisors.GetUptime(client.ServiceClient(), HypervisorFake.ID) testhelper.AssertNoErr(t, err) testhelper.CheckDeepEquals(t, &expected, actual) } diff --git a/openstack/compute/v2/extensions/hypervisors/urls.go b/openstack/compute/v2/extensions/hypervisors/urls.go deleted file mode 100644 index c95314372..000000000 --- a/openstack/compute/v2/extensions/hypervisors/urls.go +++ /dev/null @@ -1,19 +0,0 @@ -package hypervisors - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func hypervisorsListDetailURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("os-hypervisors", "detail") -} - -func hypervisorsStatisticsURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("os-hypervisors", "statistics") -} - -func hypervisorsGetURL(c *golangsdk.ServiceClient, hypervisorID string) string { - return c.ServiceURL("os-hypervisors", hypervisorID) -} - -func hypervisorsUptimeURL(c *golangsdk.ServiceClient, hypervisorID string) string { - return c.ServiceURL("os-hypervisors", hypervisorID, "uptime") -} diff --git a/openstack/compute/v2/extensions/keypairs/create.go b/openstack/compute/v2/extensions/keypairs/create.go new file mode 100644 index 000000000..cc66fe9fb --- /dev/null +++ b/openstack/compute/v2/extensions/keypairs/create.go @@ -0,0 +1,53 @@ +package keypairs + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/servers" +) + +// CreateOptsExt adds a KeyPair option to the base CreateOpts. +type CreateOptsExt struct { + servers.CreateOpts + // KeyName is the name of the key pair. + KeyName string `json:"key_name,omitempty"` +} + +// ToServerCreateMap adds the key_name to the base server creation options. +func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) { + base, err := opts.CreateOpts.ToServerCreateMap() + if err != nil { + return nil, err + } + + if opts.KeyName == "" { + return base, nil + } + + serverMap := base["server"].(map[string]interface{}) + serverMap["key_name"] = opts.KeyName + + return base, nil +} + +// CreateOpts specifies KeyPair creation or import parameters. +type CreateOpts struct { + // Name is a friendly name to refer to this KeyPair in other services. + Name string `json:"name" required:"true"` + // PublicKey [optional] is a pregenerated OpenSSH-formatted public key. + // If provided, this key will be imported and no new key will be created. + PublicKey string `json:"public_key,omitempty"` +} + +// Create requests the creation of a new KeyPair on the server, or to import a pre-existing keypair. +func Create(client *golangsdk.ServiceClient, opts CreateOpts) (*KeyPair, error) { + b, err := build.RequestBody(opts, "keypair") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("os-keypairs"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/keypairs/delete.go b/openstack/compute/v2/extensions/keypairs/delete.go new file mode 100644 index 000000000..6ab4a86b0 --- /dev/null +++ b/openstack/compute/v2/extensions/keypairs/delete.go @@ -0,0 +1,9 @@ +package keypairs + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Delete requests the deletion of a previous stored KeyPair from the server. +func Delete(client *golangsdk.ServiceClient, name string) (err error) { + _, err = client.Delete(client.ServiceURL("os-keypairs", name), nil) + return +} diff --git a/openstack/compute/v2/extensions/keypairs/get.go b/openstack/compute/v2/extensions/keypairs/get.go new file mode 100644 index 000000000..ebec85e11 --- /dev/null +++ b/openstack/compute/v2/extensions/keypairs/get.go @@ -0,0 +1,9 @@ +package keypairs + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Get returns public data about a previously uploaded KeyPair. +func Get(client *golangsdk.ServiceClient, name string) (*KeyPair, error) { + raw, err := client.Get(client.ServiceURL("os-keypairs", name), nil, nil) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/keypairs/list.go b/openstack/compute/v2/extensions/keypairs/list.go new file mode 100644 index 000000000..fa0023fb5 --- /dev/null +++ b/openstack/compute/v2/extensions/keypairs/list.go @@ -0,0 +1,25 @@ +package keypairs + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// List returns a Pager that allows you to iterate over a collection of KeyPairs. +func List(client *golangsdk.ServiceClient) ([]KeyPair, error) { + raw, err := client.Get(client.ServiceURL("os-keypairs"), nil, nil) + if err != nil { + return nil, err + } + + var res []struct { + KeyPair KeyPair `json:"keypair"` + } + err = extract.IntoSlicePtr(raw.Body, &res, "keypairs") + + results := make([]KeyPair, len(res)) + for i, pair := range res { + results[i] = pair.KeyPair + } + return results, err +} diff --git a/openstack/compute/v2/extensions/keypairs/requests.go b/openstack/compute/v2/extensions/keypairs/requests.go deleted file mode 100644 index 9bad3ad36..000000000 --- a/openstack/compute/v2/extensions/keypairs/requests.go +++ /dev/null @@ -1,86 +0,0 @@ -package keypairs - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/servers" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// CreateOptsExt adds a KeyPair option to the base CreateOpts. -type CreateOptsExt struct { - servers.CreateOptsBuilder - - // KeyName is the name of the key pair. - KeyName string `json:"key_name,omitempty"` -} - -// ToServerCreateMap adds the key_name to the base server creation options. -func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) { - base, err := opts.CreateOptsBuilder.ToServerCreateMap() - if err != nil { - return nil, err - } - - if opts.KeyName == "" { - return base, nil - } - - serverMap := base["server"].(map[string]interface{}) - serverMap["key_name"] = opts.KeyName - - return base, nil -} - -// List returns a Pager that allows you to iterate over a collection of KeyPairs. -func List(client *golangsdk.ServiceClient) pagination.Pager { - return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { - return KeyPairPage{pagination.SinglePageBase(r)} - }) -} - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToKeyPairCreateMap() (map[string]interface{}, error) -} - -// CreateOpts specifies KeyPair creation or import parameters. -type CreateOpts struct { - // Name is a friendly name to refer to this KeyPair in other services. - Name string `json:"name" required:"true"` - - // PublicKey [optional] is a pregenerated OpenSSH-formatted public key. - // If provided, this key will be imported and no new key will be created. - PublicKey string `json:"public_key,omitempty"` -} - -// ToKeyPairCreateMap constructs a request body from CreateOpts. -func (opts CreateOpts) ToKeyPairCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "keypair") -} - -// Create requests the creation of a new KeyPair on the server, or to import a -// pre-existing keypair. -func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToKeyPairCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Get returns public data about a previously uploaded KeyPair. -func Get(client *golangsdk.ServiceClient, name string) (r GetResult) { - _, r.Err = client.Get(getURL(client, name), &r.Body, nil) - return -} - -// Delete requests the deletion of a previous stored KeyPair from the server. -func Delete(client *golangsdk.ServiceClient, name string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, name), nil) - return -} diff --git a/openstack/compute/v2/extensions/keypairs/results.go b/openstack/compute/v2/extensions/keypairs/results.go index 33b633b94..b8f675ea9 100644 --- a/openstack/compute/v2/extensions/keypairs/results.go +++ b/openstack/compute/v2/extensions/keypairs/results.go @@ -1,91 +1,36 @@ package keypairs import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" + "net/http" + + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" ) -// KeyPair is an SSH key known to the OpenStack Cloud that is available to be -// injected into servers. +// KeyPair is an SSH key known to the OpenStack Cloud that is available to be injected into servers. type KeyPair struct { // Name is used to refer to this keypair from other services within this // region. Name string `json:"name"` - // Fingerprint is a short sequence of bytes that can be used to authenticate // or validate a longer public key. Fingerprint string `json:"fingerprint"` - // PublicKey is the public key from this pair, in OpenSSH format. // "ssh-rsa AAAAB3Nz..." PublicKey string `json:"public_key"` - // PrivateKey is the private key from this pair, in PEM format. // "-----BEGIN RSA PRIVATE KEY-----\nMIICXA..." // It is only present if this KeyPair was just returned from a Create call. PrivateKey string `json:"private_key"` - // UserID is the user who owns this KeyPair. UserID string `json:"user_id"` } -// KeyPairPage stores a single page of all KeyPair results from a List call. -// Use the ExtractKeyPairs function to convert the results to a slice of -// KeyPairs. -type KeyPairPage struct { - pagination.SinglePageBase -} - -// IsEmpty determines whether or not a KeyPairPage is empty. -func (page KeyPairPage) IsEmpty() (bool, error) { - ks, err := ExtractKeyPairs(page) - return len(ks) == 0, err -} - -// ExtractKeyPairs interprets a page of results as a slice of KeyPairs. -func ExtractKeyPairs(r pagination.Page) ([]KeyPair, error) { - type pair struct { - KeyPair KeyPair `json:"keypair"` - } - var s struct { - KeyPairs []pair `json:"keypairs"` - } - err := (r.(KeyPairPage)).ExtractInto(&s) - results := make([]KeyPair, len(s.KeyPairs)) - for i, pair := range s.KeyPairs { - results[i] = pair.KeyPair +func extra(err error, raw *http.Response) (*KeyPair, error) { + if err != nil { + return nil, err } - return results, err -} - -type keyPairResult struct { - golangsdk.Result -} - -// Extract is a method that attempts to interpret any KeyPair resource response -// as a KeyPair struct. -func (r keyPairResult) Extract() (*KeyPair, error) { - var s struct { - KeyPair *KeyPair `json:"keypair"` - } - err := r.ExtractInto(&s) - return s.KeyPair, err -} - -// CreateResult is the response from a Create operation. Call its Extract method -// to interpret it as a KeyPair. -type CreateResult struct { - keyPairResult -} - -// GetResult is the response from a Get operation. Call its Extract method to -// interpret it as a KeyPair. -type GetResult struct { - keyPairResult -} -// DeleteResult is the response from a Delete operation. Call its ExtractErr -// method to determine if the call succeeded or failed. -type DeleteResult struct { - golangsdk.ErrResult + var res KeyPair + err = extract.IntoStructPtr(raw.Body, &res, "keypair") + return &res, err } diff --git a/openstack/compute/v2/extensions/keypairs/testing/doc.go b/openstack/compute/v2/extensions/keypairs/testing/doc.go deleted file mode 100644 index 8d4200983..000000000 --- a/openstack/compute/v2/extensions/keypairs/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// keypairs unit tests -package testing diff --git a/openstack/compute/v2/extensions/keypairs/testing/requests_test.go b/openstack/compute/v2/extensions/keypairs/testing/requests_test.go index 635dcd27a..a48715802 100644 --- a/openstack/compute/v2/extensions/keypairs/testing/requests_test.go +++ b/openstack/compute/v2/extensions/keypairs/testing/requests_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/keypairs" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) @@ -14,17 +13,9 @@ func TestList(t *testing.T) { defer th.TeardownHTTP() HandleListSuccessfully(t) - count := 0 - err := keypairs.List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { - count++ - actual, err := keypairs.ExtractKeyPairs(page) - th.AssertNoErr(t, err) - th.CheckDeepEquals(t, ExpectedKeyPairSlice, actual) - - return true, nil - }) + actual, err := keypairs.List(client.ServiceClient()) th.AssertNoErr(t, err) - th.CheckEquals(t, 1, count) + th.CheckDeepEquals(t, ExpectedKeyPairSlice, actual) } func TestCreate(t *testing.T) { @@ -34,7 +25,7 @@ func TestCreate(t *testing.T) { actual, err := keypairs.Create(client.ServiceClient(), keypairs.CreateOpts{ Name: "createdkey", - }).Extract() + }) th.AssertNoErr(t, err) th.CheckDeepEquals(t, &CreatedKeyPair, actual) } @@ -47,7 +38,7 @@ func TestImport(t *testing.T) { actual, err := keypairs.Create(client.ServiceClient(), keypairs.CreateOpts{ Name: "importedkey", PublicKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDx8nkQv/zgGgB4rMYmIf+6A4l6Rr+o/6lHBQdW5aYd44bd8JttDCE/F/pNRr0lRE+PiqSPO8nDPHw0010JeMH9gYgnnFlyY3/OcJ02RhIPyyxYpv9FhY+2YiUkpwFOcLImyrxEsYXpD/0d3ac30bNH6Sw9JD9UZHYcpSxsIbECHw== Generated by Nova", - }).Extract() + }) th.AssertNoErr(t, err) th.CheckDeepEquals(t, &ImportedKeyPair, actual) } @@ -57,7 +48,7 @@ func TestGet(t *testing.T) { defer th.TeardownHTTP() HandleGetSuccessfully(t) - actual, err := keypairs.Get(client.ServiceClient(), "firstkey").Extract() + actual, err := keypairs.Get(client.ServiceClient(), "firstkey") th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstKeyPair, actual) } @@ -67,6 +58,6 @@ func TestDelete(t *testing.T) { defer th.TeardownHTTP() HandleDeleteSuccessfully(t) - err := keypairs.Delete(client.ServiceClient(), "deletedkey").ExtractErr() + err := keypairs.Delete(client.ServiceClient(), "deletedkey") th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/keypairs/urls.go b/openstack/compute/v2/extensions/keypairs/urls.go deleted file mode 100644 index faedfe663..000000000 --- a/openstack/compute/v2/extensions/keypairs/urls.go +++ /dev/null @@ -1,25 +0,0 @@ -package keypairs - -import "github.com/opentelekomcloud/gophertelekomcloud" - -const resourcePath = "os-keypairs" - -func resourceURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL(resourcePath) -} - -func listURL(c *golangsdk.ServiceClient) string { - return resourceURL(c) -} - -func createURL(c *golangsdk.ServiceClient) string { - return resourceURL(c) -} - -func getURL(c *golangsdk.ServiceClient, name string) string { - return c.ServiceURL(resourcePath, name) -} - -func deleteURL(c *golangsdk.ServiceClient, name string) string { - return getURL(c, name) -} diff --git a/openstack/compute/v2/extensions/limits/get.go b/openstack/compute/v2/extensions/limits/get.go new file mode 100644 index 000000000..2819d84de --- /dev/null +++ b/openstack/compute/v2/extensions/limits/get.go @@ -0,0 +1,29 @@ +package limits + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// GetOpts enables retrieving limits by a specific tenant. +type GetOpts struct { + // The tenant ID to retrieve limits for. + TenantID string `q:"tenant_id"` +} + +// Get returns the limits about the currently scoped tenant. +func Get(client *golangsdk.ServiceClient, opts GetOpts) (*Limits, error) { + query, err := golangsdk.BuildQueryString(opts) + if err != nil { + return nil, err + } + + raw, err := client.Get(client.ServiceURL("limits")+query.String(), nil, nil) + if err != nil { + return nil, err + } + + var res Limits + err = extract.IntoStructPtr(raw.Body, &res, "limits") + return &res, err +} diff --git a/openstack/compute/v2/extensions/limits/requests.go b/openstack/compute/v2/extensions/limits/requests.go deleted file mode 100644 index d5c68e5a3..000000000 --- a/openstack/compute/v2/extensions/limits/requests.go +++ /dev/null @@ -1,42 +0,0 @@ -package limits - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -// GetOptsBuilder allows extensions to add additional parameters to the -// Get request. -type GetOptsBuilder interface { - ToLimitsQuery() (string, error) -} - -// GetOpts enables retrieving limits by a specific tenant. -type GetOpts struct { - // The tenant ID to retrieve limits for. - TenantID string `q:"tenant_id"` -} - -// ToLimitsQuery formats a GetOpts into a query string. -func (opts GetOpts) ToLimitsQuery() (string, error) { - q, err := golangsdk.BuildQueryString(opts) - if err != nil { - return "", err - } - return q.String(), err -} - -// Get returns the limits about the currently scoped tenant. -func Get(client *golangsdk.ServiceClient, opts GetOptsBuilder) (r GetResult) { - url := getURL(client) - if opts != nil { - query, err := opts.ToLimitsQuery() - if err != nil { - r.Err = err - return - } - url += query - } - - _, r.Err = client.Get(url, &r.Body, nil) - return -} diff --git a/openstack/compute/v2/extensions/limits/results.go b/openstack/compute/v2/extensions/limits/results.go index 81ecb8fe2..430aece13 100644 --- a/openstack/compute/v2/extensions/limits/results.go +++ b/openstack/compute/v2/extensions/limits/results.go @@ -1,90 +1,50 @@ package limits -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - // Limits is a struct that contains the response of a limit query. type Limits struct { // Absolute contains the limits and usage information. Absolute Absolute `json:"absolute"` } -// Usage is a struct that contains the current resource usage and limits -// of a tenant. +// Absolute Usage is a struct that contains the current resource usage and limits of a tenant. type Absolute struct { // MaxTotalCores is the number of cores available to a tenant. MaxTotalCores int `json:"maxTotalCores"` - // MaxImageMeta is the amount of image metadata available to a tenant. MaxImageMeta int `json:"maxImageMeta"` - // MaxServerMeta is the amount of server metadata available to a tenant. MaxServerMeta int `json:"maxServerMeta"` - // MaxPersonality is the amount of personality/files available to a tenant. MaxPersonality int `json:"maxPersonality"` - // MaxPersonalitySize is the personality file size available to a tenant. MaxPersonalitySize int `json:"maxPersonalitySize"` - // MaxTotalKeypairs is the total keypairs available to a tenant. MaxTotalKeypairs int `json:"maxTotalKeypairs"` - // MaxSecurityGroups is the number of security groups available to a tenant. MaxSecurityGroups int `json:"maxSecurityGroups"` - - // MaxSecurityGroupRules is the number of security group rules available to - // a tenant. + // MaxSecurityGroupRules is the number of security group rules available to a tenant. MaxSecurityGroupRules int `json:"maxSecurityGroupRules"` - // MaxServerGroups is the number of server groups available to a tenant. MaxServerGroups int `json:"maxServerGroups"` - - // MaxServerGroupMembers is the number of server group members available - // to a tenant. + // MaxServerGroupMembers is the number of server group members available to a tenant. MaxServerGroupMembers int `json:"maxServerGroupMembers"` - // MaxTotalFloatingIps is the number of floating IPs available to a tenant. MaxTotalFloatingIps int `json:"maxTotalFloatingIps"` - // MaxTotalInstances is the number of instances/servers available to a tenant. MaxTotalInstances int `json:"maxTotalInstances"` - // MaxTotalRAMSize is the total amount of RAM available to a tenant measured // in megabytes (MB). MaxTotalRAMSize int `json:"maxTotalRAMSize"` - // TotalCoresUsed is the number of cores currently in use. TotalCoresUsed int `json:"totalCoresUsed"` - // TotalInstancesUsed is the number of instances/servers in use. TotalInstancesUsed int `json:"totalInstancesUsed"` - // TotalFloatingIpsUsed is the number of floating IPs in use. TotalFloatingIpsUsed int `json:"totalFloatingIpsUsed"` - // TotalRAMUsed is the total RAM/memory in use measured in megabytes (MB). TotalRAMUsed int `json:"totalRAMUsed"` - // TotalSecurityGroupsUsed is the total number of security groups in use. TotalSecurityGroupsUsed int `json:"totalSecurityGroupsUsed"` - // TotalServerGroupsUsed is the total number of server groups in use. TotalServerGroupsUsed int `json:"totalServerGroupsUsed"` } - -// Extract interprets a limits result as a Limits. -func (r GetResult) Extract() (*Limits, error) { - var s struct { - Limits *Limits `json:"limits"` - } - err := r.ExtractInto(&s) - return s.Limits, err -} - -// GetResult is the response from a Get operation. Call its Extract -// method to interpret it as an Absolute. -type GetResult struct { - golangsdk.Result -} diff --git a/openstack/compute/v2/extensions/limits/testing/requests_test.go b/openstack/compute/v2/extensions/limits/testing/requests_test.go index b61d703db..c7dad786c 100644 --- a/openstack/compute/v2/extensions/limits/testing/requests_test.go +++ b/openstack/compute/v2/extensions/limits/testing/requests_test.go @@ -17,7 +17,7 @@ func TestGet(t *testing.T) { TenantID: TenantID, } - actual, err := limits.Get(client.ServiceClient(), getOpts).Extract() + actual, err := limits.Get(client.ServiceClient(), getOpts) th.AssertNoErr(t, err) th.CheckDeepEquals(t, &LimitsResult, actual) } diff --git a/openstack/compute/v2/extensions/limits/urls.go b/openstack/compute/v2/extensions/limits/urls.go deleted file mode 100644 index 268f791d1..000000000 --- a/openstack/compute/v2/extensions/limits/urls.go +++ /dev/null @@ -1,11 +0,0 @@ -package limits - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -const resourcePath = "limits" - -func getURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL(resourcePath) -} diff --git a/openstack/compute/v2/extensions/lockunlock/requests.go b/openstack/compute/v2/extensions/lockunlock/requests.go index dc3cc58cb..a99fc1bc1 100644 --- a/openstack/compute/v2/extensions/lockunlock/requests.go +++ b/openstack/compute/v2/extensions/lockunlock/requests.go @@ -7,13 +7,13 @@ func actionURL(client *golangsdk.ServiceClient, id string) string { } // Lock is the operation responsible for locking a Compute server. -func Lock(client *golangsdk.ServiceClient, id string) (r LockResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"lock": nil}, nil, nil) +func Lock(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Post(actionURL(client, id), map[string]interface{}{"lock": nil}, nil, nil) return } // Unlock is the operation responsible for unlocking a Compute server. -func Unlock(client *golangsdk.ServiceClient, id string) (r UnlockResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"unlock": nil}, nil, nil) +func Unlock(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Post(actionURL(client, id), map[string]interface{}{"unlock": nil}, nil, nil) return } diff --git a/openstack/compute/v2/extensions/lockunlock/results.go b/openstack/compute/v2/extensions/lockunlock/results.go deleted file mode 100644 index b0f9e0629..000000000 --- a/openstack/compute/v2/extensions/lockunlock/results.go +++ /dev/null @@ -1,16 +0,0 @@ -package lockunlock - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -// LockResult and UnlockResult are the responses from a Lock and Unlock -// operations respectively. Call their ExtractErr methods to determine if the -// requests suceeded or failed. -type LockResult struct { - golangsdk.ErrResult -} - -type UnlockResult struct { - golangsdk.ErrResult -} diff --git a/openstack/compute/v2/extensions/lockunlock/testing/doc.go b/openstack/compute/v2/extensions/lockunlock/testing/doc.go deleted file mode 100644 index 59cb9be47..000000000 --- a/openstack/compute/v2/extensions/lockunlock/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// unlocklock unit tests -package testing diff --git a/openstack/compute/v2/extensions/lockunlock/testing/request_test.go b/openstack/compute/v2/extensions/lockunlock/testing/request_test.go index 9bbdc85a1..f3db2b04a 100644 --- a/openstack/compute/v2/extensions/lockunlock/testing/request_test.go +++ b/openstack/compute/v2/extensions/lockunlock/testing/request_test.go @@ -16,7 +16,7 @@ func TestLock(t *testing.T) { mockStartServerResponse(t, serverID) - err := lockunlock.Lock(client.ServiceClient(), serverID).ExtractErr() + err := lockunlock.Lock(client.ServiceClient(), serverID) th.AssertNoErr(t, err) } @@ -26,6 +26,6 @@ func TestUnlock(t *testing.T) { mockStopServerResponse(t, serverID) - err := lockunlock.Unlock(client.ServiceClient(), serverID).ExtractErr() + err := lockunlock.Unlock(client.ServiceClient(), serverID) th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/migrate/requests.go b/openstack/compute/v2/extensions/migrate/live_,migrate.go similarity index 55% rename from openstack/compute/v2/extensions/migrate/requests.go rename to openstack/compute/v2/extensions/migrate/live_,migrate.go index aea121218..f1c6c2c56 100644 --- a/openstack/compute/v2/extensions/migrate/requests.go +++ b/openstack/compute/v2/extensions/migrate/live_,migrate.go @@ -2,49 +2,31 @@ package migrate import ( "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" ) -// Migrate will initiate a migration of the instance to another host. -func Migrate(client *golangsdk.ServiceClient, id string) (r MigrateResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"migrate": nil}, nil, nil) - return -} - -// LiveMigrateOptsBuilder allows extensions to add additional parameters to the -// LiveMigrate request. -type LiveMigrateOptsBuilder interface { - ToLiveMigrateMap() (map[string]interface{}, error) -} - // LiveMigrateOpts specifies parameters of live migrate action. type LiveMigrateOpts struct { // The host to which to migrate the server. // If this parameter is None, the scheduler chooses a host. Host *string `json:"host"` - // Set to True to migrate local disks by using block migration. // If the source or destination host uses shared storage and you set // this value to True, the live migration fails. BlockMigration *bool `json:"block_migration,omitempty"` - // Set to True to enable over commit when the destination host is checked // for available disk space. Set to False to disable over commit. This setting // affects only the libvirt virt driver. DiskOverCommit *bool `json:"disk_over_commit,omitempty"` } -// ToLiveMigrateMap constructs a request body from LiveMigrateOpts. -func (opts LiveMigrateOpts) ToLiveMigrateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "os-migrateLive") -} - // LiveMigrate will initiate a live-migration (without rebooting) of the instance to another host. -func LiveMigrate(client *golangsdk.ServiceClient, id string, opts LiveMigrateOptsBuilder) (r MigrateResult) { - b, err := opts.ToLiveMigrateMap() +func LiveMigrate(client *golangsdk.ServiceClient, id string, opts LiveMigrateOpts) (err error) { + b, err := build.RequestBody(opts, "os-migrateLive") if err != nil { - r.Err = err return } - _, r.Err = client.Post(actionURL(client, id), b, nil, nil) + + _, err = client.Post(client.ServiceURL("servers", id, "action"), b, nil, nil) return } diff --git a/openstack/compute/v2/extensions/migrate/migrate.go b/openstack/compute/v2/extensions/migrate/migrate.go new file mode 100644 index 000000000..c66f9edaf --- /dev/null +++ b/openstack/compute/v2/extensions/migrate/migrate.go @@ -0,0 +1,9 @@ +package migrate + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Migrate will initiate a migration of the instance to another host. +func Migrate(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Post(client.ServiceURL("servers", id, "action"), map[string]interface{}{"migrate": nil}, nil, nil) + return +} diff --git a/openstack/compute/v2/extensions/migrate/results.go b/openstack/compute/v2/extensions/migrate/results.go deleted file mode 100644 index 78b11a1b8..000000000 --- a/openstack/compute/v2/extensions/migrate/results.go +++ /dev/null @@ -1,11 +0,0 @@ -package migrate - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -// MigrateResult is the response from a Migrate operation. Call its ExtractErr -// method to determine if the request suceeded or failed. -type MigrateResult struct { - golangsdk.ErrResult -} diff --git a/openstack/compute/v2/extensions/migrate/testing/doc.go b/openstack/compute/v2/extensions/migrate/testing/doc.go deleted file mode 100644 index 613547573..000000000 --- a/openstack/compute/v2/extensions/migrate/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// compute_extensions_startstop_v2 -package testing diff --git a/openstack/compute/v2/extensions/migrate/testing/requests_test.go b/openstack/compute/v2/extensions/migrate/testing/requests_test.go index d06d4fe40..f2af03223 100644 --- a/openstack/compute/v2/extensions/migrate/testing/requests_test.go +++ b/openstack/compute/v2/extensions/migrate/testing/requests_test.go @@ -16,7 +16,7 @@ func TestMigrate(t *testing.T) { mockMigrateResponse(t, serverID) - err := migrate.Migrate(client.ServiceClient(), serverID).ExtractErr() + err := migrate.Migrate(client.ServiceClient(), serverID) th.AssertNoErr(t, err) } @@ -36,6 +36,6 @@ func TestLiveMigrate(t *testing.T) { DiskOverCommit: &diskOverCommit, } - err := migrate.LiveMigrate(client.ServiceClient(), serverID, migrationOpts).ExtractErr() + err := migrate.LiveMigrate(client.ServiceClient(), serverID, migrationOpts) th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/migrate/urls.go b/openstack/compute/v2/extensions/migrate/urls.go deleted file mode 100644 index 658030fbb..000000000 --- a/openstack/compute/v2/extensions/migrate/urls.go +++ /dev/null @@ -1,9 +0,0 @@ -package migrate - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -func actionURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL("servers", id, "action") -} diff --git a/openstack/compute/v2/extensions/networks/get.go b/openstack/compute/v2/extensions/networks/get.go new file mode 100644 index 000000000..9fac99388 --- /dev/null +++ b/openstack/compute/v2/extensions/networks/get.go @@ -0,0 +1,18 @@ +package networks + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// Get returns data about a previously created Network. +func Get(client *golangsdk.ServiceClient, id string) (*Network, error) { + raw, err := client.Get(client.ServiceURL("os-networks", id), nil, nil) + if err != nil { + return nil, err + } + + var res Network + err = extract.IntoStructPtr(raw.Body, &res, "network") + return &res, err +} diff --git a/openstack/compute/v2/extensions/networks/list.go b/openstack/compute/v2/extensions/networks/list.go new file mode 100644 index 000000000..4e435b5d9 --- /dev/null +++ b/openstack/compute/v2/extensions/networks/list.go @@ -0,0 +1,18 @@ +package networks + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// List returns a Pager that allows you to iterate over a collection of Network. +func List(client *golangsdk.ServiceClient) ([]Network, error) { + raw, err := client.Get(client.ServiceURL("os-networks"), nil, nil) + if err != nil { + return nil, err + } + + var res []Network + err = extract.IntoSlicePtr(raw.Body, &res, "networks") + return res, err +} diff --git a/openstack/compute/v2/extensions/networks/requests.go b/openstack/compute/v2/extensions/networks/requests.go deleted file mode 100644 index a137c1cfa..000000000 --- a/openstack/compute/v2/extensions/networks/requests.go +++ /dev/null @@ -1,19 +0,0 @@ -package networks - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// List returns a Pager that allows you to iterate over a collection of Network. -func List(client *golangsdk.ServiceClient) pagination.Pager { - return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { - return NetworkPage{pagination.SinglePageBase(r)} - }) -} - -// Get returns data about a previously created Network. -func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) - return -} diff --git a/openstack/compute/v2/extensions/networks/results.go b/openstack/compute/v2/extensions/networks/results.go index 4acc4aba2..eebf994d8 100644 --- a/openstack/compute/v2/extensions/networks/results.go +++ b/openstack/compute/v2/extensions/networks/results.go @@ -2,132 +2,64 @@ package networks import ( "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" ) // A Network represents a network in an OpenStack cloud. type Network struct { // The Bridge that VIFs on this network are connected to Bridge string `json:"bridge"` - // BridgeInterface is what interface is connected to the Bridge BridgeInterface string `json:"bridge_interface"` - // The Broadcast address of the network. Broadcast string `json:"broadcast"` - // CIDR is the IPv4 subnet. CIDR string `json:"cidr"` - // CIDRv6 is the IPv6 subnet. CIDRv6 string `json:"cidr_v6"` - // CreatedAt is when the network was created.. CreatedAt golangsdk.JSONRFC3339MilliNoZ `json:"created_at,omitempty"` - // Deleted shows if the network has been deleted. Deleted bool `json:"deleted"` - // DeletedAt is the time when the network was deleted. DeletedAt golangsdk.JSONRFC3339MilliNoZ `json:"deleted_at,omitempty"` - // DHCPStart is the start of the DHCP address range. DHCPStart string `json:"dhcp_start"` - // DNS1 is the first DNS server to use through DHCP. DNS1 string `json:"dns_1"` - // DNS2 is the first DNS server to use through DHCP. DNS2 string `json:"dns_2"` - // Gateway is the network gateway. Gateway string `json:"gateway"` - // Gatewayv6 is the IPv6 network gateway. Gatewayv6 string `json:"gateway_v6"` - // Host is the host that the network service is running on. Host string `json:"host"` - // ID is the UUID of the network. ID string `json:"id"` - // Injected determines if network information is injected into the host. Injected bool `json:"injected"` - // Label is the common name that the network has.. Label string `json:"label"` - // MultiHost is if multi-host networking is enablec.. MultiHost bool `json:"multi_host"` - // Netmask is the network netmask. Netmask string `json:"netmask"` - // Netmaskv6 is the IPv6 netmask. Netmaskv6 string `json:"netmask_v6"` - // Priority is the network interface priority. Priority int `json:"priority"` - // ProjectID is the project associated with this network. ProjectID string `json:"project_id"` - // RXTXBase configures bandwidth entitlement. RXTXBase int `json:"rxtx_base"` - // UpdatedAt is the time when the network was last updated. UpdatedAt golangsdk.JSONRFC3339MilliNoZ `json:"updated_at,omitempty"` - // VLAN is the vlan this network runs on. VLAN int `json:"vlan"` - // VPNPrivateAddress is the private address of the CloudPipe VPN. VPNPrivateAddress string `json:"vpn_private_address"` - // VPNPublicAddress is the public address of the CloudPipe VPN. VPNPublicAddress string `json:"vpn_public_address"` - // VPNPublicPort is the port of the CloudPipe VPN. VPNPublicPort int `json:"vpn_public_port"` } - -// NetworkPage stores a single page of all Network results from a List call. -type NetworkPage struct { - pagination.SinglePageBase -} - -// IsEmpty determines whether or not a NetworkPage is empty. -func (page NetworkPage) IsEmpty() (bool, error) { - va, err := ExtractNetworks(page) - return len(va) == 0, err -} - -// ExtractNetworks interprets a page of results as a slice of Networks. -func ExtractNetworks(r pagination.Page) ([]Network, error) { - var s struct { - Networks []Network `json:"networks"` - } - err := (r.(NetworkPage)).ExtractInto(&s) - return s.Networks, err -} - -type NetworkResult struct { - golangsdk.Result -} - -// Extract is a method that attempts to interpret any Network resource -// response as a Network struct. -func (r NetworkResult) Extract() (*Network, error) { - var s struct { - Network *Network `json:"network"` - } - err := r.ExtractInto(&s) - return s.Network, err -} - -// GetResult is the response from a Get operation. Call its Extract method to -// interpret it as a Network. -type GetResult struct { - NetworkResult -} diff --git a/openstack/compute/v2/extensions/networks/testing/doc.go b/openstack/compute/v2/extensions/networks/testing/doc.go deleted file mode 100644 index fc8511de4..000000000 --- a/openstack/compute/v2/extensions/networks/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// networks unit tests -package testing diff --git a/openstack/compute/v2/extensions/networks/testing/requests_test.go b/openstack/compute/v2/extensions/networks/testing/requests_test.go index 96dd44d8f..824612206 100644 --- a/openstack/compute/v2/extensions/networks/testing/requests_test.go +++ b/openstack/compute/v2/extensions/networks/testing/requests_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/networks" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) @@ -14,17 +13,9 @@ func TestList(t *testing.T) { defer th.TeardownHTTP() HandleListSuccessfully(t) - count := 0 - err := networks.List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { - count++ - actual, err := networks.ExtractNetworks(page) - th.AssertNoErr(t, err) - th.CheckDeepEquals(t, ExpectedNetworkSlice, actual) - - return true, nil - }) + actual, err := networks.List(client.ServiceClient()) th.AssertNoErr(t, err) - th.CheckEquals(t, 1, count) + th.CheckDeepEquals(t, ExpectedNetworkSlice, actual) } func TestGet(t *testing.T) { @@ -32,7 +23,7 @@ func TestGet(t *testing.T) { defer th.TeardownHTTP() HandleGetSuccessfully(t) - actual, err := networks.Get(client.ServiceClient(), "20c8acc0-f747-4d71-a389-46d078ebf000").Extract() + actual, err := networks.Get(client.ServiceClient(), "20c8acc0-f747-4d71-a389-46d078ebf000") th.AssertNoErr(t, err) th.CheckDeepEquals(t, &SecondNetwork, actual) } diff --git a/openstack/compute/v2/extensions/networks/urls.go b/openstack/compute/v2/extensions/networks/urls.go deleted file mode 100644 index d580e05d3..000000000 --- a/openstack/compute/v2/extensions/networks/urls.go +++ /dev/null @@ -1,17 +0,0 @@ -package networks - -import "github.com/opentelekomcloud/gophertelekomcloud" - -const resourcePath = "os-networks" - -func resourceURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL(resourcePath) -} - -func listURL(c *golangsdk.ServiceClient) string { - return resourceURL(c) -} - -func getURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL(resourcePath, id) -} diff --git a/openstack/compute/v2/extensions/pauseunpause/requests.go b/openstack/compute/v2/extensions/pauseunpause/requests.go index b68910251..7e7e56554 100644 --- a/openstack/compute/v2/extensions/pauseunpause/requests.go +++ b/openstack/compute/v2/extensions/pauseunpause/requests.go @@ -7,13 +7,13 @@ func actionURL(client *golangsdk.ServiceClient, id string) string { } // Pause is the operation responsible for pausing a Compute server. -func Pause(client *golangsdk.ServiceClient, id string) (r PauseResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"pause": nil}, nil, nil) +func Pause(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Post(actionURL(client, id), map[string]interface{}{"pause": nil}, nil, nil) return } // Unpause is the operation responsible for unpausing a Compute server. -func Unpause(client *golangsdk.ServiceClient, id string) (r UnpauseResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"unpause": nil}, nil, nil) +func Unpause(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Post(actionURL(client, id), map[string]interface{}{"unpause": nil}, nil, nil) return } diff --git a/openstack/compute/v2/extensions/pauseunpause/results.go b/openstack/compute/v2/extensions/pauseunpause/results.go deleted file mode 100644 index 65c47cfc1..000000000 --- a/openstack/compute/v2/extensions/pauseunpause/results.go +++ /dev/null @@ -1,15 +0,0 @@ -package pauseunpause - -import "github.com/opentelekomcloud/gophertelekomcloud" - -// PauseResult is the response from a Pause operation. Call its ExtractErr -// method to determine if the request succeeded or failed. -type PauseResult struct { - golangsdk.ErrResult -} - -// UnpauseResult is the response from an Unpause operation. Call its ExtractErr -// method to determine if the request succeeded or failed. -type UnpauseResult struct { - golangsdk.ErrResult -} diff --git a/openstack/compute/v2/extensions/pauseunpause/testing/doc.go b/openstack/compute/v2/extensions/pauseunpause/testing/doc.go deleted file mode 100644 index 095386750..000000000 --- a/openstack/compute/v2/extensions/pauseunpause/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// pauseunpause unit tests -package testing diff --git a/openstack/compute/v2/extensions/pauseunpause/testing/requests_test.go b/openstack/compute/v2/extensions/pauseunpause/testing/requests_test.go index e3e340265..b60bf2638 100644 --- a/openstack/compute/v2/extensions/pauseunpause/testing/requests_test.go +++ b/openstack/compute/v2/extensions/pauseunpause/testing/requests_test.go @@ -16,7 +16,7 @@ func TestPause(t *testing.T) { mockPauseServerResponse(t, serverID) - err := pauseunpause.Pause(client.ServiceClient(), serverID).ExtractErr() + err := pauseunpause.Pause(client.ServiceClient(), serverID) th.AssertNoErr(t, err) } @@ -26,6 +26,6 @@ func TestUnpause(t *testing.T) { mockUnpauseServerResponse(t, serverID) - err := pauseunpause.Unpause(client.ServiceClient(), serverID).ExtractErr() + err := pauseunpause.Unpause(client.ServiceClient(), serverID) th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/quotasets/delete.go b/openstack/compute/v2/extensions/quotasets/delete.go new file mode 100644 index 000000000..ef2c2b63b --- /dev/null +++ b/openstack/compute/v2/extensions/quotasets/delete.go @@ -0,0 +1,9 @@ +package quotasets + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Delete resets the quotas for the given tenant to their default values. +func Delete(client *golangsdk.ServiceClient, tenantID string) (err error) { + _, err = client.Delete(client.ServiceURL("os-quota-sets", tenantID), nil) + return +} diff --git a/openstack/compute/v2/extensions/quotasets/get.go b/openstack/compute/v2/extensions/quotasets/get.go new file mode 100644 index 000000000..a7c55f18e --- /dev/null +++ b/openstack/compute/v2/extensions/quotasets/get.go @@ -0,0 +1,11 @@ +package quotasets + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" +) + +// Get returns public data about a previously created QuotaSet. +func Get(client *golangsdk.ServiceClient, tenantID string) (*QuotaSet, error) { + raw, err := client.Get(client.ServiceURL("os-quota-sets", tenantID), nil, nil) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/quotasets/get_detail.go b/openstack/compute/v2/extensions/quotasets/get_detail.go new file mode 100644 index 000000000..21e5ee3db --- /dev/null +++ b/openstack/compute/v2/extensions/quotasets/get_detail.go @@ -0,0 +1,67 @@ +package quotasets + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// GetDetail returns detailed public data about a previously created QuotaSet. +func GetDetail(client *golangsdk.ServiceClient, tenantID string) (*QuotaDetailSet, error) { + raw, err := client.Get(client.ServiceURL("os-quota-sets", tenantID, "detail"), nil, nil) + if err != nil { + return nil, err + } + + var res QuotaDetailSet + err = extract.IntoStructPtr(raw.Body, &res, "quota_set") + return &res, err +} + +// QuotaDetailSet represents details of both operational limits of compute +// resources and the current usage of those resources. +type QuotaDetailSet struct { + // ID is the tenant ID associated with this QuotaDetailSet. + ID string `json:"id"` + // FixedIPs is number of fixed ips alloted this QuotaDetailSet. + FixedIPs QuotaDetail `json:"fixed_ips"` + // FloatingIPs is number of floating ips alloted this QuotaDetailSet. + FloatingIPs QuotaDetail `json:"floating_ips"` + // InjectedFileContentBytes is the allowed bytes for each injected file. + InjectedFileContentBytes QuotaDetail `json:"injected_file_content_bytes"` + // InjectedFilePathBytes is allowed bytes for each injected file path. + InjectedFilePathBytes QuotaDetail `json:"injected_file_path_bytes"` + // InjectedFiles is the number of injected files allowed for each project. + InjectedFiles QuotaDetail `json:"injected_files"` + // KeyPairs is number of ssh keypairs. + KeyPairs QuotaDetail `json:"key_pairs"` + // MetadataItems is number of metadata items allowed for each instance. + MetadataItems QuotaDetail `json:"metadata_items"` + // RAM is megabytes allowed for each instance. + RAM QuotaDetail `json:"ram"` + // SecurityGroupRules is number of security group rules allowed for each + // security group. + SecurityGroupRules QuotaDetail `json:"security_group_rules"` + // SecurityGroups is the number of security groups allowed for each project. + SecurityGroups QuotaDetail `json:"security_groups"` + // Cores is number of instance cores allowed for each project. + Cores QuotaDetail `json:"cores"` + // Instances is number of instances allowed for each project. + Instances QuotaDetail `json:"instances"` + // ServerGroups is the number of ServerGroups allowed for the project. + ServerGroups QuotaDetail `json:"server_groups"` + // ServerGroupMembers is the number of members for each ServerGroup. + ServerGroupMembers QuotaDetail `json:"server_group_members"` +} + +// QuotaDetail is a set of details about a single operational limit that allows +// for control of compute usage. +type QuotaDetail struct { + // InUse is the current number of provisioned/allocated resources of the given type. + InUse int `json:"in_use"` + // Reserved is a transitional state when a claim against quota has been made + // but the resource is not yet fully online. + Reserved int `json:"reserved"` + // Limit is the maximum number of a given resource that can be + // allocated/provisioned. This is what "quota" usually refers to. + Limit int `json:"limit"` +} diff --git a/openstack/compute/v2/extensions/quotasets/results.go b/openstack/compute/v2/extensions/quotasets/results.go index 003318af6..32fd65e41 100644 --- a/openstack/compute/v2/extensions/quotasets/results.go +++ b/openstack/compute/v2/extensions/quotasets/results.go @@ -1,194 +1,51 @@ package quotasets import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" + "net/http" + + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" ) -// QuotaSet is a set of operational limits that allow for control of compute -// usage. +// QuotaSet is a set of operational limits that allow for control of compute usage. type QuotaSet struct { // ID is tenant associated with this QuotaSet. ID string `json:"id"` - // FixedIPs is number of fixed ips alloted this QuotaSet. FixedIPs int `json:"fixed_ips"` - // FloatingIPs is number of floating ips alloted this QuotaSet. FloatingIPs int `json:"floating_ips"` - // InjectedFileContentBytes is the allowed bytes for each injected file. InjectedFileContentBytes int `json:"injected_file_content_bytes"` - // InjectedFilePathBytes is allowed bytes for each injected file path. InjectedFilePathBytes int `json:"injected_file_path_bytes"` - // InjectedFiles is the number of injected files allowed for each project. InjectedFiles int `json:"injected_files"` - // KeyPairs is number of ssh keypairs. KeyPairs int `json:"key_pairs"` - // MetadataItems is number of metadata items allowed for each instance. MetadataItems int `json:"metadata_items"` - // RAM is megabytes allowed for each instance. RAM int `json:"ram"` - - // SecurityGroupRules is number of security group rules allowed for each - // security group. + // SecurityGroupRules is number of security group rules allowed for each security group. SecurityGroupRules int `json:"security_group_rules"` - // SecurityGroups is the number of security groups allowed for each project. SecurityGroups int `json:"security_groups"` - // Cores is number of instance cores allowed for each project. Cores int `json:"cores"` - // Instances is number of instances allowed for each project. Instances int `json:"instances"` - // ServerGroups is the number of ServerGroups allowed for the project. ServerGroups int `json:"server_groups"` - // ServerGroupMembers is the number of members for each ServerGroup. ServerGroupMembers int `json:"server_group_members"` } -// QuotaDetailSet represents details of both operational limits of compute -// resources and the current usage of those resources. -type QuotaDetailSet struct { - // ID is the tenant ID associated with this QuotaDetailSet. - ID string `json:"id"` - - // FixedIPs is number of fixed ips alloted this QuotaDetailSet. - FixedIPs QuotaDetail `json:"fixed_ips"` - - // FloatingIPs is number of floating ips alloted this QuotaDetailSet. - FloatingIPs QuotaDetail `json:"floating_ips"` - - // InjectedFileContentBytes is the allowed bytes for each injected file. - InjectedFileContentBytes QuotaDetail `json:"injected_file_content_bytes"` - - // InjectedFilePathBytes is allowed bytes for each injected file path. - InjectedFilePathBytes QuotaDetail `json:"injected_file_path_bytes"` - - // InjectedFiles is the number of injected files allowed for each project. - InjectedFiles QuotaDetail `json:"injected_files"` - - // KeyPairs is number of ssh keypairs. - KeyPairs QuotaDetail `json:"key_pairs"` - - // MetadataItems is number of metadata items allowed for each instance. - MetadataItems QuotaDetail `json:"metadata_items"` - - // RAM is megabytes allowed for each instance. - RAM QuotaDetail `json:"ram"` - - // SecurityGroupRules is number of security group rules allowed for each - // security group. - SecurityGroupRules QuotaDetail `json:"security_group_rules"` - - // SecurityGroups is the number of security groups allowed for each project. - SecurityGroups QuotaDetail `json:"security_groups"` - - // Cores is number of instance cores allowed for each project. - Cores QuotaDetail `json:"cores"` - - // Instances is number of instances allowed for each project. - Instances QuotaDetail `json:"instances"` - - // ServerGroups is the number of ServerGroups allowed for the project. - ServerGroups QuotaDetail `json:"server_groups"` - - // ServerGroupMembers is the number of members for each ServerGroup. - ServerGroupMembers QuotaDetail `json:"server_group_members"` -} - -// QuotaDetail is a set of details about a single operational limit that allows -// for control of compute usage. -type QuotaDetail struct { - // InUse is the current number of provisioned/allocated resources of the - // given type. - InUse int `json:"in_use"` - - // Reserved is a transitional state when a claim against quota has been made - // but the resource is not yet fully online. - Reserved int `json:"reserved"` - - // Limit is the maximum number of a given resource that can be - // allocated/provisioned. This is what "quota" usually refers to. - Limit int `json:"limit"` -} - -// QuotaSetPage stores a single page of all QuotaSet results from a List call. -type QuotaSetPage struct { - pagination.SinglePageBase -} - -// IsEmpty determines whether or not a QuotaSetsetPage is empty. -func (page QuotaSetPage) IsEmpty() (bool, error) { - ks, err := ExtractQuotaSets(page) - return len(ks) == 0, err -} - -// ExtractQuotaSets interprets a page of results as a slice of QuotaSets. -func ExtractQuotaSets(r pagination.Page) ([]QuotaSet, error) { - var s struct { - QuotaSets []QuotaSet `json:"quotas"` +func extra(err error, raw *http.Response) (*QuotaSet, error) { + if err != nil { + return nil, err } - err := (r.(QuotaSetPage)).ExtractInto(&s) - return s.QuotaSets, err -} - -type quotaResult struct { - golangsdk.Result -} -// Extract is a method that attempts to interpret any QuotaSet resource response -// as a QuotaSet struct. -func (r quotaResult) Extract() (*QuotaSet, error) { - var s struct { - QuotaSet *QuotaSet `json:"quota_set"` - } - err := r.ExtractInto(&s) - return s.QuotaSet, err -} - -// GetResult is the response from a Get operation. Call its Extract method to -// interpret it as a QuotaSet. -type GetResult struct { - quotaResult -} - -// UpdateResult is the response from a Update operation. Call its Extract method -// to interpret it as a QuotaSet. -type UpdateResult struct { - quotaResult -} - -// DeleteResult is the response from a Delete operation. Call its Extract method -// to interpret it as a QuotaSet. -type DeleteResult struct { - quotaResult -} - -type quotaDetailResult struct { - golangsdk.Result -} - -// GetDetailResult is the response from a Get operation. Call its Extract -// method to interpret it as a QuotaSet. -type GetDetailResult struct { - quotaDetailResult -} - -// Extract is a method that attempts to interpret any QuotaDetailSet -// resource response as a set of QuotaDetailSet structs. -func (r quotaDetailResult) Extract() (QuotaDetailSet, error) { - var s struct { - QuotaData QuotaDetailSet `json:"quota_set"` - } - err := r.ExtractInto(&s) - return s.QuotaData, err + var res QuotaSet + err = extract.IntoStructPtr(raw.Body, &res, "quota_set") + return &res, err } diff --git a/openstack/compute/v2/extensions/quotasets/testing/doc.go b/openstack/compute/v2/extensions/quotasets/testing/doc.go deleted file mode 100644 index 30d864eb9..000000000 --- a/openstack/compute/v2/extensions/quotasets/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// quotasets unit tests -package testing diff --git a/openstack/compute/v2/extensions/quotasets/testing/requests_test.go b/openstack/compute/v2/extensions/quotasets/testing/requests_test.go index 9a4a84758..9239be154 100644 --- a/openstack/compute/v2/extensions/quotasets/testing/requests_test.go +++ b/openstack/compute/v2/extensions/quotasets/testing/requests_test.go @@ -1,7 +1,6 @@ package testing import ( - "errors" "testing" "github.com/opentelekomcloud/gophertelekomcloud" @@ -14,7 +13,7 @@ func TestGet(t *testing.T) { th.SetupHTTP() t.Cleanup(th.TeardownHTTP) HandleGetSuccessfully(t) - actual, err := quotasets.Get(client.ServiceClient(), FirstTenantID).Extract() + actual, err := quotasets.Get(client.ServiceClient(), FirstTenantID) th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstQuotaSet, actual) } @@ -23,8 +22,8 @@ func TestGetDetail(t *testing.T) { th.SetupHTTP() t.Cleanup(th.TeardownHTTP) HandleGetDetailSuccessfully(t) - actual, err := quotasets.GetDetail(client.ServiceClient(), FirstTenantID).Extract() - th.CheckDeepEquals(t, FirstQuotaDetailsSet, actual) + actual, err := quotasets.GetDetail(client.ServiceClient(), FirstTenantID) + th.CheckDeepEquals(t, &FirstQuotaDetailsSet, actual) th.AssertNoErr(t, err) } @@ -32,7 +31,7 @@ func TestUpdate(t *testing.T) { th.SetupHTTP() t.Cleanup(th.TeardownHTTP) HandlePutSuccessfully(t) - actual, err := quotasets.Update(client.ServiceClient(), FirstTenantID, UpdatedQuotaSet).Extract() + actual, err := quotasets.Update(client.ServiceClient(), FirstTenantID, UpdatedQuotaSet) th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstQuotaSet, actual) } @@ -42,7 +41,7 @@ func TestPartialUpdate(t *testing.T) { t.Cleanup(th.TeardownHTTP) HandlePartialPutSuccessfully(t) opts := quotasets.UpdateOpts{Cores: golangsdk.IntToPointer(200), Force: true} - actual, err := quotasets.Update(client.ServiceClient(), FirstTenantID, opts).Extract() + actual, err := quotasets.Update(client.ServiceClient(), FirstTenantID, opts) th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstQuotaSet, actual) } @@ -51,23 +50,6 @@ func TestDelete(t *testing.T) { th.SetupHTTP() t.Cleanup(th.TeardownHTTP) HandleDeleteSuccessfully(t) - _, err := quotasets.Delete(client.ServiceClient(), FirstTenantID).Extract() + err := quotasets.Delete(client.ServiceClient(), FirstTenantID) th.AssertNoErr(t, err) } - -type ErrorUpdateOpts quotasets.UpdateOpts - -func (opts ErrorUpdateOpts) ToComputeQuotaUpdateMap() (map[string]interface{}, error) { - return nil, errors.New("this is an error") -} - -func TestErrorInToComputeQuotaUpdateMap(t *testing.T) { - opts := &ErrorUpdateOpts{} - th.SetupHTTP() - defer th.TeardownHTTP() - HandlePutSuccessfully(t) - _, err := quotasets.Update(client.ServiceClient(), FirstTenantID, opts).Extract() - if err == nil { - t.Fatal("Error handling failed") - } -} diff --git a/openstack/compute/v2/extensions/quotasets/requests.go b/openstack/compute/v2/extensions/quotasets/update.go similarity index 60% rename from openstack/compute/v2/extensions/quotasets/requests.go rename to openstack/compute/v2/extensions/quotasets/update.go index db2af8413..ba7b0a3ae 100644 --- a/openstack/compute/v2/extensions/quotasets/requests.go +++ b/openstack/compute/v2/extensions/quotasets/update.go @@ -2,98 +2,53 @@ package quotasets import ( "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" ) -// Get returns public data about a previously created QuotaSet. -func Get(client *golangsdk.ServiceClient, tenantID string) (r GetResult) { - _, r.Err = client.Get(getURL(client, tenantID), &r.Body, nil) - return -} - -// GetDetail returns detailed public data about a previously created QuotaSet. -func GetDetail(client *golangsdk.ServiceClient, tenantID string) (r GetDetailResult) { - _, r.Err = client.Get(getDetailURL(client, tenantID), &r.Body, nil) - return -} - -// Update updates the quotas for the given tenantID and returns the new QuotaSet. -func Update(client *golangsdk.ServiceClient, tenantID string, opts UpdateOptsBuilder) (r UpdateResult) { - reqBody, err := opts.ToComputeQuotaUpdateMap() - if err != nil { - r.Err = err - return - } - - _, r.Err = client.Put(updateURL(client, tenantID), reqBody, &r.Body, &golangsdk.RequestOpts{OkCodes: []int{200}}) - return -} - -// Delete resets the quotas for the given tenant to their default values. -func Delete(client *golangsdk.ServiceClient, tenantID string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, tenantID), nil) - return -} - // UpdateOpts - options for Updating the quotas of a Tenant. // All int-values are pointers, so they can be nil if they are not needed. // You can use golangsdk.IntToPointer() for convenience type UpdateOpts struct { // FixedIPs is number of fixed ips alloted this quota_set. FixedIPs *int `json:"fixed_ips,omitempty"` - // FloatingIPs is number of floating ips alloted this quota_set. FloatingIPs *int `json:"floating_ips,omitempty"` - // InjectedFileContentBytes is content bytes allowed for each injected file. InjectedFileContentBytes *int `json:"injected_file_content_bytes,omitempty"` - // InjectedFilePathBytes is allowed bytes for each injected file path. InjectedFilePathBytes *int `json:"injected_file_path_bytes,omitempty"` - // InjectedFiles is injected files allowed for each project. InjectedFiles *int `json:"injected_files,omitempty"` - // KeyPairs is number of ssh keypairs. KeyPairs *int `json:"key_pairs,omitempty"` - // MetadataItems is number of metadata items allowed for each instance. MetadataItems *int `json:"metadata_items,omitempty"` - // RAM is megabytes allowed for each instance. RAM *int `json:"ram,omitempty"` - // SecurityGroupRules is rules allowed for each security group. SecurityGroupRules *int `json:"security_group_rules,omitempty"` - // SecurityGroups security groups allowed for each project. SecurityGroups *int `json:"security_groups,omitempty"` - // Cores is number of instance cores allowed for each project. Cores *int `json:"cores,omitempty"` - // Instances is number of instances allowed for each project. Instances *int `json:"instances,omitempty"` - // Number of ServerGroups allowed for the project. ServerGroups *int `json:"server_groups,omitempty"` - // Max number of Members for each ServerGroup. ServerGroupMembers *int `json:"server_group_members,omitempty"` - // Force will update the quotaset even if the quota has already been used // and the reserved quota exceeds the new quota. Force bool `json:"force,omitempty"` } -// UpdateOptsBuilder enables extensins to add parameters to the update request. -type UpdateOptsBuilder interface { - // Extra specific name to prevent collisions with interfaces for other quotas - // (e.g. neutron) - ToComputeQuotaUpdateMap() (map[string]interface{}, error) -} +// Update updates the quotas for the given tenantID and returns the new QuotaSet. +func Update(client *golangsdk.ServiceClient, tenantID string, opts UpdateOpts) (*QuotaSet, error) { + reqBody, err := build.RequestBody(opts, "quota_set") + if err != nil { + return nil, err + } -// ToComputeQuotaUpdateMap builds the update options into a serializable -// format. -func (opts UpdateOpts) ToComputeQuotaUpdateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "quota_set") + raw, err := client.Put(client.ServiceURL("os-quota-sets", tenantID), reqBody, nil, &golangsdk.RequestOpts{OkCodes: []int{200}}) + return extra(err, raw) } diff --git a/openstack/compute/v2/extensions/quotasets/urls.go b/openstack/compute/v2/extensions/quotasets/urls.go deleted file mode 100644 index d623e9f2c..000000000 --- a/openstack/compute/v2/extensions/quotasets/urls.go +++ /dev/null @@ -1,21 +0,0 @@ -package quotasets - -import "github.com/opentelekomcloud/gophertelekomcloud" - -const resourcePath = "os-quota-sets" - -func getURL(c *golangsdk.ServiceClient, tenantID string) string { - return c.ServiceURL(resourcePath, tenantID) -} - -func getDetailURL(c *golangsdk.ServiceClient, tenantID string) string { - return c.ServiceURL(resourcePath, tenantID, "detail") -} - -func updateURL(c *golangsdk.ServiceClient, tenantID string) string { - return getURL(c, tenantID) -} - -func deleteURL(c *golangsdk.ServiceClient, tenantID string) string { - return getURL(c, tenantID) -} diff --git a/openstack/compute/v2/extensions/rescueunrescue/requests.go b/openstack/compute/v2/extensions/rescueunrescue/requests.go index 5311be34d..c7bd065e6 100644 --- a/openstack/compute/v2/extensions/rescueunrescue/requests.go +++ b/openstack/compute/v2/extensions/rescueunrescue/requests.go @@ -1,48 +1,44 @@ package rescueunrescue -import "github.com/opentelekomcloud/gophertelekomcloud" +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) -// RescueOptsBuilder is an interface that allows extensions to override the -// default structure of a Rescue request. -type RescueOptsBuilder interface { - ToServerRescueMap() (map[string]interface{}, error) -} - -// RescueOpts represents the configuration options used to control a Rescue -// option. +// RescueOpts represents the configuration options used to control a Rescue option. type RescueOpts struct { - // AdminPass is the desired administrative password for the instance in - // RESCUE mode. + // AdminPass is the desired administrative password for the instance in RESCUE mode. // If it's left blank, the server will generate a password. AdminPass string `json:"adminPass,omitempty"` - - // RescueImageRef contains reference on an image that needs to be used as - // rescue image. + // RescueImageRef contains reference on an image that needs to be used as rescue image. // If it's left blank, the server will be rescued with the default image. RescueImageRef string `json:"rescue_image_ref,omitempty"` } -// ToServerRescueMap formats a RescueOpts as a map that can be used as a JSON -// request body for the Rescue request. -func (opts RescueOpts) ToServerRescueMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "rescue") -} - // Rescue instructs the provider to place the server into RESCUE mode. -func Rescue(client *golangsdk.ServiceClient, id string, opts RescueOptsBuilder) (r RescueResult) { - b, err := opts.ToServerRescueMap() +func Rescue(client *golangsdk.ServiceClient, id string, opts RescueOpts) (string, error) { + b, err := build.RequestBody(opts, "rescue") if err != nil { - r.Err = err - return + return "", err } - _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ + + raw, err := client.Post(client.ServiceURL("servers", id, "action"), b, nil, &golangsdk.RequestOpts{ OkCodes: []int{200}, }) - return + if err != nil { + return "", err + } + + var res struct { + AdminPass string `json:"adminPass"` + } + err = extract.Into(raw.Body, &res) + return res.AdminPass, err } -// Unrescue instructs the provider to return the server from RESCUE mode. -func Unrescue(client *golangsdk.ServiceClient, id string) (r UnrescueResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"unrescue": nil}, nil, nil) +// UnRescue instructs the provider to return the server from RESCUE mode. +func UnRescue(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Post(client.ServiceURL("servers", id, "action"), map[string]interface{}{"unrescue": nil}, nil, nil) return } diff --git a/openstack/compute/v2/extensions/rescueunrescue/results.go b/openstack/compute/v2/extensions/rescueunrescue/results.go deleted file mode 100644 index f851eeff6..000000000 --- a/openstack/compute/v2/extensions/rescueunrescue/results.go +++ /dev/null @@ -1,28 +0,0 @@ -package rescueunrescue - -import "github.com/opentelekomcloud/gophertelekomcloud" - -type commonResult struct { - golangsdk.Result -} - -// RescueResult is the response from a Rescue operation. Call its Extract -// method to retrieve adminPass for a rescued server. -type RescueResult struct { - commonResult -} - -// UnrescueResult is the response from an UnRescue operation. Call its ExtractErr -// method to determine if the call succeeded or failed. -type UnrescueResult struct { - golangsdk.ErrResult -} - -// Extract interprets any RescueResult as an AdminPass, if possible. -func (r RescueResult) Extract() (string, error) { - var s struct { - AdminPass string `json:"adminPass"` - } - err := r.ExtractInto(&s) - return s.AdminPass, err -} diff --git a/openstack/compute/v2/extensions/rescueunrescue/testing/doc.go b/openstack/compute/v2/extensions/rescueunrescue/testing/doc.go deleted file mode 100644 index 7603f836a..000000000 --- a/openstack/compute/v2/extensions/rescueunrescue/testing/doc.go +++ /dev/null @@ -1 +0,0 @@ -package testing diff --git a/openstack/compute/v2/extensions/rescueunrescue/testing/requests_test.go b/openstack/compute/v2/extensions/rescueunrescue/testing/requests_test.go index 6517f7144..afad1d9ec 100644 --- a/openstack/compute/v2/extensions/rescueunrescue/testing/requests_test.go +++ b/openstack/compute/v2/extensions/rescueunrescue/testing/requests_test.go @@ -26,13 +26,13 @@ func TestRescue(t *testing.T) { s, err := rescueunrescue.Rescue(fake.ServiceClient(), "3f54d05f-3430-4d80-aa07-63e6af9e2488", rescueunrescue.RescueOpts{ AdminPass: "aUPtawPzE9NU", RescueImageRef: "115e5c5b-72f0-4a0a-9067-60706545248c", - }).Extract() + }) th.AssertNoErr(t, err) th.AssertEquals(t, "aUPtawPzE9NU", s) } -func TestUnrescue(t *testing.T) { +func TestUnRescue(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -44,6 +44,6 @@ func TestUnrescue(t *testing.T) { w.WriteHeader(http.StatusAccepted) }) - err := rescueunrescue.Unrescue(fake.ServiceClient(), "3f54d05f-3430-4d80-aa07-63e6af9e2488").ExtractErr() + err := rescueunrescue.UnRescue(fake.ServiceClient(), "3f54d05f-3430-4d80-aa07-63e6af9e2488") th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/rescueunrescue/urls.go b/openstack/compute/v2/extensions/rescueunrescue/urls.go deleted file mode 100644 index a23956615..000000000 --- a/openstack/compute/v2/extensions/rescueunrescue/urls.go +++ /dev/null @@ -1,7 +0,0 @@ -package rescueunrescue - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func actionURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL("servers", id, "action") -} diff --git a/openstack/compute/v2/extensions/resetstate/requests.go b/openstack/compute/v2/extensions/resetstate/reset_state.go similarity index 71% rename from openstack/compute/v2/extensions/resetstate/requests.go rename to openstack/compute/v2/extensions/resetstate/reset_state.go index 5c44b03d0..7b6ceaa48 100644 --- a/openstack/compute/v2/extensions/resetstate/requests.go +++ b/openstack/compute/v2/extensions/resetstate/reset_state.go @@ -10,14 +10,13 @@ type ServerState string const ( // StateActive returns the state of the server as active StateActive ServerState = "active" - // StateError returns the state of the server as error StateError ServerState = "error" ) // ResetState will reset the state of a server -func ResetState(client *golangsdk.ServiceClient, id string, state ServerState) (r ResetResult) { - stateMap := map[string]interface{}{"state": state} - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-resetState": stateMap}, nil, nil) +func ResetState(client *golangsdk.ServiceClient, id string, state ServerState) (err error) { + _, err = client.Post(client.ServiceURL("servers", id, "action"), + map[string]interface{}{"os-resetState": map[string]interface{}{"state": state}}, nil, nil) return } diff --git a/openstack/compute/v2/extensions/resetstate/results.go b/openstack/compute/v2/extensions/resetstate/results.go deleted file mode 100644 index c47601c5d..000000000 --- a/openstack/compute/v2/extensions/resetstate/results.go +++ /dev/null @@ -1,11 +0,0 @@ -package resetstate - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -// ResetResult is the response of a ResetState operation. Call its ExtractErr -// method to determine if the request suceeded or failed. -type ResetResult struct { - golangsdk.ErrResult -} diff --git a/openstack/compute/v2/extensions/resetstate/testing/doc.go b/openstack/compute/v2/extensions/resetstate/testing/doc.go deleted file mode 100644 index 7603f836a..000000000 --- a/openstack/compute/v2/extensions/resetstate/testing/doc.go +++ /dev/null @@ -1 +0,0 @@ -package testing diff --git a/openstack/compute/v2/extensions/resetstate/testing/requests_test.go b/openstack/compute/v2/extensions/resetstate/testing/requests_test.go index 78d2272f9..9be96e775 100644 --- a/openstack/compute/v2/extensions/resetstate/testing/requests_test.go +++ b/openstack/compute/v2/extensions/resetstate/testing/requests_test.go @@ -16,6 +16,6 @@ func TestResetState(t *testing.T) { mockResetStateResponse(t, serverID, "active") - err := resetstate.ResetState(client.ServiceClient(), serverID, "active").ExtractErr() + err := resetstate.ResetState(client.ServiceClient(), serverID, "active") th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/resetstate/urls.go b/openstack/compute/v2/extensions/resetstate/urls.go deleted file mode 100644 index fe602f707..000000000 --- a/openstack/compute/v2/extensions/resetstate/urls.go +++ /dev/null @@ -1,9 +0,0 @@ -package resetstate - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -func actionURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL("servers", id, "action") -} diff --git a/openstack/compute/v2/extensions/schedulerhints/requests.go b/openstack/compute/v2/extensions/schedulerhints/requests.go index d502c4a56..32358fd39 100644 --- a/openstack/compute/v2/extensions/schedulerhints/requests.go +++ b/openstack/compute/v2/extensions/schedulerhints/requests.go @@ -9,46 +9,29 @@ import ( "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/servers" ) -// SchedulerHints represents a set of scheduling hints that are passed to the -// OpenStack scheduler. +// SchedulerHints represents a set of scheduling hints that are passed to the OpenStack scheduler. type SchedulerHints struct { // Group specifies a Server Group to place the instance in. Group string - - // DifferentHost will place the instance on a compute node that does not - // host the given instances. + // DifferentHost will place the instance on compute node that does not host the given instances. DifferentHost []string - - // SameHost will place the instance on a compute node that hosts the given - // instances. + // SameHost will place the instance on compute node that hosts the given instances. SameHost []string - - // Query is a conditional statement that results in compute nodes able to - // host the instance. + // Query is a conditional statement that results in compute nodes able to host the instance. Query []interface{} - // TargetCell specifies a cell name where the instance will be placed. TargetCell string `json:"target_cell,omitempty"` - // BuildNearHostIP specifies a subnet of compute nodes to host the instance. BuildNearHostIP string - - // AdditionalProperies are arbitrary key/values that are not validated by nova. + // AdditionalProperties are arbitrary key/values that are not validated by nova. AdditionalProperties map[string]interface{} - // Specifies whether the ECS is created on a Dedicated Host (DeH) or in a shared pool. Tenancy string `json:"tenancy,omitempty"` - // DedicatedHostID specifies a DeH ID. DedicatedHostID string `json:"dedicated_host_id,omitempty"` } -// CreateOptsBuilder builds the scheduler hints into a serializable format. -type CreateOptsBuilder interface { - ToServerSchedulerHintsCreateMap() (map[string]interface{}, error) -} - -// ToServerSchedulerHintsMap builds the scheduler hints into a serializable format. +// ToServerSchedulerHintsCreateMap builds the scheduler hints into a serializable format. func (opts SchedulerHints) ToServerSchedulerHintsCreateMap() (map[string]interface{}, error) { sh := make(map[string]interface{}) @@ -150,15 +133,14 @@ func (opts SchedulerHints) ToServerSchedulerHintsCreateMap() (map[string]interfa // CreateOptsExt adds a SchedulerHints option to the base CreateOpts. type CreateOptsExt struct { - servers.CreateOptsBuilder - + servers.CreateOpts // SchedulerHints provides a set of hints to the scheduler. - SchedulerHints CreateOptsBuilder + SchedulerHints SchedulerHints } // ToServerCreateMap adds the SchedulerHints option to the base server creation options. func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) { - base, err := opts.CreateOptsBuilder.ToServerCreateMap() + base, err := opts.CreateOpts.ToServerCreateMap() if err != nil { return nil, err } diff --git a/openstack/compute/v2/extensions/schedulerhints/testing/doc.go b/openstack/compute/v2/extensions/schedulerhints/testing/doc.go deleted file mode 100644 index 1915aef2f..000000000 --- a/openstack/compute/v2/extensions/schedulerhints/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// schedulerhints unit tests -package testing diff --git a/openstack/compute/v2/extensions/schedulerhints/testing/requests_test.go b/openstack/compute/v2/extensions/schedulerhints/testing/requests_test.go index fabeaa2c9..56fac5849 100644 --- a/openstack/compute/v2/extensions/schedulerhints/testing/requests_test.go +++ b/openstack/compute/v2/extensions/schedulerhints/testing/requests_test.go @@ -34,8 +34,8 @@ func TestCreateOpts(t *testing.T) { } ext := schedulerhints.CreateOptsExt{ - CreateOptsBuilder: base, - SchedulerHints: schedulerHints, + CreateOpts: base, + SchedulerHints: schedulerHints, } expected := ` @@ -98,8 +98,8 @@ func TestCreateOptsWithComplexQuery(t *testing.T) { } ext := schedulerhints.CreateOptsExt{ - CreateOptsBuilder: base, - SchedulerHints: schedulerHints, + CreateOpts: base, + SchedulerHints: schedulerHints, } expected := ` diff --git a/openstack/compute/v2/extensions/secgroups/add_server.go b/openstack/compute/v2/extensions/secgroups/add_server.go new file mode 100644 index 000000000..b66c21252 --- /dev/null +++ b/openstack/compute/v2/extensions/secgroups/add_server.go @@ -0,0 +1,15 @@ +package secgroups + +import "github.com/opentelekomcloud/gophertelekomcloud" + +func actionMap(prefix, groupName string) map[string]map[string]string { + return map[string]map[string]string{ + prefix + "SecurityGroup": {"name": groupName}, + } +} + +// AddServer will associate a server and a security group, enforcing the rules of the group on the server. +func AddServer(client *golangsdk.ServiceClient, serverID, groupName string) (err error) { + _, err = client.Post(client.ServiceURL("servers", serverID, "action"), actionMap("add", groupName), nil, nil) + return +} diff --git a/openstack/compute/v2/extensions/secgroups/create.go b/openstack/compute/v2/extensions/secgroups/create.go new file mode 100644 index 000000000..01a9f6e42 --- /dev/null +++ b/openstack/compute/v2/extensions/secgroups/create.go @@ -0,0 +1,28 @@ +package secgroups + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// GroupOpts is the underlying struct responsible for creating or updating +// security groups. It therefore represents the mutable attributes of a security group. +type GroupOpts struct { + // the name of your security group. + Name string `json:"name" required:"true"` + // the description of your security group. + Description string `json:"description" required:"true"` +} + +// Create will create a new security group. +func Create(client *golangsdk.ServiceClient, opts GroupOpts) (*SecurityGroup, error) { + b, err := build.RequestBody(opts, "security_group") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("os-security-groups"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/secgroups/create_rule.go b/openstack/compute/v2/extensions/secgroups/create_rule.go new file mode 100644 index 000000000..a21000a46 --- /dev/null +++ b/openstack/compute/v2/extensions/secgroups/create_rule.go @@ -0,0 +1,59 @@ +package secgroups + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// CreateRuleOpts represents the configuration for adding a new rule to an +// existing security group. +type CreateRuleOpts struct { + // ID is the ID of the group that this rule will be added to. + ParentGroupID string `json:"parent_group_id" required:"true"` + + // FromPort is the lower bound of the port range that will be opened. + // Use -1 to allow all ICMP traffic. + FromPort int `json:"from_port"` + + // ToPort is the upper bound of the port range that will be opened. + // Use -1 to allow all ICMP traffic. + ToPort int `json:"to_port"` + + // IPProtocol the protocol type that will be allowed, e.g. TCP. + IPProtocol string `json:"ip_protocol" required:"true"` + + // CIDR is the network CIDR to allow traffic from. + // This is ONLY required if FromGroupID is blank. This represents the IP + // range that will be the source of network traffic to your security group. + // Use 0.0.0.0/0 to allow all IP addresses. + CIDR string `json:"cidr,omitempty" or:"FromGroupID"` + + // FromGroupID represents another security group to allow access. + // This is ONLY required if CIDR is blank. This value represents the ID of a + // group that forwards traffic to the parent group. So, instead of accepting + // network traffic from an entire IP range, you can instead refine the + // inbound source by an existing security group. + FromGroupID string `json:"group_id,omitempty" or:"CIDR"` +} + +// CreateRule will add a new rule to an existing security group (whose ID is +// specified in CreateRuleOpts). You have the option of controlling inbound +// traffic from either an IP range (CIDR) or from another security group. +func CreateRule(client *golangsdk.ServiceClient, opts CreateRuleOpts) (*Rule, error) { + b, err := build.RequestBody(opts, "security_group_rule") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("os-security-group-rules"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + if err != nil { + return nil, err + } + + var res Rule + err = extract.IntoStructPtr(raw.Body, &res, "security_group_rule") + return &res, err +} diff --git a/openstack/compute/v2/extensions/secgroups/delete.go b/openstack/compute/v2/extensions/secgroups/delete.go new file mode 100644 index 000000000..f70829112 --- /dev/null +++ b/openstack/compute/v2/extensions/secgroups/delete.go @@ -0,0 +1,9 @@ +package secgroups + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Delete will permanently delete a security group from the project. +func Delete(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Delete(client.ServiceURL("os-security-groups", id), nil) + return +} diff --git a/openstack/compute/v2/extensions/secgroups/delete_rule.go b/openstack/compute/v2/extensions/secgroups/delete_rule.go new file mode 100644 index 000000000..dabe2a162 --- /dev/null +++ b/openstack/compute/v2/extensions/secgroups/delete_rule.go @@ -0,0 +1,9 @@ +package secgroups + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// DeleteRule will permanently delete a rule from a security group. +func DeleteRule(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Delete(client.ServiceURL("os-security-group-rules", id), nil) + return +} diff --git a/openstack/compute/v2/extensions/secgroups/delete_with_retry.go b/openstack/compute/v2/extensions/secgroups/delete_with_retry.go new file mode 100644 index 000000000..ba67993bb --- /dev/null +++ b/openstack/compute/v2/extensions/secgroups/delete_with_retry.go @@ -0,0 +1,23 @@ +package secgroups + +import ( + "time" + + "github.com/opentelekomcloud/gophertelekomcloud" +) + +// DeleteWithRetry will try to permanently delete a particular security +// group based on its unique ID and RetryTimeout. +func DeleteWithRetry(client *golangsdk.ServiceClient, id string, timeout int) error { + return golangsdk.WaitFor(timeout, func() (bool, error) { + _, err := client.Delete(client.ServiceURL("os-security-groups", id), nil) + if err != nil { + if _, ok := err.(golangsdk.ErrDefault400); ok { + time.Sleep(10 * time.Second) + return false, nil + } + return false, err + } + return true, nil + }) +} diff --git a/openstack/compute/v2/extensions/secgroups/get.go b/openstack/compute/v2/extensions/secgroups/get.go new file mode 100644 index 000000000..a7e80bff3 --- /dev/null +++ b/openstack/compute/v2/extensions/secgroups/get.go @@ -0,0 +1,9 @@ +package secgroups + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Get will return details for a particular security group. +func Get(client *golangsdk.ServiceClient, id string) (*SecurityGroup, error) { + raw, err := client.Get(client.ServiceURL("os-security-groups", id), nil, nil) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/secgroups/list.go b/openstack/compute/v2/extensions/secgroups/list.go new file mode 100644 index 000000000..1a4cb47d7 --- /dev/null +++ b/openstack/compute/v2/extensions/secgroups/list.go @@ -0,0 +1,30 @@ +package secgroups + +import ( + "net/http" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// List will return a collection of all the security groups for a particular tenant. +func List(client *golangsdk.ServiceClient) ([]SecurityGroup, error) { + raw, err := client.Get(client.ServiceURL("os-security-groups"), nil, nil) + return extra2(err, raw) +} + +// ListByServer will return a collection of all the security groups which are associated with a particular server. +func ListByServer(client *golangsdk.ServiceClient, serverID string) ([]SecurityGroup, error) { + raw, err := client.Get(client.ServiceURL("servers", serverID, "os-security-groups"), nil, nil) + return extra2(err, raw) +} + +func extra2(err error, raw *http.Response) ([]SecurityGroup, error) { + if err != nil { + return nil, err + } + + var res []SecurityGroup + err = extract.IntoSlicePtr(raw.Body, &res, "security_groups") + return res, err +} diff --git a/openstack/compute/v2/extensions/secgroups/remove_server.go b/openstack/compute/v2/extensions/secgroups/remove_server.go new file mode 100644 index 000000000..def25a6d5 --- /dev/null +++ b/openstack/compute/v2/extensions/secgroups/remove_server.go @@ -0,0 +1,9 @@ +package secgroups + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// RemoveServer will disassociate a server from a security group. +func RemoveServer(client *golangsdk.ServiceClient, serverID, groupName string) (err error) { + _, err = client.Post(client.ServiceURL("servers", serverID, "action"), actionMap("remove", groupName), nil, nil) + return +} diff --git a/openstack/compute/v2/extensions/secgroups/requests.go b/openstack/compute/v2/extensions/secgroups/requests.go deleted file mode 100644 index ae4450243..000000000 --- a/openstack/compute/v2/extensions/secgroups/requests.go +++ /dev/null @@ -1,201 +0,0 @@ -package secgroups - -import ( - "time" - - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -func commonList(client *golangsdk.ServiceClient, url string) pagination.Pager { - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return SecurityGroupPage{pagination.SinglePageBase(r)} - }) -} - -// List will return a collection of all the security groups for a particular -// tenant. -func List(client *golangsdk.ServiceClient) pagination.Pager { - return commonList(client, rootURL(client)) -} - -// ListByServer will return a collection of all the security groups which are -// associated with a particular server. -func ListByServer(client *golangsdk.ServiceClient, serverID string) pagination.Pager { - return commonList(client, listByServerURL(client, serverID)) -} - -// GroupOpts is the underlying struct responsible for creating or updating -// security groups. It therefore represents the mutable attributes of a -// security group. -type GroupOpts struct { - // the name of your security group. - Name string `json:"name" required:"true"` - // the description of your security group. - Description string `json:"description" required:"true"` -} - -// CreateOpts is the struct responsible for creating a security group. -type CreateOpts GroupOpts - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToSecGroupCreateMap() (map[string]interface{}, error) -} - -// ToSecGroupCreateMap builds a request body from CreateOpts. -func (opts CreateOpts) ToSecGroupCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "security_group") -} - -// Create will create a new security group. -func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToSecGroupCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(rootURL(client), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// UpdateOpts is the struct responsible for updating an existing security group. -type UpdateOpts GroupOpts - -// UpdateOptsBuilder allows extensions to add additional parameters to the -// Update request. -type UpdateOptsBuilder interface { - ToSecGroupUpdateMap() (map[string]interface{}, error) -} - -// ToSecGroupUpdateMap builds a request body from UpdateOpts. -func (opts UpdateOpts) ToSecGroupUpdateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "security_group") -} - -// Update will modify the mutable properties of a security group, notably its -// name and description. -func Update(client *golangsdk.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToSecGroupUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(resourceURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Get will return details for a particular security group. -func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(resourceURL(client, id), &r.Body, nil) - return -} - -// Delete will permanently delete a security group from the project. -func Delete(client *golangsdk.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(resourceURL(client, id), nil) - return -} - -// DeleteWithRetry will try to permanently delete a particular security -// group based on its unique ID and RetryTimeout. -func DeleteWithRetry(c *golangsdk.ServiceClient, id string, timeout int) error { - return golangsdk.WaitFor(timeout, func() (bool, error) { - _, err := c.Delete(resourceURL(c, id), nil) - if err != nil { - if _, ok := err.(golangsdk.ErrDefault400); ok { - time.Sleep(10 * time.Second) - return false, nil - } - return false, err - } - return true, nil - }) -} - -// CreateRuleOpts represents the configuration for adding a new rule to an -// existing security group. -type CreateRuleOpts struct { - // ID is the ID of the group that this rule will be added to. - ParentGroupID string `json:"parent_group_id" required:"true"` - - // FromPort is the lower bound of the port range that will be opened. - // Use -1 to allow all ICMP traffic. - FromPort int `json:"from_port"` - - // ToPort is the upper bound of the port range that will be opened. - // Use -1 to allow all ICMP traffic. - ToPort int `json:"to_port"` - - // IPProtocol the protocol type that will be allowed, e.g. TCP. - IPProtocol string `json:"ip_protocol" required:"true"` - - // CIDR is the network CIDR to allow traffic from. - // This is ONLY required if FromGroupID is blank. This represents the IP - // range that will be the source of network traffic to your security group. - // Use 0.0.0.0/0 to allow all IP addresses. - CIDR string `json:"cidr,omitempty" or:"FromGroupID"` - - // FromGroupID represents another security group to allow access. - // This is ONLY required if CIDR is blank. This value represents the ID of a - // group that forwards traffic to the parent group. So, instead of accepting - // network traffic from an entire IP range, you can instead refine the - // inbound source by an existing security group. - FromGroupID string `json:"group_id,omitempty" or:"CIDR"` -} - -// CreateRuleOptsBuilder allows extensions to add additional parameters to the -// CreateRule request. -type CreateRuleOptsBuilder interface { - ToRuleCreateMap() (map[string]interface{}, error) -} - -// ToRuleCreateMap builds a request body from CreateRuleOpts. -func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "security_group_rule") -} - -// CreateRule will add a new rule to an existing security group (whose ID is -// specified in CreateRuleOpts). You have the option of controlling inbound -// traffic from either an IP range (CIDR) or from another security group. -func CreateRule(client *golangsdk.ServiceClient, opts CreateRuleOptsBuilder) (r CreateRuleResult) { - b, err := opts.ToRuleCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(rootRuleURL(client), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// DeleteRule will permanently delete a rule from a security group. -func DeleteRule(client *golangsdk.ServiceClient, id string) (r DeleteRuleResult) { - _, r.Err = client.Delete(resourceRuleURL(client, id), nil) - return -} - -func actionMap(prefix, groupName string) map[string]map[string]string { - return map[string]map[string]string{ - prefix + "SecurityGroup": {"name": groupName}, - } -} - -// AddServer will associate a server and a security group, enforcing the -// rules of the group on the server. -func AddServer(client *golangsdk.ServiceClient, serverID, groupName string) (r AddServerResult) { - _, r.Err = client.Post(serverActionURL(client, serverID), actionMap("add", groupName), nil, nil) - return -} - -// RemoveServer will disassociate a server from a security group. -func RemoveServer(client *golangsdk.ServiceClient, serverID, groupName string) (r RemoveServerResult) { - _, r.Err = client.Post(serverActionURL(client, serverID), actionMap("remove", groupName), nil, nil) - return -} diff --git a/openstack/compute/v2/extensions/secgroups/results.go b/openstack/compute/v2/extensions/secgroups/results.go index 54732fae7..d2a0f5c86 100644 --- a/openstack/compute/v2/extensions/secgroups/results.go +++ b/openstack/compute/v2/extensions/secgroups/results.go @@ -2,10 +2,10 @@ package secgroups import ( "encoding/json" + "net/http" "strconv" - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" ) // SecurityGroup represents a security group. @@ -14,16 +14,12 @@ type SecurityGroup struct { // represented as a string UUID; if Neutron is not installed, it will be a // numeric ID. For the sake of consistency, we always cast it to a string. ID string `json:"-"` - // The human-readable name of the group, which needs to be unique. Name string `json:"name"` - // The human-readable description of the group. Description string `json:"description"` - // The rules which determine how this security group operates. Rules []Rule `json:"rules"` - // The ID of the tenant to which this security group belongs. TenantID string `json:"tenant_id"` } @@ -51,6 +47,16 @@ func (r *SecurityGroup) UnmarshalJSON(b []byte) error { return err } +func extra(err error, raw *http.Response) (*SecurityGroup, error) { + if err != nil { + return nil, err + } + + var res SecurityGroup + err = extract.IntoStructPtr(raw.Body, &res, "security_group") + return &res, err +} + // Rule represents a security group rule, a policy which determines how a // security group operates and what inbound traffic it allows in. type Rule struct { @@ -58,22 +64,16 @@ type Rule struct { // represented as a string UUID; if Neutron is not installed, it will be a // numeric ID. For the sake of consistency, we always cast it to a string. ID string `json:"-"` - // The lower bound of the port range which this security group should open up. FromPort int `json:"from_port"` - // The upper bound of the port range which this security group should open up. ToPort int `json:"to_port"` - // The IP protocol (e.g. TCP) which the security group accepts. IPProtocol string `json:"ip_protocol"` - // The CIDR IP range whose traffic can be received. IPRange IPRange `json:"ip_range"` - // The security group ID to which this rule belongs. ParentGroupID string `json:"-"` - // Not documented. Group Group } @@ -120,95 +120,3 @@ type Group struct { TenantID string `json:"tenant_id"` Name string } - -// SecurityGroupPage is a single page of a SecurityGroup collection. -type SecurityGroupPage struct { - pagination.SinglePageBase -} - -// IsEmpty determines whether or not a page of Security Groups contains any -// results. -func (page SecurityGroupPage) IsEmpty() (bool, error) { - users, err := ExtractSecurityGroups(page) - return len(users) == 0, err -} - -// ExtractSecurityGroups returns a slice of SecurityGroups contained in a -// single page of results. -func ExtractSecurityGroups(r pagination.Page) ([]SecurityGroup, error) { - var s struct { - SecurityGroups []SecurityGroup `json:"security_groups"` - } - err := (r.(SecurityGroupPage)).ExtractInto(&s) - return s.SecurityGroups, err -} - -type commonResult struct { - golangsdk.Result -} - -// CreateResult represents the result of a create operation. Call its Extract -// method to interpret the result as a SecurityGroup. -type CreateResult struct { - commonResult -} - -// GetResult represents the result of a get operation. Call its Extract -// method to interpret the result as a SecurityGroup. -type GetResult struct { - commonResult -} - -// UpdateResult represents the result of an update operation. Call its Extract -// method to interpret the result as a SecurityGroup. -type UpdateResult struct { - commonResult -} - -// Extract will extract a SecurityGroup struct from most responses. -func (r commonResult) Extract() (*SecurityGroup, error) { - var s struct { - SecurityGroup *SecurityGroup `json:"security_group"` - } - err := r.ExtractInto(&s) - return s.SecurityGroup, err -} - -// CreateRuleResult represents the result when adding rules to a security group. -// Call its Extract method to interpret the result as a Rule. -type CreateRuleResult struct { - golangsdk.Result -} - -// Extract will extract a Rule struct from a CreateRuleResult. -func (r CreateRuleResult) Extract() (*Rule, error) { - var s struct { - Rule *Rule `json:"security_group_rule"` - } - err := r.ExtractInto(&s) - return s.Rule, err -} - -// DeleteResult is the response from delete operation. Call its ExtractErr -// method to determine if the request succeeded or failed. -type DeleteResult struct { - golangsdk.ErrResult -} - -// DeleteRuleResult is the response from a DeleteRule operation. Call its -// ExtractErr method to determine if the request succeeded or failed. -type DeleteRuleResult struct { - golangsdk.ErrResult -} - -// AddServerResult is the response from an AddServer operation. Call its -// ExtractErr method to determine if the request succeeded or failed. -type AddServerResult struct { - golangsdk.ErrResult -} - -// RemoveServerResult is the response from a RemoveServer operation. Call its -// ExtractErr method to determine if the request succeeded or failed. -type RemoveServerResult struct { - golangsdk.ErrResult -} diff --git a/openstack/compute/v2/extensions/secgroups/testing/doc.go b/openstack/compute/v2/extensions/secgroups/testing/doc.go deleted file mode 100644 index c5e60ea09..000000000 --- a/openstack/compute/v2/extensions/secgroups/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// secgroups unit tests -package testing diff --git a/openstack/compute/v2/extensions/secgroups/testing/requests_test.go b/openstack/compute/v2/extensions/secgroups/testing/requests_test.go index f5427b32c..a7a817e90 100644 --- a/openstack/compute/v2/extensions/secgroups/testing/requests_test.go +++ b/openstack/compute/v2/extensions/secgroups/testing/requests_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/secgroups" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) @@ -21,33 +20,20 @@ func TestList(t *testing.T) { mockListGroupsResponse(t) - count := 0 - - err := secgroups.List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { - count++ - actual, err := secgroups.ExtractSecurityGroups(page) - if err != nil { - t.Errorf("Failed to extract users: %v", err) - return false, err - } - - expected := []secgroups.SecurityGroup{ - { - ID: groupID, - Description: "default", - Name: "default", - Rules: []secgroups.Rule{}, - TenantID: "openstack", - }, - } - - th.CheckDeepEquals(t, expected, actual) + actual, err := secgroups.List(client.ServiceClient()) + th.AssertNoErr(t, err) - return true, nil - }) + expected := []secgroups.SecurityGroup{ + { + ID: groupID, + Description: "default", + Name: "default", + Rules: []secgroups.Rule{}, + TenantID: "openstack", + }, + } - th.AssertNoErr(t, err) - th.AssertEquals(t, 1, count) + th.CheckDeepEquals(t, expected, actual) } func TestListByServer(t *testing.T) { @@ -56,33 +42,20 @@ func TestListByServer(t *testing.T) { mockListGroupsByServerResponse(t, serverID) - count := 0 - - err := secgroups.ListByServer(client.ServiceClient(), serverID).EachPage(func(page pagination.Page) (bool, error) { - count++ - actual, err := secgroups.ExtractSecurityGroups(page) - if err != nil { - t.Errorf("Failed to extract users: %v", err) - return false, err - } - - expected := []secgroups.SecurityGroup{ - { - ID: groupID, - Description: "default", - Name: "default", - Rules: []secgroups.Rule{}, - TenantID: "openstack", - }, - } - - th.CheckDeepEquals(t, expected, actual) + actual, err := secgroups.ListByServer(client.ServiceClient(), serverID) + th.AssertNoErr(t, err) - return true, nil - }) + expected := []secgroups.SecurityGroup{ + { + ID: groupID, + Description: "default", + Name: "default", + Rules: []secgroups.Rule{}, + TenantID: "openstack", + }, + } - th.AssertNoErr(t, err) - th.AssertEquals(t, 1, count) + th.CheckDeepEquals(t, expected, actual) } func TestCreate(t *testing.T) { @@ -91,12 +64,12 @@ func TestCreate(t *testing.T) { mockCreateGroupResponse(t) - opts := secgroups.CreateOpts{ + opts := secgroups.GroupOpts{ Name: "test", Description: "something", } - group, err := secgroups.Create(client.ServiceClient(), opts).Extract() + group, err := secgroups.Create(client.ServiceClient(), opts) th.AssertNoErr(t, err) expected := &secgroups.SecurityGroup{ @@ -115,12 +88,12 @@ func TestUpdate(t *testing.T) { mockUpdateGroupResponse(t, groupID) - opts := secgroups.UpdateOpts{ + opts := secgroups.GroupOpts{ Name: "new_name", Description: "new_desc", } - group, err := secgroups.Update(client.ServiceClient(), groupID, opts).Extract() + group, err := secgroups.Update(client.ServiceClient(), groupID, opts) th.AssertNoErr(t, err) expected := &secgroups.SecurityGroup{ @@ -139,7 +112,7 @@ func TestGet(t *testing.T) { mockGetGroupsResponse(t, groupID) - group, err := secgroups.Get(client.ServiceClient(), groupID).Extract() + group, err := secgroups.Get(client.ServiceClient(), groupID) th.AssertNoErr(t, err) expected := &secgroups.SecurityGroup{ @@ -171,7 +144,7 @@ func TestGetNumericID(t *testing.T) { mockGetNumericIDGroupResponse(t, numericGroupID) - group, err := secgroups.Get(client.ServiceClient(), "12345").Extract() + group, err := secgroups.Get(client.ServiceClient(), "12345") th.AssertNoErr(t, err) expected := &secgroups.SecurityGroup{ID: "12345"} @@ -186,7 +159,7 @@ func TestGetNumericRuleID(t *testing.T) { mockGetNumericIDGroupRuleResponse(t, numericGroupID) - group, err := secgroups.Get(client.ServiceClient(), "12345").Extract() + group, err := secgroups.Get(client.ServiceClient(), "12345") th.AssertNoErr(t, err) expected := &secgroups.SecurityGroup{ @@ -207,7 +180,7 @@ func TestDelete(t *testing.T) { mockDeleteGroupResponse(t, groupID) - err := secgroups.Delete(client.ServiceClient(), groupID).ExtractErr() + err := secgroups.Delete(client.ServiceClient(), groupID) th.AssertNoErr(t, err) } @@ -225,7 +198,7 @@ func TestAddRule(t *testing.T) { CIDR: "0.0.0.0/0", } - rule, err := secgroups.CreateRule(client.ServiceClient(), opts).Extract() + rule, err := secgroups.CreateRule(client.ServiceClient(), opts) th.AssertNoErr(t, err) expected := &secgroups.Rule{ @@ -255,7 +228,7 @@ func TestAddRuleICMPZero(t *testing.T) { CIDR: "0.0.0.0/0", } - rule, err := secgroups.CreateRule(client.ServiceClient(), opts).Extract() + rule, err := secgroups.CreateRule(client.ServiceClient(), opts) th.AssertNoErr(t, err) expected := &secgroups.Rule{ @@ -277,7 +250,7 @@ func TestDeleteRule(t *testing.T) { mockDeleteRuleResponse(t, ruleID) - err := secgroups.DeleteRule(client.ServiceClient(), ruleID).ExtractErr() + err := secgroups.DeleteRule(client.ServiceClient(), ruleID) th.AssertNoErr(t, err) } @@ -287,7 +260,7 @@ func TestAddServer(t *testing.T) { mockAddServerToGroupResponse(t, serverID) - err := secgroups.AddServer(client.ServiceClient(), serverID, "test").ExtractErr() + err := secgroups.AddServer(client.ServiceClient(), serverID, "test") th.AssertNoErr(t, err) } @@ -297,6 +270,6 @@ func TestRemoveServer(t *testing.T) { mockRemoveServerFromGroupResponse(t, serverID) - err := secgroups.RemoveServer(client.ServiceClient(), serverID, "test").ExtractErr() + err := secgroups.RemoveServer(client.ServiceClient(), serverID, "test") th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/secgroups/update.go b/openstack/compute/v2/extensions/secgroups/update.go new file mode 100644 index 000000000..e91e00430 --- /dev/null +++ b/openstack/compute/v2/extensions/secgroups/update.go @@ -0,0 +1,19 @@ +package secgroups + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// Update will modify the mutable properties of a security group, notably its name and description. +func Update(client *golangsdk.ServiceClient, id string, opts GroupOpts) (*SecurityGroup, error) { + b, err := build.RequestBody(opts, "security_group") + if err != nil { + return nil, err + } + + raw, err := client.Put(client.ServiceURL("os-security-groups", id), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/secgroups/urls.go b/openstack/compute/v2/extensions/secgroups/urls.go deleted file mode 100644 index 61db77e14..000000000 --- a/openstack/compute/v2/extensions/secgroups/urls.go +++ /dev/null @@ -1,32 +0,0 @@ -package secgroups - -import "github.com/opentelekomcloud/gophertelekomcloud" - -const ( - secgrouppath = "os-security-groups" - rulepath = "os-security-group-rules" -) - -func resourceURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL(secgrouppath, id) -} - -func rootURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL(secgrouppath) -} - -func listByServerURL(c *golangsdk.ServiceClient, serverID string) string { - return c.ServiceURL("servers", serverID, secgrouppath) -} - -func rootRuleURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL(rulepath) -} - -func resourceRuleURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL(rulepath, id) -} - -func serverActionURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL("servers", id, "action") -} diff --git a/openstack/compute/v2/extensions/servergroups/create.go b/openstack/compute/v2/extensions/servergroups/create.go new file mode 100644 index 000000000..c7e23d80c --- /dev/null +++ b/openstack/compute/v2/extensions/servergroups/create.go @@ -0,0 +1,27 @@ +package servergroups + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// CreateOpts specifies Server Group creation parameters. +type CreateOpts struct { + // Name is the name of the server group + Name string `json:"name" required:"true"` + // Policies are the server group policies + Policies []string `json:"policies" required:"true"` +} + +// Create requests the creation of a new Server Group. +func Create(client *golangsdk.ServiceClient, opts CreateOpts) (*ServerGroup, error) { + b, err := build.RequestBody(opts, "server_group") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("os-server-groups"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/servergroups/delete.go b/openstack/compute/v2/extensions/servergroups/delete.go new file mode 100644 index 000000000..5559efdfe --- /dev/null +++ b/openstack/compute/v2/extensions/servergroups/delete.go @@ -0,0 +1,9 @@ +package servergroups + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Delete requests the deletion of a previously allocated ServerGroup. +func Delete(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Delete(client.ServiceURL("os-server-groups", id), nil) + return +} diff --git a/openstack/compute/v2/extensions/servergroups/get.go b/openstack/compute/v2/extensions/servergroups/get.go new file mode 100644 index 000000000..b85ed7922 --- /dev/null +++ b/openstack/compute/v2/extensions/servergroups/get.go @@ -0,0 +1,9 @@ +package servergroups + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Get returns data about a previously created ServerGroup. +func Get(client *golangsdk.ServiceClient, id string) (*ServerGroup, error) { + raw, err := client.Get(client.ServiceURL("os-server-groups", id), nil, nil) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/servergroups/list.go b/openstack/compute/v2/extensions/servergroups/list.go new file mode 100644 index 000000000..816c29b74 --- /dev/null +++ b/openstack/compute/v2/extensions/servergroups/list.go @@ -0,0 +1,18 @@ +package servergroups + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// List returns a Pager that allows you to iterate over a collection of ServerGroups. +func List(client *golangsdk.ServiceClient) ([]ServerGroup, error) { + raw, err := client.Get(client.ServiceURL("os-server-groups"), nil, nil) + if err != nil { + return nil, err + } + + var res []ServerGroup + err = extract.IntoSlicePtr(raw.Body, &res, "server_groups") + return res, err +} diff --git a/openstack/compute/v2/extensions/servergroups/requests.go b/openstack/compute/v2/extensions/servergroups/requests.go deleted file mode 100644 index 37dd9e919..000000000 --- a/openstack/compute/v2/extensions/servergroups/requests.go +++ /dev/null @@ -1,59 +0,0 @@ -package servergroups - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// List returns a Pager that allows you to iterate over a collection of -// ServerGroups. -func List(client *golangsdk.ServiceClient) pagination.Pager { - return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { - return ServerGroupPage{pagination.SinglePageBase(r)} - }) -} - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToServerGroupCreateMap() (map[string]interface{}, error) -} - -// CreateOpts specifies Server Group creation parameters. -type CreateOpts struct { - // Name is the name of the server group - Name string `json:"name" required:"true"` - - // Policies are the server group policies - Policies []string `json:"policies" required:"true"` -} - -// ToServerGroupCreateMap constructs a request body from CreateOpts. -func (opts CreateOpts) ToServerGroupCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "server_group") -} - -// Create requests the creation of a new Server Group. -func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToServerGroupCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Get returns data about a previously created ServerGroup. -func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) - return -} - -// Delete requests the deletion of a previously allocated ServerGroup. -func Delete(client *golangsdk.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) - return -} diff --git a/openstack/compute/v2/extensions/servergroups/results.go b/openstack/compute/v2/extensions/servergroups/results.go index ee4012869..87ecb84c4 100644 --- a/openstack/compute/v2/extensions/servergroups/results.go +++ b/openstack/compute/v2/extensions/servergroups/results.go @@ -1,87 +1,34 @@ package servergroups import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" + "net/http" + + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" ) // A ServerGroup creates a policy for instance placement in the cloud. type ServerGroup struct { // ID is the unique ID of the Server Group. ID string `json:"id"` - // Name is the common name of the server group. Name string `json:"name"` - // Polices are the group policies. - // // Normally a single policy is applied: - // - // "affinity" will place all servers within the server group on the - // same compute node. - // - // "anti-affinity" will place servers within the server group on different - // compute nodes. + // "affinity" will place all servers within the server group on the same compute node. + // "anti-affinity" will place servers within the server group on different compute nodes. Policies []string `json:"policies"` - // Members are the members of the server group. Members []string `json:"members"` - - // Metadata includes a list of all user-specified key-value pairs attached - // to the Server Group. + // Metadata includes a list of all user-specified key-value pairs attached to the Server Group. Metadata map[string]interface{} } -// ServerGroupPage stores a single page of all ServerGroups results from a -// List call. -type ServerGroupPage struct { - pagination.SinglePageBase -} - -// IsEmpty determines whether or not a ServerGroupsPage is empty. -func (page ServerGroupPage) IsEmpty() (bool, error) { - va, err := ExtractServerGroups(page) - return len(va) == 0, err -} - -// ExtractServerGroups interprets a page of results as a slice of -// ServerGroups. -func ExtractServerGroups(r pagination.Page) ([]ServerGroup, error) { - var s struct { - ServerGroups []ServerGroup `json:"server_groups"` +func extra(err error, raw *http.Response) (*ServerGroup, error) { + if err != nil { + return nil, err } - err := (r.(ServerGroupPage)).ExtractInto(&s) - return s.ServerGroups, err -} - -type ServerGroupResult struct { - golangsdk.Result -} - -// Extract is a method that attempts to interpret any Server Group resource -// response as a ServerGroup struct. -func (r ServerGroupResult) Extract() (*ServerGroup, error) { - var s struct { - ServerGroup *ServerGroup `json:"server_group"` - } - err := r.ExtractInto(&s) - return s.ServerGroup, err -} - -// CreateResult is the response from a Create operation. Call its Extract method -// to interpret it as a ServerGroup. -type CreateResult struct { - ServerGroupResult -} - -// GetResult is the response from a Get operation. Call its Extract method to -// interpret it as a ServerGroup. -type GetResult struct { - ServerGroupResult -} -// DeleteResult is the response from a Delete operation. Call its ExtractErr -// method to determine if the call succeeded or failed. -type DeleteResult struct { - golangsdk.ErrResult + var res ServerGroup + err = extract.IntoStructPtr(raw.Body, &res, "server_group") + return &res, err } diff --git a/openstack/compute/v2/extensions/servergroups/testing/doc.go b/openstack/compute/v2/extensions/servergroups/testing/doc.go deleted file mode 100644 index 644bb49df..000000000 --- a/openstack/compute/v2/extensions/servergroups/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// servergroups unit tests -package testing diff --git a/openstack/compute/v2/extensions/servergroups/testing/requests_test.go b/openstack/compute/v2/extensions/servergroups/testing/requests_test.go index be739b040..b78a7b072 100644 --- a/openstack/compute/v2/extensions/servergroups/testing/requests_test.go +++ b/openstack/compute/v2/extensions/servergroups/testing/requests_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/servergroups" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) @@ -14,17 +13,9 @@ func TestList(t *testing.T) { defer th.TeardownHTTP() HandleListSuccessfully(t) - count := 0 - err := servergroups.List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { - count++ - actual, err := servergroups.ExtractServerGroups(page) - th.AssertNoErr(t, err) - th.CheckDeepEquals(t, ExpectedServerGroupSlice, actual) - - return true, nil - }) + actual, err := servergroups.List(client.ServiceClient()) th.AssertNoErr(t, err) - th.CheckEquals(t, 1, count) + th.CheckDeepEquals(t, ExpectedServerGroupSlice, actual) } func TestCreate(t *testing.T) { @@ -35,7 +26,7 @@ func TestCreate(t *testing.T) { actual, err := servergroups.Create(client.ServiceClient(), servergroups.CreateOpts{ Name: "test", Policies: []string{"anti-affinity"}, - }).Extract() + }) th.AssertNoErr(t, err) th.CheckDeepEquals(t, &CreatedServerGroup, actual) } @@ -45,7 +36,7 @@ func TestGet(t *testing.T) { defer th.TeardownHTTP() HandleGetSuccessfully(t) - actual, err := servergroups.Get(client.ServiceClient(), "4d8c3732-a248-40ed-bebc-539a6ffd25c0").Extract() + actual, err := servergroups.Get(client.ServiceClient(), "4d8c3732-a248-40ed-bebc-539a6ffd25c0") th.AssertNoErr(t, err) th.CheckDeepEquals(t, &FirstServerGroup, actual) } @@ -55,6 +46,6 @@ func TestDelete(t *testing.T) { defer th.TeardownHTTP() HandleDeleteSuccessfully(t) - err := servergroups.Delete(client.ServiceClient(), "616fb98f-46ca-475e-917e-2563e5a8cd19").ExtractErr() + err := servergroups.Delete(client.ServiceClient(), "616fb98f-46ca-475e-917e-2563e5a8cd19") th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/servergroups/urls.go b/openstack/compute/v2/extensions/servergroups/urls.go deleted file mode 100644 index 010a53830..000000000 --- a/openstack/compute/v2/extensions/servergroups/urls.go +++ /dev/null @@ -1,25 +0,0 @@ -package servergroups - -import "github.com/opentelekomcloud/gophertelekomcloud" - -const resourcePath = "os-server-groups" - -func resourceURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL(resourcePath) -} - -func listURL(c *golangsdk.ServiceClient) string { - return resourceURL(c) -} - -func createURL(c *golangsdk.ServiceClient) string { - return resourceURL(c) -} - -func getURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL(resourcePath, id) -} - -func deleteURL(c *golangsdk.ServiceClient, id string) string { - return getURL(c, id) -} diff --git a/openstack/compute/v2/extensions/serverusage/testing/doc.go b/openstack/compute/v2/extensions/serverusage/testing/doc.go deleted file mode 100644 index 7603f836a..000000000 --- a/openstack/compute/v2/extensions/serverusage/testing/doc.go +++ /dev/null @@ -1 +0,0 @@ -package testing diff --git a/openstack/compute/v2/extensions/serverusage/testing/requests_test.go b/openstack/compute/v2/extensions/serverusage/testing/requests_test.go index de0221f2f..ac1db12ef 100644 --- a/openstack/compute/v2/extensions/serverusage/testing/requests_test.go +++ b/openstack/compute/v2/extensions/serverusage/testing/requests_test.go @@ -29,7 +29,7 @@ func TestServerWithUsageExt(t *testing.T) { serverusage.UsageExt } var serverWithUsageExt serverUsageExt - err := servers.Get(fake.ServiceClient(), "d650a0ce-17c3-497d-961a-43c4af80998a").ExtractInto(&serverWithUsageExt) + err := servers.GetInto(fake.ServiceClient(), "d650a0ce-17c3-497d-961a-43c4af80998a", &serverWithUsageExt) th.AssertNoErr(t, err) th.AssertEquals(t, serverWithUsageExt.LaunchedAt, time.Date(2018, 07, 27, 9, 15, 55, 0, time.UTC)) diff --git a/openstack/compute/v2/extensions/services/results.go b/openstack/compute/v2/extensions/services/list.go similarity index 65% rename from openstack/compute/v2/extensions/services/results.go rename to openstack/compute/v2/extensions/services/list.go index 64ef5ebf1..a69c8102f 100644 --- a/openstack/compute/v2/extensions/services/results.go +++ b/openstack/compute/v2/extensions/services/list.go @@ -5,32 +5,37 @@ import ( "time" "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" ) +// List makes a request against the API to list services. +func List(client *golangsdk.ServiceClient) ([]Service, error) { + raw, err := client.Get(client.ServiceURL("os-services"), nil, nil) + if err != nil { + return nil, err + } + + var res []Service + err = extract.IntoSlicePtr(raw.Body, &res, "services") + return res, err +} + // Service represents a Compute service in the OpenStack cloud. type Service struct { // The binary name of the service. Binary string `json:"binary"` - // The reason for disabling a service. DisabledReason string `json:"disabled_reason"` - // The name of the host. Host string `json:"host"` - // The id of the service. ID int `json:"id"` - // The state of the service. One of up or down. State string `json:"state"` - // The status of the service. One of enabled or disabled. Status string `json:"status"` - // The date and time when the resource was updated. UpdatedAt time.Time `json:"-"` - // The availability zone name. Zone string `json:"zone"` } @@ -52,22 +57,3 @@ func (r *Service) UnmarshalJSON(b []byte) error { return nil } - -// ServicePage represents a single page of all Services from a List request. -type ServicePage struct { - pagination.SinglePageBase -} - -// IsEmpty determines whether or not a page of Services contains any results. -func (page ServicePage) IsEmpty() (bool, error) { - services, err := ExtractServices(page) - return len(services) == 0, err -} - -func ExtractServices(r pagination.Page) ([]Service, error) { - var s struct { - Service []Service `json:"services"` - } - err := (r.(ServicePage)).ExtractInto(&s) - return s.Service, err -} diff --git a/openstack/compute/v2/extensions/services/requests.go b/openstack/compute/v2/extensions/services/requests.go deleted file mode 100644 index 81b1d9af8..000000000 --- a/openstack/compute/v2/extensions/services/requests.go +++ /dev/null @@ -1,13 +0,0 @@ -package services - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// List makes a request against the API to list services. -func List(client *golangsdk.ServiceClient) pagination.Pager { - return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { - return ServicePage{pagination.SinglePageBase(r)} - }) -} diff --git a/openstack/compute/v2/extensions/services/testing/requests_test.go b/openstack/compute/v2/extensions/services/testing/requests_test.go index e5e1509a2..911da0f5d 100644 --- a/openstack/compute/v2/extensions/services/testing/requests_test.go +++ b/openstack/compute/v2/extensions/services/testing/requests_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/services" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) @@ -14,29 +13,14 @@ func TestListServices(t *testing.T) { defer testhelper.TeardownHTTP() HandleListSuccessfully(t) - pages := 0 - err := services.List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { - pages++ - - actual, err := services.ExtractServices(page) - if err != nil { - return false, err - } - - if len(actual) != 4 { - t.Fatalf("Expected 4 services, got %d", len(actual)) - } - testhelper.CheckDeepEquals(t, FirstFakeService, actual[0]) - testhelper.CheckDeepEquals(t, SecondFakeService, actual[1]) - testhelper.CheckDeepEquals(t, ThirdFakeService, actual[2]) - testhelper.CheckDeepEquals(t, FourthFakeService, actual[3]) - - return true, nil - }) - + actual, err := services.List(client.ServiceClient()) testhelper.AssertNoErr(t, err) - if pages != 1 { - t.Errorf("Expected 1 page, saw %d", pages) + if len(actual) != 4 { + t.Fatalf("Expected 4 services, got %d", len(actual)) } + testhelper.CheckDeepEquals(t, FirstFakeService, actual[0]) + testhelper.CheckDeepEquals(t, SecondFakeService, actual[1]) + testhelper.CheckDeepEquals(t, ThirdFakeService, actual[2]) + testhelper.CheckDeepEquals(t, FourthFakeService, actual[3]) } diff --git a/openstack/compute/v2/extensions/services/urls.go b/openstack/compute/v2/extensions/services/urls.go deleted file mode 100644 index a718789cd..000000000 --- a/openstack/compute/v2/extensions/services/urls.go +++ /dev/null @@ -1,7 +0,0 @@ -package services - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func listURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("os-services") -} diff --git a/openstack/compute/v2/extensions/startstop/requests.go b/openstack/compute/v2/extensions/startstop/requests.go index e796c3243..8cda1521a 100644 --- a/openstack/compute/v2/extensions/startstop/requests.go +++ b/openstack/compute/v2/extensions/startstop/requests.go @@ -7,13 +7,13 @@ func actionURL(client *golangsdk.ServiceClient, id string) string { } // Start is the operation responsible for starting a Compute server. -func Start(client *golangsdk.ServiceClient, id string) (r StartResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-start": nil}, nil, nil) +func Start(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Post(actionURL(client, id), map[string]interface{}{"os-start": nil}, nil, nil) return } // Stop is the operation responsible for stopping a Compute server. -func Stop(client *golangsdk.ServiceClient, id string) (r StopResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-stop": nil}, nil, nil) +func Stop(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Post(actionURL(client, id), map[string]interface{}{"os-stop": nil}, nil, nil) return } diff --git a/openstack/compute/v2/extensions/startstop/results.go b/openstack/compute/v2/extensions/startstop/results.go deleted file mode 100644 index 3ecf3687b..000000000 --- a/openstack/compute/v2/extensions/startstop/results.go +++ /dev/null @@ -1,15 +0,0 @@ -package startstop - -import "github.com/opentelekomcloud/gophertelekomcloud" - -// StartResult is the response from a Start operation. Call its ExtractErr -// method to determine if the request succeeded or failed. -type StartResult struct { - golangsdk.ErrResult -} - -// StopResult is the response from Stop operation. Call its ExtractErr -// method to determine if the request succeeded or failed. -type StopResult struct { - golangsdk.ErrResult -} diff --git a/openstack/compute/v2/extensions/startstop/testing/doc.go b/openstack/compute/v2/extensions/startstop/testing/doc.go deleted file mode 100644 index b6c5b8c14..000000000 --- a/openstack/compute/v2/extensions/startstop/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// startstop unit tests -package testing diff --git a/openstack/compute/v2/extensions/startstop/testing/requests_test.go b/openstack/compute/v2/extensions/startstop/testing/requests_test.go index 3df294fc0..fb07dec32 100644 --- a/openstack/compute/v2/extensions/startstop/testing/requests_test.go +++ b/openstack/compute/v2/extensions/startstop/testing/requests_test.go @@ -16,7 +16,7 @@ func TestStart(t *testing.T) { mockStartServerResponse(t, serverID) - err := startstop.Start(client.ServiceClient(), serverID).ExtractErr() + err := startstop.Start(client.ServiceClient(), serverID) th.AssertNoErr(t, err) } @@ -26,6 +26,6 @@ func TestStop(t *testing.T) { mockStopServerResponse(t, serverID) - err := startstop.Stop(client.ServiceClient(), serverID).ExtractErr() + err := startstop.Stop(client.ServiceClient(), serverID) th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/suspendresume/requests.go b/openstack/compute/v2/extensions/suspendresume/requests.go index d4f533c71..c822ab4fe 100644 --- a/openstack/compute/v2/extensions/suspendresume/requests.go +++ b/openstack/compute/v2/extensions/suspendresume/requests.go @@ -7,13 +7,13 @@ func actionURL(client *golangsdk.ServiceClient, id string) string { } // Suspend is the operation responsible for suspending a Compute server. -func Suspend(client *golangsdk.ServiceClient, id string) (r SuspendResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"suspend": nil}, nil, nil) +func Suspend(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Post(actionURL(client, id), map[string]interface{}{"suspend": nil}, nil, nil) return } // Resume is the operation responsible for resuming a Compute server. -func Resume(client *golangsdk.ServiceClient, id string) (r UnsuspendResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"resume": nil}, nil, nil) +func Resume(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Post(actionURL(client, id), map[string]interface{}{"resume": nil}, nil, nil) return } diff --git a/openstack/compute/v2/extensions/suspendresume/results.go b/openstack/compute/v2/extensions/suspendresume/results.go deleted file mode 100644 index c98aeb5fa..000000000 --- a/openstack/compute/v2/extensions/suspendresume/results.go +++ /dev/null @@ -1,15 +0,0 @@ -package suspendresume - -import "github.com/opentelekomcloud/gophertelekomcloud" - -// SuspendResult is the response from a Suspend operation. Call its -// ExtractErr method to determine if the request succeeded or failed. -type SuspendResult struct { - golangsdk.ErrResult -} - -// UnsuspendResult is the response from an Unsuspend operation. Call -// its ExtractErr method to determine if the request succeeded or failed. -type UnsuspendResult struct { - golangsdk.ErrResult -} diff --git a/openstack/compute/v2/extensions/suspendresume/testing/doc.go b/openstack/compute/v2/extensions/suspendresume/testing/doc.go deleted file mode 100644 index 834f25516..000000000 --- a/openstack/compute/v2/extensions/suspendresume/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// suspendresume unit tests -package testing diff --git a/openstack/compute/v2/extensions/suspendresume/testing/requests_test.go b/openstack/compute/v2/extensions/suspendresume/testing/requests_test.go index 7154f2d87..ce9d54581 100644 --- a/openstack/compute/v2/extensions/suspendresume/testing/requests_test.go +++ b/openstack/compute/v2/extensions/suspendresume/testing/requests_test.go @@ -16,7 +16,7 @@ func TestSuspend(t *testing.T) { mockSuspendServerResponse(t, serverID) - err := suspendresume.Suspend(client.ServiceClient(), serverID).ExtractErr() + err := suspendresume.Suspend(client.ServiceClient(), serverID) th.AssertNoErr(t, err) } @@ -26,6 +26,6 @@ func TestResume(t *testing.T) { mockResumeServerResponse(t, serverID) - err := suspendresume.Resume(client.ServiceClient(), serverID).ExtractErr() + err := suspendresume.Resume(client.ServiceClient(), serverID) th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/tags/requests.go b/openstack/compute/v2/extensions/tags/requests.go index 38b579803..3440d4cc9 100644 --- a/openstack/compute/v2/extensions/tags/requests.go +++ b/openstack/compute/v2/extensions/tags/requests.go @@ -1,52 +1,47 @@ package tags import ( + "net/http" + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" ) -// CreateOptsBuilder describes struct types that can be accepted by the Create call. -// The CreateOpts struct in this package does. -type CreateOptsBuilder interface { - // Returns value that can be passed to json.Marshal - ToTagsCreateMap() (map[string]interface{}, error) -} - -// CreateOpts implements CreateOptsBuilder -type CreateOpts struct { - // Tags is a set of tags. - Tags []string `json:"tags" required:"true"` +type Tags struct { + // Tags is a list of any tags. Tags are arbitrarily defined strings attached to a resource. + Tags []string `json:"tags"` } -// ToImageCreateMap assembles a request body based on the contents of -// a CreateOpts. -func (opts CreateOpts) ToTagsCreateMap() (map[string]interface{}, error) { - b, err := golangsdk.BuildRequestBody(opts, "") +// Create implements create tags request +func Create(client *golangsdk.ServiceClient, serverId string, opts Tags) (*Tags, error) { + b, err := build.RequestBody(opts, "") if err != nil { return nil, err } - return b, nil -} - -// Create implements create tags request -func Create(client *golangsdk.ServiceClient, server_id string, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToTagsCreateMap() - if err != nil { - r.Err = err - return r - } - _, r.Err = client.Put(createURL(client, server_id), b, &r.Body, &golangsdk.RequestOpts{OkCodes: []int{200}}) - return + raw, err := client.Put(client.ServiceURL("servers", serverId, "tags"), b, nil, &golangsdk.RequestOpts{OkCodes: []int{200}}) + return extra(err, raw) } // Get implements tags get request -func Get(client *golangsdk.ServiceClient, server_id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, server_id), &r.Body, nil) - return +func Get(client *golangsdk.ServiceClient, serverId string) (*Tags, error) { + raw, err := client.Get(client.ServiceURL("servers", serverId, "tags"), nil, nil) + return extra(err, raw) } // Delete implements image delete request -func Delete(client *golangsdk.ServiceClient, server_id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, server_id), nil) +func Delete(client *golangsdk.ServiceClient, serverId string) (err error) { + _, err = client.Delete(client.ServiceURL("servers", serverId, "tags"), nil) return } + +func extra(err error, raw *http.Response) (*Tags, error) { + if err != nil { + return nil, err + } + + var res Tags + err = extract.Into(raw.Body, &res) + return &res, err +} diff --git a/openstack/compute/v2/extensions/tags/results.go b/openstack/compute/v2/extensions/tags/results.go deleted file mode 100644 index 72c84dc31..000000000 --- a/openstack/compute/v2/extensions/tags/results.go +++ /dev/null @@ -1,38 +0,0 @@ -package tags - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -type commonResult struct { - golangsdk.Result -} - -// Tags model -type Tags struct { - // Tags is a list of any tags. Tags are arbitrarily defined strings - // attached to a resource. - Tags []string `json:"tags"` -} - -// Extract interprets any commonResult as a Tags. -func (r commonResult) Extract() (*Tags, error) { - var s *Tags - err := r.ExtractInto(&s) - return s, err -} - -// CreateResult represents the result of a Create operation -type CreateResult struct { - commonResult -} - -// GetResult represents the result of a Get operation -type GetResult struct { - commonResult -} - -// DeleteResult model -type DeleteResult struct { - golangsdk.ErrResult -} diff --git a/openstack/compute/v2/extensions/tags/urls.go b/openstack/compute/v2/extensions/tags/urls.go deleted file mode 100644 index 57a3e0e2d..000000000 --- a/openstack/compute/v2/extensions/tags/urls.go +++ /dev/null @@ -1,17 +0,0 @@ -package tags - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -func createURL(c *golangsdk.ServiceClient, server_id string) string { - return c.ServiceURL("servers", server_id, "tags") -} - -func getURL(c *golangsdk.ServiceClient, server_id string) string { - return c.ServiceURL("servers", server_id, "tags") -} - -func deleteURL(c *golangsdk.ServiceClient, server_id string) string { - return c.ServiceURL("servers", server_id, "tags") -} diff --git a/openstack/compute/v2/extensions/tenantnetworks/get.go b/openstack/compute/v2/extensions/tenantnetworks/get.go new file mode 100644 index 000000000..45136321f --- /dev/null +++ b/openstack/compute/v2/extensions/tenantnetworks/get.go @@ -0,0 +1,28 @@ +package tenantnetworks + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// Get returns data about a previously created Network. +func Get(client *golangsdk.ServiceClient, id string) (*Network, error) { + raw, err := client.Get(client.ServiceURL("os-tenant-networks", id), nil, nil) + if err != nil { + return nil, err + } + + var res Network + err = extract.IntoStructPtr(raw.Body, &res, "network") + return &res, err +} + +// A Network represents a network that a server communicates on. +type Network struct { + // CIDR is the IPv4 subnet. + CIDR string `json:"cidr"` + // ID is the UUID of the network. + ID string `json:"id"` + // Name is the common name that the network has. + Name string `json:"label"` +} diff --git a/openstack/compute/v2/extensions/tenantnetworks/list.go b/openstack/compute/v2/extensions/tenantnetworks/list.go new file mode 100644 index 000000000..5697fb73b --- /dev/null +++ b/openstack/compute/v2/extensions/tenantnetworks/list.go @@ -0,0 +1,18 @@ +package tenantnetworks + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// List returns a Pager that allows you to iterate over a collection of Networks. +func List(client *golangsdk.ServiceClient) ([]Network, error) { + raw, err := client.Get(client.ServiceURL("os-tenant-networks"), nil, nil) + if err != nil { + return nil, err + } + + var res []Network + err = extract.IntoSlicePtr(raw.Body, &res, "networks") + return res, err +} diff --git a/openstack/compute/v2/extensions/tenantnetworks/requests.go b/openstack/compute/v2/extensions/tenantnetworks/requests.go deleted file mode 100644 index b8f1fe2f5..000000000 --- a/openstack/compute/v2/extensions/tenantnetworks/requests.go +++ /dev/null @@ -1,19 +0,0 @@ -package tenantnetworks - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// List returns a Pager that allows you to iterate over a collection of Networks. -func List(client *golangsdk.ServiceClient) pagination.Pager { - return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page { - return NetworkPage{pagination.SinglePageBase(r)} - }) -} - -// Get returns data about a previously created Network. -func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) - return -} diff --git a/openstack/compute/v2/extensions/tenantnetworks/results.go b/openstack/compute/v2/extensions/tenantnetworks/results.go deleted file mode 100644 index 69fb5832c..000000000 --- a/openstack/compute/v2/extensions/tenantnetworks/results.go +++ /dev/null @@ -1,58 +0,0 @@ -package tenantnetworks - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// A Network represents a network that a server communicates on. -type Network struct { - // CIDR is the IPv4 subnet. - CIDR string `json:"cidr"` - - // ID is the UUID of the network. - ID string `json:"id"` - - // Name is the common name that the network has. - Name string `json:"label"` -} - -// NetworkPage stores a single page of all Networks results from a List call. -type NetworkPage struct { - pagination.SinglePageBase -} - -// IsEmpty determines whether or not a NetworkPage is empty. -func (page NetworkPage) IsEmpty() (bool, error) { - va, err := ExtractNetworks(page) - return len(va) == 0, err -} - -// ExtractNetworks interprets a page of results as a slice of Network. -func ExtractNetworks(r pagination.Page) ([]Network, error) { - var s struct { - Networks []Network `json:"networks"` - } - err := (r.(NetworkPage)).ExtractInto(&s) - return s.Networks, err -} - -type NetworkResult struct { - golangsdk.Result -} - -// Extract is a method that attempts to interpret any Network resource response -// as a Network struct. -func (r NetworkResult) Extract() (*Network, error) { - var s struct { - Network *Network `json:"network"` - } - err := r.ExtractInto(&s) - return s.Network, err -} - -// GetResult is the response from a Get operation. Call its Extract method to -// interpret it as a Network. -type GetResult struct { - NetworkResult -} diff --git a/openstack/compute/v2/extensions/tenantnetworks/testing/doc.go b/openstack/compute/v2/extensions/tenantnetworks/testing/doc.go deleted file mode 100644 index 4639153ff..000000000 --- a/openstack/compute/v2/extensions/tenantnetworks/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// tenantnetworks unit tests -package testing diff --git a/openstack/compute/v2/extensions/tenantnetworks/testing/requests_test.go b/openstack/compute/v2/extensions/tenantnetworks/testing/requests_test.go index be9783d3f..438ecaed1 100644 --- a/openstack/compute/v2/extensions/tenantnetworks/testing/requests_test.go +++ b/openstack/compute/v2/extensions/tenantnetworks/testing/requests_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/tenantnetworks" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) @@ -14,17 +13,9 @@ func TestList(t *testing.T) { defer th.TeardownHTTP() HandleListSuccessfully(t) - count := 0 - err := tenantnetworks.List(client.ServiceClient()).EachPage(func(page pagination.Page) (bool, error) { - count++ - actual, err := tenantnetworks.ExtractNetworks(page) - th.AssertNoErr(t, err) - th.CheckDeepEquals(t, ExpectedNetworkSlice, actual) - - return true, nil - }) + actual, err := tenantnetworks.List(client.ServiceClient()) th.AssertNoErr(t, err) - th.CheckEquals(t, 1, count) + th.CheckDeepEquals(t, ExpectedNetworkSlice, actual) } func TestGet(t *testing.T) { @@ -32,7 +23,7 @@ func TestGet(t *testing.T) { defer th.TeardownHTTP() HandleGetSuccessfully(t) - actual, err := tenantnetworks.Get(client.ServiceClient(), "20c8acc0-f747-4d71-a389-46d078ebf000").Extract() + actual, err := tenantnetworks.Get(client.ServiceClient(), "20c8acc0-f747-4d71-a389-46d078ebf000") th.AssertNoErr(t, err) th.CheckDeepEquals(t, &SecondNetwork, actual) } diff --git a/openstack/compute/v2/extensions/tenantnetworks/urls.go b/openstack/compute/v2/extensions/tenantnetworks/urls.go deleted file mode 100644 index 484ebbd6f..000000000 --- a/openstack/compute/v2/extensions/tenantnetworks/urls.go +++ /dev/null @@ -1,17 +0,0 @@ -package tenantnetworks - -import "github.com/opentelekomcloud/gophertelekomcloud" - -const resourcePath = "os-tenant-networks" - -func resourceURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL(resourcePath) -} - -func listURL(c *golangsdk.ServiceClient) string { - return resourceURL(c) -} - -func getURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL(resourcePath, id) -} diff --git a/openstack/compute/v2/extensions/testing/doc.go b/openstack/compute/v2/extensions/testing/doc.go deleted file mode 100644 index 3c5d45926..000000000 --- a/openstack/compute/v2/extensions/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// extensions unit tests -package testing diff --git a/openstack/compute/v2/extensions/usage/all_tenants.go b/openstack/compute/v2/extensions/usage/all_tenants.go new file mode 100644 index 000000000..b6c0d0b92 --- /dev/null +++ b/openstack/compute/v2/extensions/usage/all_tenants.go @@ -0,0 +1,80 @@ +package usage + +import ( + "net/url" + "time" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" + "github.com/opentelekomcloud/gophertelekomcloud/pagination" +) + +// AllTenantsOpts are options for fetching usage of all tenants. +type AllTenantsOpts struct { + // Detailed will return detailed results. + Detailed bool + // The ending time to calculate usage statistics on compute and storage resources. + End *time.Time `q:"end"` + // The beginning time to calculate usage statistics on compute and storage resources. + Start *time.Time `q:"start"` +} + +// ToUsageAllTenantsQuery formats a AllTenantsOpts into a query string. +func (opts AllTenantsOpts) ToUsageAllTenantsQuery() (string, error) { + params := make(url.Values) + if opts.Start != nil { + params.Add("start", opts.Start.Format(golangsdk.RFC3339MilliNoZ)) + } + + if opts.End != nil { + params.Add("end", opts.End.Format(golangsdk.RFC3339MilliNoZ)) + } + + if opts.Detailed { + params.Add("detailed", "1") + } + + q := &url.URL{RawQuery: params.Encode()} + return q.String(), nil +} + +// AllTenants returns usage data about all tenants. +func AllTenants(client *golangsdk.ServiceClient, opts AllTenantsOpts) pagination.Pager { + query, err := opts.ToUsageAllTenantsQuery() + if err != nil { + return pagination.Pager{Err: err} + } + + return pagination.NewPager(client, client.ServiceURL("os-simple-tenant-usage")+query, + func(r pagination.PageResult) pagination.Page { + return AllTenantsPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// AllTenantsPage stores a single, only page of TenantUsage results from a AllTenants call. +type AllTenantsPage struct { + pagination.LinkedPageBase +} + +// ExtractAllTenants interprets a AllTenantsPage as a TenantUsage result. +func ExtractAllTenants(page pagination.Page) ([]TenantUsage, error) { + var res []TenantUsage + err := extract.IntoSlicePtr(page.(AllTenantsPage).BodyReader(), &res, "tenant_usages") + return res, err +} + +// IsEmpty determines whether an AllTenantsPage is empty. +func (r AllTenantsPage) IsEmpty() (bool, error) { + usages, err := ExtractAllTenants(r) + return len(usages) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the next page of results. +func (r AllTenantsPage) NextPageURL() (string, error) { + var res []golangsdk.Link + err := extract.IntoSlicePtr(r.BodyReader(), &res, "tenant_usages_links") + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(res) +} diff --git a/openstack/compute/v2/extensions/usage/requests.go b/openstack/compute/v2/extensions/usage/requests.go deleted file mode 100644 index 40e7afc39..000000000 --- a/openstack/compute/v2/extensions/usage/requests.go +++ /dev/null @@ -1,106 +0,0 @@ -package usage - -import ( - "net/url" - "time" - - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// SingleTenantOpts are options for fetching usage of a single tenant. -type SingleTenantOpts struct { - // The ending time to calculate usage statistics on compute and storage resources. - End *time.Time `q:"end"` - - // The beginning time to calculate usage statistics on compute and storage resources. - Start *time.Time `q:"start"` -} - -// SingleTenantOptsBuilder allows extensions to add additional parameters to the -// SingleTenant request. -type SingleTenantOptsBuilder interface { - ToUsageSingleTenantQuery() (string, error) -} - -// ToUsageSingleTenantQuery formats a SingleTenantOpts into a query string. -func (opts SingleTenantOpts) ToUsageSingleTenantQuery() (string, error) { - params := make(url.Values) - if opts.Start != nil { - params.Add("start", opts.Start.Format(golangsdk.RFC3339MilliNoZ)) - } - - if opts.End != nil { - params.Add("end", opts.End.Format(golangsdk.RFC3339MilliNoZ)) - } - - q := &url.URL{RawQuery: params.Encode()} - return q.String(), nil -} - -// SingleTenant returns usage data about a single tenant. -func SingleTenant(client *golangsdk.ServiceClient, tenantID string, opts SingleTenantOptsBuilder) pagination.Pager { - u := getTenantURL(client, tenantID) - if opts != nil { - query, err := opts.ToUsageSingleTenantQuery() - if err != nil { - return pagination.Pager{Err: err} - } - u += query - } - return pagination.NewPager(client, u, func(r pagination.PageResult) pagination.Page { - return SingleTenantPage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// AllTenantsOpts are options for fetching usage of all tenants. -type AllTenantsOpts struct { - // Detailed will return detailed results. - Detailed bool - - // The ending time to calculate usage statistics on compute and storage resources. - End *time.Time `q:"end"` - - // The beginning time to calculate usage statistics on compute and storage resources. - Start *time.Time `q:"start"` -} - -// AllTenantsOptsBuilder allows extensions to add additional parameters to the -// AllTenants request. -type AllTenantsOptsBuilder interface { - ToUsageAllTenantsQuery() (string, error) -} - -// ToUsageAllTenantsQuery formats a AllTenantsOpts into a query string. -func (opts AllTenantsOpts) ToUsageAllTenantsQuery() (string, error) { - params := make(url.Values) - if opts.Start != nil { - params.Add("start", opts.Start.Format(golangsdk.RFC3339MilliNoZ)) - } - - if opts.End != nil { - params.Add("end", opts.End.Format(golangsdk.RFC3339MilliNoZ)) - } - - if opts.Detailed { - params.Add("detailed", "1") - } - - q := &url.URL{RawQuery: params.Encode()} - return q.String(), nil -} - -// AllTenants returns usage data about all tenants. -func AllTenants(client *golangsdk.ServiceClient, opts AllTenantsOptsBuilder) pagination.Pager { - u := allTenantsURL(client) - if opts != nil { - query, err := opts.ToUsageAllTenantsQuery() - if err != nil { - return pagination.Pager{Err: err} - } - u += query - } - return pagination.NewPager(client, u, func(r pagination.PageResult) pagination.Page { - return AllTenantsPage{pagination.LinkedPageBase{PageResult: r}} - }) -} diff --git a/openstack/compute/v2/extensions/usage/results.go b/openstack/compute/v2/extensions/usage/results.go index fdd58c738..3bd484e52 100644 --- a/openstack/compute/v2/extensions/usage/results.go +++ b/openstack/compute/v2/extensions/usage/results.go @@ -5,32 +5,24 @@ import ( "time" "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" ) // TenantUsage is a set of usage information about a tenant over the sampling window type TenantUsage struct { // ServerUsages is an array of ServerUsage maps ServerUsages []ServerUsage `json:"server_usages"` - // Start is the beginning time to calculate usage statistics on compute and storage resources Start time.Time `json:"-"` - // Stop is the ending time to calculate usage statistics on compute and storage resources Stop time.Time `json:"-"` - // TenantID is the ID of the tenant whose usage is being reported on TenantID string `json:"tenant_id"` - // TotalHours is the total duration that servers exist (in hours) TotalHours float64 `json:"total_hours"` - // TotalLocalGBUsage multiplies the server disk size (in GiB) by hours the server exists, and then adding that all together for each server TotalLocalGBUsage float64 `json:"total_local_gb_usage"` - // TotalMemoryMBUsage multiplies the server memory size (in MB) by hours the server exists, and then adding that all together for each server TotalMemoryMBUsage float64 `json:"total_memory_mb_usage"` - // TotalVCPUsUsage multiplies the number of virtual CPUs of the server by hours the server exists, and then adding that all together for each server TotalVCPUsUsage float64 `json:"total_vcpus_usage"` } @@ -59,37 +51,26 @@ func (u *TenantUsage) UnmarshalJSON(b []byte) error { type ServerUsage struct { // EndedAt is the date and time when the server was deleted EndedAt time.Time `json:"-"` - // Flavor is the display name of a flavor Flavor string `json:"flavor"` - // Hours is the duration that the server exists in hours Hours float64 `json:"hours"` - // InstanceID is the UUID of the instance InstanceID string `json:"instance_id"` - // LocalGB is the sum of the root disk size of the server and the ephemeral disk size of it (in GiB) LocalGB int `json:"local_gb"` - // MemoryMB is the memory size of the server (in MB) MemoryMB int `json:"memory_mb"` - // Name is the name assigned to the server when it was created Name string `json:"name"` - // StartedAt is the date and time when the server was started StartedAt time.Time `json:"-"` - // State is the VM power state State string `json:"state"` - // TenantID is the UUID of the tenant in a multi-tenancy cloud TenantID string `json:"tenant_id"` - // Uptime is the uptime of the server in seconds Uptime int `json:"uptime"` - // VCPUs is the number of virtual CPUs that the server uses VCPUs int `json:"vcpus"` } @@ -113,71 +94,3 @@ func (u *ServerUsage) UnmarshalJSON(b []byte) error { return nil } - -// SingleTenantPage stores a single, only page of TenantUsage results from a -// SingleTenant call. -type SingleTenantPage struct { - pagination.LinkedPageBase -} - -// IsEmpty determines whether or not a SingleTenantPage is empty. -func (r SingleTenantPage) IsEmpty() (bool, error) { - ks, err := ExtractSingleTenant(r) - return ks == nil, err -} - -// NextPageURL uses the response's embedded link reference to navigate to the -// next page of results. -func (r SingleTenantPage) NextPageURL() (string, error) { - var s struct { - Links []golangsdk.Link `json:"tenant_usage_links"` - } - err := r.ExtractInto(&s) - if err != nil { - return "", err - } - return golangsdk.ExtractNextURL(s.Links) -} - -// ExtractSingleTenant interprets a SingleTenantPage as a TenantUsage result. -func ExtractSingleTenant(page pagination.Page) (*TenantUsage, error) { - var s struct { - TenantUsage *TenantUsage `json:"tenant_usage"` - } - err := (page.(SingleTenantPage)).ExtractInto(&s) - return s.TenantUsage, err -} - -// AllTenantsPage stores a single, only page of TenantUsage results from a -// AllTenants call. -type AllTenantsPage struct { - pagination.LinkedPageBase -} - -// ExtractAllTenants interprets a AllTenantsPage as a TenantUsage result. -func ExtractAllTenants(page pagination.Page) ([]TenantUsage, error) { - var s struct { - TenantUsages []TenantUsage `json:"tenant_usages"` - } - err := (page.(AllTenantsPage)).ExtractInto(&s) - return s.TenantUsages, err -} - -// IsEmpty determines whether or not an AllTenantsPage is empty. -func (r AllTenantsPage) IsEmpty() (bool, error) { - usages, err := ExtractAllTenants(r) - return len(usages) == 0, err -} - -// NextPageURL uses the response's embedded link reference to navigate to the -// next page of results. -func (r AllTenantsPage) NextPageURL() (string, error) { - var s struct { - Links []golangsdk.Link `json:"tenant_usages_links"` - } - err := r.ExtractInto(&s) - if err != nil { - return "", err - } - return golangsdk.ExtractNextURL(s.Links) -} diff --git a/openstack/compute/v2/extensions/usage/single_tenant.go b/openstack/compute/v2/extensions/usage/single_tenant.go new file mode 100644 index 000000000..0b5e9e192 --- /dev/null +++ b/openstack/compute/v2/extensions/usage/single_tenant.go @@ -0,0 +1,74 @@ +package usage + +import ( + "net/url" + "time" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" + "github.com/opentelekomcloud/gophertelekomcloud/pagination" +) + +// SingleTenantOpts are options for fetching usage of a single tenant. +type SingleTenantOpts struct { + // The ending time to calculate usage statistics on compute and storage resources. + End *time.Time `q:"end"` + // The beginning time to calculate usage statistics on compute and storage resources. + Start *time.Time `q:"start"` +} + +// ToUsageSingleTenantQuery formats a SingleTenantOpts into a query string. +func (opts SingleTenantOpts) ToUsageSingleTenantQuery() (string, error) { + params := make(url.Values) + if opts.Start != nil { + params.Add("start", opts.Start.Format(golangsdk.RFC3339MilliNoZ)) + } + + if opts.End != nil { + params.Add("end", opts.End.Format(golangsdk.RFC3339MilliNoZ)) + } + + q := &url.URL{RawQuery: params.Encode()} + return q.String(), nil +} + +// SingleTenant returns usage data about a single tenant. +func SingleTenant(client *golangsdk.ServiceClient, tenantID string, opts SingleTenantOpts) pagination.Pager { + query, err := opts.ToUsageSingleTenantQuery() + if err != nil { + return pagination.Pager{Err: err} + } + + return pagination.NewPager(client, client.ServiceURL("os-simple-tenant-usage", tenantID)+query, + func(r pagination.PageResult) pagination.Page { + return SingleTenantPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// SingleTenantPage stores a single, only page of TenantUsage results from a SingleTenant call. +type SingleTenantPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines whether a SingleTenantPage is empty. +func (r SingleTenantPage) IsEmpty() (bool, error) { + ks, err := ExtractSingleTenant(r) + return ks == nil, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the next page of results. +func (r SingleTenantPage) NextPageURL() (string, error) { + var res []golangsdk.Link + err := extract.IntoSlicePtr(r.BodyReader(), &res, "tenant_usage_links") + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(res) +} + +// ExtractSingleTenant interprets a SingleTenantPage as a TenantUsage result. +func ExtractSingleTenant(page pagination.Page) (*TenantUsage, error) { + var res TenantUsage + err := extract.IntoStructPtr(page.(SingleTenantPage).BodyReader(), &res, "tenant_usage") + return &res, err +} diff --git a/openstack/compute/v2/extensions/usage/testing/doc.go b/openstack/compute/v2/extensions/usage/testing/doc.go deleted file mode 100644 index a3521795b..000000000 --- a/openstack/compute/v2/extensions/usage/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// simple tenant usage unit tests -package testing diff --git a/openstack/compute/v2/extensions/usage/testing/requests_test.go b/openstack/compute/v2/extensions/usage/testing/requests_test.go index d550471cc..9b4609f47 100644 --- a/openstack/compute/v2/extensions/usage/testing/requests_test.go +++ b/openstack/compute/v2/extensions/usage/testing/requests_test.go @@ -15,7 +15,7 @@ func TestGetTenant(t *testing.T) { HandleGetSingleTenantSuccessfully(t) count := 0 - err := usage.SingleTenant(client.ServiceClient(), FirstTenantID, nil).EachPage(func(page pagination.Page) (bool, error) { + err := usage.SingleTenant(client.ServiceClient(), FirstTenantID, usage.SingleTenantOpts{}).EachPage(func(page pagination.Page) (bool, error) { count++ actual, err := usage.ExtractSingleTenant(page) diff --git a/openstack/compute/v2/extensions/usage/urls.go b/openstack/compute/v2/extensions/usage/urls.go deleted file mode 100644 index f09b67777..000000000 --- a/openstack/compute/v2/extensions/usage/urls.go +++ /dev/null @@ -1,13 +0,0 @@ -package usage - -import "github.com/opentelekomcloud/gophertelekomcloud" - -const resourcePath = "os-simple-tenant-usage" - -func allTenantsURL(client *golangsdk.ServiceClient) string { - return client.ServiceURL(resourcePath) -} - -func getTenantURL(client *golangsdk.ServiceClient, tenantID string) string { - return client.ServiceURL(resourcePath, tenantID) -} diff --git a/openstack/compute/v2/extensions/volumeattach/create.go b/openstack/compute/v2/extensions/volumeattach/create.go new file mode 100644 index 000000000..919cf42e5 --- /dev/null +++ b/openstack/compute/v2/extensions/volumeattach/create.go @@ -0,0 +1,27 @@ +package volumeattach + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// CreateOpts specifies volume attachment creation or import parameters. +type CreateOpts struct { + // Device is the device that the volume will attach to the instance as. Omit for "auto". + Device string `json:"device,omitempty"` + // VolumeID is the ID of the volume to attach to the instance. + VolumeID string `json:"volumeId" required:"true"` +} + +// Create requests the creation of a new volume attachment on the server. +func Create(client *golangsdk.ServiceClient, serverID string, opts CreateOpts) (*VolumeAttachment, error) { + b, err := build.RequestBody(opts, "volumeAttachment") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("servers", serverID, "os-volume_attachments"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/volumeattach/delete.go b/openstack/compute/v2/extensions/volumeattach/delete.go new file mode 100644 index 000000000..c630cddba --- /dev/null +++ b/openstack/compute/v2/extensions/volumeattach/delete.go @@ -0,0 +1,9 @@ +package volumeattach + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Delete requests the deletion of a previous stored VolumeAttachment from the server. +func Delete(client *golangsdk.ServiceClient, serverID, attachmentID string) (err error) { + _, err = client.Delete(client.ServiceURL("servers", serverID, "os-volume_attachments", attachmentID), nil) + return +} diff --git a/openstack/compute/v2/extensions/volumeattach/get.go b/openstack/compute/v2/extensions/volumeattach/get.go new file mode 100644 index 000000000..db06d9aa6 --- /dev/null +++ b/openstack/compute/v2/extensions/volumeattach/get.go @@ -0,0 +1,9 @@ +package volumeattach + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Get returns public data about a previously created VolumeAttachment. +func Get(client *golangsdk.ServiceClient, serverID, attachmentID string) (*VolumeAttachment, error) { + raw, err := client.Get(client.ServiceURL("servers", serverID, "os-volume_attachments", attachmentID), nil, nil) + return extra(err, raw) +} diff --git a/openstack/compute/v2/extensions/volumeattach/list.go b/openstack/compute/v2/extensions/volumeattach/list.go new file mode 100644 index 000000000..10127be44 --- /dev/null +++ b/openstack/compute/v2/extensions/volumeattach/list.go @@ -0,0 +1,19 @@ +package volumeattach + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// List returns a Pager that allows you to iterate over a collection of +// VolumeAttachments. +func List(client *golangsdk.ServiceClient, serverID string) ([]VolumeAttachment, error) { + raw, err := client.Get(client.ServiceURL("servers", serverID, "os-volume_attachments"), nil, nil) + if err != nil { + return nil, err + } + + var res []VolumeAttachment + err = extract.IntoSlicePtr(raw.Body, &res, "volumeAttachments") + return res, err +} diff --git a/openstack/compute/v2/extensions/volumeattach/requests.go b/openstack/compute/v2/extensions/volumeattach/requests.go index 934c42099..33eda050d 100644 --- a/openstack/compute/v2/extensions/volumeattach/requests.go +++ b/openstack/compute/v2/extensions/volumeattach/requests.go @@ -1,60 +1 @@ package volumeattach - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// List returns a Pager that allows you to iterate over a collection of -// VolumeAttachments. -func List(client *golangsdk.ServiceClient, serverID string) pagination.Pager { - return pagination.NewPager(client, listURL(client, serverID), func(r pagination.PageResult) pagination.Page { - return VolumeAttachmentPage{pagination.SinglePageBase(r)} - }) -} - -// CreateOptsBuilder allows extensions to add parameters to the Create request. -type CreateOptsBuilder interface { - ToVolumeAttachmentCreateMap() (map[string]interface{}, error) -} - -// CreateOpts specifies volume attachment creation or import parameters. -type CreateOpts struct { - // Device is the device that the volume will attach to the instance as. - // Omit for "auto". - Device string `json:"device,omitempty"` - - // VolumeID is the ID of the volume to attach to the instance. - VolumeID string `json:"volumeId" required:"true"` -} - -// ToVolumeAttachmentCreateMap constructs a request body from CreateOpts. -func (opts CreateOpts) ToVolumeAttachmentCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "volumeAttachment") -} - -// Create requests the creation of a new volume attachment on the server. -func Create(client *golangsdk.ServiceClient, serverID string, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToVolumeAttachmentCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client, serverID), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Get returns public data about a previously created VolumeAttachment. -func Get(client *golangsdk.ServiceClient, serverID, attachmentID string) (r GetResult) { - _, r.Err = client.Get(getURL(client, serverID, attachmentID), &r.Body, nil) - return -} - -// Delete requests the deletion of a previous stored VolumeAttachment from -// the server. -func Delete(client *golangsdk.ServiceClient, serverID, attachmentID string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, serverID, attachmentID), nil) - return -} diff --git a/openstack/compute/v2/extensions/volumeattach/results.go b/openstack/compute/v2/extensions/volumeattach/results.go index 6650634a9..5d9dda594 100644 --- a/openstack/compute/v2/extensions/volumeattach/results.go +++ b/openstack/compute/v2/extensions/volumeattach/results.go @@ -1,77 +1,29 @@ package volumeattach import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" + "net/http" + + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" ) -// VolumeAttachment contains attachment information between a volume -// and server. +// VolumeAttachment contains attachment information between a volume and server. type VolumeAttachment struct { // ID is a unique id of the attachment. ID string `json:"id"` - // Device is what device the volume is attached as. Device string `json:"device"` - // VolumeID is the ID of the attached volume. VolumeID string `json:"volumeId"` - // ServerID is the ID of the instance that has the volume attached. ServerID string `json:"serverId"` } -// VolumeAttachmentPage stores a single page all of VolumeAttachment -// results from a List call. -type VolumeAttachmentPage struct { - pagination.SinglePageBase -} - -// IsEmpty determines whether or not a VolumeAttachmentPage is empty. -func (page VolumeAttachmentPage) IsEmpty() (bool, error) { - va, err := ExtractVolumeAttachments(page) - return len(va) == 0, err -} - -// ExtractVolumeAttachments interprets a page of results as a slice of -// VolumeAttachment. -func ExtractVolumeAttachments(r pagination.Page) ([]VolumeAttachment, error) { - var s struct { - VolumeAttachments []VolumeAttachment `json:"volumeAttachments"` - } - err := (r.(VolumeAttachmentPage)).ExtractInto(&s) - return s.VolumeAttachments, err -} - -// VolumeAttachmentResult is the result from a volume attachment operation. -type VolumeAttachmentResult struct { - golangsdk.Result -} - -// Extract is a method that attempts to interpret any VolumeAttachment resource -// response as a VolumeAttachment struct. -func (r VolumeAttachmentResult) Extract() (*VolumeAttachment, error) { - var s struct { - VolumeAttachment *VolumeAttachment `json:"volumeAttachment"` +func extra(err error, raw *http.Response) (*VolumeAttachment, error) { + if err != nil { + return nil, err } - err := r.ExtractInto(&s) - return s.VolumeAttachment, err -} - -// CreateResult is the response from a Create operation. Call its Extract method -// to interpret it as a VolumeAttachment. -type CreateResult struct { - VolumeAttachmentResult -} - -// GetResult is the response from a Get operation. Call its Extract method to -// interpret it as a VolumeAttachment. -type GetResult struct { - VolumeAttachmentResult -} -// DeleteResult is the response from a Delete operation. Call its ExtractErr -// method to determine if the call succeeded or failed. -type DeleteResult struct { - golangsdk.ErrResult + var res VolumeAttachment + err = extract.IntoStructPtr(raw.Body, &res, "volumeAttachment") + return &res, err } diff --git a/openstack/compute/v2/extensions/volumeattach/testing/doc.go b/openstack/compute/v2/extensions/volumeattach/testing/doc.go deleted file mode 100644 index 11dfc0694..000000000 --- a/openstack/compute/v2/extensions/volumeattach/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// volumeattach unit tests -package testing diff --git a/openstack/compute/v2/extensions/volumeattach/testing/requests_test.go b/openstack/compute/v2/extensions/volumeattach/testing/requests_test.go index e050c7ebe..51d36af93 100644 --- a/openstack/compute/v2/extensions/volumeattach/testing/requests_test.go +++ b/openstack/compute/v2/extensions/volumeattach/testing/requests_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/volumeattach" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) @@ -45,17 +44,9 @@ func TestList(t *testing.T) { serverID := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" - count := 0 - err := volumeattach.List(client.ServiceClient(), serverID).EachPage(func(page pagination.Page) (bool, error) { - count++ - actual, err := volumeattach.ExtractVolumeAttachments(page) - th.AssertNoErr(t, err) - th.CheckDeepEquals(t, ExpectedVolumeAttachmentSlice, actual) - - return true, nil - }) + actual, err := volumeattach.List(client.ServiceClient(), serverID) th.AssertNoErr(t, err) - th.CheckEquals(t, 1, count) + th.CheckDeepEquals(t, ExpectedVolumeAttachmentSlice, actual) } func TestCreate(t *testing.T) { @@ -69,7 +60,7 @@ func TestCreate(t *testing.T) { actual, err := volumeattach.Create(client.ServiceClient(), serverID, volumeattach.CreateOpts{ Device: "/dev/vdc", VolumeID: "a26887c6-c47b-4654-abb5-dfadf7d3f804", - }).Extract() + }) th.AssertNoErr(t, err) th.CheckDeepEquals(t, &CreatedVolumeAttachment, actual) } @@ -83,7 +74,7 @@ func TestGet(t *testing.T) { aID := "a26887c6-c47b-4654-abb5-dfadf7d3f804" serverID := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" - actual, err := volumeattach.Get(client.ServiceClient(), serverID, aID).Extract() + actual, err := volumeattach.Get(client.ServiceClient(), serverID, aID) th.AssertNoErr(t, err) th.CheckDeepEquals(t, &SecondVolumeAttachment, actual) } @@ -97,6 +88,6 @@ func TestDelete(t *testing.T) { aID := "a26887c6-c47b-4654-abb5-dfadf7d3f804" serverID := "4d8c3732-a248-40ed-bebc-539a6ffd25c0" - err := volumeattach.Delete(client.ServiceClient(), serverID, aID).ExtractErr() + err := volumeattach.Delete(client.ServiceClient(), serverID, aID) th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/extensions/volumeattach/urls.go b/openstack/compute/v2/extensions/volumeattach/urls.go deleted file mode 100644 index 7ecd6fc8d..000000000 --- a/openstack/compute/v2/extensions/volumeattach/urls.go +++ /dev/null @@ -1,25 +0,0 @@ -package volumeattach - -import "github.com/opentelekomcloud/gophertelekomcloud" - -const resourcePath = "os-volume_attachments" - -func resourceURL(c *golangsdk.ServiceClient, serverID string) string { - return c.ServiceURL("servers", serverID, resourcePath) -} - -func listURL(c *golangsdk.ServiceClient, serverID string) string { - return resourceURL(c, serverID) -} - -func createURL(c *golangsdk.ServiceClient, serverID string) string { - return resourceURL(c, serverID) -} - -func getURL(c *golangsdk.ServiceClient, serverID, aID string) string { - return c.ServiceURL("servers", serverID, resourcePath, aID) -} - -func deleteURL(c *golangsdk.ServiceClient, serverID, aID string) string { - return getURL(c, serverID, aID) -} diff --git a/openstack/compute/v2/flavors/IDFromName.go b/openstack/compute/v2/flavors/IDFromName.go new file mode 100644 index 000000000..be631690e --- /dev/null +++ b/openstack/compute/v2/flavors/IDFromName.go @@ -0,0 +1,43 @@ +package flavors + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" +) + +// IDFromName is a convenience function that returns a flavor's ID given its name. +func IDFromName(client *golangsdk.ServiceClient, name string) (string, error) { + count := 0 + id := "" + allPages, err := ListDetail(client, ListOpts{}).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractFlavors(allPages) + if err != nil { + return "", err + } + + for _, f := range all { + if f.Name == name { + count++ + id = f.ID + } + } + + switch count { + case 0: + err := &golangsdk.ErrResourceNotFound{} + err.ResourceType = "flavor" + err.Name = name + return "", err + case 1: + return id, nil + default: + err := &golangsdk.ErrMultipleResourcesFound{} + err.ResourceType = "flavor" + err.Name = name + err.Count = count + return "", err + } +} diff --git a/openstack/compute/v2/flavors/add_access.go b/openstack/compute/v2/flavors/add_access.go new file mode 100644 index 000000000..84f17ff35 --- /dev/null +++ b/openstack/compute/v2/flavors/add_access.go @@ -0,0 +1,33 @@ +package flavors + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// AccessOpts represents options for adding access to a flavor. +type AccessOpts struct { + // Tenant is the project/tenant ID to grant access. + Tenant string `json:"tenant"` +} + +// AddAccess grants a tenant/project access to a flavor. +func AddAccess(client *golangsdk.ServiceClient, id string, opts AccessOpts) ([]FlavorAccess, error) { + b, err := build.RequestBody(opts, "addTenantAccess") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("flavors", id, "action"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extraAcc(err, raw) +} + +// FlavorAccess represents an ACL of tenant access to a specific Flavor. +type FlavorAccess struct { + // FlavorID is the unique ID of the flavor. + FlavorID string `json:"flavor_id"` + // TenantID is the unique ID of the tenant. + TenantID string `json:"tenant_id"` +} diff --git a/openstack/compute/v2/flavors/create.go b/openstack/compute/v2/flavors/create.go new file mode 100644 index 000000000..a08f4e9c4 --- /dev/null +++ b/openstack/compute/v2/flavors/create.go @@ -0,0 +1,54 @@ +package flavors + +import ( + "net/http" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// CreateOpts specifies parameters used for creating a flavor. +type CreateOpts struct { + // Name is the name of the flavor. + Name string `json:"name" required:"true"` + // RAM is the memory of the flavor, measured in MB. + RAM int `json:"ram" required:"true"` + // VCPUs is the number of vcpus for the flavor. + VCPUs int `json:"vcpus" required:"true"` + // Disk the amount of root disk space, measured in GB. + Disk *int `json:"disk" required:"true"` + // ID is a unique ID for the flavor. + ID string `json:"id,omitempty"` + // Swap is the amount of swap space for the flavor, measured in MB. + Swap *int `json:"swap,omitempty"` + // RxTxFactor alters the network bandwidth of a flavor. + RxTxFactor float64 `json:"rxtx_factor,omitempty"` + // IsPublic flags a flavor as being available to all projects or not. + IsPublic *bool `json:"os-flavor-access:is_public,omitempty"` + // Ephemeral is the amount of ephemeral disk space, measured in GB. + Ephemeral *int `json:"OS-FLV-EXT-DATA:ephemeral,omitempty"` +} + +// Create requests the creation of a new flavor. +func Create(client *golangsdk.ServiceClient, opts CreateOpts) (*Flavor, error) { + b, err := build.RequestBody(opts, "flavor") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("flavors"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200, 201}, + }) + return extraFla(err, raw) +} + +func extraFla(err error, raw *http.Response) (*Flavor, error) { + if err != nil { + return nil, err + } + + var res Flavor + err = extract.IntoStructPtr(raw.Body, &res, "flavor") + return &res, err +} diff --git a/openstack/compute/v2/flavors/create_extra_specs.go b/openstack/compute/v2/flavors/create_extra_specs.go new file mode 100644 index 000000000..719a4fc7f --- /dev/null +++ b/openstack/compute/v2/flavors/create_extra_specs.go @@ -0,0 +1,29 @@ +package flavors + +import ( + "net/http" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// CreateExtraSpecs will create or update the extraAcc-specs key-value pairs for the specified Flavor. +func CreateExtraSpecs(client *golangsdk.ServiceClient, flavorID string, opts map[string]string) (map[string]string, error) { + raw, err := client.Post(client.ServiceURL("flavors", flavorID, "os-extra_specs"), + map[string]interface{}{"extra_specs": opts}, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extraSpes(err, raw) +} + +func extraSpes(err error, raw *http.Response) (map[string]string, error) { + if err != nil { + return nil, err + } + + var res struct { + ExtraSpecs map[string]string `json:"extra_specs"` + } + err = extract.Into(raw.Body, &res) + return res.ExtraSpecs, err +} diff --git a/openstack/compute/v2/flavors/delete.go b/openstack/compute/v2/flavors/delete.go new file mode 100644 index 000000000..e8118c2a0 --- /dev/null +++ b/openstack/compute/v2/flavors/delete.go @@ -0,0 +1,9 @@ +package flavors + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Delete deletes the specified flavor ID. +func Delete(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Delete(client.ServiceURL("flavors", id), nil) + return +} diff --git a/openstack/compute/v2/flavors/delete_extra_spec.go b/openstack/compute/v2/flavors/delete_extra_spec.go new file mode 100644 index 000000000..256827b12 --- /dev/null +++ b/openstack/compute/v2/flavors/delete_extra_spec.go @@ -0,0 +1,11 @@ +package flavors + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// DeleteExtraSpec will delete the key-value pair with the given key for the given flavor ID. +func DeleteExtraSpec(client *golangsdk.ServiceClient, flavorID, key string) (err error) { + _, err = client.Delete(client.ServiceURL("flavors", flavorID, "os-extra_specs", key), &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/openstack/compute/v2/flavors/get.go b/openstack/compute/v2/flavors/get.go new file mode 100644 index 000000000..5d3c1725a --- /dev/null +++ b/openstack/compute/v2/flavors/get.go @@ -0,0 +1,68 @@ +package flavors + +import ( + "encoding/json" + "strconv" + + "github.com/opentelekomcloud/gophertelekomcloud" +) + +// Get retrieves details of a single flavor. Use ExtractFlavor to convert its result into a Flavor. +func Get(client *golangsdk.ServiceClient, id string) (*Flavor, error) { + raw, err := client.Get(client.ServiceURL("flavors", id), nil, nil) + return extraFla(err, raw) +} + +// Flavor represent (virtual) hardware configurations for server resources in a region. +type Flavor struct { + // ID is the flavor's unique ID. + ID string `json:"id"` + // Disk is the amount of root disk, measured in GB. + Disk int `json:"disk"` + // RAM is the amount of memory, measured in MB. + RAM int `json:"ram"` + // Name is the name of the flavor. + Name string `json:"name"` + // RxTxFactor describes bandwidth alterations of the flavor. + RxTxFactor float64 `json:"rxtx_factor"` + // Swap is the amount of swap space, measured in MB. + Swap int `json:"-"` + // VCPUs indicates how many (virtual) CPUs are available for this flavor. + VCPUs int `json:"vcpus"` + // IsPublic indicates whether the flavor is public. + IsPublic bool `json:"os-flavor-access:is_public"` + // Ephemeral is the amount of ephemeral disk space, measured in GB. + Ephemeral int `json:"OS-FLV-EXT-DATA:ephemeral"` +} + +func (r *Flavor) UnmarshalJSON(b []byte) error { + type tmp Flavor + var s struct { + tmp + Swap interface{} `json:"swap"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = Flavor(s.tmp) + + switch t := s.Swap.(type) { + case float64: + r.Swap = int(t) + case string: + switch t { + case "": + r.Swap = 0 + default: + swap, err := strconv.ParseFloat(t, 64) + if err != nil { + return err + } + r.Swap = int(swap) + } + } + + return nil +} diff --git a/openstack/compute/v2/flavors/get_extra_spec.go b/openstack/compute/v2/flavors/get_extra_spec.go new file mode 100644 index 000000000..259da0949 --- /dev/null +++ b/openstack/compute/v2/flavors/get_extra_spec.go @@ -0,0 +1,23 @@ +package flavors + +import ( + "net/http" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +func GetExtraSpec(client *golangsdk.ServiceClient, flavorID string, key string) (map[string]string, error) { + raw, err := client.Get(client.ServiceURL("flavors", flavorID, "os-extra_specs", key), nil, nil) + return extraSpe(err, raw) +} + +func extraSpe(err error, raw *http.Response) (map[string]string, error) { + if err != nil { + return nil, err + } + + var res map[string]string + err = extract.Into(raw.Body, &res) + return res, err +} diff --git a/openstack/compute/v2/flavors/list_accesses.go b/openstack/compute/v2/flavors/list_accesses.go new file mode 100644 index 000000000..644129af7 --- /dev/null +++ b/openstack/compute/v2/flavors/list_accesses.go @@ -0,0 +1,18 @@ +package flavors + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// ListAccesses retrieves the tenants which have access to a flavor. +func ListAccesses(client *golangsdk.ServiceClient, id string) ([]FlavorAccess, error) { + raw, err := client.Get(client.ServiceURL("flavors", id, "os-flavor-access"), nil, nil) + if err != nil { + return nil, err + } + + var res []FlavorAccess + err = extract.IntoSlicePtr(raw.Body, &res, "flavor_access") + return res, err +} diff --git a/openstack/compute/v2/flavors/list_detail.go b/openstack/compute/v2/flavors/list_detail.go new file mode 100644 index 000000000..5263034d1 --- /dev/null +++ b/openstack/compute/v2/flavors/list_detail.go @@ -0,0 +1,96 @@ +package flavors + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" + "github.com/opentelekomcloud/gophertelekomcloud/pagination" +) + +/* +AccessType maps to OpenStack's Flavor.is_public field. Although the is_public +field is boolean, the request options are ternary, which is why AccessType is +a string. The following values are allowed: + +The AccessType argument is optional, and if it is not supplied, OpenStack +returns the PublicAccess flavors. +*/ +type AccessType string + +const ( + // PublicAccess returns public flavors and private flavors associated with that project. + PublicAccess AccessType = "true" + // PrivateAccess (admin only) returns private flavors, across all projects. + PrivateAccess AccessType = "false" + // AllAccess (admin only) returns public and private flavors across all projects. + AllAccess AccessType = "None" +) + +/* +ListOpts filters the results returned by the List() function. +For example, a flavor with a minDisk field of 10 will not be returned if you +specify MinDisk set to 20. + +Typically, software will use the last ID of the previous call to List to set +the Marker for the current call. +*/ +type ListOpts struct { + // ChangesSince, if provided, instructs List to return only those things which + // have changed since the timestamp provided. + ChangesSince string `q:"changes-since"` + // MinDisk and MinRAM, if provided, elides flavors which do not meet your criteria. + MinDisk int `q:"minDisk"` + MinRAM int `q:"minRam"` + // SortDir allows to select sort direction. It can be "asc" or "desc" (default). + SortDir string `q:"sort_dir"` + // SortKey allows to sort by one of the flavors attributes. Default is flavorId. + SortKey string `q:"sort_key"` + // Marker and Limit control paging. Marker instructs List where to start listing from. + Marker string `q:"marker"` + // Limit instructs List to refrain from sending excessively large lists of flavors. + Limit int `q:"limit"` + // AccessType, if provided, instructs List which set of flavors to return. + // If IsPublic not provided, flavors for the current project are returned. + AccessType AccessType `q:"is_public"` +} + +// ListDetail instructs OpenStack to provide a list of flavors. +// You may provide criteria by which List curtails its results for easier processing. +func ListDetail(client *golangsdk.ServiceClient, opts ListOpts) pagination.Pager { + query, err := golangsdk.BuildQueryString(opts) + if err != nil { + return pagination.Pager{Err: err} + } + + return pagination.NewPager(client, client.ServiceURL("flavors", "detail")+query.String(), + func(r pagination.PageResult) pagination.Page { + return FlavorPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// FlavorPage contains a single page of all flavors from a ListDetails call. +type FlavorPage struct { + pagination.LinkedPageBase +} + +// IsEmpty determines if a FlavorPage contains any results. +func (page FlavorPage) IsEmpty() (bool, error) { + flavors, err := ExtractFlavors(page) + return len(flavors) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the next page of results. +func (page FlavorPage) NextPageURL() (string, error) { + var res []golangsdk.Link + err := extract.IntoSlicePtr(page.BodyReader(), &res, "flavors_links") + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(res) +} + +// ExtractFlavors provides access to the list of flavors in a page acquired from the ListDetail operation. +func ExtractFlavors(r pagination.Page) ([]Flavor, error) { + var res []Flavor + err := extract.IntoSlicePtr(r.(FlavorPage).BodyReader(), &res, "flavors") + return res, err +} diff --git a/openstack/compute/v2/flavors/list_extra_specs.go b/openstack/compute/v2/flavors/list_extra_specs.go new file mode 100644 index 000000000..1339674af --- /dev/null +++ b/openstack/compute/v2/flavors/list_extra_specs.go @@ -0,0 +1,9 @@ +package flavors + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// ListExtraSpecs requests all the extraAcc-specs for the given flavor ID. +func ListExtraSpecs(client *golangsdk.ServiceClient, flavorID string) (map[string]string, error) { + raw, err := client.Get(client.ServiceURL("flavors", flavorID, "os-extra_specs"), nil, nil) + return extraSpes(err, raw) +} diff --git a/openstack/compute/v2/flavors/remove_access.go b/openstack/compute/v2/flavors/remove_access.go new file mode 100644 index 000000000..ed07a9bb1 --- /dev/null +++ b/openstack/compute/v2/flavors/remove_access.go @@ -0,0 +1,32 @@ +package flavors + +import ( + "net/http" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// RemoveAccess removes/revokes a tenant/project access to a flavor. +func RemoveAccess(client *golangsdk.ServiceClient, id string, opts AccessOpts) ([]FlavorAccess, error) { + b, err := build.RequestBody(opts, "removeTenantAccess") + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("flavors", id, "action"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extraAcc(err, raw) +} + +func extraAcc(err error, raw *http.Response) ([]FlavorAccess, error) { + if err != nil { + return nil, err + } + + var res []FlavorAccess + err = extract.IntoSlicePtr(raw.Body, &res, "flavor_access") + return res, err +} diff --git a/openstack/compute/v2/flavors/requests.go b/openstack/compute/v2/flavors/requests.go deleted file mode 100644 index 0a2ccbc9c..000000000 --- a/openstack/compute/v2/flavors/requests.go +++ /dev/null @@ -1,360 +0,0 @@ -package flavors - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// ListOptsBuilder allows extensions to add additional parameters to the -// List request. -type ListOptsBuilder interface { - ToFlavorListQuery() (string, error) -} - -/* - AccessType maps to OpenStack's Flavor.is_public field. Although the is_public - field is boolean, the request options are ternary, which is why AccessType is - a string. The following values are allowed: - - The AccessType arguement is optional, and if it is not supplied, OpenStack - returns the PublicAccess flavors. -*/ -type AccessType string - -const ( - // PublicAccess returns public flavors and private flavors associated with - // that project. - PublicAccess AccessType = "true" - - // PrivateAccess (admin only) returns private flavors, across all projects. - PrivateAccess AccessType = "false" - - // AllAccess (admin only) returns public and private flavors across all - // projects. - AllAccess AccessType = "None" -) - -/* - ListOpts filters the results returned by the List() function. - For example, a flavor with a minDisk field of 10 will not be returned if you - specify MinDisk set to 20. - - Typically, software will use the last ID of the previous call to List to set - the Marker for the current call. -*/ -type ListOpts struct { - // ChangesSince, if provided, instructs List to return only those things which - // have changed since the timestamp provided. - ChangesSince string `q:"changes-since"` - - // MinDisk and MinRAM, if provided, elides flavors which do not meet your - // criteria. - MinDisk int `q:"minDisk"` - MinRAM int `q:"minRam"` - - // SortDir allows to select sort direction. - // It can be "asc" or "desc" (default). - SortDir string `q:"sort_dir"` - - // SortKey allows to sort by one of the flavors attributes. - // Default is flavorid. - SortKey string `q:"sort_key"` - - // Marker and Limit control paging. - // Marker instructs List where to start listing from. - Marker string `q:"marker"` - - // Limit instructs List to refrain from sending excessively large lists of - // flavors. - Limit int `q:"limit"` - - // AccessType, if provided, instructs List which set of flavors to return. - // If IsPublic not provided, flavors for the current project are returned. - AccessType AccessType `q:"is_public"` -} - -// ToFlavorListQuery formats a ListOpts into a query string. -func (opts ListOpts) ToFlavorListQuery() (string, error) { - q, err := golangsdk.BuildQueryString(opts) - if err != nil { - return "", err - } - return q.String(), err -} - -// ListDetail instructs OpenStack to provide a list of flavors. -// You may provide criteria by which List curtails its results for easier -// processing. -func ListDetail(client *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { - url := listURL(client) - if opts != nil { - query, err := opts.ToFlavorListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return FlavorPage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -type CreateOptsBuilder interface { - ToFlavorCreateMap() (map[string]interface{}, error) -} - -// CreateOpts specifies parameters used for creating a flavor. -type CreateOpts struct { - // Name is the name of the flavor. - Name string `json:"name" required:"true"` - - // RAM is the memory of the flavor, measured in MB. - RAM int `json:"ram" required:"true"` - - // VCPUs is the number of vcpus for the flavor. - VCPUs int `json:"vcpus" required:"true"` - - // Disk the amount of root disk space, measured in GB. - Disk *int `json:"disk" required:"true"` - - // ID is a unique ID for the flavor. - ID string `json:"id,omitempty"` - - // Swap is the amount of swap space for the flavor, measured in MB. - Swap *int `json:"swap,omitempty"` - - // RxTxFactor alters the network bandwidth of a flavor. - RxTxFactor float64 `json:"rxtx_factor,omitempty"` - - // IsPublic flags a flavor as being available to all projects or not. - IsPublic *bool `json:"os-flavor-access:is_public,omitempty"` - - // Ephemeral is the amount of ephemeral disk space, measured in GB. - Ephemeral *int `json:"OS-FLV-EXT-DATA:ephemeral,omitempty"` -} - -// ToFlavorCreateMap constructs a request body from CreateOpts. -func (opts CreateOpts) ToFlavorCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "flavor") -} - -// Create requests the creation of a new flavor. -func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToFlavorCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200, 201}, - }) - return -} - -// Get retrieves details of a single flavor. Use ExtractFlavor to convert its -// result into a Flavor. -func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) - return -} - -// Delete deletes the specified flavor ID. -func Delete(client *golangsdk.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) - return -} - -// ListAccesses retrieves the tenants which have access to a flavor. -func ListAccesses(client *golangsdk.ServiceClient, id string) pagination.Pager { - url := accessURL(client, id) - - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return AccessPage{pagination.SinglePageBase(r)} - }) -} - -// AddAccessOptsBuilder allows extensions to add additional parameters to the -// AddAccess requests. -type AddAccessOptsBuilder interface { - ToFlavorAddAccessMap() (map[string]interface{}, error) -} - -// AddAccessOpts represents options for adding access to a flavor. -type AddAccessOpts struct { - // Tenant is the project/tenant ID to grant access. - Tenant string `json:"tenant"` -} - -// ToFlavorAddAccessMap constructs a request body from AddAccessOpts. -func (opts AddAccessOpts) ToFlavorAddAccessMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "addTenantAccess") -} - -// AddAccess grants a tenant/project access to a flavor. -func AddAccess(client *golangsdk.ServiceClient, id string, opts AddAccessOptsBuilder) (r AddAccessResult) { - b, err := opts.ToFlavorAddAccessMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// RemoveAccessOptsBuilder allows extensions to add additional parameters to the -// RemoveAccess requests. -type RemoveAccessOptsBuilder interface { - ToFlavorRemoveAccessMap() (map[string]interface{}, error) -} - -// RemoveAccessOpts represents options for removing access to a flavor. -type RemoveAccessOpts struct { - // Tenant is the project/tenant ID to grant access. - Tenant string `json:"tenant"` -} - -// ToFlavorRemoveAccessMap constructs a request body from RemoveAccessOpts. -func (opts RemoveAccessOpts) ToFlavorRemoveAccessMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "removeTenantAccess") -} - -// RemoveAccess removes/revokes a tenant/project access to a flavor. -func RemoveAccess(client *golangsdk.ServiceClient, id string, opts RemoveAccessOptsBuilder) (r RemoveAccessResult) { - b, err := opts.ToFlavorRemoveAccessMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// ExtraSpecs requests all the extra-specs for the given flavor ID. -func ListExtraSpecs(client *golangsdk.ServiceClient, flavorID string) (r ListExtraSpecsResult) { - _, r.Err = client.Get(extraSpecsListURL(client, flavorID), &r.Body, nil) - return -} - -func GetExtraSpec(client *golangsdk.ServiceClient, flavorID string, key string) (r GetExtraSpecResult) { - _, r.Err = client.Get(extraSpecsGetURL(client, flavorID, key), &r.Body, nil) - return -} - -// CreateExtraSpecsOptsBuilder allows extensions to add additional parameters to the -// CreateExtraSpecs requests. -type CreateExtraSpecsOptsBuilder interface { - ToFlavorExtraSpecsCreateMap() (map[string]interface{}, error) -} - -// ExtraSpecsOpts is a map that contains key-value pairs. -type ExtraSpecsOpts map[string]string - -// ToFlavorExtraSpecsCreateMap assembles a body for a Create request based on -// the contents of ExtraSpecsOpts. -func (opts ExtraSpecsOpts) ToFlavorExtraSpecsCreateMap() (map[string]interface{}, error) { - return map[string]interface{}{"extra_specs": opts}, nil -} - -// CreateExtraSpecs will create or update the extra-specs key-value pairs for -// the specified Flavor. -func CreateExtraSpecs(client *golangsdk.ServiceClient, flavorID string, opts CreateExtraSpecsOptsBuilder) (r CreateExtraSpecsResult) { - b, err := opts.ToFlavorExtraSpecsCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(extraSpecsCreateURL(client, flavorID), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// UpdateExtraSpecOptsBuilder allows extensions to add additional parameters to -// the Update request. -type UpdateExtraSpecOptsBuilder interface { - ToFlavorExtraSpecUpdateMap() (map[string]string, string, error) -} - -// ToFlavorExtraSpecUpdateMap assembles a body for an Update request based on -// the contents of a ExtraSpecOpts. -func (opts ExtraSpecsOpts) ToFlavorExtraSpecUpdateMap() (map[string]string, string, error) { - if len(opts) != 1 { - err := golangsdk.ErrInvalidInput{} - err.Argument = "flavors.ExtraSpecOpts" - err.Info = "Must have 1 and only one key-value pair" - return nil, "", err - } - - var key string - for k := range opts { - key = k - } - - return opts, key, nil -} - -// UpdateExtraSpec will updates the value of the specified flavor's extra spec -// for the key in opts. -func UpdateExtraSpec(client *golangsdk.ServiceClient, flavorID string, opts UpdateExtraSpecOptsBuilder) (r UpdateExtraSpecResult) { - b, key, err := opts.ToFlavorExtraSpecUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(extraSpecUpdateURL(client, flavorID, key), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// DeleteExtraSpec will delete the key-value pair with the given key for the given -// flavor ID. -func DeleteExtraSpec(client *golangsdk.ServiceClient, flavorID, key string) (r DeleteExtraSpecResult) { - _, r.Err = client.Delete(extraSpecDeleteURL(client, flavorID, key), &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// IDFromName is a convienience function that returns a flavor's ID given its -// name. -func IDFromName(client *golangsdk.ServiceClient, name string) (string, error) { - count := 0 - id := "" - allPages, err := ListDetail(client, nil).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractFlavors(allPages) - if err != nil { - return "", err - } - - for _, f := range all { - if f.Name == name { - count++ - id = f.ID - } - } - - switch count { - case 0: - err := &golangsdk.ErrResourceNotFound{} - err.ResourceType = "flavor" - err.Name = name - return "", err - case 1: - return id, nil - default: - err := &golangsdk.ErrMultipleResourcesFound{} - err.ResourceType = "flavor" - err.Name = name - err.Count = count - return "", err - } -} diff --git a/openstack/compute/v2/flavors/results.go b/openstack/compute/v2/flavors/results.go deleted file mode 100644 index 0fad3c48e..000000000 --- a/openstack/compute/v2/flavors/results.go +++ /dev/null @@ -1,252 +0,0 @@ -package flavors - -import ( - "encoding/json" - "strconv" - - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -type commonResult struct { - golangsdk.Result -} - -// CreateResult is the response of a Get operations. Call its Extract method to -// interpret it as a Flavor. -type CreateResult struct { - commonResult -} - -// GetResult is the response of a Get operations. Call its Extract method to -// interpret it as a Flavor. -type GetResult struct { - commonResult -} - -// DeleteResult is the result from a Delete operation. Call its ExtractErr -// method to determine if the call succeeded or failed. -type DeleteResult struct { - golangsdk.ErrResult -} - -// Extract provides access to the individual Flavor returned by the Get and -// Create functions. -func (r commonResult) Extract() (*Flavor, error) { - var s struct { - Flavor *Flavor `json:"flavor"` - } - err := r.ExtractInto(&s) - return s.Flavor, err -} - -// Flavor represent (virtual) hardware configurations for server resources -// in a region. -type Flavor struct { - // ID is the flavor's unique ID. - ID string `json:"id"` - - // Disk is the amount of root disk, measured in GB. - Disk int `json:"disk"` - - // RAM is the amount of memory, measured in MB. - RAM int `json:"ram"` - - // Name is the name of the flavor. - Name string `json:"name"` - - // RxTxFactor describes bandwidth alterations of the flavor. - RxTxFactor float64 `json:"rxtx_factor"` - - // Swap is the amount of swap space, measured in MB. - Swap int `json:"-"` - - // VCPUs indicates how many (virtual) CPUs are available for this flavor. - VCPUs int `json:"vcpus"` - - // IsPublic indicates whether the flavor is public. - IsPublic bool `json:"os-flavor-access:is_public"` - - // Ephemeral is the amount of ephemeral disk space, measured in GB. - Ephemeral int `json:"OS-FLV-EXT-DATA:ephemeral"` -} - -func (r *Flavor) UnmarshalJSON(b []byte) error { - type tmp Flavor - var s struct { - tmp - Swap interface{} `json:"swap"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - - *r = Flavor(s.tmp) - - switch t := s.Swap.(type) { - case float64: - r.Swap = int(t) - case string: - switch t { - case "": - r.Swap = 0 - default: - swap, err := strconv.ParseFloat(t, 64) - if err != nil { - return err - } - r.Swap = int(swap) - } - } - - return nil -} - -// FlavorPage contains a single page of all flavors from a ListDetails call. -type FlavorPage struct { - pagination.LinkedPageBase -} - -// IsEmpty determines if a FlavorPage contains any results. -func (page FlavorPage) IsEmpty() (bool, error) { - flavors, err := ExtractFlavors(page) - return len(flavors) == 0, err -} - -// NextPageURL uses the response's embedded link reference to navigate to the -// next page of results. -func (page FlavorPage) NextPageURL() (string, error) { - var s struct { - Links []golangsdk.Link `json:"flavors_links"` - } - err := page.ExtractInto(&s) - if err != nil { - return "", err - } - return golangsdk.ExtractNextURL(s.Links) -} - -// ExtractFlavors provides access to the list of flavors in a page acquired -// from the ListDetail operation. -func ExtractFlavors(r pagination.Page) ([]Flavor, error) { - var s struct { - Flavors []Flavor `json:"flavors"` - } - err := (r.(FlavorPage)).ExtractInto(&s) - return s.Flavors, err -} - -// AccessPage contains a single page of all FlavorAccess entries for a flavor. -type AccessPage struct { - pagination.SinglePageBase -} - -// IsEmpty indicates whether an AccessPage is empty. -func (page AccessPage) IsEmpty() (bool, error) { - v, err := ExtractAccesses(page) - return len(v) == 0, err -} - -// ExtractAccesses interprets a page of results as a slice of FlavorAccess. -func ExtractAccesses(r pagination.Page) ([]FlavorAccess, error) { - var s struct { - FlavorAccesses []FlavorAccess `json:"flavor_access"` - } - err := (r.(AccessPage)).ExtractInto(&s) - return s.FlavorAccesses, err -} - -type accessResult struct { - golangsdk.Result -} - -// AddAccessResult is the response of an AddAccess operation. Call its -// Extract method to interpret it as a slice of FlavorAccess. -type AddAccessResult struct { - accessResult -} - -// RemoveAccessResult is the response of a RemoveAccess operation. Call its -// Extract method to interpret it as a slice of FlavorAccess. -type RemoveAccessResult struct { - accessResult -} - -// Extract provides access to the result of an access create or delete. -// The result will be all accesses that the flavor has. -func (r accessResult) Extract() ([]FlavorAccess, error) { - var s struct { - FlavorAccesses []FlavorAccess `json:"flavor_access"` - } - err := r.ExtractInto(&s) - return s.FlavorAccesses, err -} - -// FlavorAccess represents an ACL of tenant access to a specific Flavor. -type FlavorAccess struct { - // FlavorID is the unique ID of the flavor. - FlavorID string `json:"flavor_id"` - - // TenantID is the unique ID of the tenant. - TenantID string `json:"tenant_id"` -} - -// Extract interprets any extraSpecsResult as ExtraSpecs, if possible. -func (r extraSpecsResult) Extract() (map[string]string, error) { - var s struct { - ExtraSpecs map[string]string `json:"extra_specs"` - } - err := r.ExtractInto(&s) - return s.ExtraSpecs, err -} - -// extraSpecsResult contains the result of a call for (potentially) multiple -// key-value pairs. Call its Extract method to interpret it as a -// map[string]interface. -type extraSpecsResult struct { - golangsdk.Result -} - -// ListExtraSpecsResult contains the result of a Get operation. Call its Extract -// method to interpret it as a map[string]interface. -type ListExtraSpecsResult struct { - extraSpecsResult -} - -// CreateExtraSpecResult contains the result of a Create operation. Call its -// Extract method to interpret it as a map[string]interface. -type CreateExtraSpecsResult struct { - extraSpecsResult -} - -// extraSpecResult contains the result of a call for individual a single -// key-value pair. -type extraSpecResult struct { - golangsdk.Result -} - -// GetExtraSpecResult contains the result of a Get operation. Call its Extract -// method to interpret it as a map[string]interface. -type GetExtraSpecResult struct { - extraSpecResult -} - -// UpdateExtraSpecResult contains the result of an Update operation. Call its -// Extract method to interpret it as a map[string]interface. -type UpdateExtraSpecResult struct { - extraSpecResult -} - -// DeleteExtraSpecResult contains the result of a Delete operation. Call its -// ExtractErr method to determine if the call succeeded or failed. -type DeleteExtraSpecResult struct { - golangsdk.ErrResult -} - -// Extract interprets any extraSpecResult as an ExtraSpec, if possible. -func (r extraSpecResult) Extract() (map[string]string, error) { - var s map[string]string - err := r.ExtractInto(&s) - return s, err -} diff --git a/openstack/compute/v2/flavors/testing/doc.go b/openstack/compute/v2/flavors/testing/doc.go deleted file mode 100644 index c27087b56..000000000 --- a/openstack/compute/v2/flavors/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// flavors unit tests -package testing diff --git a/openstack/compute/v2/flavors/testing/requests_test.go b/openstack/compute/v2/flavors/testing/requests_test.go index 6f31097e5..501abe25f 100644 --- a/openstack/compute/v2/flavors/testing/requests_test.go +++ b/openstack/compute/v2/flavors/testing/requests_test.go @@ -75,7 +75,7 @@ func TestListFlavors(t *testing.T) { pageCount := 0 // Get public and private flavors - err := flavors.ListDetail(fake.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { + err := flavors.ListDetail(fake.ServiceClient(), flavors.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { pageCount += 1 actual, err := flavors.ExtractFlavors(page) @@ -126,7 +126,7 @@ func TestGetFlavor(t *testing.T) { `) }) - actual, err := flavors.Get(fake.ServiceClient(), "12345").Extract() + actual, err := flavors.Get(fake.ServiceClient(), "12345") if err != nil { t.Fatalf("Unable to get flavor: %v", err) } @@ -168,7 +168,7 @@ func TestCreateFlavor(t *testing.T) { }) disk := 1 - opts := &flavors.CreateOpts{ + opts := flavors.CreateOpts{ ID: "1", Name: "m1.tiny", Disk: &disk, @@ -176,7 +176,7 @@ func TestCreateFlavor(t *testing.T) { VCPUs: 1, RxTxFactor: 1.0, } - actual, err := flavors.Create(fake.ServiceClient(), opts).Extract() + actual, err := flavors.Create(fake.ServiceClient(), opts) if err != nil { t.Fatalf("Unable to create flavor: %v", err) } @@ -205,7 +205,7 @@ func TestDeleteFlavor(t *testing.T) { }) res := flavors.Delete(fake.ServiceClient(), "12345678") - th.AssertNoErr(t, res.Err) + th.AssertNoErr(t, res) } func TestFlavorAccessesList(t *testing.T) { @@ -235,10 +235,7 @@ func TestFlavorAccessesList(t *testing.T) { }, } - allPages, err := flavors.ListAccesses(fake.ServiceClient(), "12345678").AllPages() - th.AssertNoErr(t, err) - - actual, err := flavors.ExtractAccesses(allPages) + actual, err := flavors.ListAccesses(fake.ServiceClient(), "12345678") th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, actual) } @@ -280,11 +277,11 @@ func TestFlavorAccessAdd(t *testing.T) { }, } - addAccessOpts := flavors.AddAccessOpts{ + addAccessOpts := flavors.AccessOpts{ Tenant: "2f954bcf047c4ee9b09a37d49ae6db54", } - actual, err := flavors.AddAccess(fake.ServiceClient(), "12345678", addAccessOpts).Extract() + actual, err := flavors.AddAccess(fake.ServiceClient(), "12345678", addAccessOpts) th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, actual) } @@ -314,11 +311,11 @@ func TestFlavorAccessRemove(t *testing.T) { `) }) - removeAccessOpts := flavors.RemoveAccessOpts{ + removeAccessOpts := flavors.AccessOpts{ Tenant: "2f954bcf047c4ee9b09a37d49ae6db54", } - actual, err := flavors.RemoveAccess(fake.ServiceClient(), "12345678", removeAccessOpts).Extract() + actual, err := flavors.RemoveAccess(fake.ServiceClient(), "12345678", removeAccessOpts) th.AssertNoErr(t, err) th.AssertDeepEquals(t, []flavors.FlavorAccess{}, actual) } @@ -329,7 +326,7 @@ func TestFlavorExtraSpecsList(t *testing.T) { HandleExtraSpecsListSuccessfully(t) expected := ExtraSpecs - actual, err := flavors.ListExtraSpecs(fake.ServiceClient(), "1").Extract() + actual, err := flavors.ListExtraSpecs(fake.ServiceClient(), "1") th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) } @@ -340,7 +337,7 @@ func TestFlavorExtraSpecGet(t *testing.T) { HandleExtraSpecGetSuccessfully(t) expected := ExtraSpec - actual, err := flavors.GetExtraSpec(fake.ServiceClient(), "1", "hw:cpu_policy").Extract() + actual, err := flavors.GetExtraSpec(fake.ServiceClient(), "1", "hw:cpu_policy") th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) } @@ -350,12 +347,12 @@ func TestFlavorExtraSpecsCreate(t *testing.T) { defer th.TeardownHTTP() HandleExtraSpecsCreateSuccessfully(t) - createOpts := flavors.ExtraSpecsOpts{ + createOpts := map[string]string{ "hw:cpu_policy": "CPU-POLICY", "hw:cpu_thread_policy": "CPU-THREAD-POLICY", } expected := ExtraSpecs - actual, err := flavors.CreateExtraSpecs(fake.ServiceClient(), "1", createOpts).Extract() + actual, err := flavors.CreateExtraSpecs(fake.ServiceClient(), "1", createOpts) th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) } @@ -365,11 +362,11 @@ func TestFlavorExtraSpecUpdate(t *testing.T) { defer th.TeardownHTTP() HandleExtraSpecUpdateSuccessfully(t) - updateOpts := flavors.ExtraSpecsOpts{ + updateOpts := map[string]string{ "hw:cpu_policy": "CPU-POLICY-2", } expected := UpdatedExtraSpec - actual, err := flavors.UpdateExtraSpec(fake.ServiceClient(), "1", updateOpts).Extract() + actual, err := flavors.UpdateExtraSpec(fake.ServiceClient(), "1", updateOpts) th.AssertNoErr(t, err) th.CheckDeepEquals(t, expected, actual) } @@ -380,5 +377,5 @@ func TestFlavorExtraSpecDelete(t *testing.T) { HandleExtraSpecDeleteSuccessfully(t) res := flavors.DeleteExtraSpec(fake.ServiceClient(), "1", "hw:cpu_policy") - th.AssertNoErr(t, res.Err) + th.AssertNoErr(t, res) } diff --git a/openstack/compute/v2/flavors/update_extra_spec.go b/openstack/compute/v2/flavors/update_extra_spec.go new file mode 100644 index 000000000..008c97004 --- /dev/null +++ b/openstack/compute/v2/flavors/update_extra_spec.go @@ -0,0 +1,33 @@ +package flavors + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// toFlavorExtraSpecUpdateMap assembles a body for an Update request based on the contents of a ExtraSpecOpts. +func toFlavorExtraSpecUpdateMap(opts map[string]string) (map[string]string, string, error) { + if len(opts) != 1 { + err := golangsdk.ErrInvalidInput{} + err.Argument = "flavors.ExtraSpecOpts" + err.Info = "Must have 1 and only one key-value pair" + return nil, "", err + } + + var key string + for k := range opts { + key = k + } + + return opts, key, nil +} + +// UpdateExtraSpec will update the value of the specified flavor's extraAcc spec for the key in opts. +func UpdateExtraSpec(client *golangsdk.ServiceClient, flavorID string, opts map[string]string) (map[string]string, error) { + b, key, err := toFlavorExtraSpecUpdateMap(opts) + if err != nil { + return nil, err + } + + raw, err := client.Put(client.ServiceURL("flavors", flavorID, "os-extra_specs", key), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extraSpe(err, raw) +} diff --git a/openstack/compute/v2/flavors/urls.go b/openstack/compute/v2/flavors/urls.go deleted file mode 100644 index 951c0ed4b..000000000 --- a/openstack/compute/v2/flavors/urls.go +++ /dev/null @@ -1,49 +0,0 @@ -package flavors - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -func getURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL("flavors", id) -} - -func listURL(client *golangsdk.ServiceClient) string { - return client.ServiceURL("flavors", "detail") -} - -func createURL(client *golangsdk.ServiceClient) string { - return client.ServiceURL("flavors") -} - -func deleteURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL("flavors", id) -} - -func accessURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL("flavors", id, "os-flavor-access") -} - -func accessActionURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL("flavors", id, "action") -} - -func extraSpecsListURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL("flavors", id, "os-extra_specs") -} - -func extraSpecsGetURL(client *golangsdk.ServiceClient, id, key string) string { - return client.ServiceURL("flavors", id, "os-extra_specs", key) -} - -func extraSpecsCreateURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL("flavors", id, "os-extra_specs") -} - -func extraSpecUpdateURL(client *golangsdk.ServiceClient, id, key string) string { - return client.ServiceURL("flavors", id, "os-extra_specs", key) -} - -func extraSpecDeleteURL(client *golangsdk.ServiceClient, id, key string) string { - return client.ServiceURL("flavors", id, "os-extra_specs", key) -} diff --git a/openstack/compute/v2/images/Delete.go b/openstack/compute/v2/images/Delete.go new file mode 100644 index 000000000..ccade9919 --- /dev/null +++ b/openstack/compute/v2/images/Delete.go @@ -0,0 +1,9 @@ +package images + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Delete deletes the specified image ID. +func Delete(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Delete(client.ServiceURL("images", id), nil) + return +} diff --git a/openstack/compute/v2/images/Get.go b/openstack/compute/v2/images/Get.go new file mode 100644 index 000000000..3f9e220c3 --- /dev/null +++ b/openstack/compute/v2/images/Get.go @@ -0,0 +1,42 @@ +package images + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// Get returns data about a specific image by its ID. +func Get(client *golangsdk.ServiceClient, id string) (*Image, error) { + raw, err := client.Get(client.ServiceURL("images", id), nil, nil) + if err != nil { + return nil, err + } + + var res Image + err = extract.IntoStructPtr(raw.Body, &res, "image") + return &res, err +} + +// Image represents an Image returned by the Compute API. +type Image struct { + // ID is the unique ID of an image. + ID string + // Created is the date when the image was created. + Created string + // MinDisk is the minimum amount of disk a flavor must have to be able + // to create a server based on the image, measured in GB. + MinDisk int + // MinRAM is the minimum amount of RAM a flavor must have to be able + // to create a server based on the image, measured in MB. + MinRAM int + // Name provides a human-readable moniker for the OS image. + Name string + // The Progress and Status fields indicate image-creation status. + Progress int + // Status is the current status of the image. + Status string + // Update is the date when the image was updated. + Updated string + // Metadata provides free-form key/value pairs that further describe the image. + Metadata map[string]interface{} +} diff --git a/openstack/compute/v2/images/IDFromName.go b/openstack/compute/v2/images/IDFromName.go new file mode 100644 index 000000000..2e8e57368 --- /dev/null +++ b/openstack/compute/v2/images/IDFromName.go @@ -0,0 +1,43 @@ +package images + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" +) + +// IDFromName is a convenience function that returns an image's ID given its name. +func IDFromName(client *golangsdk.ServiceClient, name string) (string, error) { + count := 0 + id := "" + allPages, err := ListDetail(client, ListOpts{}).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractImages(allPages) + if err != nil { + return "", err + } + + for _, f := range all { + if f.Name == name { + count++ + id = f.ID + } + } + + switch count { + case 0: + err := &golangsdk.ErrResourceNotFound{} + err.ResourceType = "image" + err.Name = name + return "", err + case 1: + return id, nil + default: + err := &golangsdk.ErrMultipleResourcesFound{} + err.ResourceType = "image" + err.Name = name + err.Count = count + return "", err + } +} diff --git a/openstack/compute/v2/images/ListDetail.go b/openstack/compute/v2/images/ListDetail.go new file mode 100644 index 000000000..46951ff82 --- /dev/null +++ b/openstack/compute/v2/images/ListDetail.go @@ -0,0 +1,67 @@ +package images + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" + "github.com/opentelekomcloud/gophertelekomcloud/pagination" +) + +// ListOpts contain options filtering Images returned from a call to ListDetail. +type ListOpts struct { + // ChangesSince filters Images based on the last changed status (in date-time format). + ChangesSince string `q:"changes-since"` + // Limit limits the number of Images to return. + Limit int `q:"limit"` + // Mark is an Image UUID at which to set a marker. + Marker string `q:"marker"` + // Name is the name of the Image. + Name string `q:"name"` + // Server is the name of the Server (in URL format). + Server string `q:"server"` + // Status is the current status of the Image. + Status string `q:"status"` + // Type is the type of image (e.g. BASE, SERVER, ALL). + Type string `q:"type"` +} + +// ListDetail enumerates the available images. +func ListDetail(client *golangsdk.ServiceClient, opts ListOpts) pagination.Pager { + query, err := golangsdk.BuildQueryString(opts) + if err != nil { + return pagination.Pager{Err: err} + } + + return pagination.NewPager(client, client.ServiceURL("images", "detail")+query.String(), + func(r pagination.PageResult) pagination.Page { + return ImagePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// ImagePage contains a single page of all Images return from a ListDetail +// operation. Use ExtractImages to convert it into a slice of usable structs. +type ImagePage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if an ImagePage contains no Image results. +func (page ImagePage) IsEmpty() (bool, error) { + images, err := ExtractImages(page) + return len(images) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the next page of results. +func (page ImagePage) NextPageURL() (string, error) { + var res []golangsdk.Link + err := extract.IntoSlicePtr(page.BodyReader(), &res, "images_links") + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(res) +} + +// ExtractImages converts a page of List results into a slice of usable Image structs. +func ExtractImages(r pagination.Page) ([]Image, error) { + var res []Image + err := extract.IntoSlicePtr(r.(ImagePage).BodyReader(), &res, "images") + return res, err +} diff --git a/openstack/compute/v2/images/requests.go b/openstack/compute/v2/images/requests.go deleted file mode 100644 index 89e1489b2..000000000 --- a/openstack/compute/v2/images/requests.go +++ /dev/null @@ -1,112 +0,0 @@ -package images - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// ListOptsBuilder allows extensions to add additional parameters to the -// ListDetail request. -type ListOptsBuilder interface { - ToImageListQuery() (string, error) -} - -// ListOpts contain options filtering Images returned from a call to ListDetail. -type ListOpts struct { - // ChangesSince filters Images based on the last changed status (in date-time - // format). - ChangesSince string `q:"changes-since"` - - // Limit limits the number of Images to return. - Limit int `q:"limit"` - - // Mark is an Image UUID at which to set a marker. - Marker string `q:"marker"` - - // Name is the name of the Image. - Name string `q:"name"` - - // Server is the name of the Server (in URL format). - Server string `q:"server"` - - // Status is the current status of the Image. - Status string `q:"status"` - - // Type is the type of image (e.g. BASE, SERVER, ALL). - Type string `q:"type"` -} - -// ToImageListQuery formats a ListOpts into a query string. -func (opts ListOpts) ToImageListQuery() (string, error) { - q, err := golangsdk.BuildQueryString(opts) - if err != nil { - return "", err - } - return q.String(), err -} - -// ListDetail enumerates the available images. -func ListDetail(client *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { - url := listDetailURL(client) - if opts != nil { - query, err := opts.ToImageListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return ImagePage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// Get returns data about a specific image by its ID. -func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) - return -} - -// Delete deletes the specified image ID. -func Delete(client *golangsdk.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) - return -} - -// IDFromName is a convienience function that returns an image's ID given its -// name. -func IDFromName(client *golangsdk.ServiceClient, name string) (string, error) { - count := 0 - id := "" - allPages, err := ListDetail(client, nil).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractImages(allPages) - if err != nil { - return "", err - } - - for _, f := range all { - if f.Name == name { - count++ - id = f.ID - } - } - - switch count { - case 0: - err := &golangsdk.ErrResourceNotFound{} - err.ResourceType = "image" - err.Name = name - return "", err - case 1: - return id, nil - default: - err := &golangsdk.ErrMultipleResourcesFound{} - err.ResourceType = "image" - err.Name = name - err.Count = count - return "", err - } -} diff --git a/openstack/compute/v2/images/results.go b/openstack/compute/v2/images/results.go deleted file mode 100644 index 5ff178365..000000000 --- a/openstack/compute/v2/images/results.go +++ /dev/null @@ -1,95 +0,0 @@ -package images - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// GetResult is the response from a Get operation. Call its Extract method to -// interpret it as an Image. -type GetResult struct { - golangsdk.Result -} - -// DeleteResult is the result from a Delete operation. Call its ExtractErr -// method to determine if the call succeeded or failed. -type DeleteResult struct { - golangsdk.ErrResult -} - -// Extract interprets a GetResult as an Image. -func (r GetResult) Extract() (*Image, error) { - var s struct { - Image *Image `json:"image"` - } - err := r.ExtractInto(&s) - return s.Image, err -} - -// Image represents an Image returned by the Compute API. -type Image struct { - // ID is the unique ID of an image. - ID string - - // Created is the date when the image was created. - Created string - - // MinDisk is the minimum amount of disk a flavor must have to be able - // to create a server based on the image, measured in GB. - MinDisk int - - // MinRAM is the minimum amount of RAM a flavor must have to be able - // to create a server based on the image, measured in MB. - MinRAM int - - // Name provides a human-readable moniker for the OS image. - Name string - - // The Progress and Status fields indicate image-creation status. - Progress int - - // Status is the current status of the image. - Status string - - // Update is the date when the image was updated. - Updated string - - // Metadata provides free-form key/value pairs that further describe the - // image. - Metadata map[string]interface{} -} - -// ImagePage contains a single page of all Images returne from a ListDetail -// operation. Use ExtractImages to convert it into a slice of usable structs. -type ImagePage struct { - pagination.LinkedPageBase -} - -// IsEmpty returns true if an ImagePage contains no Image results. -func (page ImagePage) IsEmpty() (bool, error) { - images, err := ExtractImages(page) - return len(images) == 0, err -} - -// NextPageURL uses the response's embedded link reference to navigate to the -// next page of results. -func (page ImagePage) NextPageURL() (string, error) { - var s struct { - Links []golangsdk.Link `json:"images_links"` - } - err := page.ExtractInto(&s) - if err != nil { - return "", err - } - return golangsdk.ExtractNextURL(s.Links) -} - -// ExtractImages converts a page of List results into a slice of usable Image -// structs. -func ExtractImages(r pagination.Page) ([]Image, error) { - var s struct { - Images []Image `json:"images"` - } - err := (r.(ImagePage)).ExtractInto(&s) - return s.Images, err -} diff --git a/openstack/compute/v2/images/testing/doc.go b/openstack/compute/v2/images/testing/doc.go deleted file mode 100644 index db1045153..000000000 --- a/openstack/compute/v2/images/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// images unit tests -package testing diff --git a/openstack/compute/v2/images/testing/requests_test.go b/openstack/compute/v2/images/testing/requests_test.go index eefff2123..b768b6d22 100644 --- a/openstack/compute/v2/images/testing/requests_test.go +++ b/openstack/compute/v2/images/testing/requests_test.go @@ -69,7 +69,7 @@ func TestListImages(t *testing.T) { }) pages := 0 - options := &images.ListOpts{Limit: 2} + options := images.ListOpts{Limit: 2} err := images.ListDetail(fake.ServiceClient(), options).EachPage(func(page pagination.Page) (bool, error) { pages++ @@ -154,7 +154,7 @@ func TestGetImage(t *testing.T) { `) }) - actual, err := images.Get(fake.ServiceClient(), "12345678").Extract() + actual, err := images.Get(fake.ServiceClient(), "12345678") th.AssertNoErr(t, err) expected := &images.Image{ @@ -204,5 +204,5 @@ func TestDeleteImage(t *testing.T) { }) res := images.Delete(fake.ServiceClient(), "12345678") - th.AssertNoErr(t, res.Err) + th.AssertNoErr(t, res) } diff --git a/openstack/compute/v2/images/urls.go b/openstack/compute/v2/images/urls.go deleted file mode 100644 index 45dcc3f77..000000000 --- a/openstack/compute/v2/images/urls.go +++ /dev/null @@ -1,15 +0,0 @@ -package images - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func listDetailURL(client *golangsdk.ServiceClient) string { - return client.ServiceURL("images", "detail") -} - -func getURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL("images", id) -} - -func deleteURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL("images", id) -} diff --git a/openstack/compute/v2/servers/ChangeAdminPassword.go b/openstack/compute/v2/servers/ChangeAdminPassword.go new file mode 100644 index 000000000..119cfcf23 --- /dev/null +++ b/openstack/compute/v2/servers/ChangeAdminPassword.go @@ -0,0 +1,15 @@ +package servers + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// ChangeAdminPassword alters the administrator or root password for a specified server. +func ChangeAdminPassword(client *golangsdk.ServiceClient, id, newPassword string) (err error) { + b := map[string]interface{}{ + "changePassword": map[string]string{ + "adminPass": newPassword, + }, + } + + _, err = client.Post(client.ServiceURL("servers", id, "action"), b, nil, nil) + return +} diff --git a/openstack/compute/v2/servers/ConfirmResize.go b/openstack/compute/v2/servers/ConfirmResize.go new file mode 100644 index 000000000..067d67d55 --- /dev/null +++ b/openstack/compute/v2/servers/ConfirmResize.go @@ -0,0 +1,12 @@ +package servers + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// ConfirmResize confirms a previous resize operation on a server. See Resize() for more details. +func ConfirmResize(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Post(client.ServiceURL("servers", id, "action"), map[string]interface{}{"confirmResize": nil}, + nil, &golangsdk.RequestOpts{ + OkCodes: []int{201, 202, 204}, + }) + return +} diff --git a/openstack/compute/v2/servers/Create.go b/openstack/compute/v2/servers/Create.go new file mode 100644 index 000000000..e924ec78d --- /dev/null +++ b/openstack/compute/v2/servers/Create.go @@ -0,0 +1,166 @@ +package servers + +import ( + "encoding/base64" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/flavors" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/images" +) + +// CreateOpts specifies server creation parameters. +type CreateOpts struct { + // Name is the name to assign to the newly launched server. + Name string `json:"name" required:"true"` + // ImageRef [optional; required if ImageName is not provided] is the ID or + // full URL to the image that contains the server's OS and initial state. + // Also, optional if using the boot-from-volume extension. + ImageRef string `json:"imageRef"` + // ImageName [optional; required if ImageRef is not provided] is the name of + // the image that contains the server's OS and initial state. + // Also, optional if using the boot-from-volume extension. + ImageName string `json:"-"` + // FlavorRef [optional; required if FlavorName is not provided] is the ID or + // full URL to the flavor that describes the server's specs. + FlavorRef string `json:"flavorRef"` + // FlavorName [optional; required if FlavorRef is not provided] is the name of + // the flavor that describes the server's specs. + FlavorName string `json:"-"` + // SecurityGroups lists the names of the security groups to which this server should belong. + SecurityGroups []string `json:"-"` + // UserData contains configuration information or scripts to use upon launch. + // Create will base64-encode it for you, if it isn't already. + UserData []byte `json:"-"` + // AvailabilityZone in which to launch the server. + AvailabilityZone string `json:"availability_zone,omitempty"` + // Networks dictates how this server will be attached to available networks. + // By default, the server will be attached to all isolated networks for the tenant. + Networks []Network `json:"-"` + // Metadata contains key-value pairs (up to 255 bytes each) to attach to the server. + Metadata map[string]string `json:"metadata,omitempty"` + // Personality includes files to inject into the server at launch. + // Create will base64-encode file contents for you. + Personality []*File `json:"personality,omitempty"` + // ConfigDrive enables metadata injection through a configuration drive. + ConfigDrive *bool `json:"config_drive,omitempty"` + // AdminPass sets the root user password. If not set, a randomly-generated + // password will be created and returned to the response. + AdminPass string `json:"adminPass,omitempty"` + // AccessIPv4 specifies an IPv4 address for the instance. + AccessIPv4 string `json:"accessIPv4,omitempty"` + // AccessIPv6 specifies an IPv6 address for the instance. + AccessIPv6 string `json:"accessIPv6,omitempty"` + // ServiceClient will allow calls to be made to retrieve an image or flavor ID by name. + ServiceClient *golangsdk.ServiceClient `json:"-"` +} + +// Network is used within CreateOpts to control a new server's network attachments. +type Network struct { + // UUID of a network to attach to the newly provisioned server. + // Required unless Port is provided. + UUID string + // Port of a neutron network to attach to the newly provisioned server. + // Required unless UUID is provided. + Port string + // FixedIP specifies a fixed IPv4 address to be used on this network. + FixedIP string +} + +// CreateOptsBuilder CreateOptsWithCustomField +type CreateOptsBuilder interface { + ToServerCreateMap() (map[string]interface{}, error) +} + +// ToServerCreateMap assembles a request body based on the contents of a CreateOpts. +func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { + sc := opts.ServiceClient + opts.ServiceClient = nil + b, err := golangsdk.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + if opts.UserData != nil { + var userData string + if _, err := base64.StdEncoding.DecodeString(string(opts.UserData)); err != nil { + userData = base64.StdEncoding.EncodeToString(opts.UserData) + } else { + userData = string(opts.UserData) + } + b["user_data"] = &userData + } + + if len(opts.SecurityGroups) > 0 { + securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups)) + for i, groupName := range opts.SecurityGroups { + securityGroups[i] = map[string]interface{}{"name": groupName} + } + b["security_groups"] = securityGroups + } + + if len(opts.Networks) > 0 { + networks := make([]map[string]interface{}, len(opts.Networks)) + for i, net := range opts.Networks { + networks[i] = make(map[string]interface{}) + if net.UUID != "" { + networks[i]["uuid"] = net.UUID + } + if net.Port != "" { + networks[i]["port"] = net.Port + } + if net.FixedIP != "" { + networks[i]["fixed_ip"] = net.FixedIP + } + } + b["networks"] = networks + } + + // If ImageRef isn't provided, check if ImageName was provided to ascertain + // the image ID. + if opts.ImageRef == "" { + if opts.ImageName != "" { + if sc == nil { + err := ErrNoClientProvidedForIDByName{} + err.Argument = "ServiceClient" + return nil, err + } + imageID, err := images.IDFromName(sc, opts.ImageName) + if err != nil { + return nil, err + } + b["imageRef"] = imageID + } + } + + // If FlavorRef isn't provided, use FlavorName to ascertain the flavor ID. + if opts.FlavorRef == "" { + if opts.FlavorName == "" { + err := ErrNeitherFlavorIDNorFlavorNameProvided{} + err.Argument = "FlavorRef/FlavorName" + return nil, err + } + if sc == nil { + err := ErrNoClientProvidedForIDByName{} + err.Argument = "ServiceClient" + return nil, err + } + flavorID, err := flavors.IDFromName(sc, opts.FlavorName) + if err != nil { + return nil, err + } + b["flavorRef"] = flavorID + } + + return map[string]interface{}{"server": b}, nil +} + +// Create requests a server to be provisioned to the user in the current tenant. +func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (*Server, error) { + b, err := opts.ToServerCreateMap() + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("servers"), b, nil, nil) + return ExtractSer(err, raw) +} diff --git a/openstack/compute/v2/servers/CreateImage.go b/openstack/compute/v2/servers/CreateImage.go new file mode 100644 index 000000000..83c241ad3 --- /dev/null +++ b/openstack/compute/v2/servers/CreateImage.go @@ -0,0 +1,45 @@ +package servers + +import ( + "fmt" + "net/url" + "path" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// CreateImageOpts provides options to pass to the CreateImage request. +type CreateImageOpts struct { + // Name of the image/snapshot. + Name string `json:"name" required:"true"` + // Metadata contains key-value pairs (up to 255 bytes each) to attach to the created image. + Metadata map[string]string `json:"metadata,omitempty"` +} + +// CreateImage makes a request against the nova API to schedule an image to be created of the server +func CreateImage(client *golangsdk.ServiceClient, id string, opts CreateImageOpts) (string, error) { + b, err := build.RequestBody(opts, "createImage") + if err != nil { + return "", err + } + + raw, err := client.Post(client.ServiceURL("servers", id, "action"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{202}, + }) + if err != nil { + return "", err + } + + // Get the image id from the header + u, err := url.ParseRequestURI(raw.Header.Get("Location")) + if err != nil { + return "", err + } + + imageID := path.Base(u.Path) + if imageID == "." || imageID == "/" { + return "", fmt.Errorf("failed to parse the ID of newly created image: %s", u) + } + return imageID, nil +} diff --git a/openstack/compute/v2/servers/CreateMetadatum.go b/openstack/compute/v2/servers/CreateMetadatum.go new file mode 100644 index 000000000..7cff8504d --- /dev/null +++ b/openstack/compute/v2/servers/CreateMetadatum.go @@ -0,0 +1,37 @@ +package servers + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" +) + +// MetadatumOpts is a map of length one that contains a key-value pair. +type MetadatumOpts map[string]string + +// toMetadatumCreateMap assembles a body for a Create request based on the contents of a MetadataumOpts. +func toMetadatumCreateMap(opts MetadatumOpts) (map[string]interface{}, string, error) { + if len(opts) != 1 { + err := golangsdk.ErrInvalidInput{} + err.Argument = "servers.MetadatumOpts" + err.Info = "Must have 1 and only 1 key-value pair" + return nil, "", err + } + metadatum := map[string]interface{}{"meta": opts} + var key string + for k := range metadatum["meta"].(MetadatumOpts) { + key = k + } + return metadatum, key, nil +} + +// CreateMetadatum will create or update the key-value pair with the given key for the given server ID. +func CreateMetadatum(client *golangsdk.ServiceClient, id string, opts MetadatumOpts) (map[string]string, error) { + b, key, err := toMetadatumCreateMap(opts) + if err != nil { + return nil, err + } + + raw, err := client.Put(client.ServiceURL("servers", id, "metadata", key), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extraTum(err, raw) +} diff --git a/openstack/compute/v2/servers/Delete.go b/openstack/compute/v2/servers/Delete.go new file mode 100644 index 000000000..d4245aaf1 --- /dev/null +++ b/openstack/compute/v2/servers/Delete.go @@ -0,0 +1,9 @@ +package servers + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Delete requests that a server previously provisioned be removed from your account. +func Delete(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Delete(client.ServiceURL("servers", id), nil) + return +} diff --git a/openstack/compute/v2/servers/DeleteMetadatum.go b/openstack/compute/v2/servers/DeleteMetadatum.go new file mode 100644 index 000000000..7c0498ff0 --- /dev/null +++ b/openstack/compute/v2/servers/DeleteMetadatum.go @@ -0,0 +1,9 @@ +package servers + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// DeleteMetadatum will delete the key-value pair with the given key for the given server ID. +func DeleteMetadatum(client *golangsdk.ServiceClient, id, key string) (err error) { + _, err = client.Delete(client.ServiceURL("servers", id, "metadata", key), nil) + return +} diff --git a/openstack/compute/v2/servers/ForceDelete.go b/openstack/compute/v2/servers/ForceDelete.go new file mode 100644 index 000000000..9f7fe483f --- /dev/null +++ b/openstack/compute/v2/servers/ForceDelete.go @@ -0,0 +1,9 @@ +package servers + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// ForceDelete forces the deletion of a server. +func ForceDelete(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Post(client.ServiceURL("servers", id, "action"), map[string]interface{}{"forceDelete": ""}, nil, nil) + return +} diff --git a/openstack/compute/v2/servers/Get.go b/openstack/compute/v2/servers/Get.go new file mode 100644 index 000000000..2c79ec6c2 --- /dev/null +++ b/openstack/compute/v2/servers/Get.go @@ -0,0 +1,31 @@ +package servers + +import ( + "net/http" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// Get requests details on a single server, by ID. +func Get(client *golangsdk.ServiceClient, id string) (*Server, error) { + raw, err := get(client, id) + return ExtractSer(err, raw) +} + +func GetInto(client *golangsdk.ServiceClient, id string, v interface{}) (err error) { + raw, err := get(client, id) + if err != nil { + return + } + + err = extract.IntoStructPtr(raw.Body, v, "server") + return +} + +func get(client *golangsdk.ServiceClient, id string) (*http.Response, error) { + raw, err := client.Get(client.ServiceURL("servers", id), nil, &golangsdk.RequestOpts{ + OkCodes: []int{200, 203}, + }) + return raw, err +} diff --git a/openstack/compute/v2/servers/GetNICs.go b/openstack/compute/v2/servers/GetNICs.go new file mode 100644 index 000000000..d11b688fd --- /dev/null +++ b/openstack/compute/v2/servers/GetNICs.go @@ -0,0 +1,10 @@ +package servers + +import "github.com/opentelekomcloud/gophertelekomcloud" + +func GetNICs(client *golangsdk.ServiceClient, id string) (*Server, error) { + raw, err := client.Get(client.ServiceURL("servers", id, "os-interface"), nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return ExtractSer(err, raw) +} diff --git a/openstack/compute/v2/servers/GetPassword.go b/openstack/compute/v2/servers/GetPassword.go new file mode 100644 index 000000000..c9f5f462d --- /dev/null +++ b/openstack/compute/v2/servers/GetPassword.go @@ -0,0 +1,48 @@ +package servers + +import ( + "crypto/rsa" + "encoding/base64" + "fmt" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// GetPassword makes a request against the nova API to get the encrypted administrative password. +// ExtractPassword gets the encrypted password. +// If privateKey != nil the password is decrypted with the private key. +// If privateKey == nil the encrypted password is returned and can be decrypted +// with: +// +// echo '' | base64 -D | openssl rsautl -decrypt -inkey +func GetPassword(client *golangsdk.ServiceClient, serverId string, privateKey *rsa.PrivateKey) (string, error) { + raw, err := client.Get(client.ServiceURL("servers", serverId, "os-server-password"), nil, nil) + if err != nil { + return "", err + } + + var res struct { + Password string `json:"password"` + } + err = extract.Into(raw.Body, &res) + if err == nil && privateKey != nil && res.Password != "" { + return DecryptPassword(res.Password, privateKey) + } + return res.Password, err +} + +func DecryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) { + b64EncryptedPassword := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedPassword))) + + n, err := base64.StdEncoding.Decode(b64EncryptedPassword, []byte(encryptedPassword)) + if err != nil { + return "", fmt.Errorf("failed to base64 decode encrypted password: %w", err) + } + password, err := rsa.DecryptPKCS1v15(nil, privateKey, b64EncryptedPassword[0:n]) + if err != nil { + return "", fmt.Errorf("failed to decrypt password: %w", err) + } + + return string(password), nil +} diff --git a/openstack/compute/v2/servers/List.go b/openstack/compute/v2/servers/List.go new file mode 100644 index 000000000..1fa420162 --- /dev/null +++ b/openstack/compute/v2/servers/List.go @@ -0,0 +1,48 @@ +package servers + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/pagination" +) + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the server attributes you want to see returned. Marker and Limit are used for pagination. +type ListOpts struct { + // ChangesSince is a time/date stamp for when the server last changed status. + ChangesSince string `q:"changes-since"` + // Image is the name of the image in URL format. + Image string `q:"image"` + // Flavor is the name of the flavor in URL format. + Flavor string `q:"flavor"` + // Name of the server as a string; can be queried with regular expressions. + // Realize that ?name=bob returns both bob and bobb. If you need to match bob + // only, you can use a regular expression matching the syntax of the + // underlying database server implemented for Compute. + Name string `q:"name"` + // Status is the value of the status of the server so that you can filter on "ACTIVE" for example. + Status string `q:"status"` + // Host is the name of the host as a string. + Host string `q:"host"` + // Marker is a UUID of the server at which you want to set a marker. + Marker string `q:"marker"` + // Limit is an integer value for the limit of values to return. + Limit int `q:"limit"` + // AllTenants is a bool to show all tenants. + AllTenants bool `q:"all_tenants"` + // TenantID lists servers for a particular tenant. Setting "AllTenants = true" is required. + TenantID string `q:"tenant_id"` +} + +// List makes a request against the API to list servers accessible to you. +func List(client *golangsdk.ServiceClient, opts ListOpts) pagination.Pager { + query, err := golangsdk.BuildQueryString(opts) + if err != nil { + return pagination.Pager{Err: err} + } + + return pagination.NewPager(client, client.ServiceURL("servers", "detail")+query.String(), + func(r pagination.PageResult) pagination.Page { + return ServerPage{pagination.LinkedPageBase{PageResult: r}} + }) +} diff --git a/openstack/compute/v2/servers/ListAddresses.go b/openstack/compute/v2/servers/ListAddresses.go new file mode 100644 index 000000000..fc3a698c5 --- /dev/null +++ b/openstack/compute/v2/servers/ListAddresses.go @@ -0,0 +1,20 @@ +package servers + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// ListAddresses makes a request against the API to list the servers IP addresses. +func ListAddresses(client *golangsdk.ServiceClient, id string) (map[string][]Address, error) { + raw, err := client.Get(client.ServiceURL("servers", id, "ips"), nil, nil) + if err != nil { + return nil, err + } + + var res struct { + Addresses map[string][]Address `json:"addresses"` + } + err = extract.Into(raw.Body, &res) + return res.Addresses, err +} diff --git a/openstack/compute/v2/servers/ListAddressesByNetwork.go b/openstack/compute/v2/servers/ListAddressesByNetwork.go new file mode 100644 index 000000000..ec11a4848 --- /dev/null +++ b/openstack/compute/v2/servers/ListAddressesByNetwork.go @@ -0,0 +1,24 @@ +package servers + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// ListAddressesByNetwork makes a request against the API to list the servers IP addresses for the given network. +func ListAddressesByNetwork(client *golangsdk.ServiceClient, id, network string) ([]Address, error) { + raw, err := client.Get(client.ServiceURL("servers", id, "ips", network), nil, nil) + if err != nil { + return nil, err + } + + var res map[string][]Address + err = extract.Into(raw.Body, &res) + + var key string + for k := range res { + key = k + } + + return res[key], err +} diff --git a/openstack/compute/v2/servers/Metadata.go b/openstack/compute/v2/servers/Metadata.go new file mode 100644 index 000000000..75bb6d7db --- /dev/null +++ b/openstack/compute/v2/servers/Metadata.go @@ -0,0 +1,11 @@ +package servers + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" +) + +// Metadata requests all the metadata for the given server ID. +func Metadata(client *golangsdk.ServiceClient, id string) (map[string]string, error) { + raw, err := client.Get(client.ServiceURL("servers", id, "metadata"), nil, nil) + return extraMet(err, raw) +} diff --git a/openstack/compute/v2/servers/Metadatum.go b/openstack/compute/v2/servers/Metadatum.go new file mode 100644 index 000000000..00b3fa47a --- /dev/null +++ b/openstack/compute/v2/servers/Metadatum.go @@ -0,0 +1,9 @@ +package servers + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Metadatum requests the key-value pair with the given key for the given server ID. +func Metadatum(client *golangsdk.ServiceClient, id, key string) (map[string]string, error) { + raw, err := client.Get(client.ServiceURL("servers", id, "metadata", key), nil, nil) + return extraTum(err, raw) +} diff --git a/openstack/compute/v2/servers/Reboot.go b/openstack/compute/v2/servers/Reboot.go new file mode 100644 index 000000000..c8a6ebe4b --- /dev/null +++ b/openstack/compute/v2/servers/Reboot.go @@ -0,0 +1,49 @@ +package servers + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// RebootMethod describes the mechanisms by which a server reboot can be requested. +type RebootMethod string + +// These constants determine how a server should be rebooted. +// See the Reboot() function for further details. +const ( + SoftReboot RebootMethod = "SOFT" + HardReboot RebootMethod = "HARD" + OSReboot = SoftReboot + PowerCycle = HardReboot +) + +// RebootOpts provides options to the reboot request. +type RebootOpts struct { + // Type is the type of reboot to perform on the server. + Type RebootMethod `json:"type" required:"true"` +} + +/* +Reboot requests that a given server reboot. + +Two methods exist for rebooting a server: + +HardReboot (aka PowerCycle) starts the server instance by physically cutting +power to the machine, or if a VM, terminating it at the hypervisor level. +It's done. Caput. Full stop. +Then, after a brief while, power is rtored or the VM instance restarted. + +SoftReboot (aka OSReboot) simply tells the OS to restart under its own +procedure. +E.g., in Linux, asking it to enter runlevel 6, or executing +"sudo shutdown -r now", or by asking Windows to rtart the machine. +*/ +func Reboot(client *golangsdk.ServiceClient, id string, opts RebootOpts) (err error) { + b, err := build.RequestBody(opts, "reboot") + if err != nil { + return + } + + _, err = client.Post(client.ServiceURL("servers", id, "action"), b, nil, nil) + return +} diff --git a/openstack/compute/v2/servers/Rebuild.go b/openstack/compute/v2/servers/Rebuild.go new file mode 100644 index 000000000..b3373f772 --- /dev/null +++ b/openstack/compute/v2/servers/Rebuild.go @@ -0,0 +1,79 @@ +package servers + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/images" +) + +// RebuildOpts represents the configuration options used in a server rebuild operation. +type RebuildOpts struct { + // AdminPass is the server's admin password + AdminPass string `json:"adminPass,omitempty"` + // ImageID is the ID of the image you want your server to be provisioned on. + ImageID string `json:"imageRef"` + // ImageName is readable name of an image. + ImageName string `json:"-"` + // Name to set the server to + Name string `json:"name,omitempty"` + // AccessIPv4 [optional] provides a new IPv4 address for the instance. + AccessIPv4 string `json:"accessIPv4,omitempty"` + // AccessIPv6 [optional] provides a new IPv6 address for the instance. + AccessIPv6 string `json:"accessIPv6,omitempty"` + // Metadata [optional] contains key-value pairs (up to 255 bytes each) + // to attach to the server. + Metadata map[string]string `json:"metadata,omitempty"` + // Personality [optional] includes files to inject into the server at launch. + // Rebuild will base64-encode file contents for you. + Personality []*File `json:"personality,omitempty"` + // ServiceClient will allow calls to be made to retrieve an image or + // flavor ID by name. + ServiceClient *golangsdk.ServiceClient `json:"-"` +} + +// File is used within CreateOpts and RebuildOpts to inject a file into the server at launch. +// File implements the json.Marshaler interface, so when a Create or Rebuild +// operation is requested, json.Marshal will call File's MarshalJSON method. +type File struct { + // Path of the file. + Path string + // Contents of the file. Maximum content size is 255 bytes. + Contents []byte +} + +// ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON +func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) { + b, err := golangsdk.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + // If ImageRef isn't provided, check if ImageName was provided to ascertain + // the image ID. + if opts.ImageID == "" { + if opts.ImageName != "" { + if opts.ServiceClient == nil { + err := ErrNoClientProvidedForIDByName{} + err.Argument = "ServiceClient" + return nil, err + } + imageID, err := images.IDFromName(opts.ServiceClient, opts.ImageName) + if err != nil { + return nil, err + } + b["imageRef"] = imageID + } + } + + return map[string]interface{}{"rebuild": b}, nil +} + +// Rebuild will reprovision the server according to the configuration options provided in the RebuildOpts struct. +func Rebuild(client *golangsdk.ServiceClient, id string, opts RebuildOpts) (*Server, error) { + b, err := opts.ToServerRebuildMap() + if err != nil { + return nil, err + } + + raw, err := client.Post(client.ServiceURL("servers", id, "action"), b, nil, nil) + return ExtractSer(err, raw) +} diff --git a/openstack/compute/v2/servers/ResetMetadata.go b/openstack/compute/v2/servers/ResetMetadata.go new file mode 100644 index 000000000..b2c78bdb8 --- /dev/null +++ b/openstack/compute/v2/servers/ResetMetadata.go @@ -0,0 +1,17 @@ +package servers + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// MetadataOpts is a map that contains key-value pairs. +type MetadataOpts map[string]string + +// ResetMetadata will create multiple new key-value pairs for the given server ID. +// Note: Using this operation will erase any already-existing metadata and create the new metadata provided. +// To keep any already-existing metadata, use the UpdateMetadata function. +func ResetMetadata(client *golangsdk.ServiceClient, id string, opts map[string]string) (map[string]string, error) { + raw, err := client.Put(client.ServiceURL("servers", id, "metadata"), map[string]interface{}{"metadata": opts}, + nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extraMet(err, raw) +} diff --git a/openstack/compute/v2/servers/Resize.go b/openstack/compute/v2/servers/Resize.go new file mode 100644 index 000000000..bf1b18851 --- /dev/null +++ b/openstack/compute/v2/servers/Resize.go @@ -0,0 +1,31 @@ +package servers + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// ResizeOpts represents the configuration options used to control a Resize operation. +type ResizeOpts struct { + // FlavorRef is the ID of the flavor you wish your server to become. + FlavorRef string `json:"flavorRef" required:"true"` +} + +// Resize instructs the provider to change the flavor of the server. +// +// Note that this implies rebuilding it. +// +// Unfortunately, one cannot pass rebuild parameters to the resize function. +// When the resize completes, the server will be in VERIFY_RESIZE state. +// While in this state, you can explore the use of the new server's +// configuration. If you like it, call ConfirmResize() to commit the resize +// permanently. Otherwise, call RevertResize() to restore the old configuration. +func Resize(client *golangsdk.ServiceClient, id string, opts ResizeOpts) (err error) { + b, err := build.RequestBody(opts, "resize") + if err != nil { + return + } + + _, err = client.Post(client.ServiceURL("servers", id, "action"), b, nil, nil) + return +} diff --git a/openstack/compute/v2/servers/RevertResize.go b/openstack/compute/v2/servers/RevertResize.go new file mode 100644 index 000000000..255ea84d8 --- /dev/null +++ b/openstack/compute/v2/servers/RevertResize.go @@ -0,0 +1,9 @@ +package servers + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// RevertResize cancels a previous resize operation on a server. See Resize() for more details. +func RevertResize(client *golangsdk.ServiceClient, id string) (err error) { + _, err = client.Post(client.ServiceURL("servers", id, "action"), map[string]interface{}{"revertResize": nil}, nil, nil) + return +} diff --git a/openstack/compute/v2/servers/ShowConsoleOutput.go b/openstack/compute/v2/servers/ShowConsoleOutput.go new file mode 100644 index 000000000..ee9d3e276 --- /dev/null +++ b/openstack/compute/v2/servers/ShowConsoleOutput.go @@ -0,0 +1,35 @@ +package servers + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// ShowConsoleOutputOpts satisfies the ShowConsoleOutputOptsBuilder +type ShowConsoleOutputOpts struct { + // The number of lines to fetch from the end of console log. + // All lines will be returned if this is not specified. + Length int `json:"length,omitempty"` +} + +// ShowConsoleOutput makes a request against the nova API to get console log from the server +func ShowConsoleOutput(client *golangsdk.ServiceClient, id string, opts ShowConsoleOutputOpts) (string, error) { + b, err := build.RequestBody(opts, "os-getConsoleOutput") + if err != nil { + return "", err + } + + raw, err := client.Post(client.ServiceURL("servers", id, "action"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + if err != nil { + return "", err + } + + var res struct { + Output string `json:"output"` + } + err = extract.Into(raw.Body, &res) + return res.Output, err +} diff --git a/openstack/compute/v2/servers/Update.go b/openstack/compute/v2/servers/Update.go new file mode 100644 index 000000000..287578357 --- /dev/null +++ b/openstack/compute/v2/servers/Update.go @@ -0,0 +1,31 @@ +package servers + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// UpdateOpts specifies the base attributes that may be updated on an existing server. +type UpdateOpts struct { + // Name changes the displayed name of the server. + // The server host name will *not* change. + // Server names are not constrained to be unique, even within the same tenant. + Name string `json:"name,omitempty"` + // AccessIPv4 provides a new IPv4 address for the instance. + AccessIPv4 string `json:"accessIPv4,omitempty"` + // AccessIPv6 provides a new IPv6 address for the instance. + AccessIPv6 string `json:"accessIPv6,omitempty"` +} + +// Update requests that various attributes of the indicated server be changed. +func Update(client *golangsdk.ServiceClient, id string, opts UpdateOpts) (*Server, error) { + b, err := build.RequestBody(opts, "server") + if err != nil { + return nil, err + } + + raw, err := client.Put(client.ServiceURL("servers", id), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return ExtractSer(err, raw) +} diff --git a/openstack/compute/v2/servers/UpdateMetadata.go b/openstack/compute/v2/servers/UpdateMetadata.go new file mode 100644 index 000000000..c0a1f0d44 --- /dev/null +++ b/openstack/compute/v2/servers/UpdateMetadata.go @@ -0,0 +1,15 @@ +package servers + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// UpdateMetadata updates (or creates) all the metadata specified by opts for +// the given server ID. This operation does not affect already-existing metadata +// that is not specified by opts. +func UpdateMetadata(client *golangsdk.ServiceClient, id string, opts map[string]string) (map[string]string, error) { + b := map[string]interface{}{"metadata": opts} + + raw, err := client.Post(client.ServiceURL("servers", id, "metadata"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extraMet(err, raw) +} diff --git a/openstack/compute/v2/servers/requests.go b/openstack/compute/v2/servers/requests.go deleted file mode 100644 index 9f7196281..000000000 --- a/openstack/compute/v2/servers/requests.go +++ /dev/null @@ -1,806 +0,0 @@ -package servers - -import ( - "encoding/base64" - "encoding/json" - - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/flavors" - "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/images" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// ListOptsBuilder allows extensions to add additional parameters to the -// List request. -type ListOptsBuilder interface { - ToServerListQuery() (string, error) -} - -// ListOpts allows the filtering and sorting of paginated collections through -// the API. Filtering is achieved by passing in struct field values that map to -// the server attributes you want to see returned. Marker and Limit are used -// for pagination. -type ListOpts struct { - // ChangesSince is a time/date stamp for when the server last changed status. - ChangesSince string `q:"changes-since"` - - // Image is the name of the image in URL format. - Image string `q:"image"` - - // Flavor is the name of the flavor in URL format. - Flavor string `q:"flavor"` - - // Name of the server as a string; can be queried with regular expressions. - // Realize that ?name=bob returns both bob and bobb. If you need to match bob - // only, you can use a regular expression matching the syntax of the - // underlying database server implemented for Compute. - Name string `q:"name"` - - // Status is the value of the status of the server so that you can filter on - // "ACTIVE" for example. - Status string `q:"status"` - - // Host is the name of the host as a string. - Host string `q:"host"` - - // Marker is a UUID of the server at which you want to set a marker. - Marker string `q:"marker"` - - // Limit is an integer value for the limit of values to return. - Limit int `q:"limit"` - - // AllTenants is a bool to show all tenants. - AllTenants bool `q:"all_tenants"` - - // TenantID lists servers for a particular tenant. - // Setting "AllTenants = true" is required. - TenantID string `q:"tenant_id"` -} - -// ToServerListQuery formats a ListOpts into a query string. -func (opts ListOpts) ToServerListQuery() (string, error) { - q, err := golangsdk.BuildQueryString(opts) - if err != nil { - return "", err - } - return q.String(), err -} - -// List makes a request against the API to list servers accessible to you. -func List(client *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { - url := listDetailURL(client) - if opts != nil { - query, err := opts.ToServerListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return ServerPage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToServerCreateMap() (map[string]interface{}, error) -} - -// Network is used within CreateOpts to control a new server's network -// attachments. -type Network struct { - // UUID of a network to attach to the newly provisioned server. - // Required unless Port is provided. - UUID string - - // Port of a neutron network to attach to the newly provisioned server. - // Required unless UUID is provided. - Port string - - // FixedIP specifies a fixed IPv4 address to be used on this network. - FixedIP string -} - -// Personality is an array of files that are injected into the server at launch. -type Personality []*File - -// File is used within CreateOpts and RebuildOpts to inject a file into the -// server at launch. -// File implements the json.Marshaler interface, so when a Create or Rebuild -// operation is requested, json.Marshal will call File's MarshalJSON method. -type File struct { - // Path of the file. - Path string - - // Contents of the file. Maximum content size is 255 bytes. - Contents []byte -} - -// MarshalJSON marshals the escaped file, base64 encoding the contents. -func (f *File) MarshalJSON() ([]byte, error) { - file := struct { - Path string `json:"path"` - Contents string `json:"contents"` - }{ - Path: f.Path, - Contents: base64.StdEncoding.EncodeToString(f.Contents), - } - return json.Marshal(file) -} - -// CreateOpts specifies server creation parameters. -type CreateOpts struct { - // Name is the name to assign to the newly launched server. - Name string `json:"name" required:"true"` - - // ImageRef [optional; required if ImageName is not provided] is the ID or - // full URL to the image that contains the server's OS and initial state. - // Also optional if using the boot-from-volume extension. - ImageRef string `json:"imageRef"` - - // ImageName [optional; required if ImageRef is not provided] is the name of - // the image that contains the server's OS and initial state. - // Also optional if using the boot-from-volume extension. - ImageName string `json:"-"` - - // FlavorRef [optional; required if FlavorName is not provided] is the ID or - // full URL to the flavor that describes the server's specs. - FlavorRef string `json:"flavorRef"` - - // FlavorName [optional; required if FlavorRef is not provided] is the name of - // the flavor that describes the server's specs. - FlavorName string `json:"-"` - - // SecurityGroups lists the names of the security groups to which this server - // should belong. - SecurityGroups []string `json:"-"` - - // UserData contains configuration information or scripts to use upon launch. - // Create will base64-encode it for you, if it isn't already. - UserData []byte `json:"-"` - - // AvailabilityZone in which to launch the server. - AvailabilityZone string `json:"availability_zone,omitempty"` - - // Networks dictates how this server will be attached to available networks. - // By default, the server will be attached to all isolated networks for the - // tenant. - Networks []Network `json:"-"` - - // Metadata contains key-value pairs (up to 255 bytes each) to attach to the - // server. - Metadata map[string]string `json:"metadata,omitempty"` - - // Personality includes files to inject into the server at launch. - // Create will base64-encode file contents for you. - Personality Personality `json:"personality,omitempty"` - - // ConfigDrive enables metadata injection through a configuration drive. - ConfigDrive *bool `json:"config_drive,omitempty"` - - // AdminPass sets the root user password. If not set, a randomly-generated - // password will be created and returned in the response. - AdminPass string `json:"adminPass,omitempty"` - - // AccessIPv4 specifies an IPv4 address for the instance. - AccessIPv4 string `json:"accessIPv4,omitempty"` - - // AccessIPv6 pecifies an IPv6 address for the instance. - AccessIPv6 string `json:"accessIPv6,omitempty"` - - // ServiceClient will allow calls to be made to retrieve an image or - // flavor ID by name. - ServiceClient *golangsdk.ServiceClient `json:"-"` -} - -// ToServerCreateMap assembles a request body based on the contents of a -// CreateOpts. -func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { - sc := opts.ServiceClient - opts.ServiceClient = nil - b, err := golangsdk.BuildRequestBody(opts, "") - if err != nil { - return nil, err - } - - if opts.UserData != nil { - var userData string - if _, err := base64.StdEncoding.DecodeString(string(opts.UserData)); err != nil { - userData = base64.StdEncoding.EncodeToString(opts.UserData) - } else { - userData = string(opts.UserData) - } - b["user_data"] = &userData - } - - if len(opts.SecurityGroups) > 0 { - securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups)) - for i, groupName := range opts.SecurityGroups { - securityGroups[i] = map[string]interface{}{"name": groupName} - } - b["security_groups"] = securityGroups - } - - if len(opts.Networks) > 0 { - networks := make([]map[string]interface{}, len(opts.Networks)) - for i, net := range opts.Networks { - networks[i] = make(map[string]interface{}) - if net.UUID != "" { - networks[i]["uuid"] = net.UUID - } - if net.Port != "" { - networks[i]["port"] = net.Port - } - if net.FixedIP != "" { - networks[i]["fixed_ip"] = net.FixedIP - } - } - b["networks"] = networks - } - - // If ImageRef isn't provided, check if ImageName was provided to ascertain - // the image ID. - if opts.ImageRef == "" { - if opts.ImageName != "" { - if sc == nil { - err := ErrNoClientProvidedForIDByName{} - err.Argument = "ServiceClient" - return nil, err - } - imageID, err := images.IDFromName(sc, opts.ImageName) - if err != nil { - return nil, err - } - b["imageRef"] = imageID - } - } - - // If FlavorRef isn't provided, use FlavorName to ascertain the flavor ID. - if opts.FlavorRef == "" { - if opts.FlavorName == "" { - err := ErrNeitherFlavorIDNorFlavorNameProvided{} - err.Argument = "FlavorRef/FlavorName" - return nil, err - } - if sc == nil { - err := ErrNoClientProvidedForIDByName{} - err.Argument = "ServiceClient" - return nil, err - } - flavorID, err := flavors.IDFromName(sc, opts.FlavorName) - if err != nil { - return nil, err - } - b["flavorRef"] = flavorID - } - - return map[string]interface{}{"server": b}, nil -} - -// Create requests a server to be provisioned to the user in the current tenant. -func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - reqBody, err := opts.ToServerCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(listURL(client), reqBody, &r.Body, nil) - return -} - -// Delete requests that a server previously provisioned be removed from your -// account. -func Delete(client *golangsdk.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) - return -} - -// ForceDelete forces the deletion of a server. -func ForceDelete(client *golangsdk.ServiceClient, id string) (r ActionResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil) - return -} - -// Get requests details on a single server, by ID. -func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200, 203}, - }) - return -} - -func GetNICs(client *golangsdk.ServiceClient, id string) (r GetNICResult) { - _, r.Err = client.Get(getNICManagementURL(client, id), &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// UpdateOptsBuilder allows extensions to add additional attributes to the -// Update request. -type UpdateOptsBuilder interface { - ToServerUpdateMap() (map[string]interface{}, error) -} - -// UpdateOpts specifies the base attributes that may be updated on an existing -// server. -type UpdateOpts struct { - // Name changes the displayed name of the server. - // The server host name will *not* change. - // Server names are not constrained to be unique, even within the same tenant. - Name string `json:"name,omitempty"` - - // AccessIPv4 provides a new IPv4 address for the instance. - AccessIPv4 string `json:"accessIPv4,omitempty"` - - // AccessIPv6 provides a new IPv6 address for the instance. - AccessIPv6 string `json:"accessIPv6,omitempty"` -} - -// ToServerUpdateMap formats an UpdateOpts structure into a request body. -func (opts UpdateOpts) ToServerUpdateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "server") -} - -// Update requests that various attributes of the indicated server be changed. -func Update(client *golangsdk.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToServerUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// ChangeAdminPassword alters the administrator or root password for a specified -// server. -func ChangeAdminPassword(client *golangsdk.ServiceClient, id, newPassword string) (r ActionResult) { - b := map[string]interface{}{ - "changePassword": map[string]string{ - "adminPass": newPassword, - }, - } - _, r.Err = client.Post(actionURL(client, id), b, nil, nil) - return -} - -// RebootMethod describes the mechanisms by which a server reboot can be requested. -type RebootMethod string - -// These constants determine how a server should be rebooted. -// See the Reboot() function for further details. -const ( - SoftReboot RebootMethod = "SOFT" - HardReboot RebootMethod = "HARD" - OSReboot = SoftReboot - PowerCycle = HardReboot -) - -// RebootOptsBuilder allows extensions to add additional parameters to the -// reboot request. -type RebootOptsBuilder interface { - ToServerRebootMap() (map[string]interface{}, error) -} - -// RebootOpts provides options to the reboot request. -type RebootOpts struct { - // Type is the type of reboot to perform on the server. - Type RebootMethod `json:"type" required:"true"` -} - -// ToServerRebootMap builds a body for the reboot request. -func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "reboot") -} - -/* - Reboot requests that a given server reboot. - - Two methods exist for rebooting a server: - - HardReboot (aka PowerCycle) starts the server instance by physically cutting - power to the machine, or if a VM, terminating it at the hypervisor level. - It's done. Caput. Full stop. - Then, after a brief while, power is rtored or the VM instance restarted. - - SoftReboot (aka OSReboot) simply tells the OS to restart under its own - procedure. - E.g., in Linux, asking it to enter runlevel 6, or executing - "sudo shutdown -r now", or by asking Windows to rtart the machine. -*/ -func Reboot(client *golangsdk.ServiceClient, id string, opts RebootOptsBuilder) (r ActionResult) { - b, err := opts.ToServerRebootMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(actionURL(client, id), b, nil, nil) - return -} - -// RebuildOptsBuilder allows extensions to provide additional parameters to the -// rebuild request. -type RebuildOptsBuilder interface { - ToServerRebuildMap() (map[string]interface{}, error) -} - -// RebuildOpts represents the configuration options used in a server rebuild -// operation. -type RebuildOpts struct { - // AdminPass is the server's admin password - AdminPass string `json:"adminPass,omitempty"` - - // ImageID is the ID of the image you want your server to be provisioned on. - ImageID string `json:"imageRef"` - - // ImageName is readable name of an image. - ImageName string `json:"-"` - - // Name to set the server to - Name string `json:"name,omitempty"` - - // AccessIPv4 [optional] provides a new IPv4 address for the instance. - AccessIPv4 string `json:"accessIPv4,omitempty"` - - // AccessIPv6 [optional] provides a new IPv6 address for the instance. - AccessIPv6 string `json:"accessIPv6,omitempty"` - - // Metadata [optional] contains key-value pairs (up to 255 bytes each) - // to attach to the server. - Metadata map[string]string `json:"metadata,omitempty"` - - // Personality [optional] includes files to inject into the server at launch. - // Rebuild will base64-encode file contents for you. - Personality Personality `json:"personality,omitempty"` - - // ServiceClient will allow calls to be made to retrieve an image or - // flavor ID by name. - ServiceClient *golangsdk.ServiceClient `json:"-"` -} - -// ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON -func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) { - b, err := golangsdk.BuildRequestBody(opts, "") - if err != nil { - return nil, err - } - - // If ImageRef isn't provided, check if ImageName was provided to ascertain - // the image ID. - if opts.ImageID == "" { - if opts.ImageName != "" { - if opts.ServiceClient == nil { - err := ErrNoClientProvidedForIDByName{} - err.Argument = "ServiceClient" - return nil, err - } - imageID, err := images.IDFromName(opts.ServiceClient, opts.ImageName) - if err != nil { - return nil, err - } - b["imageRef"] = imageID - } - } - - return map[string]interface{}{"rebuild": b}, nil -} - -// Rebuild will reprovision the server according to the configuration options -// provided in the RebuildOpts struct. -func Rebuild(client *golangsdk.ServiceClient, id string, opts RebuildOptsBuilder) (r RebuildResult) { - b, err := opts.ToServerRebuildMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(actionURL(client, id), b, &r.Body, nil) - return -} - -// ResizeOptsBuilder allows extensions to add additional parameters to the -// resize request. -type ResizeOptsBuilder interface { - ToServerResizeMap() (map[string]interface{}, error) -} - -// ResizeOpts represents the configuration options used to control a Resize -// operation. -type ResizeOpts struct { - // FlavorRef is the ID of the flavor you wish your server to become. - FlavorRef string `json:"flavorRef" required:"true"` -} - -// ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON -// request body for the Resize request. -func (opts ResizeOpts) ToServerResizeMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "resize") -} - -// Resize instructs the provider to change the flavor of the server. -// -// Note that this implies rebuilding it. -// -// Unfortunately, one cannot pass rebuild parameters to the resize function. -// When the resize completes, the server will be in VERIFY_RESIZE state. -// While in this state, you can explore the use of the new server's -// configuration. If you like it, call ConfirmResize() to commit the resize -// permanently. Otherwise, call RevertResize() to restore the old configuration. -func Resize(client *golangsdk.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) { - b, err := opts.ToServerResizeMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(actionURL(client, id), b, nil, nil) - return -} - -// ConfirmResize confirms a previous resize operation on a server. -// See Resize() for more details. -func ConfirmResize(client *golangsdk.ServiceClient, id string) (r ActionResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"confirmResize": nil}, nil, &golangsdk.RequestOpts{ - OkCodes: []int{201, 202, 204}, - }) - return -} - -// RevertResize cancels a previous resize operation on a server. -// See Resize() for more details. -func RevertResize(client *golangsdk.ServiceClient, id string) (r ActionResult) { - _, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"revertResize": nil}, nil, nil) - return -} - -// ResetMetadataOptsBuilder allows extensions to add additional parameters to -// the Reset request. -type ResetMetadataOptsBuilder interface { - ToMetadataResetMap() (map[string]interface{}, error) -} - -// MetadataOpts is a map that contains key-value pairs. -type MetadataOpts map[string]string - -// ToMetadataResetMap assembles a body for a Reset request based on the contents -// of a MetadataOpts. -func (opts MetadataOpts) ToMetadataResetMap() (map[string]interface{}, error) { - return map[string]interface{}{"metadata": opts}, nil -} - -// ToMetadataUpdateMap assembles a body for an Update request based on the -// contents of a MetadataOpts. -func (opts MetadataOpts) ToMetadataUpdateMap() (map[string]interface{}, error) { - return map[string]interface{}{"metadata": opts}, nil -} - -// ResetMetadata will create multiple new key-value pairs for the given server -// ID. -// Note: Using this operation will erase any already-existing metadata and -// create the new metadata provided. To keep any already-existing metadata, -// use the UpdateMetadatas or UpdateMetadata function. -func ResetMetadata(client *golangsdk.ServiceClient, id string, opts ResetMetadataOptsBuilder) (r ResetMetadataResult) { - b, err := opts.ToMetadataResetMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(metadataURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Metadata requests all the metadata for the given server ID. -func Metadata(client *golangsdk.ServiceClient, id string) (r GetMetadataResult) { - _, r.Err = client.Get(metadataURL(client, id), &r.Body, nil) - return -} - -// UpdateMetadataOptsBuilder allows extensions to add additional parameters to -// the Create request. -type UpdateMetadataOptsBuilder interface { - ToMetadataUpdateMap() (map[string]interface{}, error) -} - -// UpdateMetadata updates (or creates) all the metadata specified by opts for -// the given server ID. This operation does not affect already-existing metadata -// that is not specified by opts. -func UpdateMetadata(client *golangsdk.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) { - b, err := opts.ToMetadataUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(metadataURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// MetadatumOptsBuilder allows extensions to add additional parameters to the -// Create request. -type MetadatumOptsBuilder interface { - ToMetadatumCreateMap() (map[string]interface{}, string, error) -} - -// MetadatumOpts is a map of length one that contains a key-value pair. -type MetadatumOpts map[string]string - -// ToMetadatumCreateMap assembles a body for a Create request based on the -// contents of a MetadataumOpts. -func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string, error) { - if len(opts) != 1 { - err := golangsdk.ErrInvalidInput{} - err.Argument = "servers.MetadatumOpts" - err.Info = "Must have 1 and only 1 key-value pair" - return nil, "", err - } - metadatum := map[string]interface{}{"meta": opts} - var key string - for k := range metadatum["meta"].(MetadatumOpts) { - key = k - } - return metadatum, key, nil -} - -// CreateMetadatum will create or update the key-value pair with the given key -// for the given server ID. -func CreateMetadatum(client *golangsdk.ServiceClient, id string, opts MetadatumOptsBuilder) (r CreateMetadatumResult) { - b, key, err := opts.ToMetadatumCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(metadatumURL(client, id, key), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Metadatum requests the key-value pair with the given key for the given -// server ID. -func Metadatum(client *golangsdk.ServiceClient, id, key string) (r GetMetadatumResult) { - _, r.Err = client.Get(metadatumURL(client, id, key), &r.Body, nil) - return -} - -// DeleteMetadatum will delete the key-value pair with the given key for the -// given server ID. -func DeleteMetadatum(client *golangsdk.ServiceClient, id, key string) (r DeleteMetadatumResult) { - _, r.Err = client.Delete(metadatumURL(client, id, key), nil) - return -} - -// ListAddresses makes a request against the API to list the servers IP -// addresses. -func ListAddresses(client *golangsdk.ServiceClient, id string) pagination.Pager { - return pagination.NewPager(client, listAddressesURL(client, id), func(r pagination.PageResult) pagination.Page { - return AddressPage{pagination.SinglePageBase(r)} - }) -} - -// ListAddressesByNetwork makes a request against the API to list the servers IP -// addresses for the given network. -func ListAddressesByNetwork(client *golangsdk.ServiceClient, id, network string) pagination.Pager { - return pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), func(r pagination.PageResult) pagination.Page { - return NetworkAddressPage{pagination.SinglePageBase(r)} - }) -} - -// CreateImageOptsBuilder allows extensions to add additional parameters to the -// CreateImage request. -type CreateImageOptsBuilder interface { - ToServerCreateImageMap() (map[string]interface{}, error) -} - -// CreateImageOpts provides options to pass to the CreateImage request. -type CreateImageOpts struct { - // Name of the image/snapshot. - Name string `json:"name" required:"true"` - - // Metadata contains key-value pairs (up to 255 bytes each) to attach to - // the created image. - Metadata map[string]string `json:"metadata,omitempty"` -} - -// ToServerCreateImageMap formats a CreateImageOpts structure into a request -// body. -func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "createImage") -} - -// CreateImage makes a request against the nova API to schedule an image to be -// created of the server -func CreateImage(client *golangsdk.ServiceClient, id string, opts CreateImageOptsBuilder) (r CreateImageResult) { - b, err := opts.ToServerCreateImageMap() - if err != nil { - r.Err = err - return - } - resp, err := client.Post(actionURL(client, id), b, nil, &golangsdk.RequestOpts{ - OkCodes: []int{202}, - }) - if err != nil { - r.Err = err - return - } - r.Header = resp.Header - return -} - -// IDFromName is a convenience function that returns a server's ID given its name. -func IDFromName(client *golangsdk.ServiceClient, name string) (string, error) { - count := 0 - id := "" - - listOpts := ListOpts{ - Name: name, - } - - allPages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractServers(allPages) - if err != nil { - return "", err - } - - for _, f := range all { - if f.Name == name { - count++ - id = f.ID - } - } - - switch count { - case 0: - return "", golangsdk.ErrResourceNotFound{Name: name, ResourceType: "server"} - case 1: - return id, nil - default: - return "", golangsdk.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "server"} - } -} - -// GetPassword makes a request against the nova API to get the encrypted -// administrative password. -func GetPassword(client *golangsdk.ServiceClient, serverId string) (r GetPasswordResult) { - _, r.Err = client.Get(passwordURL(client, serverId), &r.Body, nil) - return -} - -// ShowConsoleOutputOptsBuilder is the interface types must satisfy in order to be -// used as ShowConsoleOutput options -type ShowConsoleOutputOptsBuilder interface { - ToServerShowConsoleOutputMap() (map[string]interface{}, error) -} - -// ShowConsoleOutputOpts satisfies the ShowConsoleOutputOptsBuilder -type ShowConsoleOutputOpts struct { - // The number of lines to fetch from the end of console log. - // All lines will be returned if this is not specified. - Length int `json:"length,omitempty"` -} - -// ToServerShowConsoleOutputMap formats a ShowConsoleOutputOpts structure into a request body. -func (opts ShowConsoleOutputOpts) ToServerShowConsoleOutputMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "os-getConsoleOutput") -} - -// ShowConsoleOutput makes a request against the nova API to get console log from the server -func ShowConsoleOutput(client *golangsdk.ServiceClient, id string, opts ShowConsoleOutputOptsBuilder) (r ShowConsoleOutputResult) { - b, err := opts.ToServerShowConsoleOutputMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(actionURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} diff --git a/openstack/compute/v2/servers/results.go b/openstack/compute/v2/servers/results.go index ad698b137..67f325d0a 100644 --- a/openstack/compute/v2/servers/results.go +++ b/openstack/compute/v2/servers/results.go @@ -1,152 +1,32 @@ package servers import ( - "crypto/rsa" "encoding/base64" "encoding/json" - "fmt" - "net/url" - "path" + "net/http" "time" "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" "github.com/opentelekomcloud/gophertelekomcloud/pagination" ) -type serverResult struct { +type ServerResult struct { golangsdk.Result } -// Extract interprets any serverResult as a Server, if possible. -func (r serverResult) Extract() (*Server, error) { - var s Server - err := r.ExtractInto(&s) - return &s, err -} - -func (r serverResult) ExtractInto(v interface{}) error { - return r.Result.ExtractIntoStructPtr(v, "server") -} - -func ExtractServersInto(r pagination.Page, v interface{}) error { - return r.(ServerPage).Result.ExtractIntoSlicePtr(v, "servers") -} - -// CreateResult is the response from a Create operation. Call its Extract -// method to interpret it as a Server. -type CreateResult struct { - serverResult -} - -// GetResult is the response from a Get operation. Call its Extract -// method to interpret it as a Server. -type GetResult struct { - serverResult -} - -// GetNICResult is the response from a Get operation. Call its Extract -// method to interpret it as a NIC. -type GetNICResult struct { - serverResult -} - -// UpdateResult is the response from an Update operation. Call its Extract -// method to interpret it as a Server. -type UpdateResult struct { - serverResult -} - -// DeleteResult is the response from a Delete operation. Call its ExtractErr -// method to determine if the call succeeded or failed. -type DeleteResult struct { - golangsdk.ErrResult -} - -// RebuildResult is the response from a Rebuild operation. Call its Extract -// method to interpret it as a Server. -type RebuildResult struct { - serverResult -} - -// ActionResult represents the result of server action operations, like reboot. -// Call its ExtractErr method to determine if the action succeeded or failed. -type ActionResult struct { - golangsdk.ErrResult -} - -// CreateImageResult is the response from a CreateImage operation. Call its -// ExtractImageID method to retrieve the ID of the newly created image. -type CreateImageResult struct { - golangsdk.Result -} - -// ShowConsoleOutputResult represents the result of console output from a server -type ShowConsoleOutputResult struct { - golangsdk.Result -} - -// Extract will return the console output from a ShowConsoleOutput request. -func (r ShowConsoleOutputResult) Extract() (string, error) { - var s struct { - Output string `json:"output"` - } - - err := r.ExtractInto(&s) - return s.Output, err -} - -// GetPasswordResult represent the result of a get os-server-password operation. -// Call its ExtractPassword method to retrieve the password. -type GetPasswordResult struct { - golangsdk.Result -} - -// ExtractPassword gets the encrypted password. -// If privateKey != nil the password is decrypted with the private key. -// If privateKey == nil the encrypted password is returned and can be decrypted -// with: -// echo '' | base64 -D | openssl rsautl -decrypt -inkey -func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) { - var s struct { - Password string `json:"password"` - } - err := r.ExtractInto(&s) - if err == nil && privateKey != nil && s.Password != "" { - return decryptPassword(s.Password, privateKey) - } - return s.Password, err -} - -func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) { - b64EncryptedPassword := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedPassword))) - - n, err := base64.StdEncoding.Decode(b64EncryptedPassword, []byte(encryptedPassword)) +func ExtractSer(err error, raw *http.Response) (*Server, error) { if err != nil { - return "", fmt.Errorf("failed to base64 decode encrypted password: %w", err) - } - password, err := rsa.DecryptPKCS1v15(nil, privateKey, b64EncryptedPassword[0:n]) - if err != nil { - return "", fmt.Errorf("failed to decrypt password: %w", err) + return nil, err } - return string(password), nil + var res Server + err = extract.IntoStructPtr(raw.Body, &res, "server") + return &res, err } -// ExtractImageID gets the ID of the newly created server image from the header. -func (r CreateImageResult) ExtractImageID() (string, error) { - if r.Err != nil { - return "", r.Err - } - // Get the image id from the header - u, err := url.ParseRequestURI(r.Header.Get("Location")) - if err != nil { - return "", err - } - imageID := path.Base(u.Path) - if imageID == "." || imageID == "/" { - return "", fmt.Errorf("failed to parse the ID of newly created image: %s", u) - } - return imageID, nil +func (r ServerResult) ExtractInto(v interface{}) error { + return extract.IntoStructPtr(r.BodyReader(), v, "server") } // Server represents a server/instance in the OpenStack cloud. @@ -154,73 +34,51 @@ type Server struct { // ID uniquely identifies this server amongst all other servers, // including those not accessible to the current tenant. ID string `json:"id"` - // TenantID identifies the tenant owning this server resource. TenantID string `json:"tenant_id"` - // UserID uniquely identifies the user account owning the tenant. UserID string `json:"user_id"` - // Name contains the human-readable name for the server. Name string `json:"name"` - // Updated and Created contain ISO-8601 timestamps of when the state of the // server last changed, and when it was created. Updated time.Time `json:"updated"` Created time.Time `json:"created"` - // HostID is the host where the server is located in the cloud. HostID string `json:"hostid"` - // Status contains the current operational status of the server, // such as IN_PROGRESS or ACTIVE. Status string `json:"status"` - // Progress ranges from 0..100. // A request made against the server completes only once Progress reaches 100. Progress int `json:"progress"` - // AccessIPv4 and AccessIPv6 contain the IP addresses of the server, // suitable for remote access for administration. AccessIPv4 string `json:"accessIPv4"` AccessIPv6 string `json:"accessIPv6"` - - // Image refers to a JSON object, which itself indicates the OS image used to - // deploy the server. + // Image refers to a JSON object, which itself indicates the OS image used to deploy the server. Image map[string]interface{} `json:"-"` - // Flavor refers to a JSON object, which itself indicates the hardware // configuration of the deployed server. Flavor map[string]interface{} `json:"flavor"` - - // Addresses includes a list of all IP addresses assigned to the server, - // keyed by pool. + // Addresses includes a list of all IP addresses assigned to the server, keyed by pool. Addresses map[string]interface{} `json:"addresses"` - - // Metadata includes a list of all user-specified key-value pairs attached - // to the server. + // Metadata includes a list of all user-specified key-value pairs attached to the server. Metadata map[string]string `json:"metadata"` - - // Links includes HTTP references to the itself, useful for passing along to + // Links includes HTTP references to itself, useful for passing along to // other APIs that might want a server reference. Links []interface{} `json:"links"` - // KeyName indicates which public key was injected into the server on launch. KeyName string `json:"key_name"` - // AdminPass will generally be empty (""). However, it will contain the // administrative password chosen when provisioning a new server without a // set AdminPass setting in the first place. // Note that this is the ONLY time this field will be valid. AdminPass string `json:"adminPass"` - - // SecurityGroups includes the security groups that this instance has applied - // to it. + // SecurityGroups includes the security groups that this instance has applied to it. SecurityGroups []map[string]interface{} `json:"security_groups"` - // Fault contains failure information about a server. Fault Fault `json:"fault"` - // VolumeAttached includes the volumes that attached to the server. VolumesAttached []map[string]string `json:"os-extended-volumes:volumes_attached"` } @@ -258,13 +116,6 @@ func (r *Server) UnmarshalJSON(b []byte) error { return err } -// Extract interprets any serverResult as a Server, if possible. -func (r GetNICResult) Extract() ([]NIC, error) { - var s []NIC - err := r.ExtractIntoSlicePtr(&s, "interfaceAttachments") - return s, err -} - type NIC struct { PortState string `json:"port_state"` FixedIPs []FixedIP `json:"fixed_ips"` @@ -292,92 +143,49 @@ func (r ServerPage) IsEmpty() (bool, error) { return len(s) == 0, err } -// NextPageURL uses the response's embedded link reference to navigate to the -// next page of results. +// NextPageURL uses the response's embedded link reference to navigate to the next page of results. func (r ServerPage) NextPageURL() (string, error) { - var s struct { - Links []golangsdk.Link `json:"servers_links"` - } - err := r.ExtractInto(&s) + var res []golangsdk.Link + err := extract.IntoSlicePtr(r.BodyReader(), &res, "servers_links") if err != nil { return "", err } - return golangsdk.ExtractNextURL(s.Links) + return golangsdk.ExtractNextURL(res) } -// ExtractServers interprets the results of a single page from a List() call, -// producing a slice of Server entities. +// ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities. func ExtractServers(r pagination.Page) ([]Server, error) { var s []Server err := ExtractServersInto(r, &s) return s, err } -// MetadataResult contains the result of a call for (potentially) multiple -// key-value pairs. Call its Extract method to interpret it as a -// map[string]interface. -type MetadataResult struct { - golangsdk.Result -} - -// GetMetadataResult contains the result of a Get operation. Call its Extract -// method to interpret it as a map[string]interface. -type GetMetadataResult struct { - MetadataResult -} - -// ResetMetadataResult contains the result of a Reset operation. Call its -// Extract method to interpret it as a map[string]interface. -type ResetMetadataResult struct { - MetadataResult -} - -// UpdateMetadataResult contains the result of an Update operation. Call its -// Extract method to interpret it as a map[string]interface. -type UpdateMetadataResult struct { - MetadataResult -} - -// MetadatumResult contains the result of a call for individual a single -// key-value pair. -type MetadatumResult struct { - golangsdk.Result -} - -// GetMetadatumResult contains the result of a Get operation. Call its Extract -// method to interpret it as a map[string]interface. -type GetMetadatumResult struct { - MetadatumResult +func ExtractServersInto(r pagination.Page, v interface{}) error { + return extract.IntoSlicePtr(r.(ServerPage).BodyReader(), v, "servers") } -// CreateMetadatumResult contains the result of a Create operation. Call its -// Extract method to interpret it as a map[string]interface. -type CreateMetadatumResult struct { - MetadatumResult -} +func extraTum(err error, raw *http.Response) (map[string]string, error) { + if err != nil { + return nil, err + } -// DeleteMetadatumResult contains the result of a Delete operation. Call its -// ExtractErr method to determine if the call succeeded or failed. -type DeleteMetadatumResult struct { - golangsdk.ErrResult + var res struct { + Metadatum map[string]string `json:"meta"` + } + err = extract.Into(raw.Body, &res) + return res.Metadatum, err } -// Extract interprets any MetadataResult as a Metadata, if possible. -func (r MetadataResult) Extract() (map[string]string, error) { - var s struct { - Metadata map[string]string `json:"metadata"` +func extraMet(err error, raw *http.Response) (map[string]string, error) { + if err != nil { + return nil, err } - err := r.ExtractInto(&s) - return s.Metadata, err -} -// Extract interprets any MetadatumResult as a Metadatum, if possible. -func (r MetadatumResult) Extract() (map[string]string, error) { - var s struct { - Metadatum map[string]string `json:"meta"` + var res struct { + Metadata map[string]string `json:"metadata"` } - err := r.ExtractInto(&s) - return s.Metadatum, err + err = extract.Into(raw.Body, &res) + return res.Metadata, err } // Address represents an IP address. @@ -386,58 +194,14 @@ type Address struct { Address string `json:"addr"` } -// AddressPage abstracts the raw results of making a ListAddresses() request -// against the API. As OpenStack extensions may freely alter the response bodies -// of structures returned to the client, you may only safely access the data -// provided through the ExtractAddresses call. -type AddressPage struct { - pagination.SinglePageBase -} - -// IsEmpty returns true if an AddressPage contains no networks. -func (r AddressPage) IsEmpty() (bool, error) { - addresses, err := ExtractAddresses(r) - return len(addresses) == 0, err -} - -// ExtractAddresses interprets the results of a single page from a -// ListAddresses() call, producing a map of addresses. -func ExtractAddresses(r pagination.Page) (map[string][]Address, error) { - var s struct { - Addresses map[string][]Address `json:"addresses"` - } - err := (r.(AddressPage)).ExtractInto(&s) - return s.Addresses, err -} - -// NetworkAddressPage abstracts the raw results of making a -// ListAddressesByNetwork() request against the API. -// As OpenStack extensions may freely alter the response bodies of structures -// returned to the client, you may only safely access the data provided through -// the ExtractAddresses call. -type NetworkAddressPage struct { - pagination.SinglePageBase -} - -// IsEmpty returns true if a NetworkAddressPage contains no addresses. -func (r NetworkAddressPage) IsEmpty() (bool, error) { - addresses, err := ExtractNetworkAddresses(r) - return len(addresses) == 0, err -} - -// ExtractNetworkAddresses interprets the results of a single page from a -// ListAddressesByNetwork() call, producing a slice of addresses. -func ExtractNetworkAddresses(r pagination.Page) ([]Address, error) { - var s map[string][]Address - err := (r.(NetworkAddressPage)).ExtractInto(&s) - if err != nil { - return nil, err +// MarshalJSON marshals the escaped file, base64 encoding the contents. +func (f *File) MarshalJSON() ([]byte, error) { + file := struct { + Path string `json:"path"` + Contents string `json:"contents"` + }{ + Path: f.Path, + Contents: base64.StdEncoding.EncodeToString(f.Contents), } - - var key string - for k := range s { - key = k - } - - return s[key], err + return json.Marshal(file) } diff --git a/openstack/compute/v2/servers/testing/doc.go b/openstack/compute/v2/servers/testing/doc.go deleted file mode 100644 index b3fee3aac..000000000 --- a/openstack/compute/v2/servers/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// servers unit tests -package testing diff --git a/openstack/compute/v2/servers/testing/fixtures.go b/openstack/compute/v2/servers/testing/fixtures.go index 4d46e0928..64b231e98 100644 --- a/openstack/compute/v2/servers/testing/fixtures.go +++ b/openstack/compute/v2/servers/testing/fixtures.go @@ -6,7 +6,6 @@ import ( "testing" "time" - "github.com/opentelekomcloud/gophertelekomcloud" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/servers" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" @@ -610,10 +609,6 @@ type CreateOptsWithCustomField struct { Foo string `json:"foo,omitempty"` } -func (opts CreateOptsWithCustomField) ToServerCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "server") -} - // HandleServerCreationSuccessfully sets up the test server to respond to a server creation request // with a given response. func HandleServerCreationSuccessfully(t *testing.T, response string) { @@ -732,8 +727,7 @@ func HandleServerCreationWithCustomFieldSuccessfully(t *testing.T, response stri "server": { "name": "derp", "imageRef": "f90f6034-2570-4974-8351-6b49732ef2eb", - "flavorRef": "1", - "foo": "bar" + "flavorRef": "1" } }`) diff --git a/openstack/compute/v2/servers/testing/requests_test.go b/openstack/compute/v2/servers/testing/requests_test.go index 800d72d02..6a919e4e0 100644 --- a/openstack/compute/v2/servers/testing/requests_test.go +++ b/openstack/compute/v2/servers/testing/requests_test.go @@ -94,10 +94,9 @@ func TestCreateServer(t *testing.T) { Name: "derp", ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb", FlavorRef: "1", - }).Extract() + }) th.AssertNoErr(t, err) - - th.CheckDeepEquals(t, ServerDerp, *actual) + th.CheckDeepEquals(t, &ServerDerp, actual) } func TestCreateServerWithCustomField(t *testing.T) { @@ -112,7 +111,7 @@ func TestCreateServerWithCustomField(t *testing.T) { FlavorRef: "1", }, Foo: "bar", - }).Extract() + }) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ServerDerp, *actual) @@ -130,7 +129,7 @@ func TestCreateServerWithMetadata(t *testing.T) { Metadata: map[string]string{ "abc": "def", }, - }).Extract() + }) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ServerDerp, *actual) @@ -146,7 +145,7 @@ func TestCreateServerWithUserdataString(t *testing.T) { ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb", FlavorRef: "1", UserData: []byte("userdata string"), - }).Extract() + }) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ServerDerp, *actual) @@ -164,7 +163,7 @@ func TestCreateServerWithUserdataEncoded(t *testing.T) { ImageRef: "f90f6034-2570-4974-8351-6b49732ef2eb", FlavorRef: "1", UserData: []byte(encoded), - }).Extract() + }) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ServerDerp, *actual) @@ -180,7 +179,7 @@ func TestCreateServerWithImageNameAndFlavorName(t *testing.T) { ImageName: "cirros-0.3.2-x86_64-disk", FlavorName: "m1.tiny", ServiceClient: client.ServiceClient(), - }).Extract() + }) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ServerDerp, *actual) @@ -192,7 +191,7 @@ func TestDeleteServer(t *testing.T) { HandleServerDeletionSuccessfully(t) res := servers.Delete(client.ServiceClient(), "asdfasdfasdf") - th.AssertNoErr(t, res.Err) + th.AssertNoErr(t, res) } func TestForceDeleteServer(t *testing.T) { @@ -201,7 +200,7 @@ func TestForceDeleteServer(t *testing.T) { HandleServerForceDeletionSuccessfully(t) res := servers.ForceDelete(client.ServiceClient(), "asdfasdfasdf") - th.AssertNoErr(t, res.Err) + th.AssertNoErr(t, res) } func TestGetServer(t *testing.T) { @@ -210,7 +209,7 @@ func TestGetServer(t *testing.T) { HandleServerGetSuccessfully(t) serviceClient := client.ServiceClient() - actual, err := servers.Get(serviceClient, "1234asdf").Extract() + actual, err := servers.Get(serviceClient, "1234asdf") if err != nil { t.Fatalf("Unexpected Get error: %v", err) } @@ -224,7 +223,7 @@ func TestGetFaultyServer(t *testing.T) { HandleServerGetFaultSuccessfully(t) serviceClient := client.ServiceClient() - actual, err := servers.Get(serviceClient, "1234asdf").Extract() + actual, err := servers.Get(serviceClient, "1234asdf") if err != nil { t.Fatalf("Unexpected Get error: %v", err) } @@ -246,7 +245,7 @@ func TestGetServerWithExtensions(t *testing.T) { diskconfig.ServerDiskConfigExt } - err := servers.Get(client.ServiceClient(), "1234asdf").ExtractInto(&s) + err := servers.GetInto(client.ServiceClient(), "1234asdf", &s) th.AssertNoErr(t, err) th.AssertEquals(t, "nova", s.AvailabilityZone) th.AssertEquals(t, "RUNNING", s.PowerState.String()) @@ -254,7 +253,7 @@ func TestGetServerWithExtensions(t *testing.T) { th.AssertEquals(t, "active", s.VmState) th.AssertEquals(t, diskconfig.Manual, s.DiskConfig) - err = servers.Get(client.ServiceClient(), "1234asdf").ExtractInto(s) + err = servers.GetInto(client.ServiceClient(), "1234asdf", s) if err == nil { t.Errorf("Expected error when providing non-pointer struct") } @@ -266,7 +265,7 @@ func TestUpdateServer(t *testing.T) { HandleServerUpdateSuccessfully(t) serviceClient := client.ServiceClient() - actual, err := servers.Update(serviceClient, "1234asdf", servers.UpdateOpts{Name: "new-name"}).Extract() + actual, err := servers.Update(serviceClient, "1234asdf", servers.UpdateOpts{Name: "new-name"}) if err != nil { t.Fatalf("Unexpected Update error: %v", err) } @@ -280,7 +279,7 @@ func TestChangeServerAdminPassword(t *testing.T) { HandleAdminPasswordChangeSuccessfully(t) res := servers.ChangeAdminPassword(client.ServiceClient(), "1234asdf", "new-password") - th.AssertNoErr(t, res.Err) + th.AssertNoErr(t, res) } func TestShowConsoleOutput(t *testing.T) { @@ -288,10 +287,10 @@ func TestShowConsoleOutput(t *testing.T) { defer th.TeardownHTTP() HandleShowConsoleOutputSuccessfully(t, ConsoleOutputBody) - outputOpts := &servers.ShowConsoleOutputOpts{ + outputOpts := servers.ShowConsoleOutputOpts{ Length: 50, } - actual, err := servers.ShowConsoleOutput(client.ServiceClient(), "1234asdf", outputOpts).Extract() + actual, err := servers.ShowConsoleOutput(client.ServiceClient(), "1234asdf", outputOpts) th.AssertNoErr(t, err) th.AssertByteArrayEquals(t, []byte(ConsoleOutput), []byte(actual)) @@ -302,8 +301,8 @@ func TestGetPassword(t *testing.T) { defer th.TeardownHTTP() HandlePasswordGetSuccessfully(t) - res := servers.GetPassword(client.ServiceClient(), "1234asdf") - th.AssertNoErr(t, res.Err) + _, err := servers.GetPassword(client.ServiceClient(), "1234asdf", nil) + th.AssertNoErr(t, err) } func TestRebootServer(t *testing.T) { @@ -314,7 +313,7 @@ func TestRebootServer(t *testing.T) { res := servers.Reboot(client.ServiceClient(), "1234asdf", servers.RebootOpts{ Type: servers.SoftReboot, }) - th.AssertNoErr(t, res.Err) + th.AssertNoErr(t, res) } func TestRebuildServer(t *testing.T) { @@ -329,7 +328,7 @@ func TestRebuildServer(t *testing.T) { AccessIPv4: "1.2.3.4", } - actual, err := servers.Rebuild(client.ServiceClient(), "1234asdf", opts).Extract() + actual, err := servers.Rebuild(client.ServiceClient(), "1234asdf", opts) th.AssertNoErr(t, err) th.CheckDeepEquals(t, ServerDerp, *actual) @@ -348,7 +347,7 @@ func TestResizeServer(t *testing.T) { }) res := servers.Resize(client.ServiceClient(), "1234asdf", servers.ResizeOpts{FlavorRef: "2"}) - th.AssertNoErr(t, res.Err) + th.AssertNoErr(t, res) } func TestConfirmResize(t *testing.T) { @@ -364,7 +363,7 @@ func TestConfirmResize(t *testing.T) { }) res := servers.ConfirmResize(client.ServiceClient(), "1234asdf") - th.AssertNoErr(t, res.Err) + th.AssertNoErr(t, res) } func TestRevertResize(t *testing.T) { @@ -380,7 +379,7 @@ func TestRevertResize(t *testing.T) { }) res := servers.RevertResize(client.ServiceClient(), "1234asdf") - th.AssertNoErr(t, res.Err) + th.AssertNoErr(t, res) } func TestGetMetadatum(t *testing.T) { @@ -390,7 +389,7 @@ func TestGetMetadatum(t *testing.T) { HandleMetadatumGetSuccessfully(t) expected := map[string]string{"foo": "bar"} - actual, err := servers.Metadatum(client.ServiceClient(), "1234asdf", "foo").Extract() + actual, err := servers.Metadatum(client.ServiceClient(), "1234asdf", "foo") th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, actual) } @@ -402,7 +401,7 @@ func TestCreateMetadatum(t *testing.T) { HandleMetadatumCreateSuccessfully(t) expected := map[string]string{"foo": "bar"} - actual, err := servers.CreateMetadatum(client.ServiceClient(), "1234asdf", servers.MetadatumOpts{"foo": "bar"}).Extract() + actual, err := servers.CreateMetadatum(client.ServiceClient(), "1234asdf", servers.MetadatumOpts{"foo": "bar"}) th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, actual) } @@ -413,7 +412,7 @@ func TestDeleteMetadatum(t *testing.T) { HandleMetadatumDeleteSuccessfully(t) - err := servers.DeleteMetadatum(client.ServiceClient(), "1234asdf", "foo").ExtractErr() + err := servers.DeleteMetadatum(client.ServiceClient(), "1234asdf", "foo") th.AssertNoErr(t, err) } @@ -424,7 +423,7 @@ func TestGetMetadata(t *testing.T) { HandleMetadataGetSuccessfully(t) expected := map[string]string{"foo": "bar", "this": "that"} - actual, err := servers.Metadata(client.ServiceClient(), "1234asdf").Extract() + actual, err := servers.Metadata(client.ServiceClient(), "1234asdf") th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, actual) } @@ -439,7 +438,7 @@ func TestResetMetadata(t *testing.T) { actual, err := servers.ResetMetadata(client.ServiceClient(), "1234asdf", servers.MetadataOpts{ "foo": "bar", "this": "that", - }).Extract() + }) th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, actual) } @@ -454,7 +453,7 @@ func TestUpdateMetadata(t *testing.T) { actual, err := servers.UpdateMetadata(client.ServiceClient(), "1234asdf", servers.MetadataOpts{ "foo": "baz", "this": "those", - }).Extract() + }) th.AssertNoErr(t, err) th.AssertDeepEquals(t, expected, actual) } @@ -465,22 +464,13 @@ func TestListAddresses(t *testing.T) { HandleAddressListSuccessfully(t) expected := ListAddressesExpected - pages := 0 - err := servers.ListAddresses(client.ServiceClient(), "asdfasdfasdf").EachPage(func(page pagination.Page) (bool, error) { - pages++ - - actual, err := servers.ExtractAddresses(page) - th.AssertNoErr(t, err) - - if len(actual) != 2 { - t.Fatalf("Expected 2 networks, got %d", len(actual)) - } - th.CheckDeepEquals(t, expected, actual) - - return true, nil - }) + actual, err := servers.ListAddresses(client.ServiceClient(), "asdfasdfasdf") th.AssertNoErr(t, err) - th.CheckEquals(t, 1, pages) + + if len(actual) != 2 { + t.Fatalf("Expected 2 networks, got %d", len(actual)) + } + th.CheckDeepEquals(t, expected, actual) } func TestListAddressesByNetwork(t *testing.T) { @@ -489,22 +479,13 @@ func TestListAddressesByNetwork(t *testing.T) { HandleNetworkAddressListSuccessfully(t) expected := ListNetworkAddressesExpected - pages := 0 - err := servers.ListAddressesByNetwork(client.ServiceClient(), "asdfasdfasdf", "public").EachPage(func(page pagination.Page) (bool, error) { - pages++ - - actual, err := servers.ExtractNetworkAddresses(page) - th.AssertNoErr(t, err) - - if len(actual) != 2 { - t.Fatalf("Expected 2 addresses, got %d", len(actual)) - } - th.CheckDeepEquals(t, expected, actual) - - return true, nil - }) + actual, err := servers.ListAddressesByNetwork(client.ServiceClient(), "asdfasdfasdf", "public") th.AssertNoErr(t, err) - th.CheckEquals(t, 1, pages) + + if len(actual) != 2 { + t.Fatalf("Expected 2 addresses, got %d", len(actual)) + } + th.CheckDeepEquals(t, expected, actual) } func TestCreateServerImage(t *testing.T) { @@ -512,7 +493,7 @@ func TestCreateServerImage(t *testing.T) { defer th.TeardownHTTP() HandleCreateServerImageSuccessfully(t) - _, err := servers.CreateImage(client.ServiceClient(), "serverimage", servers.CreateImageOpts{Name: "test"}).ExtractImageID() + _, err := servers.CreateImage(client.ServiceClient(), "serverimage", servers.CreateImageOpts{Name: "test"}) th.AssertNoErr(t, err) } @@ -520,8 +501,8 @@ func TestMarshalPersonality(t *testing.T) { name := "/etc/test" contents := []byte("asdfasdf") - personality := servers.Personality{ - &servers.File{ + personality := []servers.File{ + { Path: name, Contents: contents, }, diff --git a/openstack/compute/v2/servers/testing/results_test.go b/openstack/compute/v2/servers/testing/results_test.go index 14bcd2abc..4d196ce05 100644 --- a/openstack/compute/v2/servers/testing/results_test.go +++ b/openstack/compute/v2/servers/testing/results_test.go @@ -6,39 +6,16 @@ import ( "golang.org/x/crypto/ssh" - "github.com/opentelekomcloud/gophertelekomcloud" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/servers" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) -// Fail - No password in JSON. -func TestExtractPassword_no_pwd_data(t *testing.T) { - resp := servers.GetPasswordResult{Result: golangsdk.Result{Body: []byte(`{ "Crappy data": ".-.-." }`)}} - - pwd, err := resp.ExtractPassword(nil) - th.AssertNoErr(t, err) - th.AssertEquals(t, pwd, "") -} - -// Ok - return encrypted password when no private key is given. -func TestExtractPassword_encrypted_pwd(t *testing.T) { - - sejson := []byte(`{"password":"PP8EnwPO9DhEc8+O/6CKAkPF379mKsUsfFY6yyw0734XXvKsSdV9KbiHQ2hrBvzeZxtGMrlFaikVunCRizyLLWLMuOi4hoH+qy9F9sQid61gQIGkxwDAt85d/7Eau2/KzorFnZhgxArl7IiqJ67X6xjKkR3zur+Yp3V/mtVIehpPYIaAvPbcp2t4mQXl1I9J8yrQfEZOctLL1L4heDEVXnxvNihVLK6pivlVggp6SZCtjj9cduZGrYGsxsOCso1dqJQr7GCojfwvuLOoG0OYwEGuWVTZppxWxi/q1QgeHFhGKA5QUXlz7pS71oqpjYsTeViuHnfvlqb5TVYZpQ1haw=="}`) - - resp := servers.GetPasswordResult{Result: golangsdk.Result{Body: sejson}} - - pwd, err := resp.ExtractPassword(nil) - th.AssertNoErr(t, err) - th.AssertEquals(t, "PP8EnwPO9DhEc8+O/6CKAkPF379mKsUsfFY6yyw0734XXvKsSdV9KbiHQ2hrBvzeZxtGMrlFaikVunCRizyLLWLMuOi4hoH+qy9F9sQid61gQIGkxwDAt85d/7Eau2/KzorFnZhgxArl7IiqJ67X6xjKkR3zur+Yp3V/mtVIehpPYIaAvPbcp2t4mQXl1I9J8yrQfEZOctLL1L4heDEVXnxvNihVLK6pivlVggp6SZCtjj9cduZGrYGsxsOCso1dqJQr7GCojfwvuLOoG0OYwEGuWVTZppxWxi/q1QgeHFhGKA5QUXlz7pS71oqpjYsTeViuHnfvlqb5TVYZpQ1haw==", pwd) -} - // Ok - return decrypted password when private key is given. -// Decrytion can be verified by: +// Decryption can be verified by: // // echo "" | base64 -D | openssl rsautl -decrypt -inkey func TestExtractPassword_decrypted_pwd(t *testing.T) { - privateKey, err := ssh.ParseRawPrivateKey([]byte(` -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAo1ODZgwMVdTJYim9UYuYhowoPMhGEuV5IRZjcJ315r7RBSC+ @@ -72,11 +49,7 @@ KSde3I0ybDz7iS2EtceKB7m4C0slYd+oBkm4efuF00rCOKDwpFq45m0= t.Fatalf("Error parsing private key: %s\n", err) } - sejson := []byte(`{"password":"PP8EnwPO9DhEc8+O/6CKAkPF379mKsUsfFY6yyw0734XXvKsSdV9KbiHQ2hrBvzeZxtGMrlFaikVunCRizyLLWLMuOi4hoH+qy9F9sQid61gQIGkxwDAt85d/7Eau2/KzorFnZhgxArl7IiqJ67X6xjKkR3zur+Yp3V/mtVIehpPYIaAvPbcp2t4mQXl1I9J8yrQfEZOctLL1L4heDEVXnxvNihVLK6pivlVggp6SZCtjj9cduZGrYGsxsOCso1dqJQr7GCojfwvuLOoG0OYwEGuWVTZppxWxi/q1QgeHFhGKA5QUXlz7pS71oqpjYsTeViuHnfvlqb5TVYZpQ1haw=="}`) - - resp := servers.GetPasswordResult{Result: golangsdk.Result{Body: sejson}} - - pwd, err := resp.ExtractPassword(privateKey.(*rsa.PrivateKey)) + pwd, err := servers.DecryptPassword("PP8EnwPO9DhEc8+O/6CKAkPF379mKsUsfFY6yyw0734XXvKsSdV9KbiHQ2hrBvzeZxtGMrlFaikVunCRizyLLWLMuOi4hoH+qy9F9sQid61gQIGkxwDAt85d/7Eau2/KzorFnZhgxArl7IiqJ67X6xjKkR3zur+Yp3V/mtVIehpPYIaAvPbcp2t4mQXl1I9J8yrQfEZOctLL1L4heDEVXnxvNihVLK6pivlVggp6SZCtjj9cduZGrYGsxsOCso1dqJQr7GCojfwvuLOoG0OYwEGuWVTZppxWxi/q1QgeHFhGKA5QUXlz7pS71oqpjYsTeViuHnfvlqb5TVYZpQ1haw==", privateKey.(*rsa.PrivateKey)) th.AssertNoErr(t, err) th.AssertEquals(t, "ruZKK0tqxRfYm5t7lSJq", pwd) } @@ -86,8 +59,6 @@ func TestListAddressesAllPages(t *testing.T) { defer th.TeardownHTTP() HandleAddressListSuccessfully(t) - allPages, err := servers.ListAddresses(client.ServiceClient(), "asdfasdfasdf").AllPages() - th.AssertNoErr(t, err) - _, err = servers.ExtractAddresses(allPages) + _, err := servers.ListAddresses(client.ServiceClient(), "asdfasdfasdf") th.AssertNoErr(t, err) } diff --git a/openstack/compute/v2/servers/urls.go b/openstack/compute/v2/servers/urls.go deleted file mode 100644 index b05e514b1..000000000 --- a/openstack/compute/v2/servers/urls.go +++ /dev/null @@ -1,65 +0,0 @@ -package servers - -import "github.com/opentelekomcloud/gophertelekomcloud" - -const ( - rootPath = "servers" - nicPath = "os-interface" - detailPath = "detail" - actionPath = "action" - metadataPath = "metadata" - ipsPath = "ips" - passwordPath = "os-server-password" -) - -func createURL(client *golangsdk.ServiceClient) string { - return client.ServiceURL(rootPath) -} - -func listURL(client *golangsdk.ServiceClient) string { - return createURL(client) -} - -func listDetailURL(client *golangsdk.ServiceClient) string { - return client.ServiceURL(rootPath, detailPath) -} - -func deleteURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL(rootPath, id) -} - -func getURL(client *golangsdk.ServiceClient, id string) string { - return deleteURL(client, id) -} - -func updateURL(client *golangsdk.ServiceClient, id string) string { - return deleteURL(client, id) -} - -func actionURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL(rootPath, id, actionPath) -} - -func metadatumURL(client *golangsdk.ServiceClient, id, key string) string { - return client.ServiceURL(rootPath, id, metadataPath, key) -} - -func metadataURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL(rootPath, id, metadataPath) -} - -func listAddressesURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL(rootPath, id, ipsPath) -} - -func listAddressesByNetworkURL(client *golangsdk.ServiceClient, id, network string) string { - return client.ServiceURL(rootPath, id, ipsPath, network) -} - -func passwordURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL(rootPath, id, passwordPath) -} - -func getNICManagementURL(client *golangsdk.ServiceClient, id string) string { - return client.ServiceURL(rootPath, id, nicPath) -} diff --git a/openstack/compute/v2/servers/util.go b/openstack/compute/v2/servers/util.go index 6d91a544c..7fefdfff4 100644 --- a/openstack/compute/v2/servers/util.go +++ b/openstack/compute/v2/servers/util.go @@ -3,11 +3,10 @@ package servers import "github.com/opentelekomcloud/gophertelekomcloud" // WaitForStatus will continually poll a server until it successfully -// transitions to a specified status. It will do this for at most the number -// of seconds specified. -func WaitForStatus(c *golangsdk.ServiceClient, id, status string, secs int) error { +// transitions to a specified status. It will do this for at most the number of seconds specified. +func WaitForStatus(client *golangsdk.ServiceClient, id, status string, secs int) error { return golangsdk.WaitFor(secs, func() (bool, error) { - current, err := Get(c, id).Extract() + current, err := Get(client, id) if err != nil { return false, err } @@ -19,3 +18,39 @@ func WaitForStatus(c *golangsdk.ServiceClient, id, status string, secs int) erro return false, nil }) } + +// IDFromName is a convenience function that returns a server's ID given its name. +func IDFromName(client *golangsdk.ServiceClient, name string) (string, error) { + count := 0 + id := "" + + listOpts := ListOpts{ + Name: name, + } + + allPages, err := List(client, listOpts).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractServers(allPages) + if err != nil { + return "", err + } + + for _, f := range all { + if f.Name == name { + count++ + id = f.ID + } + } + + switch count { + case 0: + return "", golangsdk.ErrResourceNotFound{Name: name, ResourceType: "server"} + case 1: + return id, nil + default: + return "", golangsdk.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "server"} + } +}