From f50b059234b96f837e87edfc8ff813d7c3ce254b Mon Sep 17 00:00:00 2001 From: ShiChangkuo Date: Mon, 14 Oct 2019 14:49:36 +0800 Subject: [PATCH] Add ims v2 interface Add ims v2 interface, including create and list. Signed-off-by: ShiChangkuo --- openstack/ims/v2/cloudimages/requests.go | 222 ++++++++++++++++++++ openstack/ims/v2/cloudimages/results.go | 157 ++++++++++++++ openstack/ims/v2/cloudimages/results_job.go | 88 ++++++++ openstack/ims/v2/cloudimages/urls.go | 42 ++++ 4 files changed, 509 insertions(+) create mode 100644 openstack/ims/v2/cloudimages/requests.go create mode 100644 openstack/ims/v2/cloudimages/results.go create mode 100644 openstack/ims/v2/cloudimages/results_job.go create mode 100644 openstack/ims/v2/cloudimages/urls.go diff --git a/openstack/ims/v2/cloudimages/requests.go b/openstack/ims/v2/cloudimages/requests.go new file mode 100644 index 000000000..5942eff29 --- /dev/null +++ b/openstack/ims/v2/cloudimages/requests.go @@ -0,0 +1,222 @@ +package cloudimages + +import ( + "fmt" + "net/url" + "time" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +type ListOptsBuilder interface { + ToImageListQuery() (string, error) +} + +type ListOpts struct { + Isregistered string `q:"__isregistered"` + Imagetype string `q:"__imagetype"` + Protected bool `q:"protected"` + Visibility string `q:"visibility"` + Owner string `q:"owner"` + ID string `q:"id"` + Status string `q:"status"` + Name string `q:"name"` + ContainerFormat string `q:"container_format"` + DiskFormat string `q:"disk_format"` + MinRam int `q:"min_ram"` + MinDisk int `q:"min_disk"` + OsBit string `q:"__os_bit"` + Platform string `q:"__platform"` + Marker string `q:"marker"` + Limit int `q:"limit"` + SortKey string `q:"sort_key"` + SortDir string `q:"sort_dir"` + OsType string `q:"__os_type"` + Tag string `q:"tag"` + MemberStatus string `q:"member_status"` + SupportKvm string `q:"__support_kvm"` + SupportXen string `q:"__support_xen"` + SupportLargeMemory string `q:"__support_largememory"` + SupportDiskintensive string `q:"__support_diskintensive"` + SupportHighperformance string `q:"__support_highperformance"` + SupportXenGpuType string `q:"__support_xen_gpu_type"` + SupportKvmGpuType string `q:"__support_kvm_gpu_type"` + SupportXenHana string `q:"__support_xen_hana"` + SupportKvmInfiniband string `q:"__support_kvm_infiniband"` + VirtualEnvType string `q:"virtual_env_type"` + // CreatedAtQuery filters images based on their creation date. + CreatedAtQuery *ImageDateQuery + // UpdatedAtQuery filters images based on their updated date. + UpdatedAtQuery *ImageDateQuery +} + +// ImageDateFilter represents a valid filter to use for filtering +// images by their date during a List. +type ImageDateFilter string + +const ( + FilterGT ImageDateFilter = "gt" + FilterGTE ImageDateFilter = "gte" + FilterLT ImageDateFilter = "lt" + FilterLTE ImageDateFilter = "lte" + FilterNEQ ImageDateFilter = "neq" + FilterEQ ImageDateFilter = "eq" +) + +// ImageDateQuery represents a date field to be used for listing images. +// If no filter is specified, the query will act as though FilterEQ was +// set. +type ImageDateQuery struct { + Date time.Time + Filter ImageDateFilter +} + +// ToImageListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToImageListQuery() (string, error) { + q, err := golangsdk.BuildQueryString(opts) + params := q.Query() + + if opts.CreatedAtQuery != nil { + createdAt := opts.CreatedAtQuery.Date.Format(time.RFC3339) + if v := opts.CreatedAtQuery.Filter; v != "" { + createdAt = fmt.Sprintf("%s:%s", v, createdAt) + } + params.Add("created_at", createdAt) + } + + if opts.UpdatedAtQuery != nil { + updatedAt := opts.UpdatedAtQuery.Date.Format(time.RFC3339) + if v := opts.UpdatedAtQuery.Filter; v != "" { + updatedAt = fmt.Sprintf("%s:%s", v, updatedAt) + } + params.Add("updated_at", updatedAt) + } + + q = &url.URL{RawQuery: params.Encode()} + return q.String(), err +} + +// List implements images list request +func List(client *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := listURL(client) + if opts != nil { + query, err := opts.ToImageListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { + imagePage := ImagePage{ + serviceURL: client.ServiceURL(), + LinkedPageBase: pagination.LinkedPageBase{PageResult: r}, + } + + return imagePage + }) +} + +// CreateOptsBuilder allows extensions to add parameters to the Create request. +type CreateOptsBuilder interface { + // Returns value that can be passed to json.Marshal + ToImageCreateMap() (map[string]interface{}, error) +} + +// CreateOpts represents options used to create an image. +type CreateByServerOpts struct { + // the name of the system disk image + Name string `json:"name" required:"true"` + // Description of the image + Description string `json:"description,omitempty"` + // server id to be converted + InstanceId string `json:"instance_id" required:"true"` + // the data disks to be converted + DataImages []DataImage `json:"data_images,omitempty"` + // image label "key.value" + Tags []string `json:"tags,omitempty"` + // One or more tag key and value pairs to associate with the image + ImageTags []ImageTag `json:"image_tags,omitempty"` + // the maximum memory of the image in the unit of MB + MaxRam int `json:"max_ram,omitempty"` + // the minimum memory of the image in the unit of MB + MinRam int `json:"min_ram,omitempty"` +} + +// CreateOpts represents options used to create an image. +type CreateByOBSOpts struct { + // the name of the system disk image + Name string `json:"name" required:"true"` + // Description of image + Description string `json:"description,omitempty"` + // the OS version + OsVersion string `json:"os_version,omitempty"` + // the URL of the external image file in the OBS bucket + ImageUrl string `json:"image_url" required:"true"` + // the minimum size of the system disk in the unit of GB + MinDisk int `json:"min_disk" required:"true"` + //whether automatic configuration is enabled,the value can be true or false + IsConfig bool `json:"is_config,omitempty"` + // the master key used for encrypting an image + CmkId string `json:"cmk_id,omitempty"` + // image label "key.value" + Tags []string `json:"tags,omitempty"` + // One or more tag key and value pairs to associate with the image + ImageTags []ImageTag `json:"image_tags,omitempty"` + // the image type, the value can be ECS,BMS,FusionCompute, or Ironic + Type string `json:"type,omitempty"` + // the maximum memory of the image in the unit of MB + MaxRam int `json:"max_ram,omitempty"` + // the minimum memory of the image in the unit of MB + MinRam int `json:"min_ram,omitempty"` +} + +type DataImage struct { + // the data disk image name + Name string `json:"name" required:"true"` + // the data disk ID + VolumeId string `json:"volume_id" required:"true"` + // information about the data disk + Description string `json:"description,omitempty"` + // the data disk image tags + Tags []string `json:"tags,omitempty"` +} + +type ImageTag struct { + Key string `json:"key" required:"true"` + Value string `json:"value,omitempty"` +} + +// ToImageCreateMap assembles a request body based on the contents of +// a CreateByServerOpts. +func (opts CreateByServerOpts) ToImageCreateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "") +} + +func (opts CreateByOBSOpts) ToImageCreateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "") +} + +// Create implements create image request. +func CreateImageByServer(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r JobResult) { + b, err := opts.ToImageCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(createURL(client), b, &r.Body, &golangsdk.RequestOpts{OkCodes: []int{200}}) + return +} + +// Create implements create image request. +func CreateImageByOBS(client *golangsdk.ServiceClient, opts CreateOptsBuilder) (r JobResult) { + b, err := opts.ToImageCreateMap() + if err != nil { + r.Err = err + return + } + + _, r.Err = client.Post(createURL(client), b, &r.Body, &golangsdk.RequestOpts{OkCodes: []int{200}}) + return +} diff --git a/openstack/ims/v2/cloudimages/results.go b/openstack/ims/v2/cloudimages/results.go new file mode 100644 index 000000000..6c4bd9671 --- /dev/null +++ b/openstack/ims/v2/cloudimages/results.go @@ -0,0 +1,157 @@ +package cloudimages + +import ( + "encoding/json" + "time" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +// Image represents an image found in the IMS. +type Image struct { + // the URL for uploading and downloading the image file + File string `json:"file"` + // the image owner + Owner string `json:"owner"` + // the image id + ID string `json:"id"` + // the image URL + Self string `json:"self"` + // the image schema + Schema string `json:"schema"` + // the image status, the value can be [queued, saving, deleted, killed,active] + Status string `json:"status"` + // the image tags + Tags []string `json:"tags"` + // whether the image can be seen by others + Visibility string `json:"visibility"` + // the image name + Name string `json:"name"` + // whether the image has been deleted + Deleted bool `json:"deleted"` + // whether the image is protected + Protected bool `json:"protected"` + // the container type + ContainerFormat string `json:"container_format"` + // the minimum memory size (MB) required for running the image + MinRam int `json:"min_ram"` + // the maximum memory of the image in the unit of MB, notice: string + MaxRam string `json:"max_ram"` + // the disk format, the value can be [vhd, raw, zvhd, qcow2] + DiskFormat string `json:"disk_format"` + // the minimum disk space (GB) required for running the image + MinDisk int `json:"min_disk"` + // the environment where the image is used + VirtualEnvType string `json:"virtual_env_type"` + // *size, virtual_size and checksum parameter are unavailable currently* + Size int64 `json:"size"` + VirtualSize int `json:"virtual_size"` + Checksum string `json:"checksum"` + // created_at and updated_at are in UTC format + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + DeletedAt string `json:"deleted_at"` + // the OS architecture: 32 or 64 + OsBit string `json:"__os_bit"` + OsVersion string `json:"__os_version"` + Description string `json:"__description"` + OsType string `json:"__os_type"` + Isregistered string `json:"__isregistered"` + Platform string `json:"__platform"` + ImageSourceType string `json:"__image_source_type"` + Imagetype string `json:"__imagetype"` + Originalimagename string `json:"__originalimagename"` + BackupID string `json:"__backup_id"` + Productcode string `json:"__productcode"` + ImageSize string `json:"__image_size"` + DataOrigin string `json:"__data_origin"` + SupportKvm string `json:"__support_kvm"` + SupportXen string `json:"__support_xen"` + SupportLargeMemory string `json:"__support_largememory"` + SupportDiskintensive string `json:"__support_diskintensive"` + SupportHighperformance string `json:"__support_highperformance"` + SupportXenGpuType string `json:"__support_xen_gpu_type"` + SupportKvmGpuType string `json:"__support_kvm_gpu_type"` + SupportXenHana string `json:"__support_xen_hana"` + SupportKvmInfiniband string `json:"__support_kvm_infiniband"` + SystemSupportMarket bool `json:"__system_support_market"` + RootOrigin string `json:"__root_origin"` + SequenceNum string `json:"__sequence_num"` +} + +func (r *Image) UnmarshalJSON(b []byte) error { + type tmp Image + var s struct { + tmp + CreatedAt golangsdk.JSONRFC3339Milli `json:"created_at"` + UpdatedAt golangsdk.JSONRFC3339Milli `json:"updated_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Image(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + return err +} + +type commonResult struct { + golangsdk.Result +} + +// Extract will get the Image object out of the commonResult object. +func (r commonResult) Extract() (*Image, error) { + var s Image + err := r.ExtractInto(&s) + return &s, err +} + +// ExtractInto converts our response data into a volume struct +func (r commonResult) ExtractInto(v interface{}) error { + return r.Result.ExtractIntoStructPtr(v, "images") +} + +// ImagePage represents the results of a List request. +type ImagePage struct { + serviceURL string + pagination.LinkedPageBase +} + +// IsEmpty returns true if an ImagePage contains no Images results. +func (r ImagePage) IsEmpty() (bool, error) { + images, err := ExtractImages(r) + return len(images) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to +// the next page of results. +func (r ImagePage) NextPageURL() (string, error) { + var s struct { + Next string `json:"next"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + + if s.Next == "" { + return "", nil + } + + return nextPageURL(r.serviceURL, s.Next) +} + +// ExtractImages interprets the results of a single page from a List() call, +// producing a slice of Image entities. +func ExtractImages(r pagination.Page) ([]Image, error) { + var s struct { + Images []Image `json:"images"` + } + + err := (r.(ImagePage)).ExtractInto(&s) + return s.Images, err +} diff --git a/openstack/ims/v2/cloudimages/results_job.go b/openstack/ims/v2/cloudimages/results_job.go new file mode 100644 index 000000000..f4c190ac7 --- /dev/null +++ b/openstack/ims/v2/cloudimages/results_job.go @@ -0,0 +1,88 @@ +package cloudimages + +import ( + "fmt" + + "github.com/huaweicloud/golangsdk" +) + +type JobResponse struct { + JobID string `json:"job_id"` +} + +type JobStatus struct { + Status string `json:"status"` + Entities JobEntity `json:"entities"` + JobID string `json:"job_id"` + JobType string `json:"job_type"` + BeginTime string `json:"begin_time"` + EndTime string `json:"end_time"` + ErrorCode string `json:"error_code"` + FailReason string `json:"fail_reason"` +} + +type JobEntity struct { + ImageID string `json:"image_id"` +} + +type JobResult struct { + golangsdk.Result +} + +func (r JobResult) ExtractJobResponse() (*JobResponse, error) { + job := new(JobResponse) + err := r.ExtractInto(job) + return job, err +} + +func (r JobResult) ExtractJobStatus() (*JobStatus, error) { + job := new(JobStatus) + err := r.ExtractInto(job) + return job, err +} + +func WaitForJobSuccess(client *golangsdk.ServiceClient, secs int, jobID string) error { + jobClient := *client + // v1/{project_id}/jobs/{job_id} + jobClient.ResourceBase = jobClient.Endpoint + "v1/" + jobClient.ProjectID + "/" + return golangsdk.WaitFor(secs, func() (bool, error) { + job := new(JobStatus) + _, err := jobClient.Get(jobClient.ServiceURL("jobs", jobID), &job, nil) + if err != nil { + return false, err + } + + if job.Status == "SUCCESS" { + return true, nil + } + if job.Status == "FAIL" { + err = fmt.Errorf("Job failed with code %s: %s.\n", job.ErrorCode, job.FailReason) + return false, err + } + + return false, nil + }) +} + +func GetJobEntity(client *golangsdk.ServiceClient, jobId string, label string) (interface{}, error) { + if label != "image_id" { + return nil, fmt.Errorf("Unsupported label %s in GetJobEntity.", label) + } + + jobClient := *client + // v1/{project_id}/jobs/{job_id} + jobClient.ResourceBase = jobClient.Endpoint + "v1/" + jobClient.ProjectID + "/" + job := new(JobStatus) + _, err := jobClient.Get(jobClient.ServiceURL("jobs", jobId), &job, nil) + if err != nil { + return nil, err + } + + if job.Status == "SUCCESS" { + if e := job.Entities.ImageID; e != "" { + return e, nil + } + } + + return nil, fmt.Errorf("Unexpected conversion error in GetJobEntity.") +} diff --git a/openstack/ims/v2/cloudimages/urls.go b/openstack/ims/v2/cloudimages/urls.go new file mode 100644 index 000000000..7ecb50c4e --- /dev/null +++ b/openstack/ims/v2/cloudimages/urls.go @@ -0,0 +1,42 @@ +package cloudimages + +import ( + "net/url" + "strings" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/openstack/utils" +) + +// query images using search criteria and to display the images in a list +func listURL(c *golangsdk.ServiceClient) string { + return c.ServiceURL("cloudimages") +} + +func createURL(c *golangsdk.ServiceClient) string { + return c.ServiceURL("cloudimages/action") +} + +// builds next page full url based on current url +func nextPageURL(serviceURL, requestedNext string) (string, error) { + base, err := utils.BaseEndpoint(serviceURL) + if err != nil { + return "", err + } + + requestedNextURL, err := url.Parse(requestedNext) + if err != nil { + return "", err + } + + base = golangsdk.NormalizeURL(base) + nextPath := base + strings.TrimPrefix(requestedNextURL.Path, "/") + + nextURL, err := url.Parse(nextPath) + if err != nil { + return "", err + } + + nextURL.RawQuery = requestedNextURL.RawQuery + return nextURL.String(), nil +}