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..5ea515921 --- /dev/null +++ b/openstack/csbs/v1/backup/results.go @@ -0,0 +1,275 @@ +package backup + +import ( + "encoding/json" + "strconv" + "time" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +type Checkpoint struct { + Status string `json:"status"` + CreatedAt time.Time `json:"-"` + 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:"-"` +} + +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"` +} + +// 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"` + } + err := r.ExtractInto(&s) + return s.ResourcesCaps, err +} + +type Backup struct { + CheckpointId string `json:"checkpoint_id"` + 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 time.Time `json:"-"` + 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 time.Time `json:"-"` + 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"` +} + +// 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 { + 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..ce636c1a1 --- /dev/null +++ b/openstack/csbs/v1/backup/testing/requests_test.go @@ -0,0 +1,177 @@ +package testing + +import ( + "fmt" + "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" +) + +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) + } + + var FinishedAt, _ = time.Parse(golangsdk.RFC3339MilliNoZ, "2018-08-14T08:31:08.720800") + 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: FinishedAt, + 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") +} diff --git a/openstack/csbs/v1/policies/doc.go b/openstack/csbs/v1/policies/doc.go new file mode 100644 index 000000000..c7e674c01 --- /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.OperationDefinition{ + 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..5dc89857e --- /dev/null +++ b/openstack/csbs/v1/policies/requests.go @@ -0,0 +1,225 @@ +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"` + 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 int `json:"max_backups,omitempty"` + RetentionDurationDays int `json:"retention_duration_days,omitempty"` + Permanent bool `json:"permanent"` + 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"` + 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. +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..31598336a --- /dev/null +++ b/openstack/csbs/v1/policies/results.go @@ -0,0 +1,212 @@ +package policies + +import ( + "encoding/json" + "strconv" + + "time" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +type BackupPolicy struct { + CreatedAt time.Time `json:"-"` + 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 OperationDefinitionResp `json:"operation_definition"` + Trigger TriggerResp `json:"trigger"` + ID string `json:"id"` + TriggerID string `json:"trigger_id"` +} + +type OperationDefinitionResp struct { + MaxBackups int `json:"-"` + RetentionDurationDays int `json:"-"` + Permanent bool `json:"-"` + PlanId string `json:"plan_id"` + ProviderId string `json:"provider_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 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 +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..50dbe2b74 --- /dev/null +++ b/openstack/csbs/v1/policies/testing/fixtures.go @@ -0,0 +1,262 @@ +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, + "permanent" : false + }, + "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, + "permanent" : false + }, + "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..ef61f8943 --- /dev/null +++ b/openstack/csbs/v1/policies/testing/requests_test.go @@ -0,0 +1,215 @@ +package testing + +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" +) + +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.OperationDefinition{ + 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) + } + + var CreatedAt, _ = time.Parse(golangsdk.RFC3339MilliNoZ, "2018-08-20T10:43:56.246383") + 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.OperationDefinitionResp{ + 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: CreatedAt, + 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) +}