From 8bf13c569d721a96ddf5c3c000f57c051f0a6216 Mon Sep 17 00:00:00 2001 From: Sapan Date: Fri, 31 Aug 2018 19:51:13 +0530 Subject: [PATCH 1/5] add csbs backup management interfaces --- openstack/client.go | 6 + openstack/csbs/v1/backup/doc.go | 44 ++++ openstack/csbs/v1/backup/requests.go | 164 +++++++++++++++ openstack/csbs/v1/backup/results.go | 191 ++++++++++++++++++ openstack/csbs/v1/backup/testing/fixtures.go | 179 ++++++++++++++++ .../csbs/v1/backup/testing/requests_test.go | 173 ++++++++++++++++ openstack/csbs/v1/backup/urls.go | 26 +++ 7 files changed, 783 insertions(+) create mode 100644 openstack/csbs/v1/backup/doc.go create mode 100644 openstack/csbs/v1/backup/requests.go create mode 100644 openstack/csbs/v1/backup/results.go create mode 100644 openstack/csbs/v1/backup/testing/fixtures.go create mode 100644 openstack/csbs/v1/backup/testing/requests_test.go create mode 100644 openstack/csbs/v1/backup/urls.go diff --git a/openstack/client.go b/openstack/client.go index 2e436d929..adfb52706 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -709,3 +709,9 @@ func NewDeHServiceV1(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts sc, err := initClientOpts(client, eo, "deh") return sc, err } + +// NewCSBSService creates a ServiceClient that can be used to access the Cloud Server Backup service. +func NewCSBSService(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*golangsdk.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "data-protect") + return sc, err +} diff --git a/openstack/csbs/v1/backup/doc.go b/openstack/csbs/v1/backup/doc.go new file mode 100644 index 000000000..103e809b2 --- /dev/null +++ b/openstack/csbs/v1/backup/doc.go @@ -0,0 +1,44 @@ +/* +Package backup enables management and retrieval of +back up resources. + +Example to List Backup + listbackup := backup.ListOpts{ID: "7b99acfd-18c3-4f26-9d39-b4ebd2ea3e12"} + allbackups, err := backup.List(client,listbackup) + if err != nil { + panic(err) + } + fmt.Println(allbackups) + + + +Example to Create a Backup + createBackup:=backup.CreateOpts{BackupName: "c2c-backup", Description: "mybackup"} + out,err:=backup.Create(client,"fc4d5750-22e7-4798-8a46-f48f62c4c1da", "f8ddc472-cf00-4384-851e-5f2a68c33762", + createBackup).Extract() + fmt.Println(out) + fmt.Println(err) + +Example to Query if resources can be backed up + createQuery:=backup.ResourceBackupCapOpts{CheckProtectable:[]backup.ResourceCapQueryParams{{ResourceId: "069e678a-f1d1-4a38-880b-459bde82fcc6", + ResourceType: "OS::Nova::Server"}}} + out,err:=backup.QueryResourceBackupCapability(client,"fc4d5750-22e7-4798-8a46-f48f62c4c1da", + createQuery).ExtractQueryResponse() + fmt.Println(out) + fmt.Println(err) + + +Example to Delete a Backup + out:=backup.Delete(client,"fc4d5750-22e7-4798-8a46-f48f62c4c1da") + fmt.Println(out) + if err != nil { + panic(err) + } + +Example to Get Backup + result:=backup.Get(client,"7b99acfd-18c3-4f26-9d39-b4ebd2ea3e12") + out,err:=result.ExtractBackup() + fmt.Println(out) + +*/ +package backup diff --git a/openstack/csbs/v1/backup/requests.go b/openstack/csbs/v1/backup/requests.go new file mode 100644 index 000000000..da74e9a18 --- /dev/null +++ b/openstack/csbs/v1/backup/requests.go @@ -0,0 +1,164 @@ +package backup + +import ( + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +type ResourceTag struct { + Key string `json:"key"` + Value string `json:"value"` +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the attributes you want to see returned. Marker and Limit are used for pagination. +type ListOpts struct { + Status string `q:"status"` + Limit string `q:"limit"` + Marker string `q:"marker"` + Sort string `q:"sort"` + AllTenants string `q:"all_tenants"` + Name string `q:"name"` + ResourceId string `q:"resource_id"` + ResourceName string `q:"resource_name"` + PolicyId string `q:"policy_id"` + VmIp string `q:"ip"` + CheckpointId string `q:"checkpoint_id"` + ID string + ResourceType string `q:"resource_type"` +} + +// List returns collection of +// backups. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +func List(c *golangsdk.ServiceClient, opts ListOpts) ([]Backup, error) { + q, err := golangsdk.BuildQueryString(&opts) + if err != nil { + return nil, err + } + u := listURL(c) + q.String() + pages, err := pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return BackupPage{pagination.LinkedPageBase{PageResult: r}} + }).AllPages() + + allBackups, err := ExtractBackups(pages) + if err != nil { + return nil, err + } + + if opts.ID != "" { + return FilterBackupsById(allBackups, opts.ID) + } + + return allBackups, nil + +} + +func FilterBackupsById(backups []Backup, filterId string) ([]Backup, error) { + + var refinedBackups []Backup + + for _, backup := range backups { + + if filterId == backup.Id { + refinedBackups = append(refinedBackups, backup) + } + } + + return refinedBackups, nil +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToBackupCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains the options for create a Backup. This object is +// passed to backup.Create(). +type CreateOpts struct { + BackupName string `json:"backup_name,omitempty"` + Description string `json:"description,omitempty"` + ResourceType string `json:"resource_type,omitempty"` + Tags []ResourceTag `json:"tags,omitempty"` + ExtraInfo interface{} `json:"extra_info,omitempty"` +} + +// ToBackupCreateMap assembles a request body based on the contents of a +// CreateOpts. +func (opts CreateOpts) ToBackupCreateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "protect") +} + +// Create will create a new backup based on the values in CreateOpts. To extract +// the checkpoint object from the response, call the Extract method on the +// CreateResult. +func Create(client *golangsdk.ServiceClient, resourceId string, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToBackupCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(rootURL(client, resourceId), b, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// ResourceBackupCapabilityOptsBuilder allows extensions to add additional parameters to the +// QueryResourceBackupCapability request. +type ResourceBackupCapabilityOptsBuilder interface { + ToQueryResourceCreateMap() (map[string]interface{}, error) +} + +// ResourceBackupCapOpts contains the options for querying whether resources can be backed up. This object is +// passed to backup.QueryResourceBackupCapability(). +type ResourceBackupCapOpts struct { + CheckProtectable []ResourceCapQueryParams `json:"check_protectable" required:"true"` +} + +type ResourceCapQueryParams struct { + ResourceId string `json:"resource_id" required:"true"` + ResourceType string `json:"resource_type" required:"true"` +} + +// ToQueryResourceCreateMap assembles a request body based on the contents of a +// ResourceBackupCapOpts. +func (opts ResourceBackupCapOpts) ToQueryResourceCreateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "") +} + +// QueryResourceBackupCapability will query whether resources can be backed up based on the values in ResourceBackupCapOpts. To extract +// the ResourceCap object from the response, call the ExtractQueryResponse method on the +// QueryResult. +func QueryResourceBackupCapability(client *golangsdk.ServiceClient, opts ResourceBackupCapabilityOptsBuilder) (r QueryResult) { + b, err := opts.ToQueryResourceCreateMap() + if err != nil { + r.Err = err + + return + } + _, r.Err = client.Post(resourceURL(client), b, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Get will get a single backup with specific ID. To extract the Backup object from the response, +// call the ExtractBackup method on the GetResult. +func Get(client *golangsdk.ServiceClient, backupId string) (r GetResult) { + _, r.Err = client.Get(getURL(client, backupId), &r.Body, nil) + + return + +} + +// Delete will delete an existing backup. +func Delete(client *golangsdk.ServiceClient, checkpoint_id string) (r DeleteResult) { + _, r.Err = client.Delete(deleteURL(client, checkpoint_id), &golangsdk.RequestOpts{ + OkCodes: []int{200}, + JSONResponse: nil, + }) + return +} diff --git a/openstack/csbs/v1/backup/results.go b/openstack/csbs/v1/backup/results.go new file mode 100644 index 000000000..9f63ce0b0 --- /dev/null +++ b/openstack/csbs/v1/backup/results.go @@ -0,0 +1,191 @@ +package backup + +import ( + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +type Checkpoint struct { + Status string `json:"status"` + CreatedAt string `json:"created_at"` + Id string `json:"id"` + ResourceGraph string `json:"resource_graph"` + ProjectId string `json:"project_id"` + ProtectionPlan ProtectionPlan `json:"protection_plan"` +} + +type ProtectionPlan struct { + Id string `json:"id"` + Name string `json:"name"` + BackupResources []BackupResource `json:"resources"` +} + +type BackupResource struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + ExtraInfo string `json:"extra_info"` +} + +type ResourceCapability struct { + Result bool `json:"result"` + ResourceType string `json:"resource_type"` + ErrorCode string `json:"error_code"` + ErrorMsg string `json:"error_msg"` + ResourceId string `json:"resource_id"` +} + +func (r commonResult) ExtractQueryResponse() ([]ResourceCapability, error) { + var s struct { + ResourcesCaps []ResourceCapability `json:"protectable"` + } + err := r.ExtractInto(&s) + return s.ResourcesCaps, err +} + +type Backup struct { + CheckpointId string `json:"checkpoint_id"` + CreatedAt string `json:"created_at"` + ExtendInfo ExtendInfo `json:"extend_info"` + Id string `json:"id"` + Name string `json:"name"` + ResourceId string `json:"resource_id"` + Status string `json:"status"` + UpdatedAt string `json:"updated_at"` + VMMetadata VMMetadata `json:"backup_data"` + Description string `json:"description"` + Tags []ResourceTag `json:"tags"` + ResourceType string `json:"resource_type"` +} + +type ExtendInfo struct { + AutoTrigger bool `json:"auto_trigger"` + AverageSpeed int `json:"average_speed"` + CopyFrom string `json:"copy_from"` + CopyStatus string `json:"copy_status"` + FailCode FailCode `json:"fail_code"` + FailOp string `json:"fail_op"` + FailReason string `json:"fail_reason"` + ImageType string `json:"image_type"` + Incremental bool `json:"incremental"` + Progress int `json:"progress"` + ResourceAz string `json:"resource_az"` + ResourceName string `json:"resource_name"` + ResourceType string `json:"resource_type"` + Size int `json:"size"` + SpaceSavingRatio int `json:"space_saving_ratio"` + VolumeBackups []VolumeBackup `json:"volume_backups"` + FinishedAt string `json:"finished_at"` + TaskId string `json:"taskid"` + HypervisorType string `json:"hypervisor_type"` + SupportedRestoreMode string `json:"supported_restore_mode"` + Supportlld bool `json:"support_lld"` +} + +type VMMetadata struct { + RegionName string `json:"__openstack_region_name"` + CloudServiceType string `json:"cloudservicetype"` + Disk int `json:"disk"` + ImageType string `json:"imagetype"` + Ram int `json:"ram"` + Vcpus int `json:"vcpus"` + Eip string `json:"eip"` + PrivateIp string `json:"private_ip"` +} + +type FailCode struct { + Code string `json:"Code"` + Description string `json:"Description"` +} + +type VolumeBackup struct { + AverageSpeed int `json:"average_speed"` + Bootable bool `json:"bootable"` + Id string `json:"id"` + ImageType string `json:"image_type"` + Incremental bool `json:"incremental"` + Name string `json:"name"` + Size int `json:"size"` + SourceVolumeId string `json:"source_volume_id"` + SourceVolumeSize int `json:"source_volume_size"` + SpaceSavingRatio int `json:"space_saving_ratio"` + Status string `json:"status"` + SourceVolumeName string `json:"source_volume_name"` +} + +// Extract will get the checkpoint object from the commonResult +func (r commonResult) Extract() (*Checkpoint, error) { + var s struct { + Checkpoint *Checkpoint `json:"checkpoint"` + } + + err := r.ExtractInto(&s) + return s.Checkpoint, err +} + +// ExtractBackup will get the backup object from the commonResult +func (r commonResult) ExtractBackup() (*Backup, error) { + var s struct { + Backup *Backup `json:"checkpoint_item"` + } + + err := r.ExtractInto(&s) + return s.Backup, err +} + +// BackupPage is the page returned by a pager when traversing over a +// collection of backups. +type BackupPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of backups has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r BackupPage) NextPageURL() (string, error) { + var s struct { + Links []golangsdk.Link `json:"checkpoint_items_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a BackupPage struct is empty. +func (r BackupPage) IsEmpty() (bool, error) { + is, err := ExtractBackups(r) + return len(is) == 0, err +} + +// ExtractBackups accepts a Page struct, specifically a BackupPage struct, +// and extracts the elements into a slice of Backup structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractBackups(r pagination.Page) ([]Backup, error) { + var s struct { + Backups []Backup `json:"checkpoint_items"` + } + err := (r.(BackupPage)).ExtractInto(&s) + return s.Backups, err +} + +type commonResult struct { + golangsdk.Result +} + +type CreateResult struct { + commonResult +} + +type DeleteResult struct { + commonResult +} + +type GetResult struct { + commonResult +} + +type QueryResult struct { + commonResult +} diff --git a/openstack/csbs/v1/backup/testing/fixtures.go b/openstack/csbs/v1/backup/testing/fixtures.go new file mode 100644 index 000000000..167f743b5 --- /dev/null +++ b/openstack/csbs/v1/backup/testing/fixtures.go @@ -0,0 +1,179 @@ +package testing + +const ( + backupEndpoint = "/checkpoint_items" + checkpoint_item_id = "7b99acfd-18c3-4f26-9d39-b4ebd2ea3e12" +) + +var getResponse = ` +{ + "checkpoint_item": { + "status": "available", + "backup_data": { + "eip": "80.158.17.102", + "cloudservicetype": "QEMU", + "ram": 8192, + "vcpus": 4, + "__openstack_region_name": "", + "private_ip": "192.168.0.209", + "disk": 0, + "imagetype": "gold" + }, + "name": "backup-c2c", + "resource_id": "f8ddc472-cf00-4384-851e-5f2a68c33762", + "created_at": "2018-08-14T07:53:15.663766", + "checkpoint_id": "2eefe592-8424-4778-8d0d-962c8a5dd6a4", + "updated_at": "2018-08-17T04:33:58.025327", + "tags": [], + "extend_info": { + "auto_trigger": false, + "space_saving_ratio": 2, + "resource_name": "ecs-ggao", + "fail_reason": "", + "resource_az": "eu-de-02", + "image_type": "backup", + "finished_at": "2018-08-14T08:31:08.720800", + "average_speed": 19, + "copy_from": null, + "volume_backups": [ + { + "status": "available", + "space_saving_ratio": 1, + "name": "manualbk_ee6d_ecs-ggao", + "bootable": true, + "average_speed": 16, + "source_volume_size": 24, + "source_volume_id": "c14856d0-07e8-453b-a442-444086cbad04", + "incremental": false, + "snapshot_id": "c18513c3-1ab9-46e3-979b-7cad7c52e516", + "source_volume_name": "ecs-ggao", + "image_type": "backup", + "id": "2422bc5e-4cde-4420-964d-30b7347042a7", + "size": 47960 + }, + { + "status": "available", + "space_saving_ratio": 3, + "name": "manualbk_ee6d_ggao-repo-disk", + "bootable": false, + "average_speed": 22, + "source_volume_size": 100, + "source_volume_id": "313b9a39-cdc7-4413-8a1b-1888340bdc03", + "incremental": false, + "snapshot_id": "5c9dd5d5-fc70-42a2-8d58-c9e009ccf418", + "source_volume_name": "ggao-repo-disk", + "image_type": "backup", + "id": "533e5e53-4332-48cd-b920-ae4fd9b3ba94", + "size": 98224 + } + ], + "fail_code": {}, + "copy_status": "na", + "incremental": false, + "taskid": "1afcab08-9f97-11e8-9526-286ed488ca8c", + "hypervisor_type": "QEMU", + "supported_restore_mode": "backup", + "progress": 100, + "support_lld": true, + "fail_op": "", + "resource_type": "OS::Nova::Server", + "size": 146184 + }, + "id": "7b99acfd-18c3-4f26-9d39-b4ebd2ea3e12", + "resource_type": "OS::Nova::Server", + "description": "backup des" + } +} + ` + +var createRequest = `{ + "protect" : { + "backup_name" : "c2c-backup", + "description" : "mybackup" + } +}` + +var createResponse = `{ + "checkpoint": { + "status": "protecting", + "created_at": "2018-08-17T07:58:56.492307", + "id": "92dba83d-cc6f-4883-a20d-de6934510b7e", + "resource_graph": null, + "project_id": "91d687759aed45d28b5f6084bc2fa8ad", + "protection_plan": { + "id": "fake_b94f8b46-b0a1-485a-ad5b-9f8876b85495", + "resources": [ + { + "extra_info": "{}", + "type": "OS::Nova::Server", + "id": "f8ddc472-cf00-4384-851e-5f2a68c33762", + "name": "ecs-ggao" + } + ], + "name": "server protect plan for f8ddc472-cf00-4384-851e-5f2a68c33762" + } + } +}` + +var queryRequest = `{ + "check_protectable" : [ { + "resource_id" : "069e678a-f1d1-4a38-880b-459bde82fcc6", + "resource_type" : "OS::Nova::Server" + } ] +}` + +var queryResponse = `{ + "protectable": [ + { + "result": true, + "resource_type": "OS::Nova::Server", + "resource_id": "069e678a-f1d1-4a38-880b-459bde82fcc6" + } + ] +}` + +var listResponse = ` +{ + "checkpoint_items": [ + { + "status": "available", + "backup_data": { + "eip": "80.158.17.102", + "cloudservicetype": "QEMU", + "ram": 8192, + "vcpus": 4, + "__openstack_region_name": "", + "private_ip": "192.168.0.209", + "disk": 0, + "imagetype": "gold" + }, + "name": "backup-c2c", + "resource_id": "f8ddc472-cf00-4384-851e-5f2a68c33762", + "checkpoint_id": "2eefe592-8424-4778-8d0d-962c8a5dd6a4", + "extend_info": { + "auto_trigger": false, + "space_saving_ratio": 2, + "resource_name": "ecs-ggao", + "fail_reason": "", + "resource_az": "eu-de-02", + "image_type": "backup", + "finished_at": "2018-08-14T08:31:08.720800", + "average_speed": 19, + "copy_status": "na", + "incremental": false, + "taskid": "1afcab08-9f97-11e8-9526-286ed488ca8c", + "hypervisor_type": "QEMU", + "supported_restore_mode": "backup", + "progress": 100, + "support_lld": true, + "fail_op": "", + "resource_type": "OS::Nova::Server", + "size": 146184 + }, + "id": "7b99acfd-18c3-4f26-9d39-b4ebd2ea3e12", + "resource_type": "OS::Nova::Server", + "description": "backup des" + } +] +} +` diff --git a/openstack/csbs/v1/backup/testing/requests_test.go b/openstack/csbs/v1/backup/testing/requests_test.go new file mode 100644 index 000000000..13f3e6ebb --- /dev/null +++ b/openstack/csbs/v1/backup/testing/requests_test.go @@ -0,0 +1,173 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/huaweicloud/golangsdk/openstack/csbs/v1/backup" + th "github.com/huaweicloud/golangsdk/testhelper" + fake "github.com/huaweicloud/golangsdk/testhelper/client" +) + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc(backupEndpoint+"/"+checkpoint_item_id, 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.Fprintf(w, getResponse) + }) + + s, err := backup.Get(fake.ServiceClient(), checkpoint_item_id).ExtractBackup() + th.AssertNoErr(t, err) + th.AssertEquals(t, "7b99acfd-18c3-4f26-9d39-b4ebd2ea3e12", s.Id) + th.AssertEquals(t, "backup-c2c", s.Name) + th.AssertEquals(t, "available", s.Status) + th.AssertEquals(t, "2eefe592-8424-4778-8d0d-962c8a5dd6a4", s.CheckpointId) + th.AssertEquals(t, "backup des", s.Description) + th.AssertEquals(t, "OS::Nova::Server", s.ResourceType) +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.SetupHTTP() + defer th.TeardownHTTP() + th.Mux.HandleFunc("/providers/fc4d5750-22e7-4798-8a46-f48f62c4c1da/resources/f8ddc472-cf00-4384-851e-5f2a68c33762/action", + 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, createRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, createResponse) + }) + + options := &backup.CreateOpts{ + BackupName: "c2c-backup", + Description: "mybackup"} + n, err := backup.Create(fake.ServiceClient(), "f8ddc472-cf00-4384-851e-5f2a68c33762", options).Extract() + + th.AssertNoErr(t, err) + th.AssertEquals(t, n.Id, "92dba83d-cc6f-4883-a20d-de6934510b7e") + th.AssertEquals(t, n.Status, "protecting") + th.AssertEquals(t, n.ProtectionPlan.Id, "fake_b94f8b46-b0a1-485a-ad5b-9f8876b85495") + th.AssertEquals(t, n.ProtectionPlan.Name, "server protect plan for f8ddc472-cf00-4384-851e-5f2a68c33762") +} + +func TestQueryResourceCapability(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.SetupHTTP() + defer th.TeardownHTTP() + th.Mux.HandleFunc("/providers/fc4d5750-22e7-4798-8a46-f48f62c4c1da/resources/action", + 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, queryRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, queryResponse) + }) + + options := &backup.ResourceBackupCapOpts{CheckProtectable: []backup.ResourceCapQueryParams{ + {ResourceId: "069e678a-f1d1-4a38-880b-459bde82fcc6", + ResourceType: "OS::Nova::Server"}}} + n, err := backup.QueryResourceBackupCapability(fake.ServiceClient(), options).ExtractQueryResponse() + + th.AssertNoErr(t, err) + th.AssertEquals(t, n[0].ResourceType, "OS::Nova::Server") + th.AssertEquals(t, n[0].ResourceId, "069e678a-f1d1-4a38-880b-459bde82fcc6") + th.AssertEquals(t, n[0].Result, true) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/providers/fc4d5750-22e7-4798-8a46-f48f62c4c1da/checkpoints/fc4d5750-22e7-4798-8a46-f48f62c4c1da", + func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusOK) + }) + + result := backup.Delete(fake.ServiceClient(), "fc4d5750-22e7-4798-8a46-f48f62c4c1da") + th.AssertNoErr(t, result.Err) +} + +func TestList(t *testing.T) { + + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/checkpoint_items", 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.Fprintf(w, listResponse) + }) + + actual, err := backup.List(fake.ServiceClient(), backup.ListOpts{}) + if err != nil { + t.Errorf("Failed to extract backups: %v", err) + } + + expected := []backup.Backup{ + { + Status: "available", + VMMetadata: backup.VMMetadata{ + Eip: "80.158.17.102", + CloudServiceType: "QEMU", + Ram: 8192, + Vcpus: 4, + RegionName: "", + PrivateIp: "192.168.0.209", + Disk: 0, + ImageType: "gold", + }, + Name: "backup-c2c", + ResourceId: "f8ddc472-cf00-4384-851e-5f2a68c33762", + CheckpointId: "2eefe592-8424-4778-8d0d-962c8a5dd6a4", + ExtendInfo: backup.ExtendInfo{ + AutoTrigger: false, + SpaceSavingRatio: 2, + ResourceName: "ecs-ggao", + FailReason: "", + ResourceAz: "eu-de-02", + ImageType: "backup", + FinishedAt: "2018-08-14T08:31:08.720800", + AverageSpeed: 19, + CopyStatus: "na", + Incremental: false, + TaskId: "1afcab08-9f97-11e8-9526-286ed488ca8c", + HypervisorType: "QEMU", + SupportedRestoreMode: "backup", + Progress: 100, + Supportlld: true, + FailOp: "", + ResourceType: "OS::Nova::Server", + Size: 146184, + }, + Id: "7b99acfd-18c3-4f26-9d39-b4ebd2ea3e12", + ResourceType: "OS::Nova::Server", + Description: "backup des", + }, + } + + th.AssertDeepEquals(t, expected, actual) +} diff --git a/openstack/csbs/v1/backup/urls.go b/openstack/csbs/v1/backup/urls.go new file mode 100644 index 000000000..f15fdf5e3 --- /dev/null +++ b/openstack/csbs/v1/backup/urls.go @@ -0,0 +1,26 @@ +package backup + +import "github.com/huaweicloud/golangsdk" + +const rootPath = "providers" +const ProviderID = "fc4d5750-22e7-4798-8a46-f48f62c4c1da" + +func rootURL(c *golangsdk.ServiceClient, resourceid string) string { + return c.ServiceURL(rootPath, ProviderID, "resources", resourceid, "action") +} + +func resourceURL(c *golangsdk.ServiceClient) string { + return c.ServiceURL(rootPath, ProviderID, "resources", "action") +} + +func getURL(c *golangsdk.ServiceClient, checkpoint_item_id string) string { + return c.ServiceURL("checkpoint_items", checkpoint_item_id) +} + +func deleteURL(c *golangsdk.ServiceClient, checkpoint_id string) string { + return c.ServiceURL(rootPath, ProviderID, "checkpoints", checkpoint_id) +} + +func listURL(c *golangsdk.ServiceClient) string { + return c.ServiceURL("checkpoint_items") +} From 2506e11ad5e4dd7cda06e499ef365bc68c850b28 Mon Sep 17 00:00:00 2001 From: Sapan Date: Fri, 31 Aug 2018 19:52:14 +0530 Subject: [PATCH 2/5] add csbs backup policy management interfaces --- openstack/csbs/v1/policies/doc.go | 86 ++++++ openstack/csbs/v1/policies/requests.go | 233 ++++++++++++++++ openstack/csbs/v1/policies/results.go | 114 ++++++++ .../csbs/v1/policies/testing/fixtures.go | 260 ++++++++++++++++++ .../csbs/v1/policies/testing/requests_test.go | 212 ++++++++++++++ openstack/csbs/v1/policies/urls.go | 13 + 6 files changed, 918 insertions(+) create mode 100644 openstack/csbs/v1/policies/doc.go create mode 100644 openstack/csbs/v1/policies/requests.go create mode 100644 openstack/csbs/v1/policies/results.go create mode 100644 openstack/csbs/v1/policies/testing/fixtures.go create mode 100644 openstack/csbs/v1/policies/testing/requests_test.go create mode 100644 openstack/csbs/v1/policies/urls.go diff --git a/openstack/csbs/v1/policies/doc.go b/openstack/csbs/v1/policies/doc.go new file mode 100644 index 000000000..0bd153785 --- /dev/null +++ b/openstack/csbs/v1/policies/doc.go @@ -0,0 +1,86 @@ +/* +Package backup policies enables management and retrieval of +backup servers periodically. + +Example to List Backup Policies + listpolicies := policies.ListOpts{} + allpolicies, err := policies.List(client,listpolicies) + if err != nil { + panic(err) + } + fmt.Println(allpolicies) + + + +Example to Create a Backup Policy + policy:=policies.CreateOpts{ + Name : "c2c-policy", + Description : "My plan", + ProviderId : "fc4d5750-22e7-4798-8a46-f48f62c4c1da", + Parameters : policies.PolicyParam{ + Common:map[string]interface{}{}, + }, + ScheduledOperations : []policies.ScheduledOperation{ { + Name: "my-backup", + Description: "My backup policy", + Enabled: true, + OperationDefinition: policies.OperationDefinition{ + MaxBackups: "5", + }, + Trigger: policies.Trigger{ + Properties : policies.TriggerProperties{ + Pattern : "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nRRULE:FREQ=WEEKLY;BYDAY=TH;BYHOUR=12;BYMINUTE=27\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + }, + }, + OperationType: "backup", + }}, + Resources : []policies.Resource{{ + Id: "9422f270-6fcf-4ba2-9319-a007f2f63a8e", + Type: "OS::Nova::Server", + Name: "resource4" + }}, + } + out,err:=policies.Create(client,policy).Extract() + fmt.Println(out) + fmt.Println(err) + + +Example to Update a Backup Policy + updatepolicy:=policies.UpdateOpts{ + Name:"my-plan-c2c-update", + Parameters : policies.PolicyParamUpdate{ + Common:map[string]interface{}{}, + }, + ScheduledOperations:[]policies.ScheduledOperationToUpdate{{ + Id:"b70c712d-f48b-43f7-9a0f-3bab86d59149", + Name:"my-backup-policy", + Description:"My backup policy", + Enabled:true, + OperationDefinition:policies.OperationDefinitionToUpdate{ + RetentionDurationDays:-1, + MaxBackups:"20", + }, + Trigger:policies.Trigger{ + Properties:policies.TriggerProperties{ + Pattern:"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nRRULE:FREQ=WEEKLY;BYDAY=TH;BYHOUR=12;BYMINUTE=27\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"}} + } + } + } + } + out,err:=policies.Update(client,"5af626d2-19b9-4dc4-8e95-ddba008318b3",updatepolicy).Extract() + fmt.Println(out) + +Example to Delete a Backup Policy + out:=policies.Delete(client,"16d4bf9e-85b2-41e2-a482-e48ace2ad726") + fmt.Println(out) + if err != nil { + panic(err) + } + +Example to Get Backup Policy + result:=policies.Get(client,"5af626d2-19b9-4dc4-8e95-ddba008318b3") + out,err:=result.Extract() + fmt.Println(out) + +*/ +package policies diff --git a/openstack/csbs/v1/policies/requests.go b/openstack/csbs/v1/policies/requests.go new file mode 100644 index 000000000..a7eeb7ca5 --- /dev/null +++ b/openstack/csbs/v1/policies/requests.go @@ -0,0 +1,233 @@ +package policies + +import ( + "reflect" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +type ListOpts struct { + ID string `json:"id"` + Name string `q:"name"` + Status string `json:"status"` + Sort string `q:"sort"` + Limit int `q:"limit"` + Marker string `q:"marker"` + Offset int `q:"offset"` + AllTenants string `q:"all_tenants"` +} + +// List returns a Pager which allows you to iterate over a collection of +// backup policies. It accepts a ListOpts struct, which allows you to +// filter the returned collection for greater efficiency. +func List(c *golangsdk.ServiceClient, opts ListOpts) ([]BackupPolicy, error) { + q, err := golangsdk.BuildQueryString(&opts) + if err != nil { + return nil, err + } + u := rootURL(c) + q.String() + pages, err := pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return BackupPolicyPage{pagination.LinkedPageBase{PageResult: r}} + }).AllPages() + + allpolicy, err := ExtractBackupPolicies(pages) + if err != nil { + return nil, err + } + + return FilterPolicies(allpolicy, opts) +} + +func FilterPolicies(policies []BackupPolicy, opts ListOpts) ([]BackupPolicy, error) { + + var refinedPolicies []BackupPolicy + var matched bool + m := map[string]interface{}{} + + if opts.ID != "" { + m["ID"] = opts.ID + } + if opts.Status != "" { + m["Status"] = opts.Status + } + + if len(m) > 0 && len(policies) > 0 { + for _, policy := range policies { + matched = true + + for key, value := range m { + if sVal := getStructPolicyField(&policy, key); !(sVal == value) { + matched = false + } + } + + if matched { + refinedPolicies = append(refinedPolicies, policy) + } + } + + } else { + refinedPolicies = policies + } + + return refinedPolicies, nil +} + +func getStructPolicyField(v *BackupPolicy, field string) string { + r := reflect.ValueOf(v) + f := reflect.Indirect(r).FieldByName(field) + return string(f.String()) +} + +// CreateOptsBuilder allows extensions to add additional parameters to the +// Create request. +type CreateOptsBuilder interface { + ToBackupPolicyCreateMap() (map[string]interface{}, error) +} + +// CreateOpts contains the options for create a Backup Policy. This object is +// passed to policies.Create(). +type CreateOpts struct { + Description string `json:"description,omitempty"` + Name string `json:"name" required:"true"` + Parameters PolicyParam `json:"parameters" required:"true"` + ProviderId string `json:"provider_id" required:"true"` + Resources []Resource `json:"resources" required:"true"` + ScheduledOperations []ScheduledOperation `json:"scheduled_operations" required:"true"` + Tags []ResourceTag `json:"tags,omitempty"` +} + +type PolicyParam struct { + Common interface{} `json:"common,omitempty"` +} + +type Resource struct { + Id string `json:"id" required:"true"` + Type string `json:"type" required:"true"` + Name string `json:"name" required:"true"` + ExtraInfo interface{} `json:"extra_info,omitempty"` +} + +type ScheduledOperation struct { + Description string `json:"description,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Name string `json:"name,omitempty"` + OperationType string `json:"operation_type" required:"true"` + OperationDefinition OperationDefinition `json:"operation_definition" required:"true"` + Trigger Trigger `json:"trigger" required:"true"` +} + +type OperationDefinition struct { + MaxBackups string `json:"max_backups,omitempty"` + RetentionDurationDays string `json:"retention_duration_days,omitempty"` + Permanent bool `json:"permanent,omitempty"` + PlanId string `json:"plan_id,omitempty"` + ProviderId string `json:"provider_id,omitempty"` +} + +type Trigger struct { + Properties TriggerProperties `json:"properties" required:"true"` +} + +type TriggerProperties struct { + Pattern string `json:"pattern" required:"true"` +} + +type ResourceTag struct { + Key string `json:"key" required:"true"` + Value string `json:"value" required:"true"` +} + +// ToBackupPolicyCreateMap assembles a request body based on the contents of a +// CreateOpts. +func (opts CreateOpts) ToBackupPolicyCreateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "policy") +} + +// Create will create a new backup policy based on the values in CreateOpts. To extract +// the Backup object from the response, call the Extract method on the +// CreateResult. +func Create(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToBackupPolicyCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = client.Post(rootURL(client), b, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Get will get a single backup policy with specific ID. +// call the Extract method on the GetResult. +func Get(client *golangsdk.ServiceClient, policy_id string) (r GetResult) { + _, r.Err = client.Get(resourceURL(client, policy_id), &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + JSONBody: nil, + }) + + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToPoliciesUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contains the values used when updating a backup policy. +type UpdateOpts struct { + Description string `json:"description,omitempty"` + Name string `json:"name,omitempty"` + Parameters PolicyParam `json:"parameters,omitempty"` + Resources []Resource `json:"resources,omitempty"` + ScheduledOperations []ScheduledOperationToUpdate `json:"scheduled_operations,omitempty"` +} + +type ScheduledOperationToUpdate struct { + Description string `json:"description,omitempty"` + Enabled bool `json:"enabled,omitempty"` + TriggerId string `json:"trigger_id,omitempty"` + Name string `json:"name,omitempty"` + OperationDefinition OperationDefinitionToUpdate `json:"operation_definition,omitempty"` + Trigger Trigger `json:"trigger,omitempty"` + Id string `json:"id" required:"true"` +} + +type OperationDefinitionToUpdate struct { + MaxBackups string `json:"max_backups,omitempty"` + RetentionDurationDays int `json:"retention_duration_days,omitempty"` + Permanent bool `json:"permanent,omitempty"` + PlanId string `json:"plan_id,omitempty"` + ProviderId string `json:"provider_id,omitempty"` +} + +// ToPoliciesUpdateMap builds an update body based on UpdateOpts. +func (opts UpdateOpts) ToPoliciesUpdateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "policy") +} + +// Update allows backup policies to be updated. +// call the Extract method on the UpdateResult. +func Update(c *golangsdk.ServiceClient, policy_id string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToPoliciesUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(resourceURL(c, policy_id), b, &r.Body, &golangsdk.RequestOpts{ + OkCodes: []int{200}, + }) + return +} + +// Delete will delete an existing backup policy. +func Delete(client *golangsdk.ServiceClient, policy_id string) (r DeleteResult) { + _, r.Err = client.Delete(resourceURL(client, policy_id), &golangsdk.RequestOpts{ + OkCodes: []int{200}, + JSONResponse: nil, + }) + return +} diff --git a/openstack/csbs/v1/policies/results.go b/openstack/csbs/v1/policies/results.go new file mode 100644 index 000000000..b4b059b71 --- /dev/null +++ b/openstack/csbs/v1/policies/results.go @@ -0,0 +1,114 @@ +package policies + +import ( + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +type BackupPolicy struct { + CreatedAt string `json:"created_at"` + Description string `json:"description"` + ID string `json:"id"` + Name string `json:"name"` + Parameters PolicyParam `json:"parameters"` + ProjectId string `json:"project_id"` + ProviderId string `json:"provider_id"` + Resources []Resource `json:"resources"` + ScheduledOperations []ScheduledOperationResp `json:"scheduled_operations"` + Status string `json:"status"` + Tags []ResourceTag `json:"tags"` +} + +type ScheduledOperationResp struct { + Description string `json:"description"` + Enabled bool `json:"enabled"` + Name string `json:"name"` + OperationType string `json:"operation_type"` + OperationDefinition OperationDefinition `json:"operation_definition"` + Trigger TriggerResp `json:"trigger"` + ID string `json:"id"` + TriggerID string `json:"trigger_id"` +} + +type TriggerResp struct { + Properties TriggerPropertiesResp `json:"properties"` + Name string `json:"name"` + ID string `json:"id"` + Type string `json:"type"` +} + +type TriggerPropertiesResp struct { + Pattern string `json:"pattern"` + StartTime string `json:"start_time"` +} + +// Extract will get the backup policies object from the commonResult +func (r commonResult) Extract() (*BackupPolicy, error) { + var s struct { + BackupPolicy *BackupPolicy `json:"policy"` + } + + err := r.ExtractInto(&s) + return s.BackupPolicy, err +} + +// BackupPolicyPage is the page returned by a pager when traversing over a +// collection of backup policies. +type BackupPolicyPage struct { + pagination.LinkedPageBase +} + +// NextPageURL is invoked when a paginated collection of backup policies has reached +// the end of a page and the pager seeks to traverse over a new one. In order +// to do this, it needs to construct the next page's URL. +func (r BackupPolicyPage) NextPageURL() (string, error) { + var s struct { + Links []golangsdk.Link `json:"policies_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(s.Links) +} + +// IsEmpty checks whether a BackupPolicyPage struct is empty. +func (r BackupPolicyPage) IsEmpty() (bool, error) { + is, err := ExtractBackupPolicies(r) + return len(is) == 0, err +} + +// ExtractBackupPolicies accepts a Page struct, specifically a BackupPolicyPage struct, +// and extracts the elements into a slice of Policy structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractBackupPolicies(r pagination.Page) ([]BackupPolicy, error) { + var s struct { + BackupPolicies []BackupPolicy `json:"policies"` + } + err := (r.(BackupPolicyPage)).ExtractInto(&s) + return s.BackupPolicies, err +} + +type commonResult struct { + golangsdk.Result +} + +type CreateResult struct { + commonResult +} + +type GetResult struct { + commonResult +} + +type DeleteResult struct { + commonResult +} + +type UpdateResult struct { + commonResult +} + +type ListResult struct { + commonResult +} diff --git a/openstack/csbs/v1/policies/testing/fixtures.go b/openstack/csbs/v1/policies/testing/fixtures.go new file mode 100644 index 000000000..eb105bdd3 --- /dev/null +++ b/openstack/csbs/v1/policies/testing/fixtures.go @@ -0,0 +1,260 @@ +package testing + +const ( + policiesEndpoint = "/policies" + policies_id = "5af626d2-19b9-4dc4-8e95-ddba008318b3" +) + +var getResponse = ` + { + "policy": { + "status": "suspended", + "provider_id": "fc4d5750-22e7-4798-8a46-f48f62c4c1da", + "description": "My plan", + "tags": [], + "scheduled_operations": [ + { + "description": "My backup policy", + "enabled": true, + "trigger_id": "30411091-f206-48e9-8ef9-62be070ea217", + "trigger": { + "properties": { + "pattern": "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nRRULE:FREQ=WEEKLY;BYDAY=TH;BYHOUR=12;BYMINUTE=27\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + "start_time": "2018-08-20 07:31:32" + }, + "type": "time", + "id": "30411091-f206-48e9-8ef9-62be070ea217", + "name": "default" + }, + "operation_definition": { + "max_backups": "20", + "provider_id": "fc4d5750-22e7-4798-8a46-f48f62c4c1da", + "plan_id": "5af626d2-19b9-4dc4-8e95-ddba008318b3" + }, + "operation_type": "backup", + "id": "b70c712d-f48b-43f7-9a0f-3bab86d59149", + "name": "my-backup-policy" + } + ], + "id": "5af626d2-19b9-4dc4-8e95-ddba008318b3", + "name": "c2c-policy", + "parameters": { + "common": {} + }, + "created_at": "2018-08-20T07:31:32.718435", + "project_id": "91d687759aed45d28b5f6084bc2fa8ad", + "resources": [ + { + "type": "OS::Nova::Server", + "id": "cd5955b4-44c0-4f0a-ac57-2401b89cb347", + "name": "resource1" + } + ] + } +} +` +var createRequest = ` +{ + "policy" : { + "name" : "c2c-policy", + "description" : "My plan", + "provider_id" : "fc4d5750-22e7-4798-8a46-f48f62c4c1da", + "parameters": { + "common": {} + }, + "scheduled_operations" : [ { + "name" : "my-backup-policy", + "description" : "My backup policy", + "enabled" : true, + "operation_definition" : { + "max_backups" : "20" + }, + "trigger" : { + "properties" : { + "pattern" : "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nRRULE:FREQ=WEEKLY;BYDAY=TH;BYHOUR=12;BYMINUTE=27\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n" + } + }, + "operation_type" : "backup" + }], + "resources" : [ { + "id" : "cd5955b4-44c0-4f0a-ac57-2401b89cb347", + "type" : "OS::Nova::Server", + "name" : "resource1" + + }] + } +} + +` + +var createResponse = ` +{ + "policy": { + "status": "suspended", + "provider_id": "fc4d5750-22e7-4798-8a46-f48f62c4c1da", + "description": "My plan", + "tags": [], + "scheduled_operations": [ + { + "description": "My backup policy", + "enabled": true, + "trigger_id": "30411091-f206-48e9-8ef9-62be070ea217", + "trigger": { + "properties": { + "pattern": "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nRRULE:FREQ=WEEKLY;BYDAY=TH;BYHOUR=12;BYMINUTE=27\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + "start_time": "2018-08-20 07:31:32" + }, + "type": "time", + "id": "30411091-f206-48e9-8ef9-62be070ea217", + "name": "default" + }, + "operation_definition": { + "max_backups": "20", + "provider_id": "fc4d5750-22e7-4798-8a46-f48f62c4c1da", + "plan_id": "5af626d2-19b9-4dc4-8e95-ddba008318b3" + }, + "operation_type": "backup", + "id": "b70c712d-f48b-43f7-9a0f-3bab86d59149", + "name": "my-backup-policy" + } + ], + "id": "5af626d2-19b9-4dc4-8e95-ddba008318b3", + "name": "c2c-policy", + "parameters": { + "common": {} + }, + "created_at": "2018-08-20T07:31:32.718435", + "project_id": "91d687759aed45d28b5f6084bc2fa8ad", + "resources": [ + { + "type": "OS::Nova::Server", + "id": "cd5955b4-44c0-4f0a-ac57-2401b89cb347", + "name": "resource1" + } + ] + } +} +` + +var updateRequest = ` +{ + "policy" : { + "name" : "c2c-policy-update", + "parameters" : { + "common" : { + } + }, + "scheduled_operations" : [ { + "id" : "b70c712d-f48b-43f7-9a0f-3bab86d59149", + "name" : "my-backup-policy", + "description" : "My backup policy", + "enabled" : true, + "operation_definition" : { + "retention_duration_days" : -1, + "max_backups" : "20" + }, + "trigger" : { + "properties" : { + "pattern" : "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nRRULE:FREQ=WEEKLY;BYDAY=TH;BYHOUR=12;BYMINUTE=27\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n" + } + } + } + ] +} +} +` + +var updateResponse = ` +{ + "policy": { + "status": "suspended", + "provider_id": "fc4d5750-22e7-4798-8a46-f48f62c4c1da", + "description": "My plan", + "tags": [], + "scheduled_operations": [ + { + "description": "My backup policy", + "enabled": true, + "trigger": { + "properties": { + "pattern": "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nRRULE:FREQ=WEEKLY;BYDAY=TH;BYHOUR=12;BYMINUTE=27\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + "start_time": "2018-08-20 07:31:32" + } + }, + "operation_definition": { + "max_backups": "20", + "provider_id": "fc4d5750-22e7-4798-8a46-f48f62c4c1da", + "plan_id": "5af626d2-19b9-4dc4-8e95-ddba008318b3", + "retention_duration_days": "-1" + }, + "operation_type": "backup", + "id": "b70c712d-f48b-43f7-9a0f-3bab86d59149", + "name": "my-backup-policy" + } + ], + "id": "5af626d2-19b9-4dc4-8e95-ddba008318b3", + "user_id": null, + "name": "c2c-policy-update", + "parameters": { + "common": {} + }, + "created_at": "2018-08-20T07:31:32.718435", + "project_id": "91d687759aed45d28b5f6084bc2fa8ad", + "resources": [ + { + "type": "OS::Nova::Server", + "id": "cd5955b4-44c0-4f0a-ac57-2401b89cb347", + "name": "resource1" + } + ] + } +} +` +var listResponse = ` +{ + "policies": [ + { + "status": "suspended", + "provider_id": "fc4d5750-22e7-4798-8a46-f48f62c4c1da", + "description": "My plann", + "scheduled_operations": [ + { + "description": "My backup policy", + "enabled": true, + "trigger_id": "831b5e69-0b75-420c-918e-9cbcb32d97f1", + "trigger": { + "properties": { + "pattern": "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nRRULE:FREQ=WEEKLY;BYDAY=TH;BYHOUR=12;BYMINUTE=27\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n" + }, + "type": "time", + "id": "831b5e69-0b75-420c-918e-9cbcb32d97f1", + "name": "default" + }, + "operation_definition": { + "max_backups": "5", + "provider_id": "fc4d5750-22e7-4798-8a46-f48f62c4c1da", + "plan_id": "4d1ce19b-d681-4e44-a87e-c44eb9bfc4c7" + }, + "operation_type": "backup", + "id": "e7d50d4c-2f38-40a4-9f9b-c9c355a52417", + "name": "my-backupp" + } + ], + "id": "4d1ce19b-d681-4e44-a87e-c44eb9bfc4c7", + "name": "my-plan-test1", + "parameters": { + "common": {} + }, + "created_at": "2018-08-20T10:43:56.246383", + "project_id": "91d687759aed45d28b5f6084bc2fa8ad", + "resources": [ + { + "type": "OS::Nova::Server", + "id": "9422f270-6fcf-4ba2-9319-a007f2f63a8e", + "name": "resource4" + } + ] + } + ] +} +` diff --git a/openstack/csbs/v1/policies/testing/requests_test.go b/openstack/csbs/v1/policies/testing/requests_test.go new file mode 100644 index 000000000..56932abff --- /dev/null +++ b/openstack/csbs/v1/policies/testing/requests_test.go @@ -0,0 +1,212 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + "github.com/huaweicloud/golangsdk/openstack/csbs/v1/policies" + th "github.com/huaweicloud/golangsdk/testhelper" + fake "github.com/huaweicloud/golangsdk/testhelper/client" +) + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc(policiesEndpoint+"/"+policies_id, 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.Fprintf(w, getResponse) + }) + + s, err := policies.Get(fake.ServiceClient(), policies_id).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "5af626d2-19b9-4dc4-8e95-ddba008318b3", s.ID) + th.AssertEquals(t, "c2c-policy", s.Name) + th.AssertEquals(t, "OS::Nova::Server", s.Resources[0].Type) + th.AssertEquals(t, "resource1", s.Resources[0].Name) + th.AssertEquals(t, "cd5955b4-44c0-4f0a-ac57-2401b89cb347", s.Resources[0].Id) +} + +func TestCreate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.SetupHTTP() + defer th.TeardownHTTP() + th.Mux.HandleFunc(policiesEndpoint, 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, createRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, createResponse) + }) + + options := &policies.CreateOpts{ + Name: "c2c-policy", + Description: "My plan", + ProviderId: "fc4d5750-22e7-4798-8a46-f48f62c4c1da", + Parameters: policies.PolicyParam{ + Common: map[string]interface{}{}, + }, + ScheduledOperations: []policies.ScheduledOperation{{ + Name: "my-backup-policy", + Description: "My backup policy", + Enabled: true, + OperationDefinition: policies.OperationDefinition{ + MaxBackups: "20", + }, + Trigger: policies.Trigger{ + Properties: policies.TriggerProperties{ + Pattern: "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nRRULE:FREQ=WEEKLY;BYDAY=TH;BYHOUR=12;BYMINUTE=27\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + }, + }, + OperationType: "backup", + }}, + Resources: []policies.Resource{{ + Id: "cd5955b4-44c0-4f0a-ac57-2401b89cb347", + Type: "OS::Nova::Server", + Name: "resource1"}}, + } + n, err := policies.Create(fake.ServiceClient(), options).Extract() + + th.AssertNoErr(t, err) + th.AssertEquals(t, n.ID, "5af626d2-19b9-4dc4-8e95-ddba008318b3") + th.AssertEquals(t, n.Status, "suspended") + th.AssertEquals(t, "OS::Nova::Server", n.Resources[0].Type) + th.AssertEquals(t, "resource1", n.Resources[0].Name) + th.AssertEquals(t, "cd5955b4-44c0-4f0a-ac57-2401b89cb347", n.Resources[0].Id) +} + +func TestDelete(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc(policiesEndpoint+"/"+policies_id, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusOK) + }) + + result := policies.Delete(fake.ServiceClient(), policies_id) + + th.AssertNoErr(t, result.Err) +} + +func TestUpdate(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc(policiesEndpoint+"/"+policies_id, 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.TestHeader(t, r, "Accept", "application/json") + th.TestJSONRequest(t, r, updateRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, updateResponse) + }) + + options := &policies.UpdateOpts{ + Name: "c2c-policy-update", + Parameters: policies.PolicyParam{ + Common: map[string]interface{}{}, + }, + ScheduledOperations: []policies.ScheduledOperationToUpdate{{ + Name: "my-backup-policy", + Description: "My backup policy", + Enabled: true, + Id: "b70c712d-f48b-43f7-9a0f-3bab86d59149", + OperationDefinition: policies.OperationDefinitionToUpdate{ + RetentionDurationDays: -1, + MaxBackups: "20", + }, + Trigger: policies.Trigger{ + Properties: policies.TriggerProperties{ + Pattern: "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nRRULE:FREQ=WEEKLY;BYDAY=TH;BYHOUR=12;BYMINUTE=27\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + }, + }, + }}, + } + n, err := policies.Update(fake.ServiceClient(), policies_id, options).Extract() + + th.AssertNoErr(t, err) + th.AssertEquals(t, n.Name, "c2c-policy-update") + th.AssertEquals(t, n.ID, "5af626d2-19b9-4dc4-8e95-ddba008318b3") +} + +func TestList(t *testing.T) { + + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc(policiesEndpoint, 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.Fprintf(w, listResponse) + }) + + actual, err := policies.List(fake.ServiceClient(), policies.ListOpts{}) + if err != nil { + t.Errorf("Failed to extract backup policies: %v", err) + } + + expected := []policies.BackupPolicy{ + { + Status: "suspended", + ProviderId: "fc4d5750-22e7-4798-8a46-f48f62c4c1da", + Description: "My plann", + ScheduledOperations: []policies.ScheduledOperationResp{{ + + Description: "My backup policy", + Enabled: true, + TriggerID: "831b5e69-0b75-420c-918e-9cbcb32d97f1", + Trigger: policies.TriggerResp{ + Properties: policies.TriggerPropertiesResp{ + Pattern: "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nRRULE:FREQ=WEEKLY;BYDAY=TH;BYHOUR=12;BYMINUTE=27\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + }, + Type: "time", + ID: "831b5e69-0b75-420c-918e-9cbcb32d97f1", + Name: "default", + }, + OperationDefinition: policies.OperationDefinition{ + MaxBackups: "5", + ProviderId: "fc4d5750-22e7-4798-8a46-f48f62c4c1da", + PlanId: "4d1ce19b-d681-4e44-a87e-c44eb9bfc4c7", + }, + OperationType: "backup", + ID: "e7d50d4c-2f38-40a4-9f9b-c9c355a52417", + Name: "my-backupp", + }, + }, + ID: "4d1ce19b-d681-4e44-a87e-c44eb9bfc4c7", + Name: "my-plan-test1", + Parameters: policies.PolicyParam{ + Common: map[string]interface{}{}, + }, + CreatedAt: "2018-08-20T10:43:56.246383", + ProjectId: "91d687759aed45d28b5f6084bc2fa8ad", + Resources: []policies.Resource{ + { + Type: "OS::Nova::Server", + Id: "9422f270-6fcf-4ba2-9319-a007f2f63a8e", + Name: "resource4", + }, + }, + }, + } + + th.AssertDeepEquals(t, expected, actual) +} diff --git a/openstack/csbs/v1/policies/urls.go b/openstack/csbs/v1/policies/urls.go new file mode 100644 index 000000000..77eb0fb09 --- /dev/null +++ b/openstack/csbs/v1/policies/urls.go @@ -0,0 +1,13 @@ +package policies + +import "github.com/huaweicloud/golangsdk" + +const rootPath = "policies" + +func rootURL(c *golangsdk.ServiceClient) string { + return c.ServiceURL(rootPath) +} + +func resourceURL(c *golangsdk.ServiceClient, policyid string) string { + return c.ServiceURL(rootPath, policyid) +} From 06680b3ac61a83aa1860007821b01e21306a3f3f Mon Sep 17 00:00:00 2001 From: Sapan Date: Mon, 3 Sep 2018 12:48:55 +0530 Subject: [PATCH 3/5] update csbs backuppolicy operation def req and resp data type --- openstack/csbs/v1/policies/doc.go | 6 ++--- openstack/csbs/v1/policies/requests.go | 26 +++++++------------ openstack/csbs/v1/policies/results.go | 24 +++++++++++------ .../csbs/v1/policies/testing/fixtures.go | 4 +-- .../csbs/v1/policies/testing/requests_test.go | 8 +++--- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/openstack/csbs/v1/policies/doc.go b/openstack/csbs/v1/policies/doc.go index 0bd153785..c7e674c01 100644 --- a/openstack/csbs/v1/policies/doc.go +++ b/openstack/csbs/v1/policies/doc.go @@ -25,7 +25,7 @@ Example to Create a Backup Policy Description: "My backup policy", Enabled: true, OperationDefinition: policies.OperationDefinition{ - MaxBackups: "5", + MaxBackups: 5, }, Trigger: policies.Trigger{ Properties : policies.TriggerProperties{ @@ -56,9 +56,9 @@ Example to Update a Backup Policy Name:"my-backup-policy", Description:"My backup policy", Enabled:true, - OperationDefinition:policies.OperationDefinitionToUpdate{ + OperationDefinition:policies.OperationDefinition{ RetentionDurationDays:-1, - MaxBackups:"20", + MaxBackups:20, }, Trigger:policies.Trigger{ Properties:policies.TriggerProperties{ diff --git a/openstack/csbs/v1/policies/requests.go b/openstack/csbs/v1/policies/requests.go index a7eeb7ca5..65dc8fc2f 100644 --- a/openstack/csbs/v1/policies/requests.go +++ b/openstack/csbs/v1/policies/requests.go @@ -119,8 +119,8 @@ type ScheduledOperation struct { } type OperationDefinition struct { - MaxBackups string `json:"max_backups,omitempty"` - RetentionDurationDays string `json:"retention_duration_days,omitempty"` + MaxBackups int `json:"max_backups,omitempty"` + RetentionDurationDays int `json:"retention_duration_days,omitempty"` Permanent bool `json:"permanent,omitempty"` PlanId string `json:"plan_id,omitempty"` ProviderId string `json:"provider_id,omitempty"` @@ -187,21 +187,13 @@ type UpdateOpts struct { } type ScheduledOperationToUpdate struct { - Description string `json:"description,omitempty"` - Enabled bool `json:"enabled,omitempty"` - TriggerId string `json:"trigger_id,omitempty"` - Name string `json:"name,omitempty"` - OperationDefinition OperationDefinitionToUpdate `json:"operation_definition,omitempty"` - Trigger Trigger `json:"trigger,omitempty"` - Id string `json:"id" required:"true"` -} - -type OperationDefinitionToUpdate struct { - MaxBackups string `json:"max_backups,omitempty"` - RetentionDurationDays int `json:"retention_duration_days,omitempty"` - Permanent bool `json:"permanent,omitempty"` - PlanId string `json:"plan_id,omitempty"` - ProviderId string `json:"provider_id,omitempty"` + Description string `json:"description,omitempty"` + Enabled bool `json:"enabled,omitempty"` + TriggerId string `json:"trigger_id,omitempty"` + Name string `json:"name,omitempty"` + OperationDefinition OperationDefinition `json:"operation_definition,omitempty"` + Trigger Trigger `json:"trigger,omitempty"` + Id string `json:"id" required:"true"` } // ToPoliciesUpdateMap builds an update body based on UpdateOpts. diff --git a/openstack/csbs/v1/policies/results.go b/openstack/csbs/v1/policies/results.go index b4b059b71..c0b3e191c 100644 --- a/openstack/csbs/v1/policies/results.go +++ b/openstack/csbs/v1/policies/results.go @@ -20,14 +20,22 @@ type BackupPolicy struct { } type ScheduledOperationResp struct { - Description string `json:"description"` - Enabled bool `json:"enabled"` - Name string `json:"name"` - OperationType string `json:"operation_type"` - OperationDefinition OperationDefinition `json:"operation_definition"` - Trigger TriggerResp `json:"trigger"` - ID string `json:"id"` - TriggerID string `json:"trigger_id"` + Description string `json:"description"` + Enabled bool `json:"enabled"` + Name string `json:"name"` + OperationType string `json:"operation_type"` + OperationDefinition OperationDefinitionResp `json:"operation_definition"` + Trigger TriggerResp `json:"trigger"` + ID string `json:"id"` + TriggerID string `json:"trigger_id"` +} + +type OperationDefinitionResp struct { + MaxBackups string `json:"max_backups,omitempty"` + RetentionDurationDays string `json:"retention_duration_days,omitempty"` + Permanent bool `json:"permanent,omitempty"` + PlanId string `json:"plan_id,omitempty"` + ProviderId string `json:"provider_id,omitempty"` } type TriggerResp struct { diff --git a/openstack/csbs/v1/policies/testing/fixtures.go b/openstack/csbs/v1/policies/testing/fixtures.go index eb105bdd3..78120516e 100644 --- a/openstack/csbs/v1/policies/testing/fixtures.go +++ b/openstack/csbs/v1/policies/testing/fixtures.go @@ -67,7 +67,7 @@ var createRequest = ` "description" : "My backup policy", "enabled" : true, "operation_definition" : { - "max_backups" : "20" + "max_backups" : 20 }, "trigger" : { "properties" : { @@ -151,7 +151,7 @@ var updateRequest = ` "enabled" : true, "operation_definition" : { "retention_duration_days" : -1, - "max_backups" : "20" + "max_backups" : 20 }, "trigger" : { "properties" : { diff --git a/openstack/csbs/v1/policies/testing/requests_test.go b/openstack/csbs/v1/policies/testing/requests_test.go index 56932abff..eba7512b9 100644 --- a/openstack/csbs/v1/policies/testing/requests_test.go +++ b/openstack/csbs/v1/policies/testing/requests_test.go @@ -62,7 +62,7 @@ func TestCreate(t *testing.T) { Description: "My backup policy", Enabled: true, OperationDefinition: policies.OperationDefinition{ - MaxBackups: "20", + MaxBackups: 20, }, Trigger: policies.Trigger{ Properties: policies.TriggerProperties{ @@ -126,9 +126,9 @@ func TestUpdate(t *testing.T) { Description: "My backup policy", Enabled: true, Id: "b70c712d-f48b-43f7-9a0f-3bab86d59149", - OperationDefinition: policies.OperationDefinitionToUpdate{ + OperationDefinition: policies.OperationDefinition{ RetentionDurationDays: -1, - MaxBackups: "20", + MaxBackups: 20, }, Trigger: policies.Trigger{ Properties: policies.TriggerProperties{ @@ -181,7 +181,7 @@ func TestList(t *testing.T) { ID: "831b5e69-0b75-420c-918e-9cbcb32d97f1", Name: "default", }, - OperationDefinition: policies.OperationDefinition{ + OperationDefinition: policies.OperationDefinitionResp{ MaxBackups: "5", ProviderId: "fc4d5750-22e7-4798-8a46-f48f62c4c1da", PlanId: "4d1ce19b-d681-4e44-a87e-c44eb9bfc4c7", From 72574a328a12b8375d9957b134608f76fb789b84 Mon Sep 17 00:00:00 2001 From: Sapan Date: Mon, 10 Sep 2018 12:23:51 +0530 Subject: [PATCH 4/5] fix cs backup policy parameter response type --- openstack/csbs/v1/policies/results.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openstack/csbs/v1/policies/results.go b/openstack/csbs/v1/policies/results.go index c0b3e191c..7d4ffc20e 100644 --- a/openstack/csbs/v1/policies/results.go +++ b/openstack/csbs/v1/policies/results.go @@ -31,11 +31,11 @@ type ScheduledOperationResp struct { } type OperationDefinitionResp struct { - MaxBackups string `json:"max_backups,omitempty"` - RetentionDurationDays string `json:"retention_duration_days,omitempty"` - Permanent bool `json:"permanent,omitempty"` - PlanId string `json:"plan_id,omitempty"` - ProviderId string `json:"provider_id,omitempty"` + MaxBackups string `json:"max_backups"` + RetentionDurationDays string `json:"retention_duration_days"` + Permanent string `json:"permanent"` + PlanId string `json:"plan_id"` + ProviderId string `json:"provider_id"` } type TriggerResp struct { From cb5a61efd938be71d6d2ea0ed33c2c10698aee92 Mon Sep 17 00:00:00 2001 From: Sapan Date: Fri, 14 Sep 2018 19:13:33 +0530 Subject: [PATCH 5/5] update csbs result structure for user ease --- openstack/csbs/v1/backup/results.go | 94 +++++++++++++++- .../csbs/v1/backup/testing/requests_test.go | 6 +- openstack/csbs/v1/policies/requests.go | 6 +- openstack/csbs/v1/policies/results.go | 102 ++++++++++++++++-- .../csbs/v1/policies/testing/fixtures.go | 6 +- .../csbs/v1/policies/testing/requests_test.go | 7 +- 6 files changed, 202 insertions(+), 19 deletions(-) diff --git a/openstack/csbs/v1/backup/results.go b/openstack/csbs/v1/backup/results.go index 9f63ce0b0..5ea515921 100644 --- a/openstack/csbs/v1/backup/results.go +++ b/openstack/csbs/v1/backup/results.go @@ -1,13 +1,17 @@ package backup import ( + "encoding/json" + "strconv" + "time" + "github.com/huaweicloud/golangsdk" "github.com/huaweicloud/golangsdk/pagination" ) type Checkpoint struct { Status string `json:"status"` - CreatedAt string `json:"created_at"` + CreatedAt time.Time `json:"-"` Id string `json:"id"` ResourceGraph string `json:"resource_graph"` ProjectId string `json:"project_id"` @@ -24,7 +28,7 @@ type BackupResource struct { ID string `json:"id"` Type string `json:"type"` Name string `json:"name"` - ExtraInfo string `json:"extra_info"` + ExtraInfo string `json:"-"` } type ResourceCapability struct { @@ -35,6 +39,48 @@ type ResourceCapability struct { ResourceId string `json:"resource_id"` } +// UnmarshalJSON helps to unmarshal Checkpoint fields into needed values. +func (r *Checkpoint) UnmarshalJSON(b []byte) error { + type tmp Checkpoint + var s struct { + tmp + CreatedAt golangsdk.JSONRFC3339MilliNoZ `json:"created_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Checkpoint(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + + return err +} + +// UnmarshalJSON helps to unmarshal BackupResource fields into needed values. +func (r *BackupResource) UnmarshalJSON(b []byte) error { + type tmp BackupResource + var s struct { + tmp + ExtraInfo interface{} `json:"extra_info"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = BackupResource(s.tmp) + + switch t := s.ExtraInfo.(type) { + case float64: + r.ID = strconv.FormatFloat(t, 'f', -1, 64) + case string: + r.ID = t + } + + return err +} + func (r commonResult) ExtractQueryResponse() ([]ResourceCapability, error) { var s struct { ResourcesCaps []ResourceCapability `json:"protectable"` @@ -45,13 +91,13 @@ func (r commonResult) ExtractQueryResponse() ([]ResourceCapability, error) { type Backup struct { CheckpointId string `json:"checkpoint_id"` - CreatedAt string `json:"created_at"` + CreatedAt time.Time `json:"-"` ExtendInfo ExtendInfo `json:"extend_info"` Id string `json:"id"` Name string `json:"name"` ResourceId string `json:"resource_id"` Status string `json:"status"` - UpdatedAt string `json:"updated_at"` + UpdatedAt time.Time `json:"-"` VMMetadata VMMetadata `json:"backup_data"` Description string `json:"description"` Tags []ResourceTag `json:"tags"` @@ -75,7 +121,7 @@ type ExtendInfo struct { Size int `json:"size"` SpaceSavingRatio int `json:"space_saving_ratio"` VolumeBackups []VolumeBackup `json:"volume_backups"` - FinishedAt string `json:"finished_at"` + FinishedAt time.Time `json:"-"` TaskId string `json:"taskid"` HypervisorType string `json:"hypervisor_type"` SupportedRestoreMode string `json:"supported_restore_mode"` @@ -113,6 +159,44 @@ type VolumeBackup struct { SourceVolumeName string `json:"source_volume_name"` } +// UnmarshalJSON helps to unmarshal Backup fields into needed values. +func (r *Backup) UnmarshalJSON(b []byte) error { + type tmp Backup + 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 = Backup(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + return err +} + +// UnmarshalJSON helps to unmarshal ExtendInfo fields into needed values. +func (r *ExtendInfo) UnmarshalJSON(b []byte) error { + type tmp ExtendInfo + var s struct { + tmp + FinishedAt golangsdk.JSONRFC3339MilliNoZ `json:"finished_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = ExtendInfo(s.tmp) + + r.FinishedAt = time.Time(s.FinishedAt) + + return err +} + // Extract will get the checkpoint object from the commonResult func (r commonResult) Extract() (*Checkpoint, error) { var s struct { diff --git a/openstack/csbs/v1/backup/testing/requests_test.go b/openstack/csbs/v1/backup/testing/requests_test.go index 13f3e6ebb..ce636c1a1 100644 --- a/openstack/csbs/v1/backup/testing/requests_test.go +++ b/openstack/csbs/v1/backup/testing/requests_test.go @@ -5,6 +5,9 @@ import ( "net/http" "testing" + "time" + + "github.com/huaweicloud/golangsdk" "github.com/huaweicloud/golangsdk/openstack/csbs/v1/backup" th "github.com/huaweicloud/golangsdk/testhelper" fake "github.com/huaweicloud/golangsdk/testhelper/client" @@ -127,6 +130,7 @@ func TestList(t *testing.T) { t.Errorf("Failed to extract backups: %v", err) } + var FinishedAt, _ = time.Parse(golangsdk.RFC3339MilliNoZ, "2018-08-14T08:31:08.720800") expected := []backup.Backup{ { Status: "available", @@ -150,7 +154,7 @@ func TestList(t *testing.T) { FailReason: "", ResourceAz: "eu-de-02", ImageType: "backup", - FinishedAt: "2018-08-14T08:31:08.720800", + FinishedAt: FinishedAt, AverageSpeed: 19, CopyStatus: "na", Incremental: false, diff --git a/openstack/csbs/v1/policies/requests.go b/openstack/csbs/v1/policies/requests.go index 65dc8fc2f..5dc89857e 100644 --- a/openstack/csbs/v1/policies/requests.go +++ b/openstack/csbs/v1/policies/requests.go @@ -111,7 +111,7 @@ type Resource struct { type ScheduledOperation struct { Description string `json:"description,omitempty"` - Enabled bool `json:"enabled,omitempty"` + Enabled bool `json:"enabled"` Name string `json:"name,omitempty"` OperationType string `json:"operation_type" required:"true"` OperationDefinition OperationDefinition `json:"operation_definition" required:"true"` @@ -121,7 +121,7 @@ type ScheduledOperation struct { type OperationDefinition struct { MaxBackups int `json:"max_backups,omitempty"` RetentionDurationDays int `json:"retention_duration_days,omitempty"` - Permanent bool `json:"permanent,omitempty"` + Permanent bool `json:"permanent"` PlanId string `json:"plan_id,omitempty"` ProviderId string `json:"provider_id,omitempty"` } @@ -188,7 +188,7 @@ type UpdateOpts struct { type ScheduledOperationToUpdate struct { Description string `json:"description,omitempty"` - Enabled bool `json:"enabled,omitempty"` + Enabled bool `json:"enabled"` TriggerId string `json:"trigger_id,omitempty"` Name string `json:"name,omitempty"` OperationDefinition OperationDefinition `json:"operation_definition,omitempty"` diff --git a/openstack/csbs/v1/policies/results.go b/openstack/csbs/v1/policies/results.go index 7d4ffc20e..31598336a 100644 --- a/openstack/csbs/v1/policies/results.go +++ b/openstack/csbs/v1/policies/results.go @@ -1,12 +1,17 @@ package policies import ( + "encoding/json" + "strconv" + + "time" + "github.com/huaweicloud/golangsdk" "github.com/huaweicloud/golangsdk/pagination" ) type BackupPolicy struct { - CreatedAt string `json:"created_at"` + CreatedAt time.Time `json:"-"` Description string `json:"description"` ID string `json:"id"` Name string `json:"name"` @@ -31,9 +36,9 @@ type ScheduledOperationResp struct { } type OperationDefinitionResp struct { - MaxBackups string `json:"max_backups"` - RetentionDurationDays string `json:"retention_duration_days"` - Permanent string `json:"permanent"` + MaxBackups int `json:"-"` + RetentionDurationDays int `json:"-"` + Permanent bool `json:"-"` PlanId string `json:"plan_id"` ProviderId string `json:"provider_id"` } @@ -46,8 +51,93 @@ type TriggerResp struct { } type TriggerPropertiesResp struct { - Pattern string `json:"pattern"` - StartTime string `json:"start_time"` + Pattern string `json:"pattern"` + StartTime time.Time `json:"-"` +} + +// UnmarshalJSON helps to unmarshal BackupPolicy fields into needed values. +func (r *BackupPolicy) UnmarshalJSON(b []byte) error { + type tmp BackupPolicy + var s struct { + tmp + CreatedAt golangsdk.JSONRFC3339MilliNoZ `json:"created_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = BackupPolicy(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + + return err +} + +// UnmarshalJSON helps to unmarshal TriggerPropertiesResp fields into needed values. +func (r *TriggerPropertiesResp) UnmarshalJSON(b []byte) error { + type tmp TriggerPropertiesResp + var s struct { + tmp + StartTime golangsdk.JSONRFC3339ZNoTNoZ `json:"start_time"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = TriggerPropertiesResp(s.tmp) + + r.StartTime = time.Time(s.StartTime) + + return err +} + +// UnmarshalJSON helps to unmarshal OperationDefinitionResp fields into needed values. +func (r *OperationDefinitionResp) UnmarshalJSON(b []byte) error { + type tmp OperationDefinitionResp + var s struct { + tmp + MaxBackups string `json:"max_backups"` + RetentionDurationDays string `json:"retention_duration_days"` + Permanent string `json:"permanent"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + *r = OperationDefinitionResp(s.tmp) + + switch s.MaxBackups { + case "": + r.MaxBackups = 0 + default: + r.MaxBackups, err = strconv.Atoi(s.MaxBackups) + if err != nil { + return err + } + } + + switch s.RetentionDurationDays { + case "": + r.RetentionDurationDays = 0 + default: + r.RetentionDurationDays, err = strconv.Atoi(s.RetentionDurationDays) + if err != nil { + return err + } + } + + switch s.Permanent { + case "": + r.Permanent = false + default: + r.Permanent, err = strconv.ParseBool(s.Permanent) + if err != nil { + return err + } + } + + return err } // Extract will get the backup policies object from the commonResult diff --git a/openstack/csbs/v1/policies/testing/fixtures.go b/openstack/csbs/v1/policies/testing/fixtures.go index 78120516e..50dbe2b74 100644 --- a/openstack/csbs/v1/policies/testing/fixtures.go +++ b/openstack/csbs/v1/policies/testing/fixtures.go @@ -67,7 +67,8 @@ var createRequest = ` "description" : "My backup policy", "enabled" : true, "operation_definition" : { - "max_backups" : 20 + "max_backups" : 20, + "permanent" : false }, "trigger" : { "properties" : { @@ -151,7 +152,8 @@ var updateRequest = ` "enabled" : true, "operation_definition" : { "retention_duration_days" : -1, - "max_backups" : 20 + "max_backups" : 20, + "permanent" : false }, "trigger" : { "properties" : { diff --git a/openstack/csbs/v1/policies/testing/requests_test.go b/openstack/csbs/v1/policies/testing/requests_test.go index eba7512b9..ef61f8943 100644 --- a/openstack/csbs/v1/policies/testing/requests_test.go +++ b/openstack/csbs/v1/policies/testing/requests_test.go @@ -4,7 +4,9 @@ import ( "fmt" "net/http" "testing" + "time" + "github.com/huaweicloud/golangsdk" "github.com/huaweicloud/golangsdk/openstack/csbs/v1/policies" th "github.com/huaweicloud/golangsdk/testhelper" fake "github.com/huaweicloud/golangsdk/testhelper/client" @@ -163,6 +165,7 @@ func TestList(t *testing.T) { t.Errorf("Failed to extract backup policies: %v", err) } + var CreatedAt, _ = time.Parse(golangsdk.RFC3339MilliNoZ, "2018-08-20T10:43:56.246383") expected := []policies.BackupPolicy{ { Status: "suspended", @@ -182,7 +185,7 @@ func TestList(t *testing.T) { Name: "default", }, OperationDefinition: policies.OperationDefinitionResp{ - MaxBackups: "5", + MaxBackups: 5, ProviderId: "fc4d5750-22e7-4798-8a46-f48f62c4c1da", PlanId: "4d1ce19b-d681-4e44-a87e-c44eb9bfc4c7", }, @@ -196,7 +199,7 @@ func TestList(t *testing.T) { Parameters: policies.PolicyParam{ Common: map[string]interface{}{}, }, - CreatedAt: "2018-08-20T10:43:56.246383", + CreatedAt: CreatedAt, ProjectId: "91d687759aed45d28b5f6084bc2fa8ad", Resources: []policies.Resource{ {