diff --git a/acceptance/openstack/common.go b/acceptance/openstack/common.go index 422ec86ad..eefc5387d 100644 --- a/acceptance/openstack/common.go +++ b/acceptance/openstack/common.go @@ -10,10 +10,10 @@ import ( "github.com/opentelekomcloud/gophertelekomcloud" "github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients" "github.com/opentelekomcloud/gophertelekomcloud/acceptance/tools" - "github.com/opentelekomcloud/gophertelekomcloud/openstack/blockstorage/v2/volumes" "github.com/opentelekomcloud/gophertelekomcloud/openstack/common/extensions" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/extensions/secgroups" "github.com/opentelekomcloud/gophertelekomcloud/openstack/ecs/v1/cloudservers" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v3/volumes" "github.com/opentelekomcloud/gophertelekomcloud/openstack/imageservice/v2/images" "github.com/opentelekomcloud/gophertelekomcloud/openstack/networking/v1/subnets" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" @@ -82,11 +82,11 @@ func CreateVolume(t *testing.T) *volumes.Volume { Name: tools.RandomString("test-vol-", 6), Size: 10, VolumeType: "SSD", - }).Extract() + }) th.AssertNoErr(t, err) err = golangsdk.WaitFor(300, func() (bool, error) { - volume, err := volumes.Get(client, vol.ID).Extract() + volume, err := volumes.Get(client, vol.ID) if err != nil { return false, err } @@ -106,7 +106,10 @@ func CreateVolume(t *testing.T) *volumes.Volume { func DeleteVolume(t *testing.T, id string) { client, err := clients.NewBlockStorageV3Client() th.AssertNoErr(t, err) - th.AssertNoErr(t, volumes.Delete(client, id, volumes.DeleteOpts{Cascade: true}).ExtractErr()) + th.AssertNoErr(t, volumes.Delete(client, volumes.DeleteOpts{ + VolumeId: id, + Cascade: true, + })) } const ( diff --git a/acceptance/openstack/compute/v2/helper.go b/acceptance/openstack/compute/v2/helper.go new file mode 100644 index 000000000..50f1b6af2 --- /dev/null +++ b/acceptance/openstack/compute/v2/helper.go @@ -0,0 +1,80 @@ +package v2 + +import ( + "testing" + "time" + + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients" + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/openstack" + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/tools" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/flavors" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/images" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/servers" + th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" +) + +func CreateServer(t *testing.T, client *golangsdk.ServiceClient) (*servers.Server, error) { + t.Logf("Attempting to create ECSv2") + ecsName := tools.RandomString("create-ecs-", 3) + + az := clients.EnvOS.GetEnv("AVAILABILITY_ZONE") + if az == "" { + az = "eu-de-01" + } + + networkID := clients.EnvOS.GetEnv("NETWORK_ID") + if networkID == "" { + t.Skip("OS_NETWORK_ID env var is missing but ECS test requires using existing network") + } + + // TODO: API discarded and not working. + imageID, err := images.IDFromName(client, "Standard_Debian_10_latest") + th.AssertNoErr(t, err) + + flavorID, err := flavors.IDFromName(client, "s2.large.2") + th.AssertNoErr(t, err) + + ecs, err := servers.Create(client, servers.CreateOpts{ + Name: ecsName, + ImageRef: imageID, + FlavorRef: flavorID, + SecurityGroups: []string{ + openstack.DefaultSecurityGroup(t), + }, + AvailabilityZone: az, + Networks: []servers.Network{ + { + UUID: networkID, + }, + }, + }).Extract() + th.AssertNoErr(t, err) + + err = servers.WaitForStatus(client, ecs.ID, "ACTIVE", 1200) + th.AssertNoErr(t, err) + t.Logf("Created ECSv2: %s", ecs.ID) + return ecs, err +} + +func DeleteServer(t *testing.T, client *golangsdk.ServiceClient, ecs *servers.Server) { + t.Logf("Attempting to delete ECSv2: %s", ecs.ID) + + _, err := servers.Delete(client, ecs.ID).ExtractJobResponse() + th.AssertNoErr(t, err) + + err = golangsdk.WaitFor(1200, func() (bool, error) { + _, err := servers.Get(client, ecs.ID).Extract() + if err != nil { + if _, ok := err.(golangsdk.ErrDefault400); ok { + time.Sleep(10 * time.Second) + return false, nil + } + return false, err + } + return true, nil + }) + th.AssertNoErr(t, err) + + t.Logf("ECSv2 instance deleted: %s", ecs.ID) +} diff --git a/acceptance/openstack/compute/v2/servers_test.go b/acceptance/openstack/compute/v2/servers_test.go index 3c04bf50b..f409e63b7 100644 --- a/acceptance/openstack/compute/v2/servers_test.go +++ b/acceptance/openstack/compute/v2/servers_test.go @@ -2,14 +2,9 @@ package v2 import ( "testing" - "time" - "github.com/opentelekomcloud/gophertelekomcloud" "github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients" - "github.com/opentelekomcloud/gophertelekomcloud/acceptance/openstack" "github.com/opentelekomcloud/gophertelekomcloud/acceptance/tools" - "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/flavors" - "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/images" "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/servers" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" ) @@ -34,85 +29,22 @@ func TestServerLifecycle(t *testing.T) { client, err := clients.NewComputeV2Client() th.AssertNoErr(t, err) - t.Logf("Attempting to create ECSv2") - ecsName := tools.RandomString("create-ecs-", 3) - - az := clients.EnvOS.GetEnv("AVAILABILITY_ZONE") - if az == "" { - az = "eu-de-01" - } - - networkID := clients.EnvOS.GetEnv("NETWORK_ID") - if networkID == "" { - t.Skip("OS_NETWORK_ID env var is missing but ECS test requires using existing network") - } - - imageID, err := images.IDFromName(client, "Standard_Debian_10_latest") - th.AssertNoErr(t, err) - - flavorID, err := flavors.IDFromName(client, "s2.large.2") - th.AssertNoErr(t, err) - - createOpts := servers.CreateOpts{ - Name: ecsName, - ImageRef: imageID, - FlavorRef: flavorID, - SecurityGroups: []string{ - openstack.DefaultSecurityGroup(t), - }, - AvailabilityZone: az, - Networks: []servers.Network{ - { - UUID: networkID, - }, - }, - } - - ecs, err := servers.Create(client, createOpts).Extract() + ecs, err := CreateServer(t, client) 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() - th.AssertNoErr(t, err) - th.AssertEquals(t, ecsName, ecs.Name) + t.Cleanup(func() { + DeleteServer(t, client, ecs) + }) nicInfo, err := servers.GetNICs(client, ecs.ID).Extract() 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() - th.AssertNoErr(t, err) - - err = golangsdk.WaitFor(1200, func() (bool, error) { - _, err := servers.Get(client, ecs.ID).Extract() - if err != nil { - if _, ok := err.(golangsdk.ErrDefault400); ok { - time.Sleep(10 * time.Second) - return false, nil - } - return false, err - } - return true, nil - }) - th.AssertNoErr(t, err) - - t.Logf("ECSv2 instance deleted: %s", ecs.ID) - }() - t.Logf("Attempting to update ECSv2: %s", ecs.ID) - ecsName = tools.RandomString("update-ecs-", 3) - updateOpts := servers.UpdateOpts{ + ecsName := tools.RandomString("update-ecs-", 3) + _, err = servers.Update(client, ecs.ID, servers.UpdateOpts{ Name: ecsName, - } - - _, err = servers.Update(client, ecs.ID, updateOpts).Extract() + }).Extract() th.AssertNoErr(t, err) t.Logf("ECSv2 successfully updated: %s", ecs.ID) diff --git a/acceptance/openstack/evs/v3/blockstorage.go b/acceptance/openstack/evs/v3/blockstorage.go new file mode 100644 index 000000000..c1408740c --- /dev/null +++ b/acceptance/openstack/evs/v3/blockstorage.go @@ -0,0 +1,169 @@ +package v3 + +import ( + "testing" + + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients" + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/tools" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v3/snapshots" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v3/volumes" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v3/volumetypes" + th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" +) + +// CreateSnapshot will create a snapshot of the specified volume. +// Snapshot will be assigned a random name and description. +func CreateSnapshot(t *testing.T, client *golangsdk.ServiceClient, volume *volumes.Volume) (*snapshots.Snapshot, error) { + snapshotName := tools.RandomString("ACPTTEST", 16) + snapshotDescription := tools.RandomString("ACPTTEST", 16) + t.Logf("Attempting to create snapshot: %s", snapshotName) + + createOpts := snapshots.CreateOpts{ + VolumeID: volume.ID, + Name: snapshotName, + Description: snapshotDescription, + } + + snapshot, err := snapshots.Create(client, createOpts) + if err != nil { + return snapshot, err + } + + err = snapshots.WaitForStatus(client, snapshot.ID, "available", 60) + if err != nil { + return snapshot, err + } + + tools.PrintResource(t, snapshot) + th.AssertEquals(t, snapshot.Name, snapshotName) + th.AssertEquals(t, snapshot.VolumeID, volume.ID) + + t.Logf("Successfully created snapshot: %s", snapshot.ID) + + return snapshot, nil +} + +// CreateVolume will create a volume with a random name and size of 1GB. An +// error will be returned if the volume was unable to be created. +func CreateVolume(t *testing.T, client *golangsdk.ServiceClient) (*volumes.Volume, error) { + volumeName := tools.RandomString("ACPTTEST", 16) + volumeDescription := tools.RandomString("ACPTTEST-DESC", 16) + t.Logf("Attempting to create volume: %s", volumeName) + + createOpts := volumes.CreateOpts{ + Size: 1, + Name: volumeName, + Description: volumeDescription, + AvailabilityZone: clients.EnvOS.GetEnv("AVAILABILITY_ZONE"), + } + + volume, err := volumes.Create(client, createOpts) + if err != nil { + return volume, err + } + + err = volumes.WaitForStatus(client, volume.ID, "available", 60) + if err != nil { + return volume, err + } + + tools.PrintResource(t, volume) + th.AssertEquals(t, volume.Name, volumeName) + th.AssertEquals(t, volume.Description, volumeDescription) + th.AssertEquals(t, volume.Size, 1) + + t.Logf("Successfully created volume: %s", volume.ID) + + return volume, nil +} + +// CreateVolumeWithType will create a volume of the given volume type +// with a random name and size of 1GB. An error will be returned if +// the volume was unable to be created. +func CreateVolumeWithType(t *testing.T, client *golangsdk.ServiceClient, vt *volumetypes.VolumeType) (*volumes.Volume, error) { + volumeName := tools.RandomString("ACPTTEST", 16) + volumeDescription := tools.RandomString("ACPTTEST-DESC", 16) + t.Logf("Attempting to create volume: %s", volumeName) + + createOpts := volumes.CreateOpts{ + Size: 1, + Name: volumeName, + Description: volumeDescription, + VolumeType: vt.Name, + } + + volume, err := volumes.Create(client, createOpts) + if err != nil { + return volume, err + } + + err = volumes.WaitForStatus(client, volume.ID, "available", 60) + if err != nil { + return volume, err + } + + tools.PrintResource(t, volume) + th.AssertEquals(t, volume.Name, volumeName) + th.AssertEquals(t, volume.Description, volumeDescription) + th.AssertEquals(t, volume.Size, 1) + th.AssertEquals(t, volume.VolumeType, vt.Name) + + t.Logf("Successfully created volume: %s", volume.ID) + + return volume, nil +} + +// DeleteSnapshot will delete a snapshot. A fatal error will occur if the +// snapshot failed to be deleted. +func DeleteSnapshot(t *testing.T, client *golangsdk.ServiceClient, snapshot *snapshots.Snapshot) { + err := snapshots.Delete(client, snapshot.ID) + if err != nil { + t.Fatalf("Unable to delete snapshot %s: %+v", snapshot.ID, err) + } + + // Volumes can't be deleted until their snapshots have been, + // so block until the snapshot as been deleted. + err = tools.WaitFor(func() (bool, error) { + _, err := snapshots.Get(client, snapshot.ID) + if err != nil { + return true, nil + } + + return false, nil + }) + if err != nil { + t.Fatalf("Error waiting for snapshot to delete: %v", err) + } + + t.Logf("Deleted snapshot: %s", snapshot.ID) +} + +// DeleteVolume will delete a volume. A fatal error will occur if the volume +// failed to be deleted. This works best when used as a deferred function. +func DeleteVolume(t *testing.T, client *golangsdk.ServiceClient, volume *volumes.Volume) { + t.Logf("Attempting to delete volume: %s", volume.ID) + + err := volumes.Delete(client, volumes.DeleteOpts{ + VolumeId: volume.ID, + }) + if err != nil { + t.Fatalf("Unable to delete volume %s: %v", volume.ID, err) + } + + // VolumeTypes can't be deleted until their volumes have been, + // so block until the volume is deleted. + err = tools.WaitFor(func() (bool, error) { + _, err := volumes.Get(client, volume.ID) + if err != nil { + return true, nil + } + + return false, nil + }) + if err != nil { + t.Fatalf("Error waiting for volume to delete: %v", err) + } + + t.Logf("Successfully deleted volume: %s", volume.ID) +} diff --git a/acceptance/openstack/evs/v3/extensions/extensions.go b/acceptance/openstack/evs/v3/extensions/extensions.go new file mode 100644 index 000000000..229c745b6 --- /dev/null +++ b/acceptance/openstack/evs/v3/extensions/extensions.go @@ -0,0 +1,209 @@ +package extensions + +import ( + "fmt" + "strings" + "testing" + + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/tools" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/images" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/compute/v2/servers" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/extensions/volumeactions" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v3/volumes" + v3 "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v3/volumes" +) + +// CreateUploadImage will upload volume it as volume-baked image. A name of new image or err will be +// returned +func CreateUploadImage(t *testing.T, client *golangsdk.ServiceClient, volume *volumes.Volume) (*volumeactions.VolumeImage, error) { + if testing.Short() { + t.Skip("Skipping test that requires volume-backed image uploading in short mode.") + } + + imageName := tools.RandomString("ACPTTEST", 16) + uploadImageOpts := volumeactions.UploadImageOpts{ + ImageName: imageName, + Force: true, + } + + volumeImage, err := volumeactions.UploadImage(client, volume.ID, uploadImageOpts) + if err != nil { + return volumeImage, err + } + + t.Logf("Uploading volume %s as volume-backed image %s", volume.ID, imageName) + + if err := volumes.WaitForStatus(client, volume.ID, "available", 60); err != nil { + return volumeImage, err + } + + t.Logf("Uploaded volume %s as volume-backed image %s", volume.ID, imageName) + + return volumeImage, nil + +} + +// DeleteUploadedImage deletes uploaded image. An error will be returned +// if the deletion request failed. +func DeleteUploadedImage(t *testing.T, client *golangsdk.ServiceClient, imageID string) error { + if testing.Short() { + t.Skip("Skipping test that requires volume-backed image removing in short mode.") + } + + t.Logf("Removing image %s", imageID) + + err := images.Delete(client, imageID).ExtractErr() + if err != nil { + return err + } + + return nil +} + +// CreateVolumeAttach will attach a volume to an instance. An error will be +// returned if the attachment failed. +func CreateVolumeAttach(t *testing.T, client *golangsdk.ServiceClient, volume *volumes.Volume, server *servers.Server) error { + if testing.Short() { + t.Skip("Skipping test that requires volume attachment in short mode.") + } + + attachOpts := volumeactions.AttachOpts{ + MountPoint: "/mnt", + Mode: "rw", + InstanceUUID: server.ID, + } + + t.Logf("Attempting to attach volume %s to server %s", volume.ID, server.ID) + + if err := volumeactions.Attach(client, volume.ID, attachOpts); err != nil { + return err + } + + if err := volumes.WaitForStatus(client, volume.ID, "in-use", 60); err != nil { + return err + } + + t.Logf("Attached volume %s to server %s", volume.ID, server.ID) + + return nil +} + +// CreateVolumeReserve creates a volume reservation. An error will be returned +// if the reservation failed. +func CreateVolumeReserve(t *testing.T, client *golangsdk.ServiceClient, volume *volumes.Volume) error { + if testing.Short() { + t.Skip("Skipping test that requires volume reservation in short mode.") + } + + t.Logf("Attempting to reserve volume %s", volume.ID) + + if err := volumeactions.Reserve(client, volume.ID); err != nil { + return err + } + + t.Logf("Reserved volume %s", volume.ID) + + return nil +} + +// DeleteVolumeAttach will detach a volume from an instance. A fatal error will +// occur if the snapshot failed to be deleted. This works best when used as a +// deferred function. +func DeleteVolumeAttach(t *testing.T, client *golangsdk.ServiceClient, volume *volumes.Volume) { + t.Logf("Attepting to detach volume volume: %s", volume.ID) + + detachOpts := volumeactions.DetachOpts{ + AttachmentID: volume.Attachments[0].AttachmentID, + } + + if err := volumeactions.Detach(client, volume.ID, detachOpts); err != nil { + t.Fatalf("Unable to detach volume %s: %v", volume.ID, err) + } + + if err := volumes.WaitForStatus(client, volume.ID, "available", 60); err != nil { + t.Fatalf("Volume %s failed to become unavailable in 60 seconds: %v", volume.ID, err) + } + + t.Logf("Detached volume: %s", volume.ID) +} + +// DeleteVolumeReserve deletes a volume reservation. A fatal error will occur +// if the deletion request failed. This works best when used as a deferred +// function. +func DeleteVolumeReserve(t *testing.T, client *golangsdk.ServiceClient, volume *volumes.Volume) { + if testing.Short() { + t.Skip("Skipping test that requires volume reservation in short mode.") + } + + t.Logf("Attempting to unreserve volume %s", volume.ID) + + if err := volumeactions.Unreserve(client, volume.ID); err != nil { + t.Fatalf("Unable to unreserve volume %s: %v", volume.ID, err) + } + + t.Logf("Unreserved volume %s", volume.ID) +} + +// ExtendVolumeSize will extend the size of a volume. +func ExtendVolumeSize(t *testing.T, client *golangsdk.ServiceClient, volume *volumes.Volume) error { + t.Logf("Attempting to extend the size of volume %s", volume.ID) + + extendOpts := volumeactions.ExtendSizeOpts{ + NewSize: 2, + } + + err := volumeactions.ExtendSize(client, volume.ID, extendOpts) + if err != nil { + return err + } + + if err := volumes.WaitForStatus(client, volume.ID, "available", 60); err != nil { + return err + } + + return nil +} + +// SetBootable will set a bootable status to a volume. +func SetBootable(t *testing.T, client *golangsdk.ServiceClient, volume *volumes.Volume) error { + t.Logf("Attempting to apply bootable status to volume %s", volume.ID) + + bootableOpts := volumeactions.BootableOpts{ + Bootable: true, + } + + err := volumeactions.SetBootable(client, volume.ID, bootableOpts) + if err != nil { + return err + } + + vol, err := v3.Get(client, volume.ID) + if err != nil { + return err + } + + if strings.ToLower(vol.Bootable) != "true" { + return fmt.Errorf("Volume bootable status is %q, expected 'true'", vol.Bootable) + } + + bootableOpts = volumeactions.BootableOpts{ + Bootable: false, + } + + err = volumeactions.SetBootable(client, volume.ID, bootableOpts) + if err != nil { + return err + } + + vol, err = v3.Get(client, volume.ID) + if err != nil { + return err + } + + if strings.ToLower(vol.Bootable) == "true" { + return fmt.Errorf("Volume bootable status is %q, expected 'false'", vol.Bootable) + } + + return nil +} diff --git a/acceptance/openstack/evs/v3/extensions/schedulerhints_test.go b/acceptance/openstack/evs/v3/extensions/schedulerhints_test.go new file mode 100644 index 000000000..70e345e43 --- /dev/null +++ b/acceptance/openstack/evs/v3/extensions/schedulerhints_test.go @@ -0,0 +1,60 @@ +package extensions + +import ( + "testing" + + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/extensions/schedulerhints" + + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients" + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/tools" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v3/volumes" + th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" +) + +func TestSchedulerHints(t *testing.T) { + client, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + volumeName := tools.RandomString("ACPTTEST", 16) + createOpts := volumes.CreateOpts{ + Size: 1, + Name: volumeName, + AvailabilityZone: clients.EnvOS.GetEnv("AVAILABILITY_ZONE"), + } + + volume1, err := volumes.Create(client, createOpts) + th.AssertNoErr(t, err) + + err = volumes.WaitForStatus(client, volume1.ID, "available", 60) + th.AssertNoErr(t, err) + t.Cleanup(func() { + err := volumes.Delete(client, volumes.DeleteOpts{VolumeId: volume1.ID}) + th.AssertNoErr(t, err) + }) + + volumeName = tools.RandomString("ACPTTEST", 16) + base := volumes.CreateOpts{ + Size: 1, + Name: volumeName, + } + + schedulerHints := schedulerhints.SchedulerHints{ + SameHost: []string{ + volume1.ID, + }, + } + + createOptsWithHints := schedulerhints.CreateOptsExt{ + CreateOptsBuilder: base, + SchedulerHints: schedulerHints, + } + + volume2, err := volumes.Create(client, createOptsWithHints) + th.AssertNoErr(t, err) + + err = volumes.WaitForStatus(client, volume2.ID, "available", 60) + th.AssertNoErr(t, err) + + err = volumes.Delete(client, volumes.DeleteOpts{VolumeId: volume2.ID}) + th.AssertNoErr(t, err) +} diff --git a/acceptance/openstack/evs/v3/extensions/schedulerstats_test.go b/acceptance/openstack/evs/v3/extensions/schedulerstats_test.go new file mode 100644 index 000000000..ddb578af5 --- /dev/null +++ b/acceptance/openstack/evs/v3/extensions/schedulerstats_test.go @@ -0,0 +1,28 @@ +package extensions + +import ( + "testing" + + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients" + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/tools" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/extensions/schedulerstats" + th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" +) + +func TestSchedulerStatsList(t *testing.T) { + t.Skip("The API does not exist or has not been published in the environment") + + blockClient, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + listOpts := schedulerstats.ListOpts{ + Detail: true, + } + + allStats, err := schedulerstats.List(blockClient, listOpts) + th.AssertNoErr(t, err) + + for _, stat := range allStats { + tools.PrintResource(t, stat) + } +} diff --git a/acceptance/openstack/evs/v3/extensions/services_test.go b/acceptance/openstack/evs/v3/extensions/services_test.go new file mode 100644 index 000000000..46fd97be7 --- /dev/null +++ b/acceptance/openstack/evs/v3/extensions/services_test.go @@ -0,0 +1,25 @@ +package extensions + +import ( + "testing" + + services2 "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/extensions/services" + + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients" + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/tools" + th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" +) + +func TestServicesList(t *testing.T) { + t.Skip("The API does not exist or has not been published in the environment") + + blockClient, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + allServices, err := services2.List(blockClient, services2.ListOpts{}) + th.AssertNoErr(t, err) + + for _, service := range allServices { + tools.PrintResource(t, service) + } +} diff --git a/acceptance/openstack/evs/v3/extensions/volumeactions_test.go b/acceptance/openstack/evs/v3/extensions/volumeactions_test.go new file mode 100644 index 000000000..d9cfb418c --- /dev/null +++ b/acceptance/openstack/evs/v3/extensions/volumeactions_test.go @@ -0,0 +1,103 @@ +package extensions + +import ( + "testing" + + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients" + compute "github.com/opentelekomcloud/gophertelekomcloud/acceptance/openstack/compute/v2" + blockstorageV3 "github.com/opentelekomcloud/gophertelekomcloud/acceptance/openstack/evs/v3" + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/tools" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v3/volumes" + th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" +) + +func TestVolumeActionsUploadImageDestroy(t *testing.T) { + blockClient, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + computeClient, err := clients.NewComputeV2Client() + th.AssertNoErr(t, err) + + volume, err := blockstorageV3.CreateVolume(t, blockClient) + th.AssertNoErr(t, err) + t.Cleanup(func() { blockstorageV3.DeleteVolume(t, blockClient, volume) }) + + volumeImage, err := CreateUploadImage(t, blockClient, volume) + th.AssertNoErr(t, err) + + tools.PrintResource(t, volumeImage) + + err = DeleteUploadedImage(t, computeClient, volumeImage.ImageID) + th.AssertNoErr(t, err) +} + +func TestVolumeActionsAttachCreateDestroy(t *testing.T) { + // TODO + t.Skip("images.ListDetail discarded and not working.") + + blockClient, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + computeClient, err := clients.NewComputeV2Client() + th.AssertNoErr(t, err) + + server, err := compute.CreateServer(t, computeClient) + th.AssertNoErr(t, err) + t.Cleanup(func() { compute.DeleteServer(t, computeClient, server) }) + + volume, err := blockstorageV3.CreateVolume(t, blockClient) + th.AssertNoErr(t, err) + t.Cleanup(func() { blockstorageV3.DeleteVolume(t, blockClient, volume) }) + + err = CreateVolumeAttach(t, blockClient, volume, server) + th.AssertNoErr(t, err) + + newVolume, err := volumes.Get(blockClient, volume.ID) + th.AssertNoErr(t, err) + + DeleteVolumeAttach(t, blockClient, newVolume) +} + +func TestVolumeActionsReserveUnreserve(t *testing.T) { + client, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + volume, err := blockstorageV3.CreateVolume(t, client) + th.AssertNoErr(t, err) + t.Cleanup(func() { blockstorageV3.DeleteVolume(t, client, volume) }) + + err = CreateVolumeReserve(t, client, volume) + th.AssertNoErr(t, err) + t.Cleanup(func() { DeleteVolumeReserve(t, client, volume) }) +} + +func TestVolumeActionsExtendSize(t *testing.T) { + blockClient, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + volume, err := blockstorageV3.CreateVolume(t, blockClient) + th.AssertNoErr(t, err) + t.Cleanup(func() { blockstorageV3.DeleteVolume(t, blockClient, volume) }) + + tools.PrintResource(t, volume) + + err = ExtendVolumeSize(t, blockClient, volume) + th.AssertNoErr(t, err) + + newVolume, err := volumes.Get(blockClient, volume.ID) + th.AssertNoErr(t, err) + + tools.PrintResource(t, newVolume) +} + +func TestVolumeActionsSetBootable(t *testing.T) { + blockClient, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + volume, err := blockstorageV3.CreateVolume(t, blockClient) + th.AssertNoErr(t, err) + t.Cleanup(func() { blockstorageV3.DeleteVolume(t, blockClient, volume) }) + + err = SetBootable(t, blockClient, volume) + th.AssertNoErr(t, err) +} diff --git a/acceptance/openstack/evs/v3/quotaset_test.go b/acceptance/openstack/evs/v3/quotaset_test.go new file mode 100644 index 000000000..3161f8f43 --- /dev/null +++ b/acceptance/openstack/evs/v3/quotaset_test.go @@ -0,0 +1,78 @@ +package v3 + +import ( + "os" + "testing" + + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/extensions/quotasets" + + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients" + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/tools" + th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" +) + +func TestQuotasetGet(t *testing.T) { + client, projectID := getClientAndProject(t) + + quotaSet, err := quotasets.Get(client, projectID) + th.AssertNoErr(t, err) + + tools.PrintResource(t, quotaSet) +} + +func TestQuotasetGetDefaults(t *testing.T) { + client, projectID := getClientAndProject(t) + + quotaSet, err := quotasets.GetDefaults(client, projectID) + th.AssertNoErr(t, err) + + tools.PrintResource(t, quotaSet) +} + +func TestQuotasetGetUsage(t *testing.T) { + client, projectID := getClientAndProject(t) + + quotaSetUsage, err := quotasets.GetUsage(client, projectID) + th.AssertNoErr(t, err) + + tools.PrintResource(t, quotaSetUsage) +} + +func TestQuotasetDelete(t *testing.T) { + client, projectID := getClientAndProject(t) + + // save original quotas + _, err := quotasets.Get(client, projectID) + th.AssertNoErr(t, err) + + t.Cleanup(func() { + restore := quotasets.UpdateOpts{} + _, err = quotasets.Update(client, projectID, restore) + th.AssertNoErr(t, err) + }) + + // Obtain environment default quotaset values to validate deletion. + defaultQuotaSet, err := quotasets.GetDefaults(client, projectID) + th.AssertNoErr(t, err) + + // Test Delete + err = quotasets.Delete(client, projectID) + th.AssertNoErr(t, err) + + newQuotas, err := quotasets.Get(client, projectID) + th.AssertNoErr(t, err) + + th.AssertEquals(t, newQuotas.Volumes, defaultQuotaSet.Volumes) +} + +// getClientAndProject reduces boilerplate by returning a new blockstorage v3 +// ServiceClient and a project ID obtained from the OS_PROJECT_NAME envvar. +func getClientAndProject(t *testing.T) (*golangsdk.ServiceClient, string) { + client, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + projectID := os.Getenv("OS_PROJECT_NAME") + th.AssertNoErr(t, err) + return client, projectID +} diff --git a/acceptance/openstack/evs/v3/snapshots_test.go b/acceptance/openstack/evs/v3/snapshots_test.go new file mode 100644 index 000000000..96288fac2 --- /dev/null +++ b/acceptance/openstack/evs/v3/snapshots_test.go @@ -0,0 +1,70 @@ +package v3 + +import ( + "testing" + + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients" + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/tools" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v3/snapshots" + "github.com/opentelekomcloud/gophertelekomcloud/pagination" + th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" +) + +func TestSnapshots(t *testing.T) { + client, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + volume1, err := CreateVolume(t, client) + th.AssertNoErr(t, err) + t.Cleanup(func() { DeleteVolume(t, client, volume1) }) + + snapshot1, err := CreateSnapshot(t, client, volume1) + th.AssertNoErr(t, err) + t.Cleanup(func() { DeleteSnapshot(t, client, snapshot1) }) + + // Update snapshot + updatedSnapshotName := tools.RandomString("ACPTTEST", 16) + updatedSnapshotDescription := tools.RandomString("ACPTTEST", 16) + updateOpts := snapshots.UpdateOpts{ + Name: updatedSnapshotName, + Description: updatedSnapshotDescription, + } + t.Logf("Attempting to update snapshot: %s", updatedSnapshotName) + updatedSnapshot, err := snapshots.Update(client, snapshot1.ID, updateOpts) + th.AssertNoErr(t, err) + + tools.PrintResource(t, updatedSnapshot) + th.AssertEquals(t, updatedSnapshot.Name, updatedSnapshotName) + th.AssertEquals(t, updatedSnapshot.Description, updatedSnapshotDescription) + + volume2, err := CreateVolume(t, client) + th.AssertNoErr(t, err) + t.Cleanup(func() { DeleteVolume(t, client, volume2) }) + + snapshot2, err := CreateSnapshot(t, client, volume2) + th.AssertNoErr(t, err) + t.Cleanup(func() { DeleteSnapshot(t, client, snapshot2) }) + + listOpts := snapshots.ListOpts{ + Limit: 1, + } + + err = snapshots.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) { + actual, err := snapshots.ExtractSnapshots(page) + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, len(actual)) + + var found bool + for _, v := range actual { + if v.ID == snapshot1.ID || v.ID == snapshot2.ID { + found = true + } + } + + th.AssertEquals(t, found, true) + + return true, nil + }) + + th.AssertNoErr(t, err) +} diff --git a/acceptance/openstack/evs/v3/volumes_test.go b/acceptance/openstack/evs/v3/volumes_test.go new file mode 100644 index 000000000..06a47a106 --- /dev/null +++ b/acceptance/openstack/evs/v3/volumes_test.go @@ -0,0 +1,137 @@ +package v3 + +import ( + "testing" + + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/clients" + "github.com/opentelekomcloud/gophertelekomcloud/acceptance/tools" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v3/snapshots" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v3/volumes" + "github.com/opentelekomcloud/gophertelekomcloud/pagination" + th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" +) + +func TestVolumes(t *testing.T) { + client, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + volume1, err := CreateVolume(t, client) + th.AssertNoErr(t, err) + t.Cleanup(func() { DeleteVolume(t, client, volume1) }) + + volume2, err := CreateVolume(t, client) + th.AssertNoErr(t, err) + t.Cleanup(func() { DeleteVolume(t, client, volume2) }) + + // Update volume + updatedVolumeName := "" + updatedVolumeDescription := "" + updatedVolume, err := volumes.Update(client, volumes.UpdateOpts{ + VolumeId: volume1.ID, + Name: updatedVolumeName, + Description: updatedVolumeDescription, + }) + th.AssertNoErr(t, err) + + tools.PrintResource(t, updatedVolume) + th.AssertEquals(t, updatedVolume.Name, updatedVolumeName) + th.AssertEquals(t, updatedVolume.Description, updatedVolumeDescription) + + listOpts := volumes.ListOpts{ + Limit: 1, + } + + err = volumes.List(client, listOpts).EachPage(func(page pagination.Page) (bool, error) { + actual, err := volumes.ExtractVolumes(page) + th.AssertNoErr(t, err) + th.AssertEquals(t, 1, len(actual)) + + var found bool + for _, v := range actual { + if v.ID == volume1.ID || v.ID == volume2.ID { + found = true + } + } + + th.AssertEquals(t, found, true) + + return true, nil + }) + + th.AssertNoErr(t, err) +} + +func TestVolumesMultiAttach(t *testing.T) { + client, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + volumeName := tools.RandomString("ACPTTEST", 16) + + volOpts := volumes.CreateOpts{ + Size: 1, + Name: volumeName, + Description: "Testing creation of multiattach enabled volume", + Multiattach: true, + } + + vol, err := volumes.Create(client, volOpts) + th.AssertNoErr(t, err) + + err = volumes.WaitForStatus(client, vol.ID, "available", 60) + th.AssertNoErr(t, err) + + th.AssertEquals(t, vol.Multiattach, true) + + err = volumes.Delete(client, volumes.DeleteOpts{VolumeId: vol.ID}) + th.AssertNoErr(t, err) +} + +func TestVolumesCascadeDelete(t *testing.T) { + client, err := clients.NewBlockStorageV3Client() + th.AssertNoErr(t, err) + + vol, err := CreateVolume(t, client) + th.AssertNoErr(t, err) + + err = volumes.WaitForStatus(client, vol.ID, "available", 60) + th.AssertNoErr(t, err) + + snapshot1, err := CreateSnapshot(t, client, vol) + th.AssertNoErr(t, err) + + snapshot2, err := CreateSnapshot(t, client, vol) + th.AssertNoErr(t, err) + + t.Logf("Attempting to delete volume: %s", vol.ID) + + err = volumes.Delete(client, volumes.DeleteOpts{ + VolumeId: vol.ID, + Cascade: true, + }) + if err != nil { + t.Fatalf("Unable to delete volume %s: %v", vol.ID, err) + } + + for _, sid := range []string{snapshot1.ID, snapshot2.ID} { + err := tools.WaitFor(func() (bool, error) { + _, err := snapshots.Get(client, sid) + if err != nil { + return true, nil + } + return false, nil + }) + th.AssertNoErr(t, err) + t.Logf("Successfully deleted snapshot: %s", sid) + } + + err = tools.WaitFor(func() (bool, error) { + _, err := volumes.Get(client, vol.ID) + if err != nil { + return true, nil + } + return false, nil + }) + th.AssertNoErr(t, err) + + t.Logf("Successfully deleted volume: %s", vol.ID) +} diff --git a/openstack/blockstorage/v2/snapshots/doc.go b/openstack/blockstorage/v2/snapshots/doc.go deleted file mode 100644 index 198f83077..000000000 --- a/openstack/blockstorage/v2/snapshots/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Package snapshots provides information and interaction with snapshots in the -// OpenStack Block Storage service. A snapshot is a point in time copy of the -// data contained in an external storage volume, and can be controlled -// programmatically. -package snapshots diff --git a/openstack/blockstorage/v2/snapshots/requests.go b/openstack/blockstorage/v2/snapshots/requests.go deleted file mode 100644 index 6ada877af..000000000 --- a/openstack/blockstorage/v2/snapshots/requests.go +++ /dev/null @@ -1,167 +0,0 @@ -package snapshots - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// CreateOpts contains options for creating a Snapshot. This object is passed to -// the snapshots.Create function. For more information about these parameters, -// see the Snapshot object. -type CreateOpts struct { - VolumeID string `json:"volume_id" required:"true"` - Force bool `json:"force,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` -} - -// Create will create a new Snapshot based on the values in CreateOpts. To -// extract the Snapshot object from the response, call the Extract method on the -// CreateResult. -func Create(client *golangsdk.ServiceClient, opts CreateOpts) (r CreateResult) { - b, err := golangsdk.BuildRequestBody(opts, "snapshot") - if err != nil { - r.Err = err - return - } - - _, r.Err = client.Post(createURL(client), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{202}, - }) - return -} - -// Delete will delete the existing Snapshot with the provided ID. -func Delete(client *golangsdk.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) - return -} - -// Get retrieves the Snapshot with the provided ID. To extract the Snapshot -// object from the response, call the Extract method on the GetResult. -func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) - return -} - -// ListOptsBuilder allows extensions to add additional parameters to the List -// request. -type ListOptsBuilder interface { - ToSnapshotListQuery() (string, error) -} - -// ListOpts hold options for listing Snapshots. It is passed to the -// snapshots.List function. -type ListOpts struct { - // AllTenants will retrieve snapshots of all tenants/projects. - AllTenants bool `q:"all_tenants"` - - // Name will filter by the specified snapshot name. - Name string `q:"name"` - - // Status will filter by the specified status. - Status string `q:"status"` - - // TenantID will filter by a specific tenant/project ID. - // Setting AllTenants is required to use this. - TenantID string `q:"project_id"` - - // VolumeID will filter by a specified volume ID. - VolumeID string `q:"volume_id"` -} - -// ToSnapshotListQuery formats a ListOpts into a query string. -func (opts ListOpts) ToSnapshotListQuery() (string, error) { - q, err := golangsdk.BuildQueryString(opts) - if err != nil { - return "", err - } - return q.String(), err -} - -// List returns Snapshots optionally limited by the conditions provided in -// ListOpts. -func List(client *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { - url := listURL(client) - if opts != nil { - query, err := opts.ToSnapshotListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return SnapshotPage{pagination.SinglePageBase(r)} - }) -} - -// UpdateMetadataOptsBuilder allows extensions to add additional parameters to -// the Update request. -type UpdateMetadataOptsBuilder interface { - ToSnapshotUpdateMetadataMap() (map[string]interface{}, error) -} - -// UpdateMetadataOpts contain options for updating an existing Snapshot. This -// object is passed to the snapshots.Update function. For more information -// about the parameters, see the Snapshot object. -type UpdateMetadataOpts struct { - Metadata map[string]interface{} `json:"metadata,omitempty"` -} - -// ToSnapshotUpdateMetadataMap assembles a request body based on the contents of -// an UpdateMetadataOpts. -func (opts UpdateMetadataOpts) ToSnapshotUpdateMetadataMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "") -} - -// UpdateMetadata will update the Snapshot with provided information. To -// extract the updated Snapshot from the response, call the ExtractMetadata -// method on the UpdateMetadataResult. -func UpdateMetadata(client *golangsdk.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) { - b, err := opts.ToSnapshotUpdateMetadataMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(updateMetadataURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// IDFromName is a convienience function that returns a snapshot's ID given its name. -func IDFromName(client *golangsdk.ServiceClient, name string) (string, error) { - count := 0 - id := "" - - listOpts := ListOpts{ - Name: name, - } - - pages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractSnapshots(pages) - if err != nil { - return "", err - } - - for _, s := range all { - if s.Name == name { - count++ - id = s.ID - } - } - - switch count { - case 0: - return "", golangsdk.ErrResourceNotFound{Name: name, ResourceType: "snapshot"} - case 1: - return id, nil - default: - return "", golangsdk.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "snapshot"} - } -} diff --git a/openstack/blockstorage/v2/snapshots/results.go b/openstack/blockstorage/v2/snapshots/results.go deleted file mode 100644 index a43198651..000000000 --- a/openstack/blockstorage/v2/snapshots/results.go +++ /dev/null @@ -1,117 +0,0 @@ -package snapshots - -import ( - "encoding/json" - "time" - - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/openstack/common/metadata" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// Snapshot contains all the information associated with a Cinder Snapshot. -type Snapshot struct { - // Unique identifier. - ID string `json:"id"` - - // Date created. - CreatedAt time.Time `json:"-"` - - // Date updated. - UpdatedAt time.Time `json:"-"` - - // Display name. - Name string `json:"name"` - - // Display description. - Description string `json:"description"` - - // ID of the Volume from which this Snapshot was created. - VolumeID string `json:"volume_id"` - - // Currect status of the Snapshot. - Status string `json:"status"` - - // Size of the Snapshot, in GB. - Size int `json:"size"` - - // User-defined key-value pairs. - Metadata map[string]string `json:"metadata"` -} - -// CreateResult contains the response body and error from a Create request. -type CreateResult struct { - commonResult -} - -// GetResult contains the response body and error from a Get request. -type GetResult struct { - commonResult -} - -// DeleteResult contains the response body and error from a Delete request. -type DeleteResult struct { - golangsdk.ErrResult -} - -// SnapshotPage is a pagination.Pager that is returned from a call to the List function. -type SnapshotPage struct { - pagination.SinglePageBase -} - -func (r *Snapshot) UnmarshalJSON(b []byte) error { - type tmp Snapshot - var s struct { - tmp - CreatedAt golangsdk.JSONRFC3339MilliNoZ `json:"created_at"` - UpdatedAt golangsdk.JSONRFC3339MilliNoZ `json:"updated_at"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - *r = Snapshot(s.tmp) - - r.CreatedAt = time.Time(s.CreatedAt) - r.UpdatedAt = time.Time(s.UpdatedAt) - - return err -} - -// IsEmpty returns true if a SnapshotPage contains no Snapshots. -func (r SnapshotPage) IsEmpty() (bool, error) { - volumes, err := ExtractSnapshots(r) - return len(volumes) == 0, err -} - -// ExtractSnapshots extracts and returns Snapshots. It is used while iterating over a snapshots.List call. -func ExtractSnapshots(r pagination.Page) ([]Snapshot, error) { - var s struct { - Snapshots []Snapshot `json:"snapshots"` - } - err := (r.(SnapshotPage)).ExtractInto(&s) - return s.Snapshots, err -} - -// UpdateMetadataResult contains the response body and error from an UpdateMetadata request. -type UpdateMetadataResult struct { - commonResult -} - -// ExtractMetadata returns the metadata from a response from snapshots.UpdateMetadata. -func (r UpdateMetadataResult) ExtractMetadata() (map[string]interface{}, error) { - return metadata.Extract(r.BodyReader()) -} - -type commonResult struct { - golangsdk.Result -} - -// Extract will get the Snapshot object out of the commonResult object. -func (r commonResult) Extract() (*Snapshot, error) { - var s struct { - Snapshot *Snapshot `json:"snapshot"` - } - err := r.ExtractInto(&s) - return s.Snapshot, err -} diff --git a/openstack/blockstorage/v2/snapshots/testing/doc.go b/openstack/blockstorage/v2/snapshots/testing/doc.go deleted file mode 100644 index 9702a25fb..000000000 --- a/openstack/blockstorage/v2/snapshots/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// snapshots_v2 -package testing diff --git a/openstack/blockstorage/v2/snapshots/testing/fixtures.go b/openstack/blockstorage/v2/snapshots/testing/fixtures.go deleted file mode 100644 index 58789ba74..000000000 --- a/openstack/blockstorage/v2/snapshots/testing/fixtures.go +++ /dev/null @@ -1,134 +0,0 @@ -package testing - -import ( - "fmt" - "net/http" - "testing" - - th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" - fake "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" -) - -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - _, _ = fmt.Fprint(w, ` - { - "snapshots": [ - { - "id": "289da7f8-6440-407c-9fb4-7db01ec49164", - "name": "snapshot-001", - "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", - "description": "Daily Backup", - "status": "available", - "size": 30, - "created_at": "2017-05-30T03:35:03.000000" - }, - { - "id": "96c3bda7-c82a-4f50-be73-ca7621794835", - "name": "snapshot-002", - "volume_id": "76b8950a-8594-4e5b-8dce-0dfa9c696358", - "description": "Weekly Backup", - "status": "available", - "size": 25, - "created_at": "2017-05-30T03:35:03.000000" - } - ] - } - `) - }) -} - -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = fmt.Fprint(w, ` -{ - "snapshot": { - "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "name": "snapshot-001", - "description": "Daily backup", - "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", - "status": "available", - "size": 30, - "created_at": "2017-05-30T03:35:03.000000" - } -} - `) - }) -} - -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - th.TestHeader(t, r, "Content-Type", "application/json") - th.TestHeader(t, r, "Accept", "application/json") - th.TestJSONRequest(t, r, ` -{ - "snapshot": { - "volume_id": "1234", - "name": "snapshot-001" - } -} - `) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusAccepted) - - _, _ = fmt.Fprint(w, ` -{ - "snapshot": { - "volume_id": "1234", - "name": "snapshot-001", - "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "description": "Daily backup", - "volume_id": "1234", - "status": "available", - "size": 30, - "created_at": "2017-05-30T03:35:03.000000" - } -} - `) - }) -} - -func MockUpdateMetadataResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/123/metadata", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - th.TestHeader(t, r, "Content-Type", "application/json") - th.TestJSONRequest(t, r, ` - { - "metadata": { - "key": "v1" - } - } - `) - - _, _ = fmt.Fprint(w, ` - { - "metadata": { - "key": "v1" - } - } - `) - }) -} - -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - w.WriteHeader(http.StatusNoContent) - }) -} diff --git a/openstack/blockstorage/v2/snapshots/testing/requests_test.go b/openstack/blockstorage/v2/snapshots/testing/requests_test.go deleted file mode 100644 index eb114a94f..000000000 --- a/openstack/blockstorage/v2/snapshots/testing/requests_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package testing - -import ( - "testing" - "time" - - "github.com/opentelekomcloud/gophertelekomcloud/openstack/blockstorage/v2/snapshots" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" - th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" - "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" -) - -func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockListResponse(t) - - count := 0 - - _ = snapshots.List(client.ServiceClient(), &snapshots.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { - count++ - actual, err := snapshots.ExtractSnapshots(page) - if err != nil { - t.Errorf("Failed to extract snapshots: %v", err) - return false, err - } - - expected := []snapshots.Snapshot{ - { - ID: "289da7f8-6440-407c-9fb4-7db01ec49164", - Name: "snapshot-001", - VolumeID: "521752a6-acf6-4b2d-bc7a-119f9148cd8c", - Status: "available", - Size: 30, - CreatedAt: time.Date(2017, 5, 30, 3, 35, 3, 0, time.UTC), - Description: "Daily Backup", - }, - { - ID: "96c3bda7-c82a-4f50-be73-ca7621794835", - Name: "snapshot-002", - VolumeID: "76b8950a-8594-4e5b-8dce-0dfa9c696358", - Status: "available", - Size: 25, - CreatedAt: time.Date(2017, 5, 30, 3, 35, 3, 0, time.UTC), - Description: "Weekly Backup", - }, - } - - th.CheckDeepEquals(t, expected, actual) - - return true, nil - }) - - if count != 1 { - t.Errorf("Expected 1 page, got %d", count) - } -} - -func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockGetResponse(t) - - v, err := snapshots.Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() - th.AssertNoErr(t, err) - - th.AssertEquals(t, v.Name, "snapshot-001") - th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") -} - -func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockCreateResponse(t) - - options := snapshots.CreateOpts{VolumeID: "1234", Name: "snapshot-001"} - n, err := snapshots.Create(client.ServiceClient(), options).Extract() - th.AssertNoErr(t, err) - - th.AssertEquals(t, n.VolumeID, "1234") - th.AssertEquals(t, n.Name, "snapshot-001") - th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") -} - -func TestUpdateMetadata(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockUpdateMetadataResponse(t) - - expected := map[string]interface{}{"key": "v1"} - - options := &snapshots.UpdateMetadataOpts{ - Metadata: map[string]interface{}{ - "key": "v1", - }, - } - - actual, err := snapshots.UpdateMetadata(client.ServiceClient(), "123", options).ExtractMetadata() - - th.AssertNoErr(t, err) - th.AssertDeepEquals(t, actual, expected) -} - -func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockDeleteResponse(t) - - res := snapshots.Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertNoErr(t, res.Err) -} diff --git a/openstack/blockstorage/v2/snapshots/urls.go b/openstack/blockstorage/v2/snapshots/urls.go deleted file mode 100644 index 337009274..000000000 --- a/openstack/blockstorage/v2/snapshots/urls.go +++ /dev/null @@ -1,27 +0,0 @@ -package snapshots - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func createURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("snapshots") -} - -func deleteURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL("snapshots", id) -} - -func getURL(c *golangsdk.ServiceClient, id string) string { - return deleteURL(c, id) -} - -func listURL(c *golangsdk.ServiceClient) string { - return createURL(c) -} - -func metadataURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL("snapshots", id, "metadata") -} - -func updateMetadataURL(c *golangsdk.ServiceClient, id string) string { - return metadataURL(c, id) -} diff --git a/openstack/blockstorage/v2/snapshots/util.go b/openstack/blockstorage/v2/snapshots/util.go deleted file mode 100644 index 2375926d2..000000000 --- a/openstack/blockstorage/v2/snapshots/util.go +++ /dev/null @@ -1,22 +0,0 @@ -package snapshots - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -// WaitForStatus will continually poll the resource, checking for a particular -// status. It will do this for the amount of seconds defined. -func WaitForStatus(c *golangsdk.ServiceClient, id, status string, secs int) error { - return golangsdk.WaitFor(secs, func() (bool, error) { - current, err := Get(c, id).Extract() - if err != nil { - return false, err - } - - if current.Status == status { - return true, nil - } - - return false, nil - }) -} diff --git a/openstack/blockstorage/v2/volumes/doc.go b/openstack/blockstorage/v2/volumes/doc.go deleted file mode 100644 index 307b8b12d..000000000 --- a/openstack/blockstorage/v2/volumes/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Package volumes provides information and interaction with volumes in the -// OpenStack Block Storage service. A volume is a detachable block storage -// device, akin to a USB hard drive. It can only be attached to one instance at -// a time. -package volumes diff --git a/openstack/blockstorage/v2/volumes/testing/doc.go b/openstack/blockstorage/v2/volumes/testing/doc.go deleted file mode 100644 index aa8351ab1..000000000 --- a/openstack/blockstorage/v2/volumes/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// volumes_v2 -package testing diff --git a/openstack/blockstorage/v2/volumes/urls.go b/openstack/blockstorage/v2/volumes/urls.go deleted file mode 100644 index f145a5be2..000000000 --- a/openstack/blockstorage/v2/volumes/urls.go +++ /dev/null @@ -1,23 +0,0 @@ -package volumes - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func createURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("volumes") -} - -func listURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("volumes", "detail") -} - -func deleteURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL("volumes", id) -} - -func getURL(c *golangsdk.ServiceClient, id string) string { - return deleteURL(c, id) -} - -func updateURL(c *golangsdk.ServiceClient, id string) string { - return deleteURL(c, id) -} diff --git a/openstack/blockstorage/v3/snapshots/doc.go b/openstack/blockstorage/v3/snapshots/doc.go deleted file mode 100644 index 198f83077..000000000 --- a/openstack/blockstorage/v3/snapshots/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Package snapshots provides information and interaction with snapshots in the -// OpenStack Block Storage service. A snapshot is a point in time copy of the -// data contained in an external storage volume, and can be controlled -// programmatically. -package snapshots diff --git a/openstack/blockstorage/v3/snapshots/requests.go b/openstack/blockstorage/v3/snapshots/requests.go deleted file mode 100644 index 8b8b35c58..000000000 --- a/openstack/blockstorage/v3/snapshots/requests.go +++ /dev/null @@ -1,189 +0,0 @@ -package snapshots - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToSnapshotCreateMap() (map[string]interface{}, error) -} - -// CreateOpts contains options for creating a Snapshot. This object is passed to -// the snapshots.Create function. For more information about these parameters, -// see the Snapshot object. -type CreateOpts struct { - VolumeID string `json:"volume_id" required:"true"` - Force bool `json:"force,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` -} - -// ToSnapshotCreateMap assembles a request body based on the contents of a -// CreateOpts. -func (opts CreateOpts) ToSnapshotCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "snapshot") -} - -// Create will create a new Snapshot based on the values in CreateOpts. To -// extract the Snapshot object from the response, call the Extract method on the -// CreateResult. -func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToSnapshotCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{202}, - }) - return -} - -// Delete will delete the existing Snapshot with the provided ID. -func Delete(client *golangsdk.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) - return -} - -// Get retrieves the Snapshot with the provided ID. To extract the Snapshot -// object from the response, call the Extract method on the GetResult. -func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) - return -} - -// ListOptsBuilder allows extensions to add additional parameters to the List -// request. -type ListOptsBuilder interface { - ToSnapshotListQuery() (string, error) -} - -type ListOpts struct { - // AllTenants will retrieve snapshots of all tenants/projects. - AllTenants bool `q:"all_tenants"` - - // Name will filter by the specified snapshot name. - Name string `q:"name"` - - // Status will filter by the specified status. - Status string `q:"status"` - - // TenantID will filter by a specific tenant/project ID. - // Setting AllTenants is required to use this. - TenantID string `q:"project_id"` - - // VolumeID will filter by a specified volume ID. - VolumeID string `q:"volume_id"` - - // Comma-separated list of sort keys and optional sort directions in the - // form of [:]. - Sort string `q:"sort"` - - // Requests a page size of items. - Limit int `q:"limit"` - - // Used in conjunction with limit to return a slice of items. - Offset int `q:"offset"` - - // The ID of the last-seen item. - Marker string `q:"marker"` -} - -// ToSnapshotListQuery formats a ListOpts into a query string. -func (opts ListOpts) ToSnapshotListQuery() (string, error) { - q, err := golangsdk.BuildQueryString(opts) - if err != nil { - return "", err - } - return q.String(), err -} - -// List returns Snapshots optionally limited by the conditions provided in -// ListOpts. -func List(client *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { - url := listURL(client) - if opts != nil { - query, err := opts.ToSnapshotListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return SnapshotPage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// UpdateMetadataOptsBuilder allows extensions to add additional parameters to -// the Update request. -type UpdateMetadataOptsBuilder interface { - ToSnapshotUpdateMetadataMap() (map[string]interface{}, error) -} - -// UpdateMetadataOpts contain options for updating an existing Snapshot. This -// object is passed to the snapshots.Update function. For more information -// about the parameters, see the Snapshot object. -type UpdateMetadataOpts struct { - Metadata map[string]interface{} `json:"metadata,omitempty"` -} - -// ToSnapshotUpdateMetadataMap assembles a request body based on the contents of -// an UpdateMetadataOpts. -func (opts UpdateMetadataOpts) ToSnapshotUpdateMetadataMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "") -} - -// UpdateMetadata will update the Snapshot with provided information. To -// extract the updated Snapshot from the response, call the ExtractMetadata -// method on the UpdateMetadataResult. -func UpdateMetadata(client *golangsdk.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) { - b, err := opts.ToSnapshotUpdateMetadataMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(updateMetadataURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// IDFromName is a convienience function that returns a snapshot's ID given its name. -func IDFromName(client *golangsdk.ServiceClient, name string) (string, error) { - count := 0 - id := "" - - listOpts := ListOpts{ - Name: name, - } - - pages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractSnapshots(pages) - if err != nil { - return "", err - } - - for _, s := range all { - if s.Name == name { - count++ - id = s.ID - } - } - - switch count { - case 0: - return "", golangsdk.ErrResourceNotFound{Name: name, ResourceType: "snapshot"} - case 1: - return id, nil - default: - return "", golangsdk.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "snapshot"} - } -} diff --git a/openstack/blockstorage/v3/snapshots/results.go b/openstack/blockstorage/v3/snapshots/results.go deleted file mode 100644 index ce6061a21..000000000 --- a/openstack/blockstorage/v3/snapshots/results.go +++ /dev/null @@ -1,129 +0,0 @@ -package snapshots - -import ( - "encoding/json" - "time" - - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/openstack/common/metadata" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// Snapshot contains all the information associated with a Cinder Snapshot. -type Snapshot struct { - // Unique identifier. - ID string `json:"id"` - - // Date created. - CreatedAt time.Time `json:"-"` - - // Date updated. - UpdatedAt time.Time `json:"-"` - - // Display name. - Name string `json:"name"` - - // Display description. - Description string `json:"description"` - - // ID of the Volume from which this Snapshot was created. - VolumeID string `json:"volume_id"` - - // Currect status of the Snapshot. - Status string `json:"status"` - - // Size of the Snapshot, in GB. - Size int `json:"size"` - - // User-defined key-value pairs. - Metadata map[string]string `json:"metadata"` -} - -// CreateResult contains the response body and error from a Create request. -type CreateResult struct { - commonResult -} - -// GetResult contains the response body and error from a Get request. -type GetResult struct { - commonResult -} - -// DeleteResult contains the response body and error from a Delete request. -type DeleteResult struct { - golangsdk.ErrResult -} - -// SnapshotPage is a pagination.Pager that is returned from a call to the List function. -type SnapshotPage struct { - pagination.LinkedPageBase -} - -// UnmarshalJSON converts our JSON API response into our snapshot struct -func (r *Snapshot) UnmarshalJSON(b []byte) error { - type tmp Snapshot - var s struct { - tmp - CreatedAt golangsdk.JSONRFC3339MilliNoZ `json:"created_at"` - UpdatedAt golangsdk.JSONRFC3339MilliNoZ `json:"updated_at"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - *r = Snapshot(s.tmp) - - r.CreatedAt = time.Time(s.CreatedAt) - r.UpdatedAt = time.Time(s.UpdatedAt) - - return err -} - -// IsEmpty returns true if a SnapshotPage contains no Snapshots. -func (r SnapshotPage) IsEmpty() (bool, error) { - volumes, err := ExtractSnapshots(r) - return len(volumes) == 0, err -} - -func (r SnapshotPage) NextPageURL() (string, error) { - var s struct { - Links []golangsdk.Link `json:"snapshots_links"` - } - err := r.ExtractInto(&s) - if err != nil { - return "", err - } - return golangsdk.ExtractNextURL(s.Links) -} - -// ExtractSnapshots extracts and returns Snapshots. It is used while iterating over a snapshots.List call. -func ExtractSnapshots(r pagination.Page) ([]Snapshot, error) { - var s struct { - Snapshots []Snapshot `json:"snapshots"` - } - err := (r.(SnapshotPage)).ExtractInto(&s) - return s.Snapshots, err -} - -// UpdateMetadataResult contains the response body and error from an UpdateMetadata request. -type UpdateMetadataResult struct { - commonResult -} - -// ExtractMetadata returns the metadata from a response from snapshots.UpdateMetadata. -func (r UpdateMetadataResult) ExtractMetadata() (map[string]interface{}, error) { - return metadata.Extract(r.BodyReader()) -} - -type commonResult struct { - golangsdk.Result -} - -// Extract will get the Snapshot object out of the commonResult object. -func (r commonResult) Extract() (*Snapshot, error) { - var s struct { - Snapshot *Snapshot `json:"snapshot"` - } - err := r.ExtractInto(&s) - return s.Snapshot, err -} diff --git a/openstack/blockstorage/v3/snapshots/testing/doc.go b/openstack/blockstorage/v3/snapshots/testing/doc.go deleted file mode 100644 index 3de6274b3..000000000 --- a/openstack/blockstorage/v3/snapshots/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// snapshots_v3 -package testing diff --git a/openstack/blockstorage/v3/snapshots/testing/fixtures.go b/openstack/blockstorage/v3/snapshots/testing/fixtures.go deleted file mode 100644 index 3d52adecf..000000000 --- a/openstack/blockstorage/v3/snapshots/testing/fixtures.go +++ /dev/null @@ -1,148 +0,0 @@ -package testing - -import ( - "fmt" - "net/http" - "testing" - - th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" - fake "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" -) - -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - _ = r.ParseForm() - marker := r.Form.Get("marker") - switch marker { - case "": - _, _ = fmt.Fprintf(w, ` - { - "snapshots": [ - { - "id": "289da7f8-6440-407c-9fb4-7db01ec49164", - "name": "snapshot-001", - "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", - "description": "Daily Backup", - "status": "available", - "size": 30, - "created_at": "2017-05-30T03:35:03.000000" - }, - { - "id": "96c3bda7-c82a-4f50-be73-ca7621794835", - "name": "snapshot-002", - "volume_id": "76b8950a-8594-4e5b-8dce-0dfa9c696358", - "description": "Weekly Backup", - "status": "available", - "size": 25, - "created_at": "2017-05-30T03:35:03.000000" - } - ], - "snapshots_links": [ - { - "href": "%s/snapshots?marker=1", - "rel": "next" - }] - } - `, th.Server.URL) - case "1": - _, _ = fmt.Fprint(w, `{"snapshots": []}`) - default: - t.Fatalf("Unexpected marker: [%s]", marker) - } - }) -} - -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = fmt.Fprint(w, ` -{ - "snapshot": { - "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "name": "snapshot-001", - "description": "Daily backup", - "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", - "status": "available", - "size": 30, - "created_at": "2017-05-30T03:35:03.000000" - } -} - `) - }) -} - -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - th.TestHeader(t, r, "Content-Type", "application/json") - th.TestHeader(t, r, "Accept", "application/json") - th.TestJSONRequest(t, r, ` -{ - "snapshot": { - "volume_id": "1234", - "name": "snapshot-001" - } -} - `) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusAccepted) - - _, _ = fmt.Fprint(w, ` -{ - "snapshot": { - "volume_id": "1234", - "name": "snapshot-001", - "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "description": "Daily backup", - "volume_id": "1234", - "status": "available", - "size": 30, - "created_at": "2017-05-30T03:35:03.000000" - } -} - `) - }) -} - -func MockUpdateMetadataResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/123/metadata", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - th.TestHeader(t, r, "Content-Type", "application/json") - th.TestJSONRequest(t, r, ` - { - "metadata": { - "key": "v1" - } - } - `) - - _, _ = fmt.Fprint(w, ` - { - "metadata": { - "key": "v1" - } - } - `) - }) -} - -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - w.WriteHeader(http.StatusNoContent) - }) -} diff --git a/openstack/blockstorage/v3/snapshots/testing/requests_test.go b/openstack/blockstorage/v3/snapshots/testing/requests_test.go deleted file mode 100644 index 8b9494dae..000000000 --- a/openstack/blockstorage/v3/snapshots/testing/requests_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package testing - -import ( - "testing" - "time" - - "github.com/opentelekomcloud/gophertelekomcloud/openstack/blockstorage/v3/snapshots" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" - th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" - "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" -) - -func TestList(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockListResponse(t) - - count := 0 - - _ = snapshots.List(client.ServiceClient(), &snapshots.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { - count++ - actual, err := snapshots.ExtractSnapshots(page) - if err != nil { - t.Errorf("Failed to extract snapshots: %v", err) - return false, err - } - - expected := []snapshots.Snapshot{ - { - ID: "289da7f8-6440-407c-9fb4-7db01ec49164", - Name: "snapshot-001", - VolumeID: "521752a6-acf6-4b2d-bc7a-119f9148cd8c", - Status: "available", - Size: 30, - CreatedAt: time.Date(2017, 5, 30, 3, 35, 3, 0, time.UTC), - Description: "Daily Backup", - }, - { - ID: "96c3bda7-c82a-4f50-be73-ca7621794835", - Name: "snapshot-002", - VolumeID: "76b8950a-8594-4e5b-8dce-0dfa9c696358", - Status: "available", - Size: 25, - CreatedAt: time.Date(2017, 5, 30, 3, 35, 3, 0, time.UTC), - Description: "Weekly Backup", - }, - } - - th.CheckDeepEquals(t, expected, actual) - - return true, nil - }) - - if count != 1 { - t.Errorf("Expected 1 page, got %d", count) - } -} - -func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockGetResponse(t) - - v, err := snapshots.Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() - th.AssertNoErr(t, err) - - th.AssertEquals(t, v.Name, "snapshot-001") - th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") -} - -func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockCreateResponse(t) - - options := snapshots.CreateOpts{VolumeID: "1234", Name: "snapshot-001"} - n, err := snapshots.Create(client.ServiceClient(), options).Extract() - th.AssertNoErr(t, err) - - th.AssertEquals(t, n.VolumeID, "1234") - th.AssertEquals(t, n.Name, "snapshot-001") - th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") -} - -func TestUpdateMetadata(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockUpdateMetadataResponse(t) - - expected := map[string]interface{}{"key": "v1"} - - options := &snapshots.UpdateMetadataOpts{ - Metadata: map[string]interface{}{ - "key": "v1", - }, - } - - actual, err := snapshots.UpdateMetadata(client.ServiceClient(), "123", options).ExtractMetadata() - - th.AssertNoErr(t, err) - th.AssertDeepEquals(t, actual, expected) -} - -func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockDeleteResponse(t) - - res := snapshots.Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertNoErr(t, res.Err) -} diff --git a/openstack/blockstorage/v3/snapshots/urls.go b/openstack/blockstorage/v3/snapshots/urls.go deleted file mode 100644 index 337009274..000000000 --- a/openstack/blockstorage/v3/snapshots/urls.go +++ /dev/null @@ -1,27 +0,0 @@ -package snapshots - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func createURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("snapshots") -} - -func deleteURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL("snapshots", id) -} - -func getURL(c *golangsdk.ServiceClient, id string) string { - return deleteURL(c, id) -} - -func listURL(c *golangsdk.ServiceClient) string { - return createURL(c) -} - -func metadataURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL("snapshots", id, "metadata") -} - -func updateMetadataURL(c *golangsdk.ServiceClient, id string) string { - return metadataURL(c, id) -} diff --git a/openstack/blockstorage/v3/snapshots/util.go b/openstack/blockstorage/v3/snapshots/util.go deleted file mode 100644 index 2375926d2..000000000 --- a/openstack/blockstorage/v3/snapshots/util.go +++ /dev/null @@ -1,22 +0,0 @@ -package snapshots - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -// WaitForStatus will continually poll the resource, checking for a particular -// status. It will do this for the amount of seconds defined. -func WaitForStatus(c *golangsdk.ServiceClient, id, status string, secs int) error { - return golangsdk.WaitFor(secs, func() (bool, error) { - current, err := Get(c, id).Extract() - if err != nil { - return false, err - } - - if current.Status == status { - return true, nil - } - - return false, nil - }) -} diff --git a/openstack/blockstorage/v3/volumes/doc.go b/openstack/blockstorage/v3/volumes/doc.go deleted file mode 100644 index 307b8b12d..000000000 --- a/openstack/blockstorage/v3/volumes/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Package volumes provides information and interaction with volumes in the -// OpenStack Block Storage service. A volume is a detachable block storage -// device, akin to a USB hard drive. It can only be attached to one instance at -// a time. -package volumes diff --git a/openstack/blockstorage/v3/volumes/requests.go b/openstack/blockstorage/v3/volumes/requests.go deleted file mode 100644 index de23f0ab0..000000000 --- a/openstack/blockstorage/v3/volumes/requests.go +++ /dev/null @@ -1,210 +0,0 @@ -package volumes - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToVolumeCreateMap() (map[string]interface{}, error) -} - -// CreateOpts contains options for creating a Volume. This object is passed to -// the volumes.Create function. For more information about these parameters, -// see the Volume object. -type CreateOpts struct { - // The size of the volume, in GB - Size int `json:"size" required:"true"` - // The availability zone - AvailabilityZone string `json:"availability_zone,omitempty"` - // ConsistencyGroupID is the ID of a consistency group - ConsistencyGroupID string `json:"consistencygroup_id,omitempty"` - // The volume description - Description string `json:"description,omitempty"` - // One or more metadata key and value pairs to associate with the volume - Metadata map[string]string `json:"metadata,omitempty"` - // The volume name - Name string `json:"name,omitempty"` - // the ID of the existing volume snapshot - SnapshotID string `json:"snapshot_id,omitempty"` - // SourceReplica is a UUID of an existing volume to replicate with - SourceReplica string `json:"source_replica,omitempty"` - // the ID of the existing volume - SourceVolID string `json:"source_volid,omitempty"` - // The ID of the image from which you want to create the volume. - // Required to create a bootable volume. - ImageID string `json:"imageRef,omitempty"` - // The associated volume type - VolumeType string `json:"volume_type,omitempty"` -} - -// ToVolumeCreateMap assembles a request body based on the contents of a -// CreateOpts. -func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "volume") -} - -// Create will create a new Volume based on the values in CreateOpts. To extract -// the Volume object from the response, call the Extract method on the -// CreateResult. -func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToVolumeCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{202}, - }) - return -} - -// Delete will delete the existing Volume with the provided ID. -func Delete(client *golangsdk.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) - return -} - -// Get retrieves the Volume with the provided ID. To extract the Volume object -// from the response, call the Extract method on the GetResult. -func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) - return -} - -// ListOptsBuilder allows extensions to add additional parameters to the List -// request. -type ListOptsBuilder interface { - ToVolumeListQuery() (string, error) -} - -// ListOpts holds options for listing Volumes. It is passed to the volumes.List -// function. -type ListOpts struct { - // AllTenants will retrieve volumes of all tenants/projects. - AllTenants bool `q:"all_tenants"` - - // Metadata will filter results based on specified metadata. - Metadata map[string]string `q:"metadata"` - - // Name will filter by the specified volume name. - Name string `q:"name"` - - // Status will filter by the specified status. - Status string `q:"status"` - - // TenantID will filter by a specific tenant/project ID. - // Setting AllTenants is required for this. - TenantID string `q:"project_id"` - - // Comma-separated list of sort keys and optional sort directions in the - // form of [:]. - Sort string `q:"sort"` - - // Requests a page size of items. - Limit int `q:"limit"` - - // Used in conjunction with limit to return a slice of items. - Offset int `q:"offset"` - - // The ID of the last-seen item. - Marker string `q:"marker"` -} - -// ToVolumeListQuery formats a ListOpts into a query string. -func (opts ListOpts) ToVolumeListQuery() (string, error) { - q, err := golangsdk.BuildQueryString(opts) - if err != nil { - return "", err - } - return q.String(), err -} - -// List returns Volumes optionally limited by the conditions provided in ListOpts. -func List(client *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { - url := listURL(client) - if opts != nil { - query, err := opts.ToVolumeListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return VolumePage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// UpdateOptsBuilder allows extensions to add additional parameters to the -// Update request. -type UpdateOptsBuilder interface { - ToVolumeUpdateMap() (map[string]interface{}, error) -} - -// UpdateOpts contain options for updating an existing Volume. This object is passed -// to the volumes.Update function. For more information about the parameters, see -// the Volume object. -type UpdateOpts struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` -} - -// ToVolumeUpdateMap assembles a request body based on the contents of an -// UpdateOpts. -func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "volume") -} - -// Update will update the Volume with provided information. To extract the updated -// Volume from the response, call the Extract method on the UpdateResult. -func Update(client *golangsdk.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToVolumeUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// IDFromName is a convienience 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, - } - - pages, err := List(client, listOpts).AllPages() - if err != nil { - return "", err - } - - all, err := ExtractVolumes(pages) - if err != nil { - return "", err - } - - for _, s := range all { - if s.Name == name { - count++ - id = s.ID - } - } - - switch count { - case 0: - return "", golangsdk.ErrResourceNotFound{Name: name, ResourceType: "volume"} - case 1: - return id, nil - default: - return "", golangsdk.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"} - } -} diff --git a/openstack/blockstorage/v3/volumes/results.go b/openstack/blockstorage/v3/volumes/results.go deleted file mode 100644 index 000d63bd0..000000000 --- a/openstack/blockstorage/v3/volumes/results.go +++ /dev/null @@ -1,170 +0,0 @@ -package volumes - -import ( - "encoding/json" - "time" - - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// Attachment represents a Volume Attachment record -type Attachment struct { - AttachedAt time.Time `json:"-"` - AttachmentID string `json:"attachment_id"` - Device string `json:"device"` - HostName string `json:"host_name"` - ID string `json:"id"` - ServerID string `json:"server_id"` - VolumeID string `json:"volume_id"` -} - -// UnmarshalJSON is our unmarshalling helper -func (r *Attachment) UnmarshalJSON(b []byte) error { - type tmp Attachment - var s struct { - tmp - AttachedAt golangsdk.JSONRFC3339MilliNoZ `json:"attached_at"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - *r = Attachment(s.tmp) - - r.AttachedAt = time.Time(s.AttachedAt) - - return err -} - -// Volume contains all the information associated with an OpenStack Volume. -type Volume struct { - // Unique identifier for the volume. - ID string `json:"id"` - // Current status of the volume. - Status string `json:"status"` - // Size of the volume in GB. - Size int `json:"size"` - // AvailabilityZone is which availability zone the volume is in. - AvailabilityZone string `json:"availability_zone"` - // The date when this volume was created. - CreatedAt time.Time `json:"-"` - // The date when this volume was last updated - UpdatedAt time.Time `json:"-"` - // Instances onto which the volume is attached. - Attachments []Attachment `json:"attachments"` - // Human-readable display name for the volume. - Name string `json:"name"` - // Human-readable description for the volume. - Description string `json:"description"` - // The type of volume to create, either SATA or SSD. - VolumeType string `json:"volume_type"` - // The ID of the snapshot from which the volume was created - SnapshotID string `json:"snapshot_id"` - // The ID of another block storage volume from which the current volume was created - SourceVolID string `json:"source_volid"` - // Arbitrary key-value pairs defined by the user. - Metadata map[string]string `json:"metadata"` - // UserID is the id of the user who created the volume. - UserID string `json:"user_id"` - // Indicates whether this is a bootable volume. - Bootable string `json:"bootable"` - // Encrypted denotes if the volume is encrypted. - Encrypted bool `json:"encrypted"` - // ReplicationStatus is the status of replication. - ReplicationStatus string `json:"replication_status"` - // ConsistencyGroupID is the consistency group ID. - ConsistencyGroupID string `json:"consistencygroup_id"` - // Multiattach denotes if the volume is multi-attach capable. - Multiattach bool `json:"multiattach"` -} - -// UnmarshalJSON another unmarshalling function -func (r *Volume) UnmarshalJSON(b []byte) error { - type tmp Volume - var s struct { - tmp - CreatedAt golangsdk.JSONRFC3339MilliNoZ `json:"created_at"` - UpdatedAt golangsdk.JSONRFC3339MilliNoZ `json:"updated_at"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - *r = Volume(s.tmp) - - r.CreatedAt = time.Time(s.CreatedAt) - r.UpdatedAt = time.Time(s.UpdatedAt) - - return err -} - -// VolumePage is a pagination.pager that is returned from a call to the List function. -type VolumePage struct { - pagination.LinkedPageBase -} - -// IsEmpty returns true if a ListResult contains no Volumes. -func (r VolumePage) IsEmpty() (bool, error) { - volumes, err := ExtractVolumes(r) - return len(volumes) == 0, err -} - -func (r VolumePage) NextPageURL() (string, error) { - var s struct { - Links []golangsdk.Link `json:"volumes_links"` - } - err := r.ExtractInto(&s) - if err != nil { - return "", err - } - return golangsdk.ExtractNextURL(s.Links) -} - -// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call. -func ExtractVolumes(r pagination.Page) ([]Volume, error) { - var s []Volume - err := ExtractVolumesInto(r, &s) - return s, err -} - -type commonResult struct { - golangsdk.Result -} - -// Extract will get the Volume object out of the commonResult object. -func (r commonResult) Extract() (*Volume, error) { - var s Volume - err := r.ExtractInto(&s) - return &s, err -} - -// ExtractInto converts our response data into a volume struct -func (r commonResult) ExtractInto(v interface{}) error { - return r.Result.ExtractIntoStructPtr(v, "volume") -} - -// ExtractVolumesInto similar to ExtractInto but operates on a `list` of volumes -func ExtractVolumesInto(r pagination.Page, v interface{}) error { - return r.(VolumePage).Result.ExtractIntoSlicePtr(v, "volumes") -} - -// CreateResult contains the response body and error from a Create request. -type CreateResult struct { - commonResult -} - -// GetResult contains the response body and error from a Get request. -type GetResult struct { - commonResult -} - -// UpdateResult contains the response body and error from an Update request. -type UpdateResult struct { - commonResult -} - -// DeleteResult contains the response body and error from a Delete request. -type DeleteResult struct { - golangsdk.ErrResult -} diff --git a/openstack/blockstorage/v3/volumes/testing/doc.go b/openstack/blockstorage/v3/volumes/testing/doc.go deleted file mode 100644 index a2b24b7c1..000000000 --- a/openstack/blockstorage/v3/volumes/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// volumes_v3 -package testing diff --git a/openstack/blockstorage/v3/volumes/testing/fixtures.go b/openstack/blockstorage/v3/volumes/testing/fixtures.go deleted file mode 100644 index 2f705c550..000000000 --- a/openstack/blockstorage/v3/volumes/testing/fixtures.go +++ /dev/null @@ -1,217 +0,0 @@ -package testing - -import ( - "fmt" - "net/http" - "testing" - - th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" - fake "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" -) - -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/detail", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - _ = r.ParseForm() - marker := r.Form.Get("marker") - switch marker { - case "": - _, _ = fmt.Fprintf(w, ` - { - "volumes": [ - { - "volume_type": "lvmdriver-1", - "created_at": "2015-09-17T03:35:03.000000", - "bootable": "false", - "name": "vol-001", - "os-vol-mig-status-attr:name_id": null, - "consistencygroup_id": null, - "source_volid": null, - "os-volume-replication:driver_data": null, - "multiattach": false, - "snapshot_id": null, - "replication_status": "disabled", - "os-volume-replication:extended_status": null, - "encrypted": false, - "os-vol-host-attr:host": null, - "availability_zone": "nova", - "attachments": [{ - "server_id": "83ec2e3b-4321-422b-8706-a84185f52a0a", - "attachment_id": "05551600-a936-4d4a-ba42-79a037c1-c91a", - "attached_at": "2016-08-06T14:48:20.000000", - "host_name": "foobar", - "volume_id": "d6cacb1a-8b59-4c88-ad90-d70ebb82bb75", - "device": "/dev/vdc", - "id": "d6cacb1a-8b59-4c88-ad90-d70ebb82bb75" - }], - "id": "289da7f8-6440-407c-9fb4-7db01ec49164", - "size": 75, - "user_id": "ff1ce52c03ab433aaba9108c2e3ef541", - "os-vol-tenant-attr:tenant_id": "304dc00909ac4d0da6c62d816bcb3459", - "os-vol-mig-status-attr:migstat": null, - "metadata": {"foo": "bar"}, - "status": "available", - "description": null - }, - { - "volume_type": "lvmdriver-1", - "created_at": "2015-09-17T03:32:29.000000", - "bootable": "false", - "name": "vol-002", - "os-vol-mig-status-attr:name_id": null, - "consistencygroup_id": null, - "source_volid": null, - "os-volume-replication:driver_data": null, - "multiattach": false, - "snapshot_id": null, - "replication_status": "disabled", - "os-volume-replication:extended_status": null, - "encrypted": false, - "os-vol-host-attr:host": null, - "availability_zone": "nova", - "attachments": [], - "id": "96c3bda7-c82a-4f50-be73-ca7621794835", - "size": 75, - "user_id": "ff1ce52c03ab433aaba9108c2e3ef541", - "os-vol-tenant-attr:tenant_id": "304dc00909ac4d0da6c62d816bcb3459", - "os-vol-mig-status-attr:migstat": null, - "metadata": {}, - "status": "available", - "description": null - } - ], - "volumes_links": [ - { - "href": "%s/volumes/detail?marker=1", - "rel": "next" - }] -} - `, th.Server.URL) - case "1": - _, _ = fmt.Fprint(w, `{"volumes": []}`) - default: - t.Fatalf("Unexpected marker: [%s]", marker) - } - }) -} - -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = fmt.Fprint(w, ` -{ - "volume": { - "volume_type": "lvmdriver-1", - "created_at": "2015-09-17T03:32:29.000000", - "bootable": "false", - "name": "vol-001", - "os-vol-mig-status-attr:name_id": null, - "consistencygroup_id": null, - "source_volid": null, - "os-volume-replication:driver_data": null, - "multiattach": false, - "snapshot_id": null, - "replication_status": "disabled", - "os-volume-replication:extended_status": null, - "encrypted": false, - "os-vol-host-attr:host": null, - "availability_zone": "nova", - "attachments": [{ - "server_id": "83ec2e3b-4321-422b-8706-a84185f52a0a", - "attachment_id": "05551600-a936-4d4a-ba42-79a037c1-c91a", - "attached_at": "2016-08-06T14:48:20.000000", - "host_name": "foobar", - "volume_id": "d6cacb1a-8b59-4c88-ad90-d70ebb82bb75", - "device": "/dev/vdc", - "id": "d6cacb1a-8b59-4c88-ad90-d70ebb82bb75" - }], - "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "size": 75, - "user_id": "ff1ce52c03ab433aaba9108c2e3ef541", - "os-vol-tenant-attr:tenant_id": "304dc00909ac4d0da6c62d816bcb3459", - "os-vol-mig-status-attr:migstat": null, - "metadata": {}, - "status": "available", - "description": null - } -} - `) - }) -} - -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - th.TestHeader(t, r, "Content-Type", "application/json") - th.TestHeader(t, r, "Accept", "application/json") - th.TestJSONRequest(t, r, ` -{ - "volume": { - "name": "vol-001", - "size": 75 - } -} - `) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusAccepted) - - _, _ = fmt.Fprint(w, ` -{ - "volume": { - "size": 75, - "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "metadata": {}, - "created_at": "2015-09-17T03:32:29.044216", - "encrypted": false, - "bootable": "false", - "availability_zone": "nova", - "attachments": [], - "user_id": "ff1ce52c03ab433aaba9108c2e3ef541", - "status": "creating", - "description": null, - "volume_type": "lvmdriver-1", - "name": "vol-001", - "replication_status": "disabled", - "consistencygroup_id": null, - "source_volid": null, - "snapshot_id": null, - "multiattach": false - } -} - `) - }) -} - -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - w.WriteHeader(http.StatusAccepted) - }) -} - -func MockUpdateResponse(t *testing.T) { - th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - w.WriteHeader(http.StatusOK) - _, _ = fmt.Fprint(w, ` -{ - "volume": { - "name": "vol-002" - } -} - `) - }) -} diff --git a/openstack/blockstorage/v3/volumes/testing/requests_test.go b/openstack/blockstorage/v3/volumes/testing/requests_test.go deleted file mode 100644 index ae928cc42..000000000 --- a/openstack/blockstorage/v3/volumes/testing/requests_test.go +++ /dev/null @@ -1,257 +0,0 @@ -package testing - -import ( - "testing" - "time" - - "github.com/opentelekomcloud/gophertelekomcloud/openstack/blockstorage/v3/volumes" - "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/extensions" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" - th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" - "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" -) - -func TestListWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockListResponse(t) - - count := 0 - - _ = volumes.List(client.ServiceClient(), &volumes.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { - count++ - actual, err := volumes.ExtractVolumes(page) - if err != nil { - t.Errorf("Failed to extract volumes: %v", err) - return false, err - } - - expected := []volumes.Volume{ - { - ID: "289da7f8-6440-407c-9fb4-7db01ec49164", - Name: "vol-001", - Attachments: []volumes.Attachment{{ - ServerID: "83ec2e3b-4321-422b-8706-a84185f52a0a", - AttachmentID: "05551600-a936-4d4a-ba42-79a037c1-c91a", - AttachedAt: time.Date(2016, 8, 6, 14, 48, 20, 0, time.UTC), - HostName: "foobar", - VolumeID: "d6cacb1a-8b59-4c88-ad90-d70ebb82bb75", - Device: "/dev/vdc", - ID: "d6cacb1a-8b59-4c88-ad90-d70ebb82bb75", - }}, - AvailabilityZone: "nova", - Bootable: "false", - ConsistencyGroupID: "", - CreatedAt: time.Date(2015, 9, 17, 3, 35, 3, 0, time.UTC), - Description: "", - Encrypted: false, - Metadata: map[string]string{"foo": "bar"}, - Multiattach: false, - // TenantID: "304dc00909ac4d0da6c62d816bcb3459", - // ReplicationDriverData: "", - // ReplicationExtendedStatus: "", - ReplicationStatus: "disabled", - Size: 75, - SnapshotID: "", - SourceVolID: "", - Status: "available", - UserID: "ff1ce52c03ab433aaba9108c2e3ef541", - VolumeType: "lvmdriver-1", - }, - { - ID: "96c3bda7-c82a-4f50-be73-ca7621794835", - Name: "vol-002", - Attachments: []volumes.Attachment{}, - AvailabilityZone: "nova", - Bootable: "false", - ConsistencyGroupID: "", - CreatedAt: time.Date(2015, 9, 17, 3, 32, 29, 0, time.UTC), - Description: "", - Encrypted: false, - Metadata: map[string]string{}, - Multiattach: false, - // TenantID: "304dc00909ac4d0da6c62d816bcb3459", - // ReplicationDriverData: "", - // ReplicationExtendedStatus: "", - ReplicationStatus: "disabled", - Size: 75, - SnapshotID: "", - SourceVolID: "", - Status: "available", - UserID: "ff1ce52c03ab433aaba9108c2e3ef541", - VolumeType: "lvmdriver-1", - }, - } - - th.CheckDeepEquals(t, expected, actual) - - return true, nil - }) - - if count != 1 { - t.Errorf("Expected 1 page, got %d", count) - } -} - -func TestListAllWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockListResponse(t) - - type VolumeWithExt struct { - volumes.Volume - extensions.VolumeTenantExt - } - - allPages, err := volumes.List(client.ServiceClient(), &volumes.ListOpts{}).AllPages() - th.AssertNoErr(t, err) - - var actual []VolumeWithExt - err = volumes.ExtractVolumesInto(allPages, &actual) - th.AssertNoErr(t, err) - th.AssertEquals(t, 2, len(actual)) - th.AssertEquals(t, "304dc00909ac4d0da6c62d816bcb3459", actual[0].TenantID) -} - -func TestListAll(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockListResponse(t) - - allPages, err := volumes.List(client.ServiceClient(), &volumes.ListOpts{}).AllPages() - th.AssertNoErr(t, err) - actual, err := volumes.ExtractVolumes(allPages) - th.AssertNoErr(t, err) - - expected := []volumes.Volume{ - { - ID: "289da7f8-6440-407c-9fb4-7db01ec49164", - Name: "vol-001", - Attachments: []volumes.Attachment{{ - ServerID: "83ec2e3b-4321-422b-8706-a84185f52a0a", - AttachmentID: "05551600-a936-4d4a-ba42-79a037c1-c91a", - AttachedAt: time.Date(2016, 8, 6, 14, 48, 20, 0, time.UTC), - HostName: "foobar", - VolumeID: "d6cacb1a-8b59-4c88-ad90-d70ebb82bb75", - Device: "/dev/vdc", - ID: "d6cacb1a-8b59-4c88-ad90-d70ebb82bb75", - }}, - AvailabilityZone: "nova", - Bootable: "false", - ConsistencyGroupID: "", - CreatedAt: time.Date(2015, 9, 17, 3, 35, 3, 0, time.UTC), - Description: "", - Encrypted: false, - Metadata: map[string]string{"foo": "bar"}, - Multiattach: false, - // TenantID: "304dc00909ac4d0da6c62d816bcb3459", - // ReplicationDriverData: "", - // ReplicationExtendedStatus: "", - ReplicationStatus: "disabled", - Size: 75, - SnapshotID: "", - SourceVolID: "", - Status: "available", - UserID: "ff1ce52c03ab433aaba9108c2e3ef541", - VolumeType: "lvmdriver-1", - }, - { - ID: "96c3bda7-c82a-4f50-be73-ca7621794835", - Name: "vol-002", - Attachments: []volumes.Attachment{}, - AvailabilityZone: "nova", - Bootable: "false", - ConsistencyGroupID: "", - CreatedAt: time.Date(2015, 9, 17, 3, 32, 29, 0, time.UTC), - Description: "", - Encrypted: false, - Metadata: map[string]string{}, - Multiattach: false, - // TenantID: "304dc00909ac4d0da6c62d816bcb3459", - // ReplicationDriverData: "", - // ReplicationExtendedStatus: "", - ReplicationStatus: "disabled", - Size: 75, - SnapshotID: "", - SourceVolID: "", - Status: "available", - UserID: "ff1ce52c03ab433aaba9108c2e3ef541", - VolumeType: "lvmdriver-1", - }, - } - - th.CheckDeepEquals(t, expected, actual) - -} - -func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockGetResponse(t) - - v, err := volumes.Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() - th.AssertNoErr(t, err) - - th.AssertEquals(t, v.Name, "vol-001") - th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") -} - -func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockCreateResponse(t) - - options := &volumes.CreateOpts{Size: 75, Name: "vol-001"} - n, err := volumes.Create(client.ServiceClient(), options).Extract() - th.AssertNoErr(t, err) - - th.AssertEquals(t, n.Size, 75) - th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") -} - -func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockDeleteResponse(t) - - res := volumes.Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertNoErr(t, res.Err) -} - -func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockUpdateResponse(t) - - options := volumes.UpdateOpts{Name: "vol-002"} - v, err := volumes.Update(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() - th.AssertNoErr(t, err) - th.CheckEquals(t, "vol-002", v.Name) -} - -func TestGetWithExtensions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockGetResponse(t) - - var s struct { - volumes.Volume - extensions.VolumeTenantExt - } - err := volumes.Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&s) - th.AssertNoErr(t, err) - th.AssertEquals(t, "304dc00909ac4d0da6c62d816bcb3459", s.TenantID) - - err = volumes.Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(s) - if err == nil { - t.Errorf("Expected error when providing non-pointer struct") - } -} diff --git a/openstack/blockstorage/v3/volumes/urls.go b/openstack/blockstorage/v3/volumes/urls.go deleted file mode 100644 index f145a5be2..000000000 --- a/openstack/blockstorage/v3/volumes/urls.go +++ /dev/null @@ -1,23 +0,0 @@ -package volumes - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func createURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("volumes") -} - -func listURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("volumes", "detail") -} - -func deleteURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL("volumes", id) -} - -func getURL(c *golangsdk.ServiceClient, id string) string { - return deleteURL(c, id) -} - -func updateURL(c *golangsdk.ServiceClient, id string) string { - return deleteURL(c, id) -} diff --git a/openstack/blockstorage/v3/volumes/util.go b/openstack/blockstorage/v3/volumes/util.go deleted file mode 100644 index 49c848a62..000000000 --- a/openstack/blockstorage/v3/volumes/util.go +++ /dev/null @@ -1,22 +0,0 @@ -package volumes - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -// WaitForStatus will continually poll the resource, checking for a particular -// status. It will do this for the amount of seconds defined. -func WaitForStatus(c *golangsdk.ServiceClient, id, status string, secs int) error { - return golangsdk.WaitFor(secs, func() (bool, error) { - current, err := Get(c, id).Extract() - if err != nil { - return false, err - } - - if current.Status == status { - return true, nil - } - - return false, nil - }) -} diff --git a/openstack/blockstorage/v3/volumetypes/doc.go b/openstack/blockstorage/v3/volumetypes/doc.go deleted file mode 100644 index 8b2769cb7..000000000 --- a/openstack/blockstorage/v3/volumetypes/doc.go +++ /dev/null @@ -1,63 +0,0 @@ -/* -Package volumetypes provides information and interaction with volume types in the -OpenStack Block Storage service. A volume type is a collection of specs used to -define the volume capabilities. - -Example to list Volume Types - - allPages, err := volumetypes.List(client, volumetypes.ListOpts{}).AllPages() - if err != nil{ - panic(err) - } - volumeTypes, err := volumetypes.ExtractVolumeTypes(allPages) - if err != nil{ - panic(err) - } - for _,vt := range volumeTypes{ - fmt.Println(vt) - } - -Example to show a Volume Type - - typeID := "7ffaca22-f646-41d4-b79d-d7e4452ef8cc" - volumeType, err := volumetypes.Get(client, typeID).Extract() - if err != nil{ - panic(err) - } - fmt.Println(volumeType) - -Example to create a Volume Type - - volumeType, err := volumetypes.Create(client, volumetypes.CreateOpts{ - Name:"volume_type_001", - IsPublic:true, - Description:"description_001", - }).Extract() - if err != nil{ - panic(err) - } - fmt.Println(volumeType) - -Example to delete a Volume Type - - typeID := "7ffaca22-f646-41d4-b79d-d7e4452ef8cc" - err := volumetypes.Delete(client, typeID).ExtractErr() - if err != nil{ - panic(err) - } - -Example to update a Volume Type - - typeID := "7ffaca22-f646-41d4-b79d-d7e4452ef8cc" - volumetype, err = volumetypes.Update(client, typeID, volumetypes.UpdateOpts{ - Name: "volume_type_002", - Description:"description_002", - IsPublic:false, - }).Extract() - if err != nil{ - panic(err) - } - fmt.Println(volumetype) -*/ - -package volumetypes diff --git a/openstack/blockstorage/v3/volumetypes/requests.go b/openstack/blockstorage/v3/volumetypes/requests.go deleted file mode 100644 index 822db22f8..000000000 --- a/openstack/blockstorage/v3/volumetypes/requests.go +++ /dev/null @@ -1,141 +0,0 @@ -package volumetypes - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToVolumeTypeCreateMap() (map[string]interface{}, error) -} - -// CreateOpts contains options for creating a Volume Type. This object is passed to -// the volumetypes.Create function. For more information about these parameters, -// see the Volume Type object. -type CreateOpts struct { - // The name of the volume type - Name string `json:"name" required:"true"` - // The volume type description - Description string `json:"description,omitempty"` - // the ID of the existing volume snapshot - IsPublic *bool `json:"os-volume-type-access:is_public,omitempty"` - // Extra spec key-value pairs defined by the user. - ExtraSpecs map[string]string `json:"extra_specs"` -} - -// ToVolumeTypeCreateMap assembles a request body based on the contents of a -// CreateOpts. -func (opts CreateOpts) ToVolumeTypeCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "volume_type") -} - -// Create will create a new Volume Type based on the values in CreateOpts. To extract -// the Volume Type object from the response, call the Extract method on the -// CreateResult. -func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToVolumeTypeCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Delete will delete the existing Volume Type with the provided ID. -func Delete(client *golangsdk.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) - return -} - -// Get retrieves the Volume Type with the provided ID. To extract the Volume Type object -// from the response, call the Extract method on the GetResult. -func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) - return -} - -// ListOptsBuilder allows extensions to add additional parameters to the List -// request. -type ListOptsBuilder interface { - ToVolumeTypeListQuery() (string, error) -} - -// ListOpts holds options for listing Volume Types. It is passed to the volumetypes.List -// function. -type ListOpts struct { - // Comma-separated list of sort keys and optional sort directions in the - // form of [:]. - Sort string `q:"sort"` - // Requests a page size of items. - Limit int `q:"limit"` - // Used in conjunction with limit to return a slice of items. - Offset int `q:"offset"` - // The ID of the last-seen item. - Marker string `q:"marker"` -} - -// ToVolumeTypeListQuery formats a ListOpts into a query string. -func (opts ListOpts) ToVolumeTypeListQuery() (string, error) { - q, err := golangsdk.BuildQueryString(opts) - if err != nil { - return "", err - } - return q.String(), err -} - -// List returns Volume types. -func List(client *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { - url := listURL(client) - - if opts != nil { - query, err := opts.ToVolumeTypeListQuery() - if err != nil { - return pagination.Pager{Err: err} - } - url += query - } - - return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { - return VolumeTypePage{pagination.LinkedPageBase{PageResult: r}} - }) -} - -// UpdateOptsBuilder allows extensions to add additional parameters to the -// Update request. -type UpdateOptsBuilder interface { - ToVolumeTypeUpdateMap() (map[string]interface{}, error) -} - -// UpdateOpts contain options for updating an existing Volume Type. This object is passed -// to the volumetypes.Update function. For more information about the parameters, see -// the Volume Type object. -type UpdateOpts struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - IsPublic *bool `json:"is_public,omitempty"` -} - -// ToVolumeUpdateMap assembles a request body based on the contents of an -// UpdateOpts. -func (opts UpdateOpts) ToVolumeTypeUpdateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "volume_type") -} - -// Update will update the Volume Type with provided information. To extract the updated -// Volume Type from the response, call the Extract method on the UpdateResult. -func Update(client *golangsdk.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToVolumeTypeUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} diff --git a/openstack/blockstorage/v3/volumetypes/results.go b/openstack/blockstorage/v3/volumetypes/results.go deleted file mode 100644 index a727c1ed1..000000000 --- a/openstack/blockstorage/v3/volumetypes/results.go +++ /dev/null @@ -1,94 +0,0 @@ -package volumetypes - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" -) - -// Volume Type contains all the information associated with an OpenStack Volume Type. -type VolumeType struct { - // Unique identifier for the volume type. - ID string `json:"id"` - // Human-readable display name for the volume type. - Name string `json:"name"` - // Human-readable description for the volume type. - Description string `json:"description"` - // Arbitrary key-value pairs defined by the user. - ExtraSpecs map[string]string `json:"extra_specs"` - // Whether the volume type is publicly visible. - IsPublic bool `json:"is_public"` - // Qos Spec ID - QosSpecID string `json:"qos_specs_id"` - // Volume Type access public attribute - PublicAccess bool `json:"os-volume-type-access:is_public"` -} - -// VolumeTypePage is a pagination.pager that is returned from a call to the List function. -type VolumeTypePage struct { - pagination.LinkedPageBase -} - -// IsEmpty returns true if a ListResult contains no Volume Types. -func (r VolumeTypePage) IsEmpty() (bool, error) { - volumetypes, err := ExtractVolumeTypes(r) - return len(volumetypes) == 0, err -} - -func (r VolumeTypePage) NextPageURL() (string, error) { - var s struct { - Links []golangsdk.Link `json:"volume_type_links"` - } - err := r.ExtractInto(&s) - if err != nil { - return "", err - } - return golangsdk.ExtractNextURL(s.Links) -} - -// ExtractVolumeTypes extracts and returns Volumes. It is used while iterating over a volumetypes.List call. -func ExtractVolumeTypes(r pagination.Page) ([]VolumeType, error) { - var s []VolumeType - err := ExtractVolumeTypesInto(r, &s) - return s, err -} - -type commonResult struct { - golangsdk.Result -} - -// Extract will get the Volume Type object out of the commonResult object. -func (r commonResult) Extract() (*VolumeType, error) { - var s VolumeType - err := r.ExtractInto(&s) - return &s, err -} - -// ExtractInto converts our response data into a volume type struct -func (r commonResult) ExtractInto(v interface{}) error { - return r.Result.ExtractIntoStructPtr(v, "volume_type") -} - -// ExtractVolumesInto similar to ExtractInto but operates on a `list` of volume types -func ExtractVolumeTypesInto(r pagination.Page, v interface{}) error { - return r.(VolumeTypePage).Result.ExtractIntoSlicePtr(v, "volume_types") -} - -// GetResult contains the response body and error from a Get request. -type GetResult struct { - commonResult -} - -// CreateResult contains the response body and error from a Create request. -type CreateResult struct { - commonResult -} - -// DeleteResult contains the response body and error from a Delete request. -type DeleteResult struct { - golangsdk.ErrResult -} - -// UpdateResult contains the response body and error from an Update request. -type UpdateResult struct { - commonResult -} diff --git a/openstack/blockstorage/v3/volumetypes/testing/doc.go b/openstack/blockstorage/v3/volumetypes/testing/doc.go deleted file mode 100644 index 3fd720a67..000000000 --- a/openstack/blockstorage/v3/volumetypes/testing/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// volume_types -package testing diff --git a/openstack/blockstorage/v3/volumetypes/testing/fixtures.go b/openstack/blockstorage/v3/volumetypes/testing/fixtures.go deleted file mode 100644 index fa363a510..000000000 --- a/openstack/blockstorage/v3/volumetypes/testing/fixtures.go +++ /dev/null @@ -1,154 +0,0 @@ -package testing - -import ( - "fmt" - "net/http" - "testing" - - th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" - fake "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" -) - -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - _ = r.ParseForm() - marker := r.Form.Get("marker") - switch marker { - case "": - _, _ = fmt.Fprintf(w, ` -{ - "volume_types": [ - { - "name": "SSD", - "qos_specs_id": null, - "os-volume-type-access:is_public": true, - "extra_specs": { - "volume_backend_name": "lvmdriver-1" - }, - "is_public": true, - "id": "6685584b-1eac-4da6-b5c3-555430cf68ff", - "description": null - }, - { - "name": "SATA", - "qos_specs_id": null, - "os-volume-type-access:is_public": true, - "extra_specs": { - "volume_backend_name": "lvmdriver-1" - }, - "is_public": true, - "id": "8eb69a46-df97-4e41-9586-9a40a7533803", - "description": null - } - ], - "volume_type_links": [ - { - "href": "%s/types?marker=1", - "rel": "next" - } - ] -} - `, th.Server.URL) - case "1": - _, _ = fmt.Fprint(w, `{"volume_types": []}`) - default: - t.Fatalf("Unexpected marker: [%s]", marker) - } - }) -} - -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/types/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = fmt.Fprint(w, ` -{ - "volume_type": { - "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "name": "vol-type-001", - "os-volume-type-access:is_public": true, - "qos_specs_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "description": "volume type 001", - "is_public": true, - "extra_specs": { - "capabilities": "gpu" - } - } -} -`) - }) -} - -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "POST") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - th.TestHeader(t, r, "Content-Type", "application/json") - th.TestHeader(t, r, "Accept", "application/json") - th.TestJSONRequest(t, r, ` -{ - "volume_type": { - "name": "test_type", - "os-volume-type-access:is_public": true, - "description": "test_type_desc", - "extra_specs": { - "capabilities": "gpu" - } - } -} - `) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - _, _ = fmt.Fprint(w, ` -{ - "volume_type": { - "name": "test_type", - "extra_specs": {}, - "is_public": true, - "os-volume-type-access:is_public": true, - "id": "6d0ff92a-0007-4780-9ece-acfe5876966a", - "description": "test_type_desc", - "extra_specs": { - "capabilities": "gpu" - } - } -} - `) - }) -} - -func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/types/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - w.WriteHeader(http.StatusAccepted) - }) -} - -func MockUpdateResponse(t *testing.T) { - th.Mux.HandleFunc("/types/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "PUT") - th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - w.WriteHeader(http.StatusOK) - _, _ = fmt.Fprint(w, ` -{ - "volume_type": { - "name": "vol-type-002", - "description": "volume type 0001", - "is_public": true, - "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22" - } -}`) - }) -} diff --git a/openstack/blockstorage/v3/volumetypes/testing/requests_test.go b/openstack/blockstorage/v3/volumetypes/testing/requests_test.go deleted file mode 100644 index 74cd2030d..000000000 --- a/openstack/blockstorage/v3/volumetypes/testing/requests_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package testing - -import ( - "testing" - - "github.com/opentelekomcloud/gophertelekomcloud/openstack/blockstorage/v3/volumetypes" - "github.com/opentelekomcloud/gophertelekomcloud/pagination" - th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" - "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" -) - -func TestListAll(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockListResponse(t) - pages := 0 - err := volumetypes.List(client.ServiceClient(), nil).EachPage(func(page pagination.Page) (bool, error) { - pages++ - actual, err := volumetypes.ExtractVolumeTypes(page) - if err != nil { - return false, err - } - expected := []volumetypes.VolumeType{ - { - ID: "6685584b-1eac-4da6-b5c3-555430cf68ff", - Name: "SSD", - ExtraSpecs: map[string]string{"volume_backend_name": "lvmdriver-1"}, - IsPublic: true, - Description: "", - QosSpecID: "", - PublicAccess: true, - }, { - ID: "8eb69a46-df97-4e41-9586-9a40a7533803", - Name: "SATA", - ExtraSpecs: map[string]string{"volume_backend_name": "lvmdriver-1"}, - IsPublic: true, - Description: "", - QosSpecID: "", - PublicAccess: true, - }, - } - th.CheckDeepEquals(t, expected, actual) - return true, nil - }) - th.AssertNoErr(t, err) - th.AssertEquals(t, pages, 1) -} - -func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockGetResponse(t) - - v, err := volumetypes.Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() - th.AssertNoErr(t, err) - - th.AssertEquals(t, v.Name, "vol-type-001") - th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertEquals(t, v.ExtraSpecs["capabilities"], "gpu") - th.AssertEquals(t, v.QosSpecID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertEquals(t, v.PublicAccess, true) -} - -func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockCreateResponse(t) - - var isPublic = true - - options := &volumetypes.CreateOpts{ - Name: "test_type", - IsPublic: &isPublic, - Description: "test_type_desc", - ExtraSpecs: map[string]string{"capabilities": "gpu"}, - } - - n, err := volumetypes.Create(client.ServiceClient(), options).Extract() - th.AssertNoErr(t, err) - - th.AssertEquals(t, n.Name, "test_type") - th.AssertEquals(t, n.Description, "test_type_desc") - th.AssertEquals(t, n.IsPublic, true) - th.AssertEquals(t, n.PublicAccess, true) - th.AssertEquals(t, n.ID, "6d0ff92a-0007-4780-9ece-acfe5876966a") - th.AssertEquals(t, n.ExtraSpecs["capabilities"], "gpu") -} - -func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockDeleteResponse(t) - - res := volumetypes.Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") - th.AssertNoErr(t, res.Err) -} - -func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockUpdateResponse(t) - - var isPublic = true - options := volumetypes.UpdateOpts{ - Name: "vol-type-002", - IsPublic: &isPublic, - } - - v, err := volumetypes.Update(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() - th.AssertNoErr(t, err) - th.CheckEquals(t, "vol-type-002", v.Name) - th.CheckEquals(t, true, v.IsPublic) -} diff --git a/openstack/blockstorage/v3/volumetypes/urls.go b/openstack/blockstorage/v3/volumetypes/urls.go deleted file mode 100644 index bfc7d5418..000000000 --- a/openstack/blockstorage/v3/volumetypes/urls.go +++ /dev/null @@ -1,23 +0,0 @@ -package volumetypes - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func listURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("types") -} - -func getURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL("types", id) -} - -func createURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("types") -} - -func deleteURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL("types", id) -} - -func updateURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL("types", id) -} diff --git a/openstack/compute/v2/images/requests.go b/openstack/compute/v2/images/requests.go index 89e1489b2..f107a7ae4 100644 --- a/openstack/compute/v2/images/requests.go +++ b/openstack/compute/v2/images/requests.go @@ -46,6 +46,7 @@ func (opts ListOpts) ToImageListQuery() (string, error) { } // ListDetail enumerates the available images. +// TODO: API discarded and not working. func ListDetail(client *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { url := listDetailURL(client) if opts != nil { diff --git a/openstack/evs/extensions/ListAvailabilityZone.go b/openstack/evs/extensions/ListAvailabilityZone.go new file mode 100644 index 000000000..dd08f8695 --- /dev/null +++ b/openstack/evs/extensions/ListAvailabilityZone.go @@ -0,0 +1,33 @@ +package extensions + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// ListAvailabilityZone will return the existing availability zones. +func ListAvailabilityZone(client *golangsdk.ServiceClient) ([]AvailabilityZone, error) { + // GET /v3/{project_id}/os-availability-zone + raw, err := client.Get(client.ServiceURL("os-availability-zone"), nil, nil) + if err != nil { + return nil, err + } + + var res []AvailabilityZone + err = extract.IntoSlicePtr(raw.Body, &res, "availabilityZoneInfo") + return res, err +} + +// ZoneState represents the current state of the availability zone. +type ZoneState struct { + // Returns true if the availability zone is available + Available bool `json:"available"` +} + +// AvailabilityZone contains all the information associated with an OpenStack +// AvailabilityZone. +type AvailabilityZone struct { + // The availability zone name + ZoneName string `json:"zoneName"` + ZoneState ZoneState `json:"zoneState"` +} diff --git a/openstack/evs/extensions/quotasets/GetDefaults.go b/openstack/evs/extensions/quotasets/GetDefaults.go new file mode 100644 index 000000000..9da7bcc46 --- /dev/null +++ b/openstack/evs/extensions/quotasets/GetDefaults.go @@ -0,0 +1,17 @@ +package quotasets + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +func GetDefaults(client *golangsdk.ServiceClient, projectID string) (*QuotaSet, error) { + raw, err := client.Get(client.ServiceURL("os-quota-sets", projectID, "defaults"), nil, nil) + if err != nil { + return nil, err + } + + var res QuotaSet + err = extract.IntoStructPtr(raw.Body, &res, "quota_set") + return &res, err +} diff --git a/openstack/evs/extensions/quotasets/get.go b/openstack/evs/extensions/quotasets/get.go index e95b03b7b..051b3dff0 100644 --- a/openstack/evs/extensions/quotasets/get.go +++ b/openstack/evs/extensions/quotasets/get.go @@ -6,20 +6,8 @@ import ( ) func Get(client *golangsdk.ServiceClient, projectID string) (*QuotaSet, error) { - raw, err := client.Get(client.ServiceURL("os-quota-sets", projectID), nil, nil) - if err != nil { - return nil, err - } - - var res struct { - QuotaSet QuotaSet `json:"quota_set"` - } - err = extract.Into(raw.Body, &res) - return &res.QuotaSet, err -} - -func GetDefaults(client *golangsdk.ServiceClient, projectID string) (*QuotaSet, error) { - raw, err := client.Get(client.ServiceURL("os-quota-sets", projectID, "defaults"), nil, nil) + // GET /v3/{project_id}/os-quota-sets/{target_project_id}?usage=True + raw, err := client.Get(client.ServiceURL("os-quota-sets", projectID)+"?usage=True", nil, nil) if err != nil { return nil, err } @@ -29,22 +17,58 @@ func GetDefaults(client *golangsdk.ServiceClient, projectID string) (*QuotaSet, return &res, err } +type Detail struct { + Reserved int `json:"reserved"` + Limit int `json:"limit"` + InUse int `json:"in_use"` +} + type QuotaSet struct { - // ID is project associated with this QuotaSet. - ID string `json:"id"` - // Volumes is the number of volumes that are allowed for each project. - Volumes int `json:"volumes"` - // Snapshots is the number of snapshots that are allowed for each project. - Snapshots int `json:"snapshots"` - // Gigabytes is the size (GB) of volumes and snapshots that are allowed for - // each project. - Gigabytes int `json:"gigabytes"` - // PerVolumeGigabytes is the size (GB) of volumes and snapshots that are - // allowed for each project and the specifed volume type. - PerVolumeGigabytes int `json:"per_volume_gigabytes"` - // Backups is the number of backups that are allowed for each project. - Backups int `json:"backups"` - // BackupGigabytes is the size (GB) of backups that are allowed for each - // project. - BackupGigabytes int `json:"backup_gigabytes"` + // Specifies the size (GB) reserved for common I/O disks. Sub-parameters include reserved (reserved quota), + // limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + GigabytesSAS Detail `json:"gigabytes_SAS"` + // Specifies the number of reserved common I/O disks. Sub-parameters include reserved (reserved quota), + // limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + VolumesSATA Detail `json:"volumes_SATA"` + // Specifies the total size (GB) of disks and snapshots allowed. Sub-parameters include reserved (reserved quota), + // limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + Gigabytes Detail `json:"gigabytes"` + // Specifies the backup size (GB). Sub-parameters include reserved (reserved quota), + // limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + BackupGigabytes Detail `json:"backup_gigabytes"` + // Specifies the number of snapshots reserved for high I/O disks. Sub-parameters include reserved (reserved quota), + // limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + SnapshotsSAS Detail `json:"snapshots_SAS"` + // Specifies the number of reserved ultra-high I/O disks. Sub-parameters include reserved (reserved quota), + // limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + VolumesSSD Detail `json:"volumes_SSD"` + // Specifies the number of snapshots. Sub-parameters include reserved (reserved quota), + // limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + Snapshots Detail `json:"snapshots"` + // Specifies the tenant ID. The tenant ID is actually the project ID. + Id string `json:"id"` + // Specifies the number of reserved high I/O disks. Sub-parameters include reserved (reserved quota), + // limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + VolumesSAS Detail `json:"volumes_SAS"` + // Specifies the number of snapshots reserved for ultra-high I/O disks. Sub-parameters include reserved + // (reserved quota), limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + SnapshotsSSD Detail `json:"snapshots_SSD"` + // Specifies the number of disks. Sub-parameters include reserved (reserved quota), + // limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + Volumes Detail `json:"volumes"` + // Specifies the backup size (GB). Sub-parameters include reserved (reserved quota), + // limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + GigabytesSATA Detail `json:"gigabytes_SATA"` + // Specifies the number of backups. Sub-parameters include reserved (reserved quota), + // limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + Backups Detail `json:"backups"` + // Specifies the size (GB) reserved for ultra-high I/O disks. Sub-parameters include reserved (reserved quota), + // limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + GigabytesSSD Detail `json:"gigabytes_SSD"` + // Specifies the number of snapshots reserved for common I/O disks. Sub-parameters include reserved + // (reserved quota), limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + SnapshotsSATA Detail `json:"snapshots_SATA"` + // Specifies the capacity quota of each EVS disk. Sub-parameters include reserved (reserved quota), + // limit (maximum quota), and in_use (used quota), and are made up of key-value pairs. + PerVolumeGigabytes Detail `json:"per_volume_gigabytes"` } diff --git a/openstack/evs/extensions/quotasets/testing/fixtures.go b/openstack/evs/extensions/quotasets/testing/fixtures.go deleted file mode 100644 index e7185bb8b..000000000 --- a/openstack/evs/extensions/quotasets/testing/fixtures.go +++ /dev/null @@ -1,163 +0,0 @@ -package testing - -import ( - "fmt" - "net/http" - "testing" - - "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/extensions/quotasets" - - "github.com/opentelekomcloud/gophertelekomcloud" - th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" - "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" -) - -const FirstTenantID = "555544443333222211110000ffffeeee" - -var getExpectedJSONBody = ` -{ - "quota_set" : { - "volumes" : 8, - "snapshots" : 9, - "gigabytes" : 10, - "per_volume_gigabytes" : 11, - "backups" : 12, - "backup_gigabytes" : 13 - } -}` - -var getExpectedQuotaSet = quotasets.QuotaSet{ - Volumes: 8, - Snapshots: 9, - Gigabytes: 10, - PerVolumeGigabytes: 11, - Backups: 12, - BackupGigabytes: 13, -} - -var getUsageExpectedJSONBody = ` -{ - "quota_set": { - "id": "555544443333222211110000ffffeeee", - "volumes": { - "in_use": 15, - "limit": 16, - "reserved": 17 - }, - "snapshots": { - "in_use": 18, - "limit": 19, - "reserved": 20 - }, - "gigabytes": { - "in_use": 21, - "limit": 22, - "reserved": 23 - }, - "per_volume_gigabytes": { - "in_use": 24, - "limit": 25, - "reserved": 26 - }, - "backups": { - "in_use": 27, - "limit": 28, - "reserved": 29 - }, - "backup_gigabytes": { - "in_use": 30, - "limit": 31, - "reserved": 32 - } - } -} -` - -var getUsageExpectedQuotaSet = quotasets.QuotaUsageSet{ - ID: FirstTenantID, - Volumes: quotasets.QuotaUsage{InUse: 15, Limit: 16, Reserved: 17}, - Snapshots: quotasets.QuotaUsage{InUse: 18, Limit: 19, Reserved: 20}, - Gigabytes: quotasets.QuotaUsage{InUse: 21, Limit: 22, Reserved: 23}, - PerVolumeGigabytes: quotasets.QuotaUsage{InUse: 24, Limit: 25, Reserved: 26}, - Backups: quotasets.QuotaUsage{InUse: 27, Limit: 28, Reserved: 29}, - BackupGigabytes: quotasets.QuotaUsage{InUse: 30, Limit: 31, Reserved: 32}, -} - -var fullUpdateExpectedJSONBody = ` -{ - "quota_set": { - "volumes": 8, - "snapshots": 9, - "gigabytes": 10, - "per_volume_gigabytes": 11, - "backups": 12, - "backup_gigabytes": 13 - } -}` - -var fullUpdateOpts = quotasets.UpdateOpts{ - Volumes: golangsdk.IntToPointer(8), - Snapshots: golangsdk.IntToPointer(9), - Gigabytes: golangsdk.IntToPointer(10), - PerVolumeGigabytes: golangsdk.IntToPointer(11), - Backups: golangsdk.IntToPointer(12), - BackupGigabytes: golangsdk.IntToPointer(13), -} - -var fullUpdateExpectedQuotaSet = quotasets.QuotaSet{ - Volumes: 8, - Snapshots: 9, - Gigabytes: 10, - PerVolumeGigabytes: 11, - Backups: 12, - BackupGigabytes: 13, -} - -var partialUpdateExpectedJSONBody = ` -{ - "quota_set": { - "volumes": 200, - "snapshots": 0, - "gigabytes": 0, - "per_volume_gigabytes": 0, - "backups": 0, - "backup_gigabytes": 0 - } -}` - -var partialUpdateOpts = quotasets.UpdateOpts{ - Volumes: golangsdk.IntToPointer(200), - Snapshots: golangsdk.IntToPointer(0), - Gigabytes: golangsdk.IntToPointer(0), - PerVolumeGigabytes: golangsdk.IntToPointer(0), - Backups: golangsdk.IntToPointer(0), - BackupGigabytes: golangsdk.IntToPointer(0), -} - -var partiualUpdateExpectedQuotaSet = quotasets.QuotaSet{Volumes: 200} - -// HandleSuccessfulRequest configures the test server to respond to an HTTP request. -func HandleSuccessfulRequest(t *testing.T, httpMethod, uriPath, jsonOutput string, uriQueryParams map[string]string) { - - th.Mux.HandleFunc(uriPath, func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, httpMethod) - th.TestHeader(t, r, "X-Auth-Token", client.TokenID) - w.Header().Add("Content-Type", "application/json") - - if uriQueryParams != nil { - th.TestFormValues(t, r, uriQueryParams) - } - - _, _ = fmt.Fprint(w, jsonOutput) - }) -} - -// HandleDeleteSuccessfully tests quotaset deletion. -func HandleDeleteSuccessfully(t *testing.T) { - th.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "DELETE") - th.TestHeader(t, r, "X-Auth-Token", client.TokenID) - - w.WriteHeader(http.StatusOK) - }) -} diff --git a/openstack/evs/extensions/quotasets/testing/requests_test.go b/openstack/evs/extensions/quotasets/testing/requests_test.go deleted file mode 100644 index ccb67f398..000000000 --- a/openstack/evs/extensions/quotasets/testing/requests_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package testing - -import ( - "testing" - - "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/extensions/quotasets" - - th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" - "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" -) - -func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - uriQueryParms := map[string]string{} - HandleSuccessfulRequest(t, "GET", "/os-quota-sets/"+FirstTenantID, getExpectedJSONBody, uriQueryParms) - actual, err := quotasets.Get(client.ServiceClient(), FirstTenantID) - th.AssertNoErr(t, err) - th.CheckDeepEquals(t, &getExpectedQuotaSet, actual) -} - -func TestGetUsage(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - uriQueryParms := map[string]string{"usage": "true"} - HandleSuccessfulRequest(t, "GET", "/os-quota-sets/"+FirstTenantID, getUsageExpectedJSONBody, uriQueryParms) - actual, err := quotasets.GetUsage(client.ServiceClient(), FirstTenantID) - th.AssertNoErr(t, err) - th.CheckDeepEquals(t, &getUsageExpectedQuotaSet, actual) -} - -func TestFullUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - uriQueryParms := map[string]string{} - HandleSuccessfulRequest(t, "PUT", "/os-quota-sets/"+FirstTenantID, fullUpdateExpectedJSONBody, uriQueryParms) - actual, err := quotasets.Update(client.ServiceClient(), FirstTenantID, fullUpdateOpts) - th.AssertNoErr(t, err) - th.CheckDeepEquals(t, &fullUpdateExpectedQuotaSet, actual) -} - -func TestPartialUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - uriQueryParms := map[string]string{} - HandleSuccessfulRequest(t, "PUT", "/os-quota-sets/"+FirstTenantID, partialUpdateExpectedJSONBody, uriQueryParms) - actual, err := quotasets.Update(client.ServiceClient(), FirstTenantID, partialUpdateOpts) - th.AssertNoErr(t, err) - th.CheckDeepEquals(t, &partiualUpdateExpectedQuotaSet, actual) -} - -func TestDelete(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - HandleDeleteSuccessfully(t) - - err := quotasets.Delete(client.ServiceClient(), FirstTenantID) - th.AssertNoErr(t, err) -} diff --git a/openstack/evs/extensions/quotasets/update.go b/openstack/evs/extensions/quotasets/update.go index aef568063..a774bf43a 100644 --- a/openstack/evs/extensions/quotasets/update.go +++ b/openstack/evs/extensions/quotasets/update.go @@ -2,11 +2,12 @@ package quotasets import ( "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" ) func Update(client *golangsdk.ServiceClient, projectID string, opts UpdateOpts) (*QuotaSet, error) { - b, err := golangsdk.BuildRequestBody(opts, "quota_set") + b, err := build.RequestBody(opts, "quota_set") if err != nil { return nil, err } diff --git a/openstack/evs/extensions/schedulerhints/requests.go b/openstack/evs/extensions/schedulerhints/requests.go new file mode 100644 index 000000000..c4d070881 --- /dev/null +++ b/openstack/evs/extensions/schedulerhints/requests.go @@ -0,0 +1,109 @@ +package schedulerhints + +import ( + "regexp" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v3/volumes" +) + +// SchedulerHints represents a set of scheduling hints that are passed to the +// OpenStack scheduler. +type SchedulerHints struct { + // DifferentHost will place the volume on a different back-end that does not + // host the given volumes. + DifferentHost []string + // SameHost will place the volume on a back-end that hosts the given volumes. + SameHost []string + // LocalToInstance will place volume on same host on a given instance + LocalToInstance string + // Query is a conditional statement that results in back-ends able to + // host the volume. + Query string + // AdditionalProperies are arbitrary key/values that are not validated by nova. + AdditionalProperties map[string]interface{} +} + +// ToVolumeSchedulerHintsCreateMap builds the scheduler hints into a serializable format. +func (opts SchedulerHints) ToVolumeSchedulerHintsCreateMap() (map[string]interface{}, error) { + sh := make(map[string]interface{}) + + uuidRegex, _ := regexp.Compile("^[a-z0-9]{8}-[a-z0-9]{4}-[1-5][a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{12}$") + + if len(opts.DifferentHost) > 0 { + for _, diffHost := range opts.DifferentHost { + if !uuidRegex.MatchString(diffHost) { + err := golangsdk.ErrInvalidInput{} + err.Argument = "schedulerhints.SchedulerHints.DifferentHost" + err.Value = opts.DifferentHost + err.Info = "The hosts must be in UUID format." + return nil, err + } + } + sh["different_host"] = opts.DifferentHost + } + + if len(opts.SameHost) > 0 { + for _, sameHost := range opts.SameHost { + if !uuidRegex.MatchString(sameHost) { + err := golangsdk.ErrInvalidInput{} + err.Argument = "schedulerhints.SchedulerHints.SameHost" + err.Value = opts.SameHost + err.Info = "The hosts must be in UUID format." + return nil, err + } + } + sh["same_host"] = opts.SameHost + } + + if opts.LocalToInstance != "" { + if !uuidRegex.MatchString(opts.LocalToInstance) { + err := golangsdk.ErrInvalidInput{} + err.Argument = "schedulerhints.SchedulerHints.LocalToInstance" + err.Value = opts.LocalToInstance + err.Info = "The instance must be in UUID format." + return nil, err + } + sh["local_to_instance"] = opts.LocalToInstance + } + + if opts.Query != "" { + sh["query"] = opts.Query + } + + if opts.AdditionalProperties != nil { + for k, v := range opts.AdditionalProperties { + sh[k] = v + } + } + + return sh, nil +} + +// CreateOptsExt adds a SchedulerHints option to the base CreateOpts. +type CreateOptsExt struct { + volumes.CreateOptsBuilder + // SchedulerHints provides a set of hints to the scheduler. + SchedulerHints SchedulerHints +} + +// ToVolumeCreateMap adds the SchedulerHints option to the base volume creation options. +func (opts CreateOptsExt) ToVolumeCreateMap() (map[string]interface{}, error) { + base, err := opts.CreateOptsBuilder.ToVolumeCreateMap() + if err != nil { + return nil, err + } + + schedulerHints, err := opts.SchedulerHints.ToVolumeSchedulerHintsCreateMap() + if err != nil { + return nil, err + } + + if len(schedulerHints) == 0 { + return base, nil + } + + base["OS-SCH-HNT:scheduler_hints"] = schedulerHints + + return base, nil +} diff --git a/openstack/evs/extensions/volumeactions/SetBootable.go b/openstack/evs/extensions/volumeactions/SetBootable.go new file mode 100644 index 000000000..7f20245b3 --- /dev/null +++ b/openstack/evs/extensions/volumeactions/SetBootable.go @@ -0,0 +1,25 @@ +package volumeactions + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// BootableOpts contains options for setting bootable status to a volume. +type BootableOpts struct { + // Enables or disables the bootable attribute. You can boot an instance from a bootable volume. + Bootable bool `json:"bootable"` +} + +// SetBootable will set bootable status on a volume based on the values in BootableOpts +func SetBootable(client *golangsdk.ServiceClient, id string, opts BootableOpts) (err error) { + b, err := build.RequestBody(opts, "os-set_bootable") + if err != nil { + return + } + + _, err = client.Post(client.ServiceURL("volumes", id, "action"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/openstack/evs/extensions/volumeactions/SetReadonly.go b/openstack/evs/extensions/volumeactions/SetReadonly.go new file mode 100644 index 000000000..5eb177398 --- /dev/null +++ b/openstack/evs/extensions/volumeactions/SetReadonly.go @@ -0,0 +1,20 @@ +package volumeactions + +import ( + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +type ReadonlyOpts struct { + Readonly bool `json:"readonly"` +} + +func SetReadonly(client *golangsdk.ServiceClient, id string, opts ReadonlyOpts) (err error) { + b, err := build.RequestBody(opts, "os-update_readonly_flag") + if err != nil { + return + } + + _, err = client.Post(client.ServiceURL("volumes", id, "action"), b, nil, nil) + return +} diff --git a/openstack/evs/extensions/volumeactions/attach.go b/openstack/evs/extensions/volumeactions/attach.go index 0d176c4e3..8b82a4078 100644 --- a/openstack/evs/extensions/volumeactions/attach.go +++ b/openstack/evs/extensions/volumeactions/attach.go @@ -1,6 +1,9 @@ package volumeactions -import "github.com/opentelekomcloud/gophertelekomcloud" +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) type AttachMode string @@ -21,7 +24,7 @@ type AttachOpts struct { } func Attach(client *golangsdk.ServiceClient, id string, opts AttachOpts) (err error) { - b, err := golangsdk.BuildRequestBody(opts, "os-attach") + b, err := build.RequestBody(opts, "os-attach") if err != nil { return } diff --git a/openstack/evs/extensions/volumeactions/detach.go b/openstack/evs/extensions/volumeactions/detach.go index cd54d2780..c243328ea 100644 --- a/openstack/evs/extensions/volumeactions/detach.go +++ b/openstack/evs/extensions/volumeactions/detach.go @@ -1,6 +1,9 @@ package volumeactions -import "github.com/opentelekomcloud/gophertelekomcloud" +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) type DetachOpts struct { // AttachmentID is the ID of the attachment between a volume and instance. @@ -8,7 +11,7 @@ type DetachOpts struct { } func Detach(client *golangsdk.ServiceClient, id string, opts DetachOpts) (err error) { - b, err := golangsdk.BuildRequestBody(opts, "os-detach") + b, err := build.RequestBody(opts, "os-detach") if err != nil { return } diff --git a/openstack/evs/extensions/volumeactions/extend_size.go b/openstack/evs/extensions/volumeactions/extend_size.go index 28a24f75b..29db2468d 100644 --- a/openstack/evs/extensions/volumeactions/extend_size.go +++ b/openstack/evs/extensions/volumeactions/extend_size.go @@ -1,14 +1,18 @@ package volumeactions -import "github.com/opentelekomcloud/gophertelekomcloud" +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) type ExtendSizeOpts struct { - // NewSize is the new size of the volume, in GB. + // Specifies the size of the disk after capacity expansion, in GB. + // The new disk size ranges from the original disk size to the maximum size (32768 for a data disk and 1024 for a system disk). NewSize int `json:"new_size" required:"true"` } func ExtendSize(client *golangsdk.ServiceClient, id string, opts ExtendSizeOpts) (err error) { - b, err := golangsdk.BuildRequestBody(opts, "os-extend") + b, err := build.RequestBody(opts, "os-extend") if err != nil { return } diff --git a/openstack/evs/extensions/volumeactions/terminate_connection.go b/openstack/evs/extensions/volumeactions/terminate_connection.go index 781943259..276af084e 100644 --- a/openstack/evs/extensions/volumeactions/terminate_connection.go +++ b/openstack/evs/extensions/volumeactions/terminate_connection.go @@ -1,6 +1,9 @@ package volumeactions -import "github.com/opentelekomcloud/gophertelekomcloud" +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) type TerminateConnectionOpts struct { IP string `json:"ip,omitempty"` @@ -14,7 +17,7 @@ type TerminateConnectionOpts struct { } func (opts TerminateConnectionOpts) ToVolumeTerminateConnectionMap() (map[string]interface{}, error) { - b, err := golangsdk.BuildRequestBody(opts, "connector") + b, err := build.RequestBody(opts, "connector") return map[string]interface{}{"os-terminate_connection": b}, err } diff --git a/openstack/evs/extensions/volumeactions/upload_image.go b/openstack/evs/extensions/volumeactions/upload_image.go index 5b955f544..99d1000f7 100644 --- a/openstack/evs/extensions/volumeactions/upload_image.go +++ b/openstack/evs/extensions/volumeactions/upload_image.go @@ -5,22 +5,36 @@ import ( "time" "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" ) type UploadImageOpts struct { - // Container format, may be bare, ofv, ova, etc. + // Specifies the container type of the exported image. + // The value can be ami, ari, aki, ovf, or bare. The default value is bare. ContainerFormat string `json:"container_format,omitempty"` - // Disk format, may be raw, qcow2, vhd, vdi, vmdk, etc. + // Specifies the format of the exported image. + // The value can be vhd, zvhd, zvhd2, raw, or qcow2. The default value is zvhd2. DiskFormat string `json:"disk_format,omitempty"` - // The name of image that will be stored in glance. + // Specifies the name of the exported image. + // The name cannot start or end with space. + // The name contains 1 to 128 characters. + // The name contains the following characters: uppercase letters, lowercase letters, digits, + // and special characters, such as hyphens (-), periods (.), underscores (_), and spaces. ImageName string `json:"image_name,omitempty"` - // Force image creation, usable if volume attached to instance. + // Specifies whether to forcibly export the image. The default value is false. + // If force is set to false and the disk is in the in-use state, the image cannot be forcibly exported. + // If force is set to true and the disk is in the in-use state, the image can be forcibly exported. Force bool `json:"force,omitempty"` + // Specifies the OS type of the exported image. Currently, only windows and linux are supported. The default value is linux. + // There are two underscores (_) in front of os and one underscore (_) after os. + // This parameter setting takes effect only when the __os_type field is not included in volume_image_metadata and the disk status is available. + // If this parameter is not specified, default value linux is used as the OS type of the image. + OSType string `json:"__os_type,omitempty"` } func UploadImage(client *golangsdk.ServiceClient, id string, opts UploadImageOpts) (*VolumeImage, error) { - b, err := golangsdk.BuildRequestBody(opts, "os-volume_upload_image") + b, err := build.RequestBody(opts, "os-volume_upload_image") if err != nil { return nil, err } @@ -32,29 +46,29 @@ func UploadImage(client *golangsdk.ServiceClient, id string, opts UploadImageOpt return nil, err } - var res struct { - VolumeImage VolumeImage `json:"os-volume_upload_image"` - } - err = extract.Into(raw.Body, &res) - return &res.VolumeImage, err + var res VolumeImage + err = extract.IntoStructPtr(raw.Body, &res, "os-volume_upload_image") + return &res, err } type VolumeImage struct { // The ID of a volume an image is created from. VolumeID string `json:"id"` - // Container format, may be bare, ofv, ova, etc. + // Specifies the container type of the exported image. + // The value can be ami, ari, aki, ovf, or bare. The default value is bare. ContainerFormat string `json:"container_format"` - // Disk format, may be raw, qcow2, vhd, vdi, vmdk, etc. + // Specifies the format of the exported image. + // The value can be vhd, zvhd, zvhd2, raw, or qcow2. The default value is vhd. DiskFormat string `json:"disk_format"` // Human-readable description for the volume. Description string `json:"display_description"` - // The ID of the created image. + // Specifies the ID of the exported image. ImageID string `json:"image_id"` - // Human-readable display name for the image. + // Specifies the name of the exported image. ImageName string `json:"image_name"` // Size of the volume in GB. Size int `json:"size"` - // Current status of the volume. + // Specifies the disk status after the image is exported. The correct value is uploading. Status string `json:"status"` // The date when this volume was last updated. UpdatedAt time.Time `json:"-"` @@ -88,6 +102,7 @@ type ImageVolumeType struct { // Flag for public access. IsPublic bool `json:"is_public"` // Extra specifications for volume type. + // volumetypes.ExtraSpecs ExtraSpecs map[string]interface{} `json:"extra_specs"` // ID of quality of service specs. QosSpecsID string `json:"qos_specs_id"` diff --git a/openstack/evs/extensions/volumehost/results.go b/openstack/evs/extensions/volumehost/results.go new file mode 100644 index 000000000..5434f3772 --- /dev/null +++ b/openstack/evs/extensions/volumehost/results.go @@ -0,0 +1,7 @@ +package volumehost + +// VolumeHostExt is an extension to the base Volume object +type VolumeHostExt struct { + // Host is the identifier of the host holding the volume. + Host string `json:"os-vol-host-attr:host"` +} diff --git a/openstack/evs/extensions/volumetenants.go b/openstack/evs/extensions/volumetenants/results.go similarity index 64% rename from openstack/evs/extensions/volumetenants.go rename to openstack/evs/extensions/volumetenants/results.go index 50fdb32b4..821e523b7 100644 --- a/openstack/evs/extensions/volumetenants.go +++ b/openstack/evs/extensions/volumetenants/results.go @@ -1,5 +1,6 @@ -package extensions +package volumetenants +// VolumeTenantExt is an extension to the base Volume object type VolumeTenantExt struct { // TenantID is the id of the project that owns the volume. TenantID string `json:"os-vol-tenant-attr:tenant_id"` diff --git a/openstack/evs/extensions/volumetransfers/Accept.go b/openstack/evs/extensions/volumetransfers/Accept.go new file mode 100644 index 000000000..a2b734dbb --- /dev/null +++ b/openstack/evs/extensions/volumetransfers/Accept.go @@ -0,0 +1,27 @@ +package volumetransfers + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// AcceptOpts contains options for a Volume transfer accept reqeust. +type AcceptOpts struct { + // Specifies the authentication key of the disk transfer. + // Specifies the authentication key returned during the disk transfer creation. + AuthKey string `json:"auth_key" required:"true"` +} + +// Accept will accept a volume transfer request based on the values in AcceptOpts. +func Accept(client *golangsdk.ServiceClient, id string, opts AcceptOpts) (*Transfer, error) { + b, err := build.RequestBody(opts, "accept") + if err != nil { + return nil, err + } + + // POST /v3/{project_id}/os-volume-transfer/{transfer_id}/accept + raw, err := client.Post(client.ServiceURL("os-volume-transfer", id, "accept"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{202}, + }) + return extra(err, raw) +} diff --git a/openstack/evs/extensions/volumetransfers/Create.go b/openstack/evs/extensions/volumetransfers/Create.go new file mode 100644 index 000000000..8f0f4b2e4 --- /dev/null +++ b/openstack/evs/extensions/volumetransfers/Create.go @@ -0,0 +1,28 @@ +package volumetransfers + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// CreateOpts contains options for a Volume transfer. +type CreateOpts struct { + // The ID of the volume to transfer. + VolumeID string `json:"volume_id" required:"true"` + // Specifies the disk transfer name. The value can contain a maximum of 255 bytes. + Name string `json:"name,omitempty"` +} + +// Create will create a volume tranfer request based on the values in CreateOpts. +func Create(client *golangsdk.ServiceClient, opts CreateOpts) (*Transfer, error) { + b, err := build.RequestBody(opts, "transfer") + if err != nil { + return nil, err + } + + // POST /v3/{project_id}/os-volume-transfer + raw, err := client.Post(client.ServiceURL("os-volume-transfer"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{202}, + }) + return extra(err, raw) +} diff --git a/openstack/evs/extensions/volumetransfers/Delete.go b/openstack/evs/extensions/volumetransfers/Delete.go new file mode 100644 index 000000000..ebd9769d6 --- /dev/null +++ b/openstack/evs/extensions/volumetransfers/Delete.go @@ -0,0 +1,10 @@ +package volumetransfers + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Delete deletes a volume transfer. +func Delete(client *golangsdk.ServiceClient, id string) (err error) { + // DELETE /v3/{project_id}/os-volume-transfer/{transfer_id} + _, err = client.Delete(client.ServiceURL("os-volume-transfer", id), nil) + return +} diff --git a/openstack/evs/extensions/volumetransfers/Get.go b/openstack/evs/extensions/volumetransfers/Get.go new file mode 100644 index 000000000..ff418a014 --- /dev/null +++ b/openstack/evs/extensions/volumetransfers/Get.go @@ -0,0 +1,13 @@ +package volumetransfers + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" +) + +// Get retrieves the Transfer with the provided ID. To extract the Transfer object +// from the response, call the Extract method on the GetResult. +func Get(client *golangsdk.ServiceClient, id string) (*Transfer, error) { + // GET /v3/{project_id}/os-volume-transfer/{transfer_id} + raw, err := client.Get(client.ServiceURL("os-volume-transfer", id), nil, nil) + return extra(err, raw) +} diff --git a/openstack/evs/extensions/volumetransfers/List.go b/openstack/evs/extensions/volumetransfers/List.go new file mode 100644 index 000000000..f3a4b1681 --- /dev/null +++ b/openstack/evs/extensions/volumetransfers/List.go @@ -0,0 +1,69 @@ +package volumetransfers + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" + "github.com/opentelekomcloud/gophertelekomcloud/pagination" +) + +// ListOpts holds options for listing Transfers. It is passed to the transfers.List function. +type ListOpts struct { + // AllTenants will retrieve transfers of all tenants/projects. + AllTenants bool `q:"all_tenants"` + // Comma-separated list of sort keys and optional sort directions in the + // form of [:]. + Sort string `q:"sort"` + // Requests a page size of items. + Limit int `q:"limit"` + // Used in conjunction with limit to return a slice of items. + Offset int `q:"offset"` + // The ID of the last-seen item. + Marker string `q:"marker"` +} + +// ToTransferListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToTransferListQuery() (string, error) { + q, err := golangsdk.BuildQueryString(opts) + return q.String(), err +} + +// List returns Transfers optionally limited by the conditions provided in ListOpts. +func List(client *golangsdk.ServiceClient, opts ListOpts) pagination.Pager { + q, err := golangsdk.BuildQueryString(opts) + if err != nil { + return pagination.Pager{Err: err} + } + + // GET /v3/{project_id}/os-volume-transfer + return pagination.NewPager(client, client.ServiceURL("os-volume-transfer")+q.String(), func(r pagination.PageResult) pagination.Page { + return TransferPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// TransferPage is a pagination.pager that is returned from a call to the List function. +type TransferPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a ListResult contains no Transfers. +func (r TransferPage) IsEmpty() (bool, error) { + transfers, err := ExtractTransfers(r) + return len(transfers) == 0, err +} + +func (r TransferPage) NextPageURL() (string, error) { + var res []golangsdk.Link + err := extract.IntoSlicePtr(r.BodyReader(), &res, "transfers_links") + if err != nil { + return "", err + } + + return golangsdk.ExtractNextURL(res) +} + +// ExtractTransfers extracts and returns Transfers. It is used while iterating over a transfers.List call. +func ExtractTransfers(r pagination.Page) ([]Transfer, error) { + var res []Transfer + err := extract.IntoSlicePtr(r.(TransferPage).Result.BodyReader(), &res, "transfers") + return res, err +} diff --git a/openstack/evs/extensions/volumetransfers/results.go b/openstack/evs/extensions/volumetransfers/results.go new file mode 100644 index 000000000..5df5282c4 --- /dev/null +++ b/openstack/evs/extensions/volumetransfers/results.go @@ -0,0 +1,55 @@ +package volumetransfers + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// Transfer represents a Volume Transfer record +type Transfer struct { + // Specifies the disk transfer ID. + ID string `json:"id"` + // Specifies the authentication key of the disk transfer. + AuthKey string `json:"auth_key"` + // Specifies the disk transfer name. + Name string `json:"name"` + // Specifies the disk ID. + VolumeID string `json:"volume_id"` + // Specifies the time when the disk transfer was created. + // Time format: UTC YYYY-MM-DDTHH:MM:SS.XXXXXX + CreatedAt time.Time `json:"-"` + // Specifies the links of the disk transfer. + Links []map[string]string `json:"links"` +} + +// UnmarshalJSON is our unmarshalling helper +func (r *Transfer) UnmarshalJSON(b []byte) error { + type tmp Transfer + var s struct { + tmp + CreatedAt golangsdk.JSONRFC3339MilliNoZ `json:"created_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Transfer(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + + return err +} + +func extra(err error, raw *http.Response) (*Transfer, error) { + if err != nil { + return nil, err + } + + var res Transfer + err = extract.IntoStructPtr(raw.Body, &res, "transfer") + return &res, err +} diff --git a/openstack/evs/v1/apiversions/get.go b/openstack/evs/v1/apiversions/get.go deleted file mode 100644 index 519e0194e..000000000 --- a/openstack/evs/v1/apiversions/get.go +++ /dev/null @@ -1,19 +0,0 @@ -package apiversions - -import ( - "strings" - - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" -) - -func Get(client *golangsdk.ServiceClient, v string) (*APIVersion, error) { - raw, err := client.Get(client.ServiceURL(strings.TrimRight(v, "/")+"/"), nil, nil) - if err != nil { - return nil, err - } - - var res APIVersion - err = extract.IntoStructPtr(raw.Body, &res, "version") - return &res, err -} diff --git a/openstack/evs/v1/apiversions/list.go b/openstack/evs/v1/apiversions/list.go deleted file mode 100644 index 67ae617cd..000000000 --- a/openstack/evs/v1/apiversions/list.go +++ /dev/null @@ -1,26 +0,0 @@ -package apiversions - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" - "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" -) - -func List(client *golangsdk.ServiceClient) ([]APIVersion, error) { - raw, err := client.Get(client.ServiceURL(""), nil, nil) - if err != nil { - return nil, err - } - - var res []APIVersion - err = extract.IntoSlicePtr(raw.Body, &res, "versions") - return res, err -} - -type APIVersion struct { - // unique identifier - ID string `json:"id"` - // current status e.g. SUPPORTED - Status string `json:"status"` - // date last updated e.g. 2014-06-28T12:20:21Z - Updated string `json:"updated"` -} diff --git a/openstack/evs/v1/apiversions/testing/fixtures.go b/openstack/evs/v1/apiversions/testing/fixtures.go deleted file mode 100644 index 8264796db..000000000 --- a/openstack/evs/v1/apiversions/testing/fixtures.go +++ /dev/null @@ -1,91 +0,0 @@ -package testing - -import ( - "fmt" - "net/http" - "testing" - - th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" - "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" -) - -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", client.TokenID) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - _, _ = fmt.Fprint(w, `{ - "versions": [ - { - "status": "CURRENT", - "updated": "2012-01-04T11:33:21Z", - "id": "v1.0", - "links": [ - { - "href": "http://23.253.228.211:8776/v1/", - "rel": "self" - } - ] - }, - { - "status": "CURRENT", - "updated": "2012-11-21T11:33:21Z", - "id": "v2.0", - "links": [ - { - "href": "http://23.253.228.211:8776/v2/", - "rel": "self" - } - ] - } - ] - }`) - }) -} - -func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/v1/", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "GET") - th.TestHeader(t, r, "X-Auth-Token", client.TokenID) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - _, _ = fmt.Fprint(w, `{ - "version": { - "status": "CURRENT", - "updated": "2012-01-04T11:33:21Z", - "media-types": [ - { - "base": "application/xml", - "type": "application/vnd.openstack.volume+xml;version=1" - }, - { - "base": "application/json", - "type": "application/vnd.openstack.volume+json;version=1" - } - ], - "id": "v1.0", - "links": [ - { - "href": "http://23.253.228.211:8776/v1/", - "rel": "self" - }, - { - "href": "http://jorgew.github.com/block-storage-api/content/os-block-storage-1.0.pdf", - "type": "application/pdf", - "rel": "describedby" - }, - { - "href": "http://docs.rackspacecloud.com/servers/api/v1.1/application.wadl", - "type": "application/vnd.sun.wadl+xml", - "rel": "describedby" - } - ] - } - }`) - }) -} diff --git a/openstack/evs/v1/apiversions/testing/requests_test.go b/openstack/evs/v1/apiversions/testing/requests_test.go deleted file mode 100644 index f8d15d4cd..000000000 --- a/openstack/evs/v1/apiversions/testing/requests_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package testing - -import ( - "testing" - - "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v1/apiversions" - th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" - "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" -) - -func TestListVersions(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockListResponse(t) - - actual, err := apiversions.List(client.ServiceClient()) - th.AssertNoErr(t, err) - - expected := []apiversions.APIVersion{ - { - ID: "v1.0", - Status: "CURRENT", - Updated: "2012-01-04T11:33:21Z", - }, - { - ID: "v2.0", - Status: "CURRENT", - Updated: "2012-11-21T11:33:21Z", - }, - } - - th.AssertDeepEquals(t, expected, actual) -} - -func TestAPIInfo(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockGetResponse(t) - - actual, err := apiversions.Get(client.ServiceClient(), "v1") - th.AssertNoErr(t, err) - - expected := apiversions.APIVersion{ - ID: "v1.0", - Status: "CURRENT", - Updated: "2012-01-04T11:33:21Z", - } - - th.AssertEquals(t, actual.ID, expected.ID) - th.AssertEquals(t, actual.Status, expected.Status) - th.AssertEquals(t, actual.Updated, expected.Updated) -} diff --git a/openstack/evs/v2/snapshots/requests.go b/openstack/evs/v2/snapshots/requests.go index cb8432094..7f2ce2d7d 100644 --- a/openstack/evs/v2/snapshots/requests.go +++ b/openstack/evs/v2/snapshots/requests.go @@ -5,14 +5,9 @@ import ( "github.com/opentelekomcloud/gophertelekomcloud/pagination" ) -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToSnapshotCreateMap() (map[string]interface{}, error) -} - -// CreateOpts contains options for creating a Snapshot. -// This object is passed to the snapshots.Create function. +// CreateOpts contains options for creating a Snapshot. This object is passed to +// the snapshots.Create function. For more information about these parameters, +// see the Snapshot object. type CreateOpts struct { VolumeID string `json:"volume_id" required:"true"` Force bool `json:"force,omitempty"` @@ -21,71 +16,32 @@ type CreateOpts struct { Metadata map[string]string `json:"metadata,omitempty"` } -// ToSnapshotCreateMap assembles a request body based on the contents of a -// CreateOpts. -func (opts CreateOpts) ToSnapshotCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "snapshot") -} - // Create will create a new Snapshot based on the values in CreateOpts. To // extract the Snapshot object from the response, call the Extract method on the // CreateResult. -func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { - b, err := opts.ToSnapshotCreateMap() +func Create(client *golangsdk.ServiceClient, opts CreateOpts) (r CreateResult) { + b, err := golangsdk.BuildRequestBody(opts, "snapshot") if err != nil { r.Err = err return } - _, r.Err = client.Post(createURL(client), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{202}, - }) - return -} - -// UpdateOptsBuilder allows extensions to add additional parameters to -// the Update request. -type UpdateOptsBuilder interface { - ToSnapshotUpdateMap() (map[string]interface{}, error) -} - -// UpdateOpts contain options for updating an existing Snapshot. -// This object is passed to the snapshots.Update function. -type UpdateOpts struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` -} - -// ToSnapshotUpdateMap assembles a request body based on the contents of -// an UpdateOpts. -func (opts UpdateOpts) ToSnapshotUpdateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "snapshot") -} -// Update will update the Snapshot with provided information. To -// extract the updated Snapshot from the response, call the ExtractMetadata -// method on the UpdateResult. -func Update(client *golangsdk.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { - b, err := opts.ToSnapshotUpdateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, + _, r.Err = client.Post(client.ServiceURL("snapshots"), b, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{202}, }) return } // Delete will delete the existing Snapshot with the provided ID. func Delete(client *golangsdk.ServiceClient, id string) (r DeleteResult) { - _, r.Err = client.Delete(deleteURL(client, id), nil) + _, r.Err = client.Delete(client.ServiceURL("snapshots", id), nil) return } // Get retrieves the Snapshot with the provided ID. To extract the Snapshot // object from the response, call the Extract method on the GetResult. func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + _, r.Err = client.Get(client.ServiceURL("snapshots", id), &r.Body, nil) return } @@ -98,17 +54,21 @@ type ListOptsBuilder interface { // ListOpts hold options for listing Snapshots. It is passed to the // snapshots.List function. type ListOpts struct { + // AllTenants will retrieve snapshots of all tenants/projects. + AllTenants bool `q:"all_tenants"` + // Name will filter by the specified snapshot name. Name string `q:"name"` // Status will filter by the specified status. Status string `q:"status"` + // TenantID will filter by a specific tenant/project ID. + // Setting AllTenants is required to use this. + TenantID string `q:"project_id"` + // VolumeID will filter by a specified volume ID. VolumeID string `q:"volume_id"` - - // ID will filter by a specific snapshot ID. - ID string `q:"id"` } // ToSnapshotListQuery formats a ListOpts into a query string. @@ -123,7 +83,7 @@ func (opts ListOpts) ToSnapshotListQuery() (string, error) { // List returns Snapshots optionally limited by the conditions provided in // ListOpts. func List(client *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { - url := listURL(client) + url := client.ServiceURL("snapshots") if opts != nil { query, err := opts.ToSnapshotListQuery() if err != nil { @@ -135,3 +95,73 @@ func List(client *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Page return SnapshotPage{pagination.SinglePageBase(r)} }) } + +// UpdateMetadataOptsBuilder allows extensions to add additional parameters to +// the Update request. +type UpdateMetadataOptsBuilder interface { + ToSnapshotUpdateMetadataMap() (map[string]interface{}, error) +} + +// UpdateMetadataOpts contain options for updating an existing Snapshot. This +// object is passed to the snapshots.Update function. For more information +// about the parameters, see the Snapshot object. +type UpdateMetadataOpts struct { + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +// ToSnapshotUpdateMetadataMap assembles a request body based on the contents of +// an UpdateMetadataOpts. +func (opts UpdateMetadataOpts) ToSnapshotUpdateMetadataMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "") +} + +// UpdateMetadata will update the Snapshot with provided information. To +// extract the updated Snapshot from the response, call the ExtractMetadata +// method on the UpdateMetadataResult. +func UpdateMetadata(client *golangsdk.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) { + b, err := opts.ToSnapshotUpdateMetadataMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Put(client.ServiceURL("snapshots", id, "metadata"), b, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// IDFromName is a convienience function that returns a snapshot's ID given its name. +func IDFromName(client *golangsdk.ServiceClient, name string) (string, error) { + count := 0 + id := "" + + listOpts := ListOpts{ + Name: name, + } + + pages, err := List(client, listOpts).AllPages() + if err != nil { + return "", err + } + + all, err := ExtractSnapshots(pages) + if err != nil { + return "", err + } + + for _, s := range all { + if s.Name == name { + count++ + id = s.ID + } + } + + switch count { + case 0: + return "", golangsdk.ErrResourceNotFound{Name: name, ResourceType: "snapshot"} + case 1: + return id, nil + default: + return "", golangsdk.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "snapshot"} + } +} diff --git a/openstack/evs/v2/snapshots/results.go b/openstack/evs/v2/snapshots/results.go index c79e3de24..a43198651 100644 --- a/openstack/evs/v2/snapshots/results.go +++ b/openstack/evs/v2/snapshots/results.go @@ -5,6 +5,7 @@ import ( "time" "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/common/metadata" "github.com/opentelekomcloud/gophertelekomcloud/pagination" ) @@ -38,29 +39,6 @@ type Snapshot struct { Metadata map[string]string `json:"metadata"` } -func (r *Snapshot) UnmarshalJSON(b []byte) error { - type tmp Snapshot - var s struct { - tmp - CreatedAt golangsdk.JSONRFC3339MilliNoZ `json:"created_at"` - UpdatedAt golangsdk.JSONRFC3339MilliNoZ `json:"updated_at"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - *r = Snapshot(s.tmp) - - r.CreatedAt = time.Time(s.CreatedAt) - r.UpdatedAt = time.Time(s.UpdatedAt) - - return err -} - -type commonResult struct { - golangsdk.Result -} - // CreateResult contains the response body and error from a Create request. type CreateResult struct { commonResult @@ -71,11 +49,6 @@ type GetResult struct { commonResult } -// UpdateResult contains the response body and error from an Update request. -type UpdateResult struct { - commonResult -} - // DeleteResult contains the response body and error from a Delete request. type DeleteResult struct { golangsdk.ErrResult @@ -86,6 +59,25 @@ type SnapshotPage struct { pagination.SinglePageBase } +func (r *Snapshot) UnmarshalJSON(b []byte) error { + type tmp Snapshot + var s struct { + tmp + CreatedAt golangsdk.JSONRFC3339MilliNoZ `json:"created_at"` + UpdatedAt golangsdk.JSONRFC3339MilliNoZ `json:"updated_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Snapshot(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + return err +} + // IsEmpty returns true if a SnapshotPage contains no Snapshots. func (r SnapshotPage) IsEmpty() (bool, error) { volumes, err := ExtractSnapshots(r) @@ -101,6 +93,20 @@ func ExtractSnapshots(r pagination.Page) ([]Snapshot, error) { return s.Snapshots, err } +// UpdateMetadataResult contains the response body and error from an UpdateMetadata request. +type UpdateMetadataResult struct { + commonResult +} + +// ExtractMetadata returns the metadata from a response from snapshots.UpdateMetadata. +func (r UpdateMetadataResult) ExtractMetadata() (map[string]interface{}, error) { + return metadata.Extract(r.BodyReader()) +} + +type commonResult struct { + golangsdk.Result +} + // Extract will get the Snapshot object out of the commonResult object. func (r commonResult) Extract() (*Snapshot, error) { var s struct { diff --git a/openstack/evs/v2/snapshots/testing/fixtures.go b/openstack/evs/v2/snapshots/testing/fixtures.go index 80a980a4e..58789ba74 100644 --- a/openstack/evs/v2/snapshots/testing/fixtures.go +++ b/openstack/evs/v2/snapshots/testing/fixtures.go @@ -9,42 +9,43 @@ import ( fake "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) -func MockCreateResponse(t *testing.T) { - th.Mux.HandleFunc("/cloudsnapshots", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "POST") +func MockListResponse(t *testing.T) { + th.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - th.TestHeader(t, r, "Content-Type", "application/json") - th.TestHeader(t, r, "Accept", "application/json") - th.TestJSONRequest(t, r, ` -{ - "snapshot": { - "volume_id": "1234", - "name": "snapshot-001" - } -} - `) w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusAccepted) + w.WriteHeader(http.StatusOK) _, _ = fmt.Fprint(w, ` -{ - "snapshot": { - "volume_id": "1234", - "name": "snapshot-001", - "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "description": "Daily backup", - "status": "available", - "size": 30, - "created_at": "2020-03-27T15:35:03.000000" + { + "snapshots": [ + { + "id": "289da7f8-6440-407c-9fb4-7db01ec49164", + "name": "snapshot-001", + "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", + "description": "Daily Backup", + "status": "available", + "size": 30, + "created_at": "2017-05-30T03:35:03.000000" + }, + { + "id": "96c3bda7-c82a-4f50-be73-ca7621794835", + "name": "snapshot-002", + "volume_id": "76b8950a-8594-4e5b-8dce-0dfa9c696358", + "description": "Weekly Backup", + "status": "available", + "size": 25, + "created_at": "2017-05-30T03:35:03.000000" + } + ] } -} `) }) } func MockGetResponse(t *testing.T) { - th.Mux.HandleFunc("/cloudsnapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { + th.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "GET") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) @@ -59,81 +60,73 @@ func MockGetResponse(t *testing.T) { "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", "status": "available", "size": 30, - "created_at": "2020-03-27T15:35:03.000000" + "created_at": "2017-05-30T03:35:03.000000" } } `) }) } -func MockUpdateResponse(t *testing.T) { - th.Mux.HandleFunc("/cloudsnapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "PUT") +func MockCreateResponse(t *testing.T) { + th.Mux.HandleFunc("/snapshots", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) th.TestHeader(t, r, "Content-Type", "application/json") + th.TestHeader(t, r, "Accept", "application/json") th.TestJSONRequest(t, r, ` { "snapshot": { - "name": "snapshot-001-update", - "description": "Weekly backup" + "volume_id": "1234", + "name": "snapshot-001" } } - `) + `) + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusAccepted) _, _ = fmt.Fprint(w, ` { "snapshot": { + "volume_id": "1234", + "name": "snapshot-001", "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", - "name": "snapshot-001-update", - "description": "Weekly backup", - "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", + "description": "Daily backup", + "volume_id": "1234", "status": "available", "size": 30, - "created_at": "2020-03-27T15:35:03.000000", - "updated_at": "2020-03-27T15:55:03.000000" - } + "created_at": "2017-05-30T03:35:03.000000" + } } `) }) } -func MockListResponse(t *testing.T) { - th.Mux.HandleFunc("/cloudsnapshots/detail", func(w http.ResponseWriter, r *http.Request) { - th.TestMethod(t, r, "GET") +func MockUpdateMetadataResponse(t *testing.T) { + th.Mux.HandleFunc("/snapshots/123/metadata", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) - - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) + th.TestHeader(t, r, "Content-Type", "application/json") + th.TestJSONRequest(t, r, ` + { + "metadata": { + "key": "v1" + } + } + `) _, _ = fmt.Fprint(w, ` - { - "snapshots": [ - { - "id": "289da7f8-6440-407c-9fb4-7db01ec49164", - "name": "snapshot-001", - "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", - "description": "Daily Backup", - "status": "available", - "size": 30, - "created_at": "2020-03-27T15:35:03.000000" - }, - { - "id": "96c3bda7-c82a-4f50-be73-ca7621794835", - "name": "snapshot-002", - "volume_id": "76b8950a-8594-4e5b-8dce-0dfa9c696358", - "description": "Weekly Backup", - "status": "available", - "size": 25, - "created_at": "2020-03-27T15:35:03.000000" + { + "metadata": { + "key": "v1" } - ] - } + } `) }) } func MockDeleteResponse(t *testing.T) { - th.Mux.HandleFunc("/cloudsnapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { + th.Mux.HandleFunc("/snapshots/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) { th.TestMethod(t, r, "DELETE") th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) w.WriteHeader(http.StatusNoContent) diff --git a/openstack/evs/v2/snapshots/testing/requests_test.go b/openstack/evs/v2/snapshots/testing/requests_test.go index 946ab0c48..6e4c6305a 100644 --- a/openstack/evs/v2/snapshots/testing/requests_test.go +++ b/openstack/evs/v2/snapshots/testing/requests_test.go @@ -4,59 +4,13 @@ import ( "testing" "time" - "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v2/snapshots" + snapshots2 "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v2/snapshots" + "github.com/opentelekomcloud/gophertelekomcloud/pagination" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" ) -func TestCreate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockCreateResponse(t) - - options := snapshots.CreateOpts{VolumeID: "1234", Name: "snapshot-001"} - n, err := snapshots.Create(client.ServiceClient(), options).Extract() - th.AssertNoErr(t, err) - - th.AssertEquals(t, n.VolumeID, "1234") - th.AssertEquals(t, n.Name, "snapshot-001") - th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") -} - -func TestGet(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockGetResponse(t) - - v, err := snapshots.Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() - th.AssertNoErr(t, err) - - th.AssertEquals(t, v.Name, "snapshot-001") - th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") -} - -func TestUpdate(t *testing.T) { - th.SetupHTTP() - defer th.TeardownHTTP() - - MockUpdateResponse(t) - - options := snapshots.UpdateOpts{ - Name: "snapshot-001-update", - Description: "Weekly backup", - } - - v, err := snapshots.Update(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22", options).Extract() - - th.AssertNoErr(t, err) - th.AssertEquals(t, v.Name, "snapshot-001-update") - th.AssertEquals(t, v.Description, "Weekly backup") - th.AssertEquals(t, v.UpdatedAt, time.Date(2020, 3, 27, 15, 55, 3, 0, time.UTC)) -} - func TestList(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() @@ -65,22 +19,22 @@ func TestList(t *testing.T) { count := 0 - _ = snapshots.List(client.ServiceClient(), &snapshots.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { + _ = snapshots2.List(client.ServiceClient(), &snapshots2.ListOpts{}).EachPage(func(page pagination.Page) (bool, error) { count++ - actual, err := snapshots.ExtractSnapshots(page) + actual, err := snapshots2.ExtractSnapshots(page) if err != nil { t.Errorf("Failed to extract snapshots: %v", err) return false, err } - expected := []snapshots.Snapshot{ + expected := []snapshots2.Snapshot{ { ID: "289da7f8-6440-407c-9fb4-7db01ec49164", Name: "snapshot-001", VolumeID: "521752a6-acf6-4b2d-bc7a-119f9148cd8c", Status: "available", Size: 30, - CreatedAt: time.Date(2020, 3, 27, 15, 35, 3, 0, time.UTC), + CreatedAt: time.Date(2017, 5, 30, 3, 35, 3, 0, time.UTC), Description: "Daily Backup", }, { @@ -89,7 +43,7 @@ func TestList(t *testing.T) { VolumeID: "76b8950a-8594-4e5b-8dce-0dfa9c696358", Status: "available", Size: 25, - CreatedAt: time.Date(2020, 3, 27, 15, 35, 3, 0, time.UTC), + CreatedAt: time.Date(2017, 5, 30, 3, 35, 3, 0, time.UTC), Description: "Weekly Backup", }, } @@ -104,12 +58,60 @@ func TestList(t *testing.T) { } } +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockGetResponse(t) + + v, err := snapshots2.Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, v.Name, "snapshot-001") + th.AssertEquals(t, v.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockCreateResponse(t) + + options := snapshots2.CreateOpts{VolumeID: "1234", Name: "snapshot-001"} + n, err := snapshots2.Create(client.ServiceClient(), options).Extract() + th.AssertNoErr(t, err) + + th.AssertEquals(t, n.VolumeID, "1234") + th.AssertEquals(t, n.Name, "snapshot-001") + th.AssertEquals(t, n.ID, "d32019d3-bc6e-4319-9c1d-6722fc136a22") +} + +func TestUpdateMetadata(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + MockUpdateMetadataResponse(t) + + expected := map[string]interface{}{"key": "v1"} + + options := &snapshots2.UpdateMetadataOpts{ + Metadata: map[string]interface{}{ + "key": "v1", + }, + } + + actual, err := snapshots2.UpdateMetadata(client.ServiceClient(), "123", options).ExtractMetadata() + + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, actual, expected) +} + func TestDelete(t *testing.T) { th.SetupHTTP() defer th.TeardownHTTP() MockDeleteResponse(t) - res := snapshots.Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") + res := snapshots2.Delete(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22") th.AssertNoErr(t, res.Err) } diff --git a/openstack/evs/v2/snapshots/urls.go b/openstack/evs/v2/snapshots/urls.go deleted file mode 100644 index 95b374172..000000000 --- a/openstack/evs/v2/snapshots/urls.go +++ /dev/null @@ -1,23 +0,0 @@ -package snapshots - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func createURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("cloudsnapshots") -} - -func listURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("cloudsnapshots/detail") -} - -func deleteURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL("cloudsnapshots", id) -} - -func updateURL(c *golangsdk.ServiceClient, id string) string { - return deleteURL(c, id) -} - -func getURL(c *golangsdk.ServiceClient, id string) string { - return deleteURL(c, id) -} diff --git a/openstack/evs/v2/tags/requests.go b/openstack/evs/v2/tags/requests.go index 62d06e709..65c713905 100644 --- a/openstack/evs/v2/tags/requests.go +++ b/openstack/evs/v2/tags/requests.go @@ -35,13 +35,13 @@ func Create(client *golangsdk.ServiceClient, resource_type, resource_id string, r.Err = err return r } - _, r.Err = client.Put(createURL(client, resource_type, resource_id), b, &r.Body, &golangsdk.RequestOpts{OkCodes: []int{200}}) + _, r.Err = client.Put(client.ServiceURL("os-vendor-tags", resource_type, resource_id), b, &r.Body, &golangsdk.RequestOpts{OkCodes: []int{200}}) return } // Get implements tags get request func Get(client *golangsdk.ServiceClient, resource_type, resource_id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, resource_type, resource_id), &r.Body, nil) + _, r.Err = client.Get(client.ServiceURL("os-vendor-tags", resource_type, resource_id), &r.Body, nil) return } diff --git a/openstack/evs/v2/tags/urls.go b/openstack/evs/v2/tags/urls.go deleted file mode 100644 index 47ee57725..000000000 --- a/openstack/evs/v2/tags/urls.go +++ /dev/null @@ -1,13 +0,0 @@ -package tags - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -func createURL(c *golangsdk.ServiceClient, resource_type, resource_id string) string { - return c.ServiceURL("os-vendor-tags", resource_type, resource_id) -} - -func getURL(c *golangsdk.ServiceClient, resource_type, resource_id string) string { - return c.ServiceURL("os-vendor-tags", resource_type, resource_id) -} diff --git a/openstack/blockstorage/v2/volumes/requests.go b/openstack/evs/v2/volumes/requests.go similarity index 95% rename from openstack/blockstorage/v2/volumes/requests.go rename to openstack/evs/v2/volumes/requests.go index b88ff2e47..19c46d3c7 100644 --- a/openstack/blockstorage/v2/volumes/requests.go +++ b/openstack/evs/v2/volumes/requests.go @@ -57,7 +57,7 @@ func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateRe r.Err = err return } - _, r.Err = client.Post(createURL(client), b, &r.Body, &golangsdk.RequestOpts{ + _, r.Err = client.Post(client.ServiceURL("volumes"), b, &r.Body, &golangsdk.RequestOpts{ OkCodes: []int{202}, }) return @@ -84,7 +84,7 @@ func (opts DeleteOpts) ToVolumeDeleteQuery() (string, error) { // Delete will delete the existing Volume with the provided ID func Delete(client *golangsdk.ServiceClient, id string, opts DeleteOptsBuilder) (r DeleteResult) { - url := deleteURL(client, id) + url := client.ServiceURL("volumes", id) if opts != nil { q, err := opts.ToVolumeDeleteQuery() if err != nil { @@ -100,7 +100,7 @@ func Delete(client *golangsdk.ServiceClient, id string, opts DeleteOptsBuilder) // Get retrieves the Volume with the provided ID. To extract the Volume object // from the response, call the Extract method on the GetResult. func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) + _, r.Err = client.Get(client.ServiceURL("volumes", id), &r.Body, nil) return } @@ -154,7 +154,7 @@ func (opts ListOpts) ToVolumeListQuery() (string, error) { // List returns Volumes optionally limited by the conditions provided in ListOpts. func List(client *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { - url := listURL(client) + url := client.ServiceURL("volumes", "detail") if opts != nil { query, err := opts.ToVolumeListQuery() if err != nil { @@ -197,7 +197,7 @@ func Update(client *golangsdk.ServiceClient, id string, opts UpdateOptsBuilder) r.Err = err return } - _, r.Err = client.Put(updateURL(client, id), b, &r.Body, &golangsdk.RequestOpts{ + _, r.Err = client.Put(client.ServiceURL("volumes", id), b, &r.Body, &golangsdk.RequestOpts{ OkCodes: []int{200}, }) return diff --git a/openstack/blockstorage/v2/volumes/results.go b/openstack/evs/v2/volumes/results.go similarity index 100% rename from openstack/blockstorage/v2/volumes/results.go rename to openstack/evs/v2/volumes/results.go diff --git a/openstack/blockstorage/v2/volumes/testing/fixtures.go b/openstack/evs/v2/volumes/testing/fixtures.go similarity index 100% rename from openstack/blockstorage/v2/volumes/testing/fixtures.go rename to openstack/evs/v2/volumes/testing/fixtures.go diff --git a/openstack/blockstorage/v2/volumes/testing/requests_test.go b/openstack/evs/v2/volumes/testing/requests_test.go similarity index 97% rename from openstack/blockstorage/v2/volumes/testing/requests_test.go rename to openstack/evs/v2/volumes/testing/requests_test.go index fba0caea1..c8db5adf7 100644 --- a/openstack/blockstorage/v2/volumes/testing/requests_test.go +++ b/openstack/evs/v2/volumes/testing/requests_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" - "github.com/opentelekomcloud/gophertelekomcloud/openstack/blockstorage/v2/volumes" - "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/extensions" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/extensions/volumetenants" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/evs/v2/volumes" "github.com/opentelekomcloud/gophertelekomcloud/pagination" th "github.com/opentelekomcloud/gophertelekomcloud/testhelper" "github.com/opentelekomcloud/gophertelekomcloud/testhelper/client" @@ -102,7 +102,7 @@ func TestListAllWithExtensions(t *testing.T) { type VolumeWithExt struct { volumes.Volume - extensions.VolumeTenantExt + volumetenants.VolumeTenantExt } allPages, err := volumes.List(client.ServiceClient(), &volumes.ListOpts{}).AllPages() @@ -245,7 +245,7 @@ func TestGetWithExtensions(t *testing.T) { var s struct { volumes.Volume - extensions.VolumeTenantExt + volumetenants.VolumeTenantExt } err := volumes.Get(client.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").ExtractInto(&s) th.AssertNoErr(t, err) diff --git a/openstack/blockstorage/v2/volumes/util.go b/openstack/evs/v2/volumes/util.go similarity index 100% rename from openstack/blockstorage/v2/volumes/util.go rename to openstack/evs/v2/volumes/util.go diff --git a/openstack/evs/v3/snapshots/Create.go b/openstack/evs/v3/snapshots/Create.go new file mode 100644 index 000000000..bb232b4c3 --- /dev/null +++ b/openstack/evs/v3/snapshots/Create.go @@ -0,0 +1,42 @@ +package snapshots + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// CreateOpts contains options for creating a Snapshot. This object is passed to +// the snapshots.Create function. For more information about these parameters, +// see the Snapshot object. +type CreateOpts struct { + // Specifies the ID of the snapshot's source disk. + VolumeID string `json:"volume_id" required:"true"` + // Specifies the flag for forcibly creating a snapshot. The default value is false. + // If this parameter is set to false and the disk is in the attaching state, the snapshot cannot be forcibly created. + // If this parameter is set to true and the disk is in the attaching state, the snapshot can be forcibly created. + Force bool `json:"force,omitempty"` + // Specifies the snapshot name. The value can contain a maximum of 255 bytes. + // NOTE + // When creating a backup for a disk, a snapshot will be created and named with prefix autobk_snapshot_. + // The EVS console has imposed operation restrictions on snapshots with prefix autobk_snapshot_. Therefore, + // you are advised not to use autobk_snapshot_ as the name prefix for the snapshots you created. + // Otherwise, the snapshots cannot be used normally. + Name string `json:"name,omitempty"` + // Specifies the snapshot description. The value can be null. The value can contain a maximum of 255 bytes. + Description string `json:"description,omitempty"` + // Specifies the snapshot metadata. + Metadata map[string]string `json:"metadata,omitempty"` +} + +// Create will create a new Snapshot based on the values in CreateOpts. To +// extract the Snapshot object from the response, call the Extract method on the CreateResult. +func Create(client *golangsdk.ServiceClient, opts CreateOpts) (*Snapshot, error) { + b, err := build.RequestBody(opts, "snapshot") + if err != nil { + return nil, err + } + + // POST /v3/{project_id}/snapshots + raw, err := client.Post(client.ServiceURL("snapshots"), b, nil, nil) + return extra(err, raw) +} diff --git a/openstack/evs/v3/snapshots/Delete.go b/openstack/evs/v3/snapshots/Delete.go new file mode 100644 index 000000000..405b214e1 --- /dev/null +++ b/openstack/evs/v3/snapshots/Delete.go @@ -0,0 +1,10 @@ +package snapshots + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Delete will delete the existing Snapshot with the provided ID. +func Delete(client *golangsdk.ServiceClient, id string) (err error) { + // DELETE /v3/{project_id}/snapshots/{snapshot_id} + _, err = client.Delete(client.ServiceURL("snapshots", id), nil) + return +} diff --git a/openstack/evs/v3/snapshots/Get.go b/openstack/evs/v3/snapshots/Get.go new file mode 100644 index 000000000..43de32aa7 --- /dev/null +++ b/openstack/evs/v3/snapshots/Get.go @@ -0,0 +1,11 @@ +package snapshots + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// Get retrieves the Snapshot with the provided ID. To extract the Snapshot +// object from the response, call the Extract method on the GetResult. +func Get(client *golangsdk.ServiceClient, id string) (*Snapshot, error) { + // GET /v3/{project_id}/snapshots/{snapshot_id} + raw, err := client.Get(client.ServiceURL("snapshots", id), nil, nil) + return extra(err, raw) +} diff --git a/openstack/evs/v3/snapshots/List.go b/openstack/evs/v3/snapshots/List.go new file mode 100644 index 000000000..045300bfc --- /dev/null +++ b/openstack/evs/v3/snapshots/List.go @@ -0,0 +1,88 @@ +package snapshots + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" + "github.com/opentelekomcloud/gophertelekomcloud/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the List request. +type ListOptsBuilder interface { + ToSnapshotListQuery() (string, error) +} + +// ListOpts holds options for listing Snapshots. It is passed to the snapshots.List function. +type ListOpts struct { + // AllTenants will retrieve snapshots of all tenants/projects. + AllTenants bool `q:"all_tenants"` + // Name will filter by the specified snapshot name. + Name string `q:"name"` + // Status will filter by the specified status. + Status string `q:"status"` + // VolumeID will filter by a specified volume ID. + VolumeID string `q:"volume_id"` + // Specifies the result sorting order. The default value is desc. + // desc: indicates the descending order. + // asc: indicates the ascending order. + Sort string `q:"sort_dir"` + // Specifies the sorting query by name (sort_key=name). + // This parameter is supported when the request version is 3.30 or later. The default sorting order is the descending order. + SortKey string `q:"sort_key"` + // Requests a page size of items. + Limit int `q:"limit"` + // Used in conjunction with limit to return a slice of items. + Offset int `q:"offset"` + // Specifies the ID of the last record on the previous page. The returned value is the value of the item after this one. + Marker string `q:"marker"` + // Specifies to return parameter counts in the response. This parameter indicates the number of snapshots queried. + // This parameter is in the with_count=true format and is supported when the request version is 3.45 or later. + // This parameter can be used together with parameters marker, limit, sort_key, sort_dir, or offset in the table. + // It cannot be used with other filter parameters. + WithCount bool `q:"with_count"` + // Specifies the fuzzy search by disk name. This parameter is supported when the request version is 3.31 or later. + FuzzyName string `q:"name~"` + FuzzyStatus string `q:"status~"` + FuzzyVolumeID string `q:"volume_id~"` +} + +// List returns Snapshots optionally limited by the conditions provided in ListOpts. +func List(client *golangsdk.ServiceClient, opts ListOpts) pagination.Pager { + q, err := golangsdk.BuildQueryString(opts) + if err != nil { + return pagination.Pager{Err: err} + } + + // GET /v3/{project_id}/snapshots + return pagination.NewPager(client, client.ServiceURL("snapshots")+q.String(), func(r pagination.PageResult) pagination.Page { + return SnapshotPage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// SnapshotPage is a pagination.Pager that is returned from a call to the List function. +type SnapshotPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a SnapshotPage contains no Snapshots. +func (r SnapshotPage) IsEmpty() (bool, error) { + volumes, err := ExtractSnapshots(r) + return len(volumes) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r SnapshotPage) NextPageURL() (string, error) { + var s []golangsdk.Link + err := extract.IntoSlicePtr(r.BodyReader(), &s, "snapshots_links") + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(s) +} + +// ExtractSnapshots extracts and returns Snapshots. It is used while iterating over a snapshots.List call. +func ExtractSnapshots(r pagination.Page) ([]Snapshot, error) { + var s []Snapshot + err := extract.IntoSlicePtr(r.(SnapshotPage).BodyReader(), &s, "snapshots") + return s, err +} diff --git a/openstack/evs/v3/snapshots/Update.go b/openstack/evs/v3/snapshots/Update.go new file mode 100644 index 000000000..44888b0fe --- /dev/null +++ b/openstack/evs/v3/snapshots/Update.go @@ -0,0 +1,36 @@ +package snapshots + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// UpdateOpts contain options for updating an existing Snapshot. This object is passed +// to the snapshots.Update function. For more information about the parameters, see +// the Snapshot object. +type UpdateOpts struct { + // Specifies the snapshot name. The value can contain a maximum of 255 bytes. + // NOTE + // When creating a backup for a disk, a snapshot will be created and named with prefix autobk_snapshot_. + // The EVS console has imposed operation restrictions on snapshots with prefix autobk_snapshot_. + // Therefore, you are advised not to use autobk_snapshot_ as the name prefix for the snapshots you created. + // Otherwise, the snapshots cannot be used normally. + Name string `json:"name,omitempty"` + // Specifies the snapshot description. The value can contain a maximum of 255 bytes. + Description string `json:"description,omitempty"` +} + +// Update will update the Snapshot with provided information. To extract the updated +// Snapshot from the response, call the Extract method on the UpdateResult. +func Update(client *golangsdk.ServiceClient, id string, opts UpdateOpts) (*Snapshot, error) { + b, err := build.RequestBody(opts, "snapshot") + if err != nil { + return nil, err + } + + // PUT /v3/{project_id}/snapshots/{snapshot_id} + raw, err := client.Put(client.ServiceURL("snapshots", id), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extra(err, raw) +} diff --git a/openstack/evs/v3/snapshots/metadata/Create.go b/openstack/evs/v3/snapshots/metadata/Create.go new file mode 100644 index 000000000..51968f42e --- /dev/null +++ b/openstack/evs/v3/snapshots/metadata/Create.go @@ -0,0 +1,34 @@ +package metadata + +import ( + "net/http" + + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +func Create(client *golangsdk.ServiceClient, snapshotId string, opts map[string]string) (map[string]string, error) { + b, err := build.RequestBody(opts, "metadata") + if err != nil { + return nil, err + } + + // POST /v3/{project_id}/snapshots/{snapshot_id}/metadata + raw, err := client.Post(client.ServiceURL("snapshots", snapshotId, "metadata"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extraMetadata(err, raw) +} + +func extraMetadata(err error, raw *http.Response) (map[string]string, error) { + if err != nil { + return nil, err + } + + var res struct { + Metadata map[string]string `json:"metadata"` + } + err = extract.Into(raw.Body, &res) + return res.Metadata, err +} diff --git a/openstack/evs/v3/snapshots/metadata/Delete.go b/openstack/evs/v3/snapshots/metadata/Delete.go new file mode 100644 index 000000000..e7cdfa8a9 --- /dev/null +++ b/openstack/evs/v3/snapshots/metadata/Delete.go @@ -0,0 +1,11 @@ +package metadata + +import golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + +func Delete(client *golangsdk.ServiceClient, snapshotId string, key string) (err error) { + // DELETE /v3/{project_id}/snapshots/{snapshot_id}/metadata/{key} + _, err = client.Delete(client.ServiceURL("snapshots", snapshotId, "metadata", key), &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/openstack/evs/v3/snapshots/metadata/Get.go b/openstack/evs/v3/snapshots/metadata/Get.go new file mode 100644 index 000000000..b9bb39dc1 --- /dev/null +++ b/openstack/evs/v3/snapshots/metadata/Get.go @@ -0,0 +1,10 @@ +package metadata + +import golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + +// Get If metadata contains the __system__enableActive field, the snapshot is automatically created during the backup of a server. +func Get(client *golangsdk.ServiceClient, snapshotId string) (map[string]string, error) { + // GET /v3/{project_id}/snapshots/{snapshot_id}/metadata + raw, err := client.Get(client.ServiceURL("snapshots", snapshotId, "metadata"), nil, nil) + return extraMetadata(err, raw) +} diff --git a/openstack/evs/v3/snapshots/metadata/GetOne.go b/openstack/evs/v3/snapshots/metadata/GetOne.go new file mode 100644 index 000000000..a1acb5214 --- /dev/null +++ b/openstack/evs/v3/snapshots/metadata/GetOne.go @@ -0,0 +1,26 @@ +package metadata + +import ( + "net/http" + + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +func GetOne(client *golangsdk.ServiceClient, snapshotId string, key string) (map[string]string, error) { + // GET /v3/{project_id}/snapshots/{snapshot_id}/metadata/{key} + raw, err := client.Get(client.ServiceURL("snapshots", snapshotId, "metadata", key), nil, nil) + return extraMeta(err, raw) +} + +func extraMeta(err error, raw *http.Response) (map[string]string, error) { + if err != nil { + return nil, err + } + + var res struct { + Metadata map[string]string `json:"meta"` + } + err = extract.Into(raw.Body, &res) + return res.Metadata, err +} diff --git a/openstack/evs/v3/snapshots/metadata/Update.go b/openstack/evs/v3/snapshots/metadata/Update.go new file mode 100644 index 000000000..ca3c46bbd --- /dev/null +++ b/openstack/evs/v3/snapshots/metadata/Update.go @@ -0,0 +1,19 @@ +package metadata + +import ( + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +func Update(client *golangsdk.ServiceClient, snapshotId string, opts map[string]string) (map[string]string, error) { + b, err := build.RequestBody(opts, "metadata") + if err != nil { + return nil, err + } + + // PUT /v3/{project_id}/snapshots/{snapshot_id}/metadata + raw, err := client.Put(client.ServiceURL("snapshots", snapshotId, "metadata"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extraMetadata(err, raw) +} diff --git a/openstack/evs/v3/snapshots/metadata/UpdateOne.go b/openstack/evs/v3/snapshots/metadata/UpdateOne.go new file mode 100644 index 000000000..30118796d --- /dev/null +++ b/openstack/evs/v3/snapshots/metadata/UpdateOne.go @@ -0,0 +1,19 @@ +package metadata + +import ( + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +func UpdateOne(client *golangsdk.ServiceClient, snapshotId string, key string, opts map[string]string) (map[string]string, error) { + b, err := build.RequestBody(opts, "meta") + if err != nil { + return nil, err + } + + // PUT /v3/{project_id}/snapshots/{snapshot_id}/metadata/{key} + raw, err := client.Put(client.ServiceURL("snapshots", snapshotId, "metadata", key), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extraMeta(err, raw) +} diff --git a/openstack/evs/v3/snapshots/results.go b/openstack/evs/v3/snapshots/results.go new file mode 100644 index 000000000..f1ab27f6d --- /dev/null +++ b/openstack/evs/v3/snapshots/results.go @@ -0,0 +1,79 @@ +package snapshots + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// Snapshot contains all the information associated with a Cinder Snapshot. +type Snapshot struct { + // Unique identifier. + ID string `json:"id"` + // Date created. + CreatedAt time.Time `json:"-"` + // Date updated. + UpdatedAt time.Time `json:"-"` + // Display name. + Name string `json:"name"` + // Display description. + Description string `json:"description"` + // ID of the Volume from which this Snapshot was created. + VolumeID string `json:"volume_id"` + // Currect status of the Snapshot. + Status string `json:"status"` + // Size of the Snapshot, in GB. + Size int `json:"size"` + // User-defined key-value pairs. + Metadata map[string]string `json:"metadata"` +} + +// UnmarshalJSON converts our JSON API response into our snapshot struct +func (r *Snapshot) UnmarshalJSON(b []byte) error { + type tmp Snapshot + var s struct { + tmp + CreatedAt golangsdk.JSONRFC3339MilliNoZ `json:"created_at"` + UpdatedAt golangsdk.JSONRFC3339MilliNoZ `json:"updated_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Snapshot(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + return err +} + +func extra(err error, raw *http.Response) (*Snapshot, error) { + if err != nil { + return nil, err + } + + var res Snapshot + err = extract.IntoStructPtr(raw.Body, &res, "snapshot") + return &res, err +} + +// WaitForStatus will continually poll the resource, checking for a particular +// status. It will do this for the amount of seconds defined. +func WaitForStatus(c *golangsdk.ServiceClient, id, status string, secs int) error { + return golangsdk.WaitFor(secs, func() (bool, error) { + current, err := Get(c, id) + if err != nil { + return false, err + } + + if current.Status == status { + return true, nil + } + + return false, nil + }) +} diff --git a/openstack/evs/v3/volumes/Create.go b/openstack/evs/v3/volumes/Create.go new file mode 100644 index 000000000..3d721a070 --- /dev/null +++ b/openstack/evs/v3/volumes/Create.go @@ -0,0 +1,134 @@ +package volumes + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" +) + +type CreateOptsBuilder interface { + ToVolumeCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains options for creating a Volume. This object is passed to +// the volumes.Create function. For more information about these parameters, see the Volume object. +type CreateOpts struct { + // Specifies the disk size, in GB. Its value can be as follows: + // System disk: 1 GB to 1024 GB + // Data disk: 10 GB to 32768 GB + // + // This parameter is mandatory when you create an empty disk. + // You can specify the parameter value as required within the value range. + // + // This parameter is mandatory when you create the disk from a snapshot. + // Ensure that the disk size is greater than or equal to the snapshot size. + // + // This parameter is mandatory when you create the disk from an image. + // Ensure that the disk size is greater than or equal to the minimum disk capacity required by min_disk in the image attributes. + Size int `json:"size" required:"true"` + // Specifies the AZ where you want to create the disk. If the AZ does not exist, the disk will fail to create. + AvailabilityZone string `json:"availability_zone" required:"true"` + // ConsistencyGroupID is the ID of a consistency group + // Currently, this function is not supported. + ConsistencyGroupID string `json:"consistencygroup_id,omitempty"` + // Specifies the disk description. The value can contain a maximum of 255 bytes. + Description string `json:"description,omitempty"` + // Specifies the disk metadata. The length of the key or value in the metadata cannot exceed 255 bytes. + // For details about metadata, see Parameters in the metadata field. The table lists some fields. + // You can also specify other fields based on the disk creation requirements. + // NOTE + // Parameter values under metadata cannot be null. + Metadata map[string]string `json:"metadata,omitempty"` + // Specifies the disk name. The value can contain a maximum of 255 bytes. + Name string `json:"name,omitempty"` + // Specifies the snapshot ID. If this parameter is specified, the disk is created from a snapshot. + SnapshotID string `json:"snapshot_id,omitempty"` + // Specifies the source disk ID. If this parameter is specified, the disk is cloned from an existing disk. + // Currently, this function is not supported. + SourceReplica string `json:"source_replica,omitempty"` + // Specifies the source disk ID. If this parameter is specified, the disk is cloned from an existing disk. + // Currently, this function is not supported. + SourceVolID string `json:"source_volid,omitempty"` + // Specifies the image ID. If this parameter is specified, the disk is created from an image. + // NOTE BMS system disks cannot be created from BMS images. + ImageID string `json:"imageRef,omitempty"` + // Specifies the backup ID, from which you want to create the volume. + // Create a volume from a backup is supported since 3.47 microversion + BackupID string `json:"backup_id,omitempty"` + // Specifies the disk type. + // Currently, the value can be SSD, SAS, SATA, co-p1, uh-l1, GPSSD, or ESSD. + // SSD: specifies the ultra-high I/O disk type. + // SAS: specifies the high I/O disk type. + // SATA: specifies the common I/O disk type. + // co-p1: specifies the high I/O (performance-optimized I) disk type. + // uh-l1: specifies the ultra-high I/O (latency-optimized) disk type. + // GPSSD: specifies the general purpose SSD disk type. + // ESSD: specifies the extreme SSD disk type. + // Disks of the co-p1 and uh-l1 types are used exclusively for HPC ECSs and SAP HANA ECSs. + // If the specified disk type is not available in the AZ, the disk will fail to create. + // NOTE + // If the disk is created from a snapshot, the volume_type field must be the same as that of the snapshot's source disk. + VolumeType string `json:"volume_type,omitempty"` + // Specifies whether the disk is shareable. The default value is false. + // true: specifies a shared disk. + // false: specifies a non-shared disk. + Multiattach bool `json:"multiattach,omitempty"` +} + +// Metadata +// The preceding table provides only some parameters in metadata for your reference. You can also specify other fields based on the disk creation requirements. +// If the disk is created from a snapshot, __system__encrypted and __system__cmkid are not supported, and the newly created disk has the same encryption attribute as that of the snapshot's source disk. +// If the disk is created from an image, __system__encrypted and __system__cmkid are not supported, and the newly created disk has the same encryption attribute as that of the image. +// If the disk is created from a snapshot, hw:passthrough is not supported, and the newly created disk has the same device type as that of the snapshot's source disk. +// If the disk is created from an image, hw:passthrough is not supported, and the device type of newly created disk is VBD. +type Metadata struct { + // Specifies the encryption field in metadata. The value can be 0 (not encrypted) or 1 (encrypted). + // If this parameter does not exist, the disk will not be encrypted by default. + SystemEncrypted string `json:"__system__encrypted"` + // Specifies the encryption CMK ID in metadata. This parameter is used together with + // __system__encrypted for encryption. The length of cmkid is fixed at 36 bytes. + // NOTE + // For details about how to obtain the CMK ID, see Querying the List of CMKs in the Key Management Service API Reference. + SystemCmkId string `json:"__system__cmkid"` + // If this parameter is set to true, the disk device type is SCSI, that is, + // Small Computer System Interface (SCSI), which allows ECS OSs to directly + // access the underlying storage media and supports SCSI reservation commands. + // If this parameter is set to false, the disk device type will be VBD, which supports only simple SCSI read/write commands. + // If this parameter does not appear, the disk device type is VBD. + // NOTE + // If parameter shareable is set to true and parameter hw:passthrough is not specified, shared VBD disks are created. + Passthrough string `json:"hw:passthrough"` + // If the disk is created from a snapshot and linked cloning needs to be used, set this parameter to 0. + FullClone string `json:"full_clone"` +} + +func (c CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(c, "volume") +} + +// Create will create a new Volume based on the values in CreateOpts. +func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (*Volume, error) { + b, err := opts.ToVolumeCreateMap() + if err != nil { + return nil, err + } + + // POST /v3/{project_id}/volumes + raw, err := client.Post(client.ServiceURL("volumes"), b, nil, nil) + return extra(err, raw) +} + +// WaitForStatus will continually poll the resource, checking for a particular +// status. It will do this for the amount of seconds defined. +func WaitForStatus(c *golangsdk.ServiceClient, id, status string, secs int) error { + return golangsdk.WaitFor(secs, func() (bool, error) { + current, err := Get(c, id) + if err != nil { + return false, err + } + + if current.Status == status { + return true, nil + } + + return false, nil + }) +} diff --git a/openstack/evs/v3/volumes/Delete.go b/openstack/evs/v3/volumes/Delete.go new file mode 100644 index 000000000..0a07c322b --- /dev/null +++ b/openstack/evs/v3/volumes/Delete.go @@ -0,0 +1,22 @@ +package volumes + +import "github.com/opentelekomcloud/gophertelekomcloud" + +// DeleteOpts contains options for deleting a Volume. +type DeleteOpts struct { + VolumeId string + // Specifies to delete all snapshots associated with the disk. The default value is false. + Cascade bool `q:"cascade"` +} + +// Delete will delete the existing Volume with the provided ID. +func Delete(client *golangsdk.ServiceClient, opts DeleteOpts) (err error) { + q, err := golangsdk.BuildQueryString(opts) + if err != nil { + return + } + + // DELETE /v3/{project_id}/volumes/{volume_id} + _, err = client.Delete(client.ServiceURL("volumes", opts.VolumeId)+q.String(), nil) + return +} diff --git a/openstack/evs/v3/volumes/results.go b/openstack/evs/v3/volumes/Get.go similarity index 53% rename from openstack/evs/v3/volumes/results.go rename to openstack/evs/v3/volumes/Get.go index edc457072..576c0c91b 100644 --- a/openstack/evs/v3/volumes/results.go +++ b/openstack/evs/v3/volumes/Get.go @@ -2,42 +2,26 @@ package volumes import ( "encoding/json" + "net/http" "time" "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" ) -type Attachment struct { - AttachedAt time.Time `json:"-"` - AttachmentID string `json:"attachment_id"` - Device string `json:"device"` - HostName string `json:"host_name"` - ID string `json:"id"` - ServerID string `json:"server_id"` - VolumeID string `json:"volume_id"` +// Get retrieves the Volume with the provided ID. +func Get(client *golangsdk.ServiceClient, id string) (*Volume, error) { + // GET /v3/{project_id}/volumes/{volume_id} + raw, err := client.Get(client.ServiceURL("volumes", id), nil, nil) + return extra(err, raw) } -func (r *Attachment) UnmarshalJSON(b []byte) error { - type tmp Attachment - var s struct { - tmp - AttachedAt golangsdk.JSONRFC3339MilliNoZ `json:"attached_at"` - } - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - *r = Attachment(s.tmp) - - r.AttachedAt = time.Time(s.AttachedAt) - - return err -} - -// Volume contains all the information associated with a Volume. +// Volume contains all the information associated with an OpenStack Volume. type Volume struct { // Unique identifier for the volume. ID string `json:"id"` + // Specifies the disk URI. + Links []golangsdk.Link `json:"links"` // Current status of the volume. Status string `json:"status"` // Size of the volume in GB. @@ -54,36 +38,48 @@ type Volume struct { Name string `json:"name"` // Human-readable description for the volume. Description string `json:"description"` - // The type of volume to create, either SATA or SSD. + // Specifies the disk type. + // Currently, the value can be SSD, SAS, SATA, co-p1, uh-l1, GPSSD, or ESSD. + // SSD: specifies the ultra-high I/O disk type. + // SAS: specifies the high I/O disk type. + // SATA: specifies the common I/O disk type. + // co-p1: specifies the high I/O (performance-optimized I) disk type. + // uh-l1: specifies the ultra-high I/O (latency-optimized) disk type. + // GPSSD: specifies the general purpose SSD disk type. + // ESSD: specifies the extreme SSD disk type. + // Disks of the co-p1 and uh-l1 types are used exclusively for HPC ECSs and SAP HANA ECSs. VolumeType string `json:"volume_type"` // The ID of the snapshot from which the volume was created SnapshotID string `json:"snapshot_id"` // The ID of another block storage volume from which the current volume was created + // Currently, this field is not supported by EVS. SourceVolID string `json:"source_volid"` - // The ID of the back that can be used to create an EVS disk + // The backup ID, from which the volume was restored + // This field is supported since 3.47 microversion BackupID string `json:"backup_id"` - // Arbitrary key-value pairs defined by the metadata field table. - Metadata map[string]string `json:"metadata"` // Arbitrary key-value pairs defined by the user. - Tags map[string]string `json:"tags"` + Metadata map[string]string `json:"metadata"` // UserID is the id of the user who created the volume. UserID string `json:"user_id"` // Indicates whether this is a bootable volume. Bootable string `json:"bootable"` // Encrypted denotes if the volume is encrypted. + // Currently, this field is not supported by EVS. Encrypted bool `json:"encrypted"` // ReplicationStatus is the status of replication. + // Currently, this field is not supported by EVS. ReplicationStatus string `json:"replication_status"` // ConsistencyGroupID is the consistency group ID. ConsistencyGroupID string `json:"consistencygroup_id"` - // Multiattach denotes if the volume is multi-attach capable. + // Specifies whether the disk is shareable. Multiattach bool `json:"multiattach"` - // wwn of the volume. - WWN string `json:"wwn"` - // enterprise project ID bound to the volume - EnterpriseProjectID string `json:"enterprise_project_id"` + // Image metadata entries, only included for volumes that were created from an image, or from a snapshot of a volume originally created from an image. + VolumeImageMetadata map[string]string `json:"volume_image_metadata"` + // TenantID is the id of the project that owns the volume. + TenantID string `json:"os-vol-tenant-attr:tenant_id"` } +// UnmarshalJSON another unmarshalling function func (r *Volume) UnmarshalJSON(b []byte) error { type tmp Volume var s struct { @@ -103,23 +99,12 @@ func (r *Volume) UnmarshalJSON(b []byte) error { return err } -type commonResult struct { - golangsdk.Result -} - -// Extract will get the Volume object out of the commonResult object. -func (r commonResult) Extract() (*Volume, error) { - var s Volume - err := r.ExtractInto(&s) - return &s, err -} - -// ExtractInto converts our response data into a volume struct -func (r commonResult) ExtractInto(v interface{}) error { - return r.Result.ExtractIntoStructPtr(v, "volume") -} +func extra(err error, raw *http.Response) (*Volume, error) { + if err != nil { + return nil, err + } -// GetResult contains the response body and error from a Get request. -type GetResult struct { - commonResult + var res Volume + err = extract.IntoStructPtr(raw.Body, &res, "volume") + return &res, err } diff --git a/openstack/evs/v3/volumes/List.go b/openstack/evs/v3/volumes/List.go new file mode 100644 index 000000000..efcb590c2 --- /dev/null +++ b/openstack/evs/v3/volumes/List.go @@ -0,0 +1,86 @@ +package volumes + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" + "github.com/opentelekomcloud/gophertelekomcloud/pagination" +) + +// ListOpts holds options for listing Volumes. It is passed to the volumes.List function. +type ListOpts struct { + // Specifies the AZ. + AvailabilityZone bool `q:"availability_zone"` + // Metadata will filter results based on specified metadata. + Metadata map[string]string `q:"metadata"` + // Specifies the disk name. The value can contain a maximum of 255 bytes. + Name string `q:"name"` + // Status will filter by the specified status. + Status string `q:"status"` + // TenantID will filter by a specific tenant/project ID. + // Setting AllTenants is required for this. + TenantID string `q:"project_id"` + // Specifies the keyword based on which the returned results are sorted. + // The value can be id, status, size, or created_at, and the default value is created_at. + Sort string `q:"sort_key"` + // Specifies the result sorting order. The default value is desc. + // desc: specifies the descending order. + // asc: specifies the ascending order. + SortDir string `q:"sort_dir"` + // Specifies the maximum number of query results that can be returned. + // The value ranges from 1 to 1000, and the default value is 1000. The returned value cannot exceed this limit. + // If the tenant has more than 50 disks in total, you are advised to use this parameter and set its value to 50 to improve the query efficiency. + // Examples are provided as follows: + // GET /v3/xxx/volumes?limit=50: Queries the 1–50 disks. GET /v3/xxx/volumes?offset=50&limit=50: Queries the 51–100 disks. + Limit int `q:"limit"` + // Specifies the offset. + // All disks after this offset will be queried. The value must be an integer greater than 0 but less than the number of disks. + Offset int `q:"offset"` + // Specifies the ID of the last record on the previous page. The returned value is the value of the item after this one. + Marker string `q:"marker"` +} + +// List returns Volumes optionally limited by the conditions provided in ListOpts. +func List(client *golangsdk.ServiceClient, opts ListOpts) pagination.Pager { + q, err := golangsdk.BuildQueryString(opts) + if err != nil { + return pagination.Pager{Err: err} + } + + // GET /v3/{project_id}/volumes + return pagination.NewPager(client, client.ServiceURL("volumes")+q.String(), + func(r pagination.PageResult) pagination.Page { + return VolumePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// VolumePage is a pagination.pager that is returned from a call to the List function. +type VolumePage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a ListResult contains no Volumes. +func (r VolumePage) IsEmpty() (bool, error) { + volumes, err := ExtractVolumes(r) + return len(volumes) == 0, err +} + +func (r VolumePage) NextPageURL() (string, error) { + var s []golangsdk.Link + err := extract.IntoSlicePtr(r.BodyReader(), &s, "volumes_links") + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(s) +} + +// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call. +func ExtractVolumes(r pagination.Page) ([]Volume, error) { + var s []Volume + err := ExtractVolumesInto(r, &s) + return s, err +} + +// ExtractVolumesInto similar to ExtractInto but operates on a `list` of volumes +func ExtractVolumesInto(r pagination.Page, v interface{}) error { + return extract.IntoSlicePtr(r.(VolumePage).BodyReader(), v, "volumes") +} diff --git a/openstack/evs/v3/volumes/Update.go b/openstack/evs/v3/volumes/Update.go new file mode 100644 index 000000000..a3d28e164 --- /dev/null +++ b/openstack/evs/v3/volumes/Update.go @@ -0,0 +1,81 @@ +package volumes + +import ( + "encoding/json" + "time" + + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +// UpdateOpts contain options for updating an existing Volume. This object is passed +// to the volumes.Update function. For more information about the parameters, see +// the Volume object. +type UpdateOpts struct { + VolumeId string + // Specifies the disk name. The value can contain a maximum of 255 bytes. + Name string `json:"name,omitempty"` + // Specifies the disk description. The value can contain a maximum of 255 bytes. + Description string `json:"description,omitempty"` + // Specifies the disk metadata. + // The length of the key or value in the metadata cannot exceed 255 bytes. + Metadata map[string]string `json:"metadata,omitempty"` + // Specifies also the disk name. You can specify either parameter name or display_name. + // If both parameters are specified, the name value is used. The value can contain a maximum of 255 bytes. + DisplayName string `json:"display_name,omitempty"` + // Specifies also the disk description. You can specify either parameter description or display_description. + // If both parameters are specified, the description value is used. The value can contain a maximum of 255 bytes. + DisplayDescription string `json:"display_description,omitempty"` +} + +// Update will update the Volume with provided information. To extract the updated +// Volume from the response, call the Extract method on the UpdateResult. +func Update(client *golangsdk.ServiceClient, opts UpdateOpts) (*Volume, error) { + b, err := build.RequestBody(opts, "volume") + if err != nil { + return nil, err + } + + // PUT /v3/{project_id}/volumes/{volume_id} + raw, err := client.Put(client.ServiceURL("volumes", opts.VolumeId), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extra(err, raw) +} + +// Attachment represents a Volume Attachment record +type Attachment struct { + // Specifies the time when the disk was attached. + // Time format: UTC YYYY-MM-DDTHH:MM:SS.XXXXXX + AttachedAt time.Time `json:"-"` + // Specifies the ID of the attachment information. + AttachmentID string `json:"attachment_id"` + // Specifies the device name. + Device string `json:"device"` + // Specifies the name of the physical host accommodating the server to which the disk is attached. + HostName string `json:"host_name"` + // Specifies the ID of the attached resource. + ID string `json:"id"` + // Specifies the ID of the server to which the disk is attached. + ServerID string `json:"server_id"` + // Specifies the disk ID. + VolumeID string `json:"volume_id"` +} + +// UnmarshalJSON is our unmarshalling helper +func (r *Attachment) UnmarshalJSON(b []byte) error { + type tmp Attachment + var s struct { + tmp + AttachedAt golangsdk.JSONRFC3339MilliNoZ `json:"attached_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Attachment(s.tmp) + + r.AttachedAt = time.Time(s.AttachedAt) + + return err +} diff --git a/openstack/evs/v3/volumes/metadata/Create.go b/openstack/evs/v3/volumes/metadata/Create.go new file mode 100644 index 000000000..cf5d97d9e --- /dev/null +++ b/openstack/evs/v3/volumes/metadata/Create.go @@ -0,0 +1,34 @@ +package metadata + +import ( + "net/http" + + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +func Create(client *golangsdk.ServiceClient, volumeId string, opts map[string]string) (map[string]string, error) { + b, err := build.RequestBody(opts, "metadata") + if err != nil { + return nil, err + } + + // POST /v3/{project_id}/volumes/{volume_id}/metadata + raw, err := client.Post(client.ServiceURL("volumes", volumeId, "metadata"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extraMetadata(err, raw) +} + +func extraMetadata(err error, raw *http.Response) (map[string]string, error) { + if err != nil { + return nil, err + } + + var res struct { + Metadata map[string]string `json:"metadata"` + } + err = extract.Into(raw.Body, &res) + return res.Metadata, err +} diff --git a/openstack/evs/v3/volumes/metadata/Delete.go b/openstack/evs/v3/volumes/metadata/Delete.go new file mode 100644 index 000000000..31c7479de --- /dev/null +++ b/openstack/evs/v3/volumes/metadata/Delete.go @@ -0,0 +1,11 @@ +package metadata + +import golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + +func Delete(client *golangsdk.ServiceClient, volumeId string, key string) (err error) { + // DELETE /v3/{project_id}/volumes/{volume_id}/metadata/{key} + _, err = client.Delete(client.ServiceURL("volumes", volumeId, "metadata", key), &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} diff --git a/openstack/evs/v3/volumes/metadata/Get.go b/openstack/evs/v3/volumes/metadata/Get.go new file mode 100644 index 000000000..fd86dba01 --- /dev/null +++ b/openstack/evs/v3/volumes/metadata/Get.go @@ -0,0 +1,9 @@ +package metadata + +import golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + +func Get(client *golangsdk.ServiceClient, volumeId string) (map[string]string, error) { + // GET /v3/{project_id}/volumes/{volume_id}/metadata + raw, err := client.Get(client.ServiceURL("volumes", volumeId, "metadata"), nil, nil) + return extraMetadata(err, raw) +} diff --git a/openstack/evs/v3/volumes/metadata/GetOne.go b/openstack/evs/v3/volumes/metadata/GetOne.go new file mode 100644 index 000000000..1eb3a5462 --- /dev/null +++ b/openstack/evs/v3/volumes/metadata/GetOne.go @@ -0,0 +1,26 @@ +package metadata + +import ( + "net/http" + + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +func GetOne(client *golangsdk.ServiceClient, volumeId string, key string) (map[string]string, error) { + // GET /v3/{project_id}/volumes/{volume_id}/metadata/{key} + raw, err := client.Get(client.ServiceURL("volumes", volumeId, "metadata", key), nil, nil) + return extraMeta(err, raw) +} + +func extraMeta(err error, raw *http.Response) (map[string]string, error) { + if err != nil { + return nil, err + } + + var res struct { + Metadata map[string]string `json:"meta"` + } + err = extract.Into(raw.Body, &res) + return res.Metadata, err +} diff --git a/openstack/evs/v3/volumes/metadata/Update.go b/openstack/evs/v3/volumes/metadata/Update.go new file mode 100644 index 000000000..cfefb04e0 --- /dev/null +++ b/openstack/evs/v3/volumes/metadata/Update.go @@ -0,0 +1,19 @@ +package metadata + +import ( + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +func Update(client *golangsdk.ServiceClient, volumeId string, opts map[string]string) (map[string]string, error) { + b, err := build.RequestBody(opts, "metadata") + if err != nil { + return nil, err + } + + // PUT /v3/{project_id}/volumes/{volume_id}/metadata + raw, err := client.Put(client.ServiceURL("volumes", volumeId, "metadata"), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extraMetadata(err, raw) +} diff --git a/openstack/evs/v3/volumes/metadata/UpdateOne.go b/openstack/evs/v3/volumes/metadata/UpdateOne.go new file mode 100644 index 000000000..94559bc48 --- /dev/null +++ b/openstack/evs/v3/volumes/metadata/UpdateOne.go @@ -0,0 +1,19 @@ +package metadata + +import ( + golangsdk "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/build" +) + +func UpdateOne(client *golangsdk.ServiceClient, volumeId string, key string, opts map[string]string) (map[string]string, error) { + b, err := build.RequestBody(opts, "meta") + if err != nil { + return nil, err + } + + // PUT /v3/{project_id}/volumes/{volume_id}/metadata/{key} + raw, err := client.Put(client.ServiceURL("volumes", volumeId, "metadata", key), b, nil, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return extraMeta(err, raw) +} diff --git a/openstack/evs/v3/volumes/requests.go b/openstack/evs/v3/volumes/requests.go deleted file mode 100644 index 41565d6f1..000000000 --- a/openstack/evs/v3/volumes/requests.go +++ /dev/null @@ -1,71 +0,0 @@ -package volumes - -import ( - "github.com/opentelekomcloud/gophertelekomcloud" -) - -// CreateOptsBuilder allows extensions to add additional parameters to the -// Create request. -type CreateOptsBuilder interface { - ToVolumeCreateMap() (map[string]interface{}, error) -} - -// CreateOpts contains options for creating a Volume. This object is passed to -// the volumes.Create function. For more information about these parameters, -// see the Volume object. -type CreateOpts struct { - // The availability zone - AvailabilityZone string `json:"availability_zone" required:"true"` - // The associated volume type - VolumeType string `json:"volume_type" required:"true"` - // The volume name - Name string `json:"name,omitempty"` - // The volume description - Description string `json:"description,omitempty"` - // The size of the volume, in GB - Size int `json:"size,omitempty"` - // The number to be created in a batch - Count int `json:"count,omitempty"` - // The backup_id - BackupID string `json:"backup_id,omitempty"` - // the ID of the existing volume snapshot - SnapshotID string `json:"snapshot_id,omitempty"` - // the ID of the image in IMS - ImageRef string `json:"imageRef,omitempty"` - // This field is no longer used. Use multiattach. - Shareable string `json:"shareable,omitempty"` - // Shared disk - Multiattach bool `json:"multiattach,omitempty"` - // One or more metadata key and value pairs to associate with the volume - Metadata map[string]string `json:"metadata,omitempty"` - // One or more tag key and value pairs to associate with the volume - Tags map[string]string `json:"tags,omitempty"` - // the enterprise project id - EnterpriseProjectID string `json:"enterprise_project_id,omitempty"` -} - -// ToVolumeCreateMap assembles a request body based on the contents of a -// CreateOpts. -func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) { - return golangsdk.BuildRequestBody(opts, "volume") -} - -// Create will create a new Volume based on the values in CreateOpts. -func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r JobResult) { - b, err := opts.ToVolumeCreateMap() - if err != nil { - r.Err = err - return - } - _, r.Err = client.Post(createURL(client), b, &r.Body, &golangsdk.RequestOpts{ - OkCodes: []int{200}, - }) - return -} - -// Get retrieves the Volume with the provided ID. To extract the Volume object -// from the response, call the Extract method on the GetResult. -func Get(client *golangsdk.ServiceClient, id string) (r GetResult) { - _, r.Err = client.Get(getURL(client, id), &r.Body, nil) - return -} diff --git a/openstack/evs/v3/volumes/results_job.go b/openstack/evs/v3/volumes/results_job.go deleted file mode 100644 index 2b1696ee2..000000000 --- a/openstack/evs/v3/volumes/results_job.go +++ /dev/null @@ -1,94 +0,0 @@ -package volumes - -import ( - "fmt" - "strings" - - "github.com/opentelekomcloud/gophertelekomcloud" -) - -type JobResponse struct { - JobID string `json:"job_id"` -} - -type JobStatus struct { - Status string `json:"status"` - Entities JobEntity `json:"entities"` - JobID string `json:"job_id"` - JobType string `json:"job_type"` - BeginTime string `json:"begin_time"` - EndTime string `json:"end_time"` - ErrorCode string `json:"error_code"` - FailReason string `json:"fail_reason"` - Message string `json:"message"` - Code string `json:"code"` - SubJobs []JobStatus `json:"sub_jobs"` -} - -type JobEntity struct { - VolumeID string `json:"volume_id"` -} - -type JobResult struct { - golangsdk.Result -} - -func (r JobResult) ExtractJobResponse() (*JobResponse, error) { - job := new(JobResponse) - err := r.ExtractInto(job) - return job, err -} - -func (r JobResult) ExtractJobStatus() (*JobStatus, error) { - job := new(JobStatus) - err := r.ExtractInto(job) - return job, err -} - -func WaitForJobSuccess(client *golangsdk.ServiceClient, secs int, jobID string) error { - - jobClient := *client - jobClient.Endpoint = strings.Replace(jobClient.Endpoint, "v3", "v1", 1) - jobClient.ResourceBase = jobClient.Endpoint - return golangsdk.WaitFor(secs, func() (bool, error) { - job := new(JobStatus) - _, err := jobClient.Get(jobClient.ServiceURL("jobs", jobID), &job, nil) - if err != nil { - return false, err - } - - if job.Status == "SUCCESS" { - return true, nil - } - if job.Status == "FAIL" { - err = fmt.Errorf("Job failed with code %s: %s.\n", job.ErrorCode, job.FailReason) - return false, err - } - - return false, nil - }) -} - -func GetJobEntity(client *golangsdk.ServiceClient, jobId string, label string) (interface{}, error) { - - if label != "volume_id" { - return nil, fmt.Errorf("Unsupported label %s in GetJobEntity.", label) - } - - jobClient := *client - jobClient.Endpoint = strings.Replace(jobClient.Endpoint, "v3", "v1", 1) - jobClient.ResourceBase = jobClient.Endpoint - job := new(JobStatus) - _, err := jobClient.Get(jobClient.ServiceURL("jobs", jobId), &job, nil) - if err != nil { - return nil, err - } - - if job.Status == "SUCCESS" { - if e := job.Entities.VolumeID; e != "" { - return e, nil - } - } - - return nil, fmt.Errorf("Unexpected conversion error in GetJobEntity.") -} diff --git a/openstack/evs/v3/volumes/urls.go b/openstack/evs/v3/volumes/urls.go deleted file mode 100644 index 88cd67f31..000000000 --- a/openstack/evs/v3/volumes/urls.go +++ /dev/null @@ -1,11 +0,0 @@ -package volumes - -import "github.com/opentelekomcloud/gophertelekomcloud" - -func createURL(c *golangsdk.ServiceClient) string { - return c.ServiceURL("cloudvolumes") -} - -func getURL(c *golangsdk.ServiceClient, id string) string { - return c.ServiceURL("os-vendor-volumes", id) -} diff --git a/openstack/evs/v3/volumetypes/Get.go b/openstack/evs/v3/volumetypes/Get.go new file mode 100644 index 000000000..bf8611e90 --- /dev/null +++ b/openstack/evs/v3/volumetypes/Get.go @@ -0,0 +1,47 @@ +package volumetypes + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" +) + +// Get retrieves the Volume Type with the provided ID. +func Get(client *golangsdk.ServiceClient, id string) (*VolumeType, error) { + raw, err := client.Get(client.ServiceURL("types", id), nil, nil) + if err != nil { + return nil, err + } + + var res VolumeType + err = extract.Into(raw.Body, &res) + return &res, err +} + +// VolumeType contains all the information associated with an OpenStack Volume Type. +type VolumeType struct { + // Unique identifier for the volume type. + ID string `json:"id"` + // Human-readable display name for the volume type. + Name string `json:"name"` + // Human-readable description for the volume type. + Description string `json:"description"` + // Arbitrary key-value pairs defined by the user. + ExtraSpecs map[string]string `json:"extra_specs"` + // Whether the volume type is publicly visible. + IsPublic bool `json:"is_public"` + // Qos Spec ID + QosSpecID string `json:"qos_specs_id"` + // Volume Type access public attribute + PublicAccess bool `json:"os-volume-type-access:is_public"` +} + +type ExtraSpecs struct { + // Reserved field + VolumeBackendName string `json:"volume_backend_name"` + // Reserved field + AvailabilityZone string `json:"availability-zone"` + // Reserved field + HWAZ string `json:"HW:availability_zone"` + // Specifies the AZs that support the current disk type. + RESKEYAZ string `json:"RESKEY:availability_zones"` +} diff --git a/openstack/evs/v3/volumetypes/List.go b/openstack/evs/v3/volumetypes/List.go new file mode 100644 index 000000000..d0c2c950c --- /dev/null +++ b/openstack/evs/v3/volumetypes/List.go @@ -0,0 +1,70 @@ +package volumetypes + +import ( + "github.com/opentelekomcloud/gophertelekomcloud" + "github.com/opentelekomcloud/gophertelekomcloud/internal/extract" + "github.com/opentelekomcloud/gophertelekomcloud/pagination" +) + +// ListOpts holds options for listing Volume Types. It is passed to the volumetypes.List function. +type ListOpts struct { + // Comma-separated list of sort keys and optional sort directions in the + // form of [:]. + Sort string `q:"sort"` + // Requests a page size of items. + Limit int `q:"limit"` + // Used in conjunction with limit to return a slice of items. + Offset int `q:"offset"` + // The ID of the last-seen item. + Marker string `q:"marker"` +} + +// ToVolumeTypeListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToVolumeTypeListQuery() (string, error) { + q, err := golangsdk.BuildQueryString(opts) + return q.String(), err +} + +// List returns Volume types. +func List(client *golangsdk.ServiceClient, opts ListOpts) pagination.Pager { + q, err := golangsdk.BuildQueryString(opts) + if err != nil { + return pagination.Pager{Err: err} + } + + return pagination.NewPager(client, client.ServiceURL("types")+q.String(), func(r pagination.PageResult) pagination.Page { + return VolumeTypePage{pagination.LinkedPageBase{PageResult: r}} + }) +} + +// VolumeTypePage is a pagination.pager that is returned from a call to the List function. +type VolumeTypePage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a ListResult contains no Volume Types. +func (r VolumeTypePage) IsEmpty() (bool, error) { + volumeTypes, err := ExtractVolumeTypes(r) + return len(volumeTypes) == 0, err +} + +func (r VolumeTypePage) NextPageURL() (string, error) { + var s []golangsdk.Link + err := extract.IntoSlicePtr(r.BodyReader(), &s, "volume_type_links") + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(s) +} + +// ExtractVolumeTypesInto similar to ExtractInto but operates on a `list` of volume types +func ExtractVolumeTypesInto(r pagination.Page, v interface{}) error { + return extract.IntoSlicePtr(r.(VolumeTypePage).BodyReader(), v, "volume_types") +} + +// ExtractVolumeTypes extracts and returns Volumes. It is used while iterating over a volumetypes.List call. +func ExtractVolumeTypes(r pagination.Page) ([]VolumeType, error) { + var s []VolumeType + err := ExtractVolumeTypesInto(r, &s) + return s, err +}