From b395a965d3002099cb1d7f8d53a64cc7b9164bbf Mon Sep 17 00:00:00 2001 From: disha-wani Date: Thu, 5 Jul 2018 12:16:56 +0530 Subject: [PATCH 1/8] add DeH service support --- openstack/deh/v1/hosts/doc.go | 54 +++++++ openstack/deh/v1/hosts/requests.go | 228 +++++++++++++++++++++++++++++ openstack/deh/v1/hosts/results.go | 202 +++++++++++++++++++++++++ openstack/deh/v1/hosts/urls.go | 15 ++ 4 files changed, 499 insertions(+) create mode 100644 openstack/deh/v1/hosts/doc.go create mode 100644 openstack/deh/v1/hosts/requests.go create mode 100644 openstack/deh/v1/hosts/results.go create mode 100644 openstack/deh/v1/hosts/urls.go diff --git a/openstack/deh/v1/hosts/doc.go b/openstack/deh/v1/hosts/doc.go new file mode 100644 index 000000000..37ff2cb0e --- /dev/null +++ b/openstack/deh/v1/hosts/doc.go @@ -0,0 +1,54 @@ +package hosts + +/* +Package hosts enables management and retrieval of Dedicated Hosts + +Example to Allocate Hosts + opts := hosts.AllocateOpts{Name:"c2c-test",HostType:"h1",AvailabilityZone:"eu-de-02",AutoPlacement:"off",Quantity:1} + allocatedHosts ,err := hosts.Allocate(client,opts).Extract() + if err != nil { + panic(err) + } + fmt.Println(allocatedHosts) + + +Example to Update Hosts + updateopts := hosts.UpdateOpts{Name:"NewName3",AutoPlacement:"on"} + update := hosts.Update(client,"8ea7381e-8d84-4f9f-a7ad-d32f1e1bb5b7",updateopts) + if err != nil { + panic(update.Err) + } + fmt.Println(update) + +Example to delete Hosts + delete := hosts.Delete(client,"94d94259-3734-4ad5-bc3b-5f9f3e96d5e8") + if err != nil { + panic(delete.Err) + } + fmt.Println(delete) + +Example to List Hosts + listdeh := hosts.ListOpts{} + alldehs, err := hosts.List(client,listdeh).AllPages() + list,err:=hosts.ExtractHosts(alldehs) + if err != nil { + panic(err) + } + fmt.Println(list) + +Example to Get Host + result := hosts.Get(client, "66156a61-27c2-4169-936b-910dd9c73da3") + out, err := result.Extract() + fmt.Println(out) + +Example to List Servers + listOpts := hosts.ListServerOpts{} + allServers, err := hosts.ListServer(client, "671611d2-b45c-4648-9e78-06eb24522291",listOpts) + if err != nil { + panic(err) + } + + for _, server := range allServers { + fmt.Printf("%+v\n", server) + } +*/ diff --git a/openstack/deh/v1/hosts/requests.go b/openstack/deh/v1/hosts/requests.go new file mode 100644 index 000000000..ff3d1f15b --- /dev/null +++ b/openstack/deh/v1/hosts/requests.go @@ -0,0 +1,228 @@ +package hosts + +import ( + "reflect" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +// AllocateOptsBuilder allows extensions to add additional parameters to the +// Allocate request. +type AllocateOptsBuilder interface { + ToDeHAllocateMap() (map[string]interface{}, error) +} + +// AllocateOpts contains all the values needed to allocate a new DeH. +type AllocateOpts struct { + Name string `json:"name" required:"true"` + Az string `json:"availability_zone" required:"true"` + AutoPlacement string `json:"auto_placement,omitempty"` + HostType string `json:"host_type" required:"true"` + Quantity int `json:"quantity" required:"true"` +} + +// ToDeHAllocateMap builds a allocate request body from AllocateOpts. +func (opts AllocateOpts) ToDeHAllocateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "") +} + +// Allocate accepts a AllocateOpts struct and uses the values to allocate a new DeH. +func Allocate(c *golangsdk.ServiceClient, opts AllocateOptsBuilder) (r AllocateResult) { + b, err := opts.ToDeHAllocateMap() + if err != nil { + r.Err = err + return + } + reqOpt := &golangsdk.RequestOpts{OkCodes: []int{200, 201}} + _, r.Err = c.Post(rootURL(c), b, &r.Body, reqOpt) + return +} + +// UpdateOptsBuilder allows extensions to add additional parameters to the +// Update request. +type UpdateOptsBuilder interface { + ToDeHUpdateMap() (map[string]interface{}, error) +} + +// UpdateOpts contains all the values needed to update a DeH. +type UpdateOpts struct { + Name string `json:"name,omitempty"` + AutoPlacement string `json:"auto_placement,omitempty"` +} + +// ToDeHUpdateMap builds a update request body from UpdateOpts. +func (opts UpdateOpts) ToDeHUpdateMap() (map[string]interface{}, error) { + return golangsdk.BuildRequestBody(opts, "dedicated_host") +} + +// Update accepts a UpdateOpts struct and uses the values to update a DeH.The response code from api is 204 +func Update(c *golangsdk.ServiceClient, hostID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToDeHUpdateMap() + if err != nil { + r.Err = err + return + } + reqOpt := &golangsdk.RequestOpts{OkCodes: []int{204}} + _, r.Err = c.Put(resourceURL(c, hostID), b, nil, reqOpt) + return +} + +//Deletes the DeH using the specified hostID. +func Delete(c *golangsdk.ServiceClient, hostid string) (r DeleteResult) { + _, r.Err = c.Delete(resourceURL(c, hostid), nil) + return +} + +// ListOpts allows the filtering and sorting of paginated collections through +// the API. +type ListOpts struct { + // Specifies Dedicated Host ID. + ID string `q:"dedicated_host_id"` + // Specifies the Dedicated Host name. + Name string `q:"name"` + // Specifes the Dedicated Host type. + HostType string `q:"host_type"` + // Specifes the Dedicated Host name of type. + HostTypeName string `q:"host_type_name"` + // Specifies flavor ID. + Flavor string `q:"flavor"` + // Specifies the Dedicated Host status. + // The value can be available, fault or released. + State string `q:"state"` + // Specifies the AZ to which the Dedicated Host belongs. + Az string `q:"availability_zone"` + // Specifies the number of entries displayed on each page. + Limit string `q:"limit"` + // The value is the ID of the last record on the previous page. + Marker string `q:"marker"` + // Filters the response by a date and time stamp when the dedicated host last changed status. + ChangesSince string `q:"changes-since"` + // Specifies the UUID of the tenant in a multi-tenancy cloud. + TenantId string `q:"tenant"` +} + +// ListOptsBuilder allows extensions to add parameters to the List request. +type ListOptsBuilder interface { + ToHostListQuery() (string, error) +} + +// ToRegionListQuery formats a ListOpts into a query string. +func (opts ListOpts) ToHostListQuery() (string, error) { + q, err := golangsdk.BuildQueryString(opts) + return q.String(), err +} + +// List returns a Pager which allows you to iterate over a collection of +// dedicated hosts resources. It accepts a ListOpts struct, which allows you to +// filter the returned collection for greater efficiency. +func List(c *golangsdk.ServiceClient, opts ListOptsBuilder) pagination.Pager { + url := rootURL(c) + if opts != nil { + query, err := opts.ToHostListQuery() + if err != nil { + return pagination.Pager{Err: err} + } + url += query + } + return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { + return HostPage{pagination.LinkedPageBase{PageResult: r}} + }) + +} + +// Get retrieves a particular host based on its unique ID. +func Get(c *golangsdk.ServiceClient, id string) (r GetResult) { + _, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) + return +} + +// ListServerOpts allows the filtering and sorting of paginated collections through +// the API. Filtering is achieved by passing in struct field values that map to +// the server attributes you want to see returned. Marker and Limit are used +// for pagination. +type ListServerOpts struct { + // Specifies the number of entries displayed on each page. + Limit int `q:"limit"` + // The value is the ID of the last record on the previous page. + // If the marker value is invalid, error code 400 will be returned. + Marker string `q:"marker"` + // ID uniquely identifies this server amongst all other servers, + // including those not accessible to the current tenant. + ID string `json:"id"` + // Name contains the human-readable name for the server. + Name string `json:"name"` + // Status contains the current operational status of the server, + // such as IN_PROGRESS or ACTIVE. + Status string `json:"status"` + // UserID uniquely identifies the user account owning the tenant. + UserID string `json:"user_id"` +} + +// ListServer returns a Pager which allows you to iterate over a collection of +// dedicated hosts Server resources. It accepts a ListServerOpts struct, which allows you to +// filter the returned collection for greater efficiency. +func ListServer(c *golangsdk.ServiceClient, id string, opts ListServerOpts) ([]Server, error) { + q, err := golangsdk.BuildQueryString(&opts) + if err != nil { + return nil, err + } + u := listServerURL(c, id) + q.String() + pages, err := pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return ServerPage{pagination.LinkedPageBase{PageResult: r}} + }).AllPages() + + allservers, err := ExtractServers(pages) + if err != nil { + return nil, err + } + + return FilterServers(allservers, opts) +} + +func FilterServers(servers []Server, opts ListServerOpts) ([]Server, error) { + + var refinedServers []Server + var matched bool + m := map[string]interface{}{} + + if opts.ID != "" { + m["ID"] = opts.ID + } + if opts.Name != "" { + m["Name"] = opts.Name + } + if opts.Status != "" { + m["Status"] = opts.Status + } + if opts.UserID != "" { + m["UserID"] = opts.UserID + } + + if len(m) > 0 && len(servers) > 0 { + for _, server := range servers { + matched = true + + for key, value := range m { + if sVal := getStructServerField(&server, key); !(sVal == value) { + matched = false + } + } + + if matched { + refinedServers = append(refinedServers, server) + } + } + + } else { + refinedServers = servers + } + + return refinedServers, nil +} + +func getStructServerField(v *Server, field string) string { + r := reflect.ValueOf(v) + f := reflect.Indirect(r).FieldByName(field) + return string(f.String()) +} diff --git a/openstack/deh/v1/hosts/results.go b/openstack/deh/v1/hosts/results.go new file mode 100644 index 000000000..aaf56d808 --- /dev/null +++ b/openstack/deh/v1/hosts/results.go @@ -0,0 +1,202 @@ +package hosts + +import ( + "time" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +type Host struct { + // ID is the unique identifier for the dedicated host . + ID string `json:"dedicated_host_id"` + // Specifies the Dedicated Host name. + Name string `json:"name"` + // Specifies whether to allow a VM to be placed on this available host + // if its Dedicated Host ID is not specified during its creation. + AutoPlacement string `json:"auto_placement"` + // Specifies the AZ to which the Dedicated Host belongs. + Az string `json:"availability_zone"` + // Specifies the tenant who owns the Dedicated Host. + TenantId string `json:"project_id"` + // Specifies the host status. + State string `json:"state"` + // Specifies the number of available vCPUs for the Dedicated Host. + AvailableVcpus int `json:"available_vcpus"` + // Specifies the size of available memory for the Dedicated Host. + AvailableMemory int `json:"available_memory"` + // Time at which the dedicated host has been allocated. + AllocatedAt string `json:"allocated_at"` + // Time at which the dedicated host has been released. + ReleasedAt string `json:"released_at"` + // Specifies the number of the placed VMs. + InstanceTotal int `json:"instance_total"` + // Specifies the VMs started on the Dedicated Host. + InstanceUuids []string `json:"instance_uuids"` + // Specifies the property of host. + HostProperties HostPropertiesOpts `json:"host_properties"` +} +type HostPropertiesOpts struct { + // Specifies the property of host. + HostType string `json:"host_type"` + HostTypeName string `json:"host_type_name"` + Vcpus int `json:"vcpus"` + Cores int `json:"cores"` + Sockets int `json:"sockets"` + Memory int `json:"memory"` + AvailableInstanceCapacities []AvailableInstanceCapacitiesOpts `json:"available_instance_capacities"` +} +type AvailableInstanceCapacitiesOpts struct { + // Specifies the number of supported flavors. + Flavor string `json:"flavor"` +} + +// HostPage is the page returned by a pager when traversing over a +// collection of Hosts. +type HostPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a ListResult contains no Dedicated Hosts. +func (r HostPage) IsEmpty() (bool, error) { + stacks, err := ExtractHosts(r) + return len(stacks) == 0, err +} + +// ExtractHosts accepts a Page struct, specifically a HostPage struct, +// and extracts the elements into a slice of hosts structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractHosts(r pagination.Page) ([]Host, error) { + var s struct { + ListedStacks []Host `json:"dedicated_hosts"` + } + err := (r.(HostPage)).ExtractInto(&s) + return s.ListedStacks, err +} + +// NextPageURL is invoked when a paginated collection of hosts 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 HostPage) NextPageURL() (string, error) { + var s struct { + Links []golangsdk.Link `json:"dedicated_hostslinks"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(s.Links) +} + +type commonResult struct { + golangsdk.Result +} + +// AllocateResult represents the result of a allocate operation. Call its Extract +// method to interpret it as a host. +type AllocateResult struct { + commonResult +} + +// Extract is a function that accepts a result and extracts Allocated Hosts. +func (r AllocateResult) ExtractHost() (*AllocatedHosts, error) { + var response AllocatedHosts + err := r.ExtractInto(&response) + return &response, err +} + +//AllocatedHosts is the response structure of the allocated DeH +type AllocatedHosts struct { + AllocatedHostIds []string `json:"dedicated_host_ids"` +} + +// AllocateResult represents the result of a allocate operation. Call its Extract +// method to interpret it as a host. +type UpdateResult struct { + commonResult +} + +type DeleteResult struct { + commonResult +} + +// GetResult represents the result of a get operation. Call its Extract +// method to interpret it as a host. +type GetResult struct { + commonResult +} + +// Extract is a function that accepts a result and extracts a host. +func (r commonResult) Extract() (*Host, error) { + var s struct { + Host *Host `json:"dedicated_host"` + } + err := r.ExtractInto(&s) + return s.Host, err +} + +// Server represents a server/instance in the OpenStack cloud. +type Server struct { + // ID uniquely identifies this server amongst all other servers, + // including those not accessible to the current tenant. + ID string `json:"id"` + // TenantID identifies the tenant owning this server resource. + TenantID string `json:"tenant_id"` + // UserID uniquely identifies the user account owning the tenant. + UserID string `json:"user_id"` + // Name contains the human-readable name for the server. + Name string `json:"name"` + // Updated and Created contain ISO-8601 timestamps of when the state of the + // server last changed, and when it was created. + Updated time.Time `json:"updated"` + Created time.Time `json:"created"` + // Status contains the current operational status of the server, + // such as IN_PROGRESS or ACTIVE. + Status string `json:"status"` + // Image refers to a JSON object, which itself indicates the OS image used to + // deploy the server. + Image map[string]interface{} `json:"-"` + // Flavor refers to a JSON object, which itself indicates the hardware + // configuration of the deployed server. + Flavor map[string]interface{} `json:"flavor"` + // Addresses includes a list of all IP addresses assigned to the server, + // keyed by pool. + Addresses map[string]interface{} `json:"addresses"` + // Metadata includes a list of all user-specified key-value pairs attached + // to the server. + Metadata map[string]string `json:"metadata"` +} + +type ServerPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a page contains no Server results. +func (r ServerPage) IsEmpty() (bool, error) { + s, err := ExtractServers(r) + return len(s) == 0, err +} + +// NextPageURL uses the response's embedded link reference to navigate to the +// next page of results. +func (r ServerPage) NextPageURL() (string, error) { + var s struct { + Links []golangsdk.Link `json:"servers_links"` + } + err := r.ExtractInto(&s) + if err != nil { + return "", err + } + return golangsdk.ExtractNextURL(s.Links) +} + +// ExtractServers accepts a Page struct, specifically a ServerPage struct, +// and extracts the elements into a slice of Server structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractServers(r pagination.Page) ([]Server, error) { + var s struct { + ListedStacks []Server `json:"servers"` + } + err := (r.(ServerPage)).ExtractInto(&s) + return s.ListedStacks, err +} diff --git a/openstack/deh/v1/hosts/urls.go b/openstack/deh/v1/hosts/urls.go new file mode 100644 index 000000000..ca7d124af --- /dev/null +++ b/openstack/deh/v1/hosts/urls.go @@ -0,0 +1,15 @@ +package hosts + +import "github.com/huaweicloud/golangsdk" + +const resourcePath = "dedicated-hosts" + +func rootURL(c *golangsdk.ServiceClient) string { + return c.ServiceURL(resourcePath) +} +func resourceURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id) +} +func listServerURL(c *golangsdk.ServiceClient, id string) string { + return c.ServiceURL(resourcePath, id, "servers") +} From 4494c6a5076f6d0773c055588569e27e82a7176b Mon Sep 17 00:00:00 2001 From: disha-wani Date: Thu, 5 Jul 2018 12:17:57 +0530 Subject: [PATCH 2/8] add DeH service unit test --- openstack/deh/v1/common/common_tests.go | 18 ++ openstack/deh/v1/hosts/testing/fixtures.go | 121 ++++++++++ .../deh/v1/hosts/testing/requests_test.go | 206 ++++++++++++++++++ 3 files changed, 345 insertions(+) create mode 100644 openstack/deh/v1/common/common_tests.go create mode 100644 openstack/deh/v1/hosts/testing/fixtures.go create mode 100644 openstack/deh/v1/hosts/testing/requests_test.go diff --git a/openstack/deh/v1/common/common_tests.go b/openstack/deh/v1/common/common_tests.go new file mode 100644 index 000000000..390e846f0 --- /dev/null +++ b/openstack/deh/v1/common/common_tests.go @@ -0,0 +1,18 @@ +package common + +import ( + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/testhelper/client" +) + +const TokenID = client.TokenID + +// Fake project id to use. +const ProjectID = "17fbda95add24720a4038ba4b1c705ed" + +func ServiceClient() *golangsdk.ServiceClient { + sc := client.ServiceClient() + sc.ResourceBase = sc.Endpoint + ProjectID + "/" + sc.ProjectID = ProjectID + return sc +} diff --git a/openstack/deh/v1/hosts/testing/fixtures.go b/openstack/deh/v1/hosts/testing/fixtures.go new file mode 100644 index 000000000..0110129ae --- /dev/null +++ b/openstack/deh/v1/hosts/testing/fixtures.go @@ -0,0 +1,121 @@ +package testing + +const ( + dehEndpoint = "/dedicated-hosts" + HostID = "011d21e2-fbc3-4e4a-9993-9ea223f73264" +) + +var allocateRequest = `{ + "availability_zone": "eu-de-02", + "name": "Test-1", + "auto_placement": "off", + "host_type": "h1", + "quantity": 2 +}` + +var allocateResponse = `{ + "dedicated_host_ids": [ + "fb4733fd-70a3-44e1-a1cb-0311f028d7e5", + "7408f985-047d-4313-b3c8-8e12bef01d12" + ] +}` + +var updateRequest = `{ +"dedicated_host": + { + "auto_placement": "off", + "name": "Test-2" + } +}` + +var getResponse = ` +{ + "dedicated_host": { + "allocated_at": "2018-06-13T07:44:55Z", + "availability_zone": "eu-de-02", + "csg_host": "pod01.eu-de-02", + "name": "test-aj2", + "available_memory": 270336, + "released_at": "", + "auto_placement": "off", + "available_vcpus": 36, + "dedicated_host_id": "66156a61-27c2-4169-936b-910dd9c73da3", + "state": "available", + "instance_total": 0, + "host_properties": { + "host_type": "h1", + "vcpus": 36, + "memory": 270336, + "cores": 12, + "sockets": 2, + "host_type_name": "High performance" + }, + "csd_host": "fc-nova-compute010#8120665", + "instance_uuids": [], + "project_id": "17fbda95add24720a4038ba4b1c705ed" + } +} + ` + +var listResponse = ` +{ + "dedicated_hosts": [ { + "availability_zone": "eu-de-01", + "name": "c2c-deh-test", + "available_memory": 262144, + "auto_placement": "off", + "available_vcpus": 70, + "dedicated_host_id": "671611d2-b45c-4648-9e78-06eb24522291", + "state": "available", + "instance_total": 2, + "host_properties": { + "host_type": "general", + "vcpus": 72, + "memory": 270336, + "cores": 12, + "sockets": 2, + "host_type_name": "General computing" + }, + "instance_uuids": [ + "3de1ce75-2550-4a46-a689-dd33ca2b62d6", + "885dc71d-905d-48b5-bae7-db66801dc175" + ], + "project_id": "17fbda95add24720a4038ba4b1c705ed" + }] +} + ` + +var listserverResponse = ` +{ + "servers": [ { + "status": "ACTIVE", + "flavor": { + "id": "normal1" + }, + "addresses": { + "0b98c646-617f-4d90-9ca5-385f0cd73ea7": [ + { + "version": 4, + "addr": "192.168.3.133" + } + ] + }, + "id": "3de1ce75-2550-4a46-a689-dd33ca2b62d6", + "user_id": "6d78fa8550ae45d6932a1fadfb1fa552", + "name": "c2c-ecs-test-2", + "tenant_id": "17fbda95add24720a4038ba4b1c705ed", + "metadata": { + "metering.image_id": "c0ea3ff1-432e-4650-8a1b-372a80b2d2be", + "metering.imagetype": "gold", + "metering.resourcespeccode": "deh.linux", + "metering.cloudServiceType": "sys.service.type.ec2", + "image_name": "Standard_CentOS_7_latest", + "metering.resourcetype": "1", + "os_bit": "64", + "vpc_id": "0b98c646-617f-4d90-9ca5-385f0cd73ea7", + "os_type": "Linux", + "charging_mode": "0" + } + }] +} + ` diff --git a/openstack/deh/v1/hosts/testing/requests_test.go b/openstack/deh/v1/hosts/testing/requests_test.go new file mode 100644 index 000000000..c19d88dac --- /dev/null +++ b/openstack/deh/v1/hosts/testing/requests_test.go @@ -0,0 +1,206 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + fake "github.com/huaweicloud/golangsdk/openstack/deh/v1/common" + "github.com/huaweicloud/golangsdk/openstack/deh/v1/hosts" + th "github.com/huaweicloud/golangsdk/testhelper" + "github.com/huaweicloud/golangsdk/testhelper/client" +) + +func TestGet(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc(dehEndpoint+"/"+HostID, 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 := hosts.Get(client.ServiceClient(), HostID).Extract() + th.AssertNoErr(t, err) + th.AssertEquals(t, "66156a61-27c2-4169-936b-910dd9c73da3", s.ID) + th.AssertEquals(t, "test-aj2", s.Name) + th.AssertEquals(t, "eu-de-02", s.Az) + th.AssertEquals(t, "available", s.State) + th.AssertDeepEquals(t, hosts.HostPropertiesOpts{ + HostTypeName: "High performance", + HostType: "h1", + Vcpus: 36, + Memory: 270336, + Cores: 12, + Sockets: 2, + }, s.HostProperties) +} + +func TestList(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc(dehEndpoint, 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) + }) + + //count := 0 + + host, err := hosts.List(client.ServiceClient(), hosts.ListOpts{}).AllPages() + + actual, err := hosts.ExtractHosts(host) + + if err != nil { + t.Errorf("Failed to extract hosts: %v", err) + } + + expected := []hosts.Host{ + { + Az: "eu-de-01", + Name: "c2c-deh-test", + AvailableMemory: 262144, + AvailableVcpus: 70, + ID: "671611d2-b45c-4648-9e78-06eb24522291", + State: "available", + InstanceTotal: 2, + AutoPlacement: "off", + TenantId: "17fbda95add24720a4038ba4b1c705ed", + HostProperties: hosts.HostPropertiesOpts{ + HostType: "general", + Vcpus: 72, + Memory: 270336, + Cores: 12, + Sockets: 2, + HostTypeName: "General computing", + }, + InstanceUuids: []string{"3de1ce75-2550-4a46-a689-dd33ca2b62d6", + "885dc71d-905d-48b5-bae7-db66801dc175"}, + }, + } + + th.AssertDeepEquals(t, expected, actual) +} + +func TestListServer(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc(dehEndpoint+"/"+HostID+"/servers", 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, listserverResponse) + }) + + actual, err := hosts.ListServer(client.ServiceClient(), HostID, hosts.ListServerOpts{}) + th.AssertNoErr(t, err) + + expected := []hosts.Server{ + { + Status: "ACTIVE", + Addresses: map[string]interface{}{ + "0b98c646-617f-4d90-9ca5-385f0cd73ea7": []interface{}{ + map[string]interface{}{ + "version": float64(4), + "addr": "192.168.3.133", + }, + }, + }, + Flavor: map[string]interface{}{ + "id": "normal1", + }, + ID: "3de1ce75-2550-4a46-a689-dd33ca2b62d6", + UserID: "6d78fa8550ae45d6932a1fadfb1fa552", + Name: "c2c-ecs-test-2", + TenantID: "17fbda95add24720a4038ba4b1c705ed", + Metadata: map[string]string{ + "metering.image_id": "c0ea3ff1-432e-4650-8a1b-372a80b2d2be", + "metering.imagetype": "gold", + "metering.resourcespeccode": "deh.linux", + "metering.cloudServiceType": "sys.service.type.ec2", + "image_name": "Standard_CentOS_7_latest", + "metering.resourcetype": "1", + "os_bit": "64", + "vpc_id": "0b98c646-617f-4d90-9ca5-385f0cd73ea7", + "os_type": "Linux", + "charging_mode": "0", + }, + }, + } + th.AssertDeepEquals(t, expected, actual) +} + +func TestAllocateDeH(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + th.Mux.HandleFunc(dehEndpoint, 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, allocateRequest) + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, allocateResponse) + }) + + c := client.ServiceClient() + allocateOpts := hosts.AllocateOpts{Name: "Test-1", + Az: "eu-de-02", + HostType: "h1", + AutoPlacement: "off", + Quantity: 2} + s, err := hosts.Allocate(c, allocateOpts).ExtractHost() + + th.AssertNoErr(t, err) + th.AssertDeepEquals(t, s, &hosts.AllocatedHosts{ + AllocatedHostIds: []string{"fb4733fd-70a3-44e1-a1cb-0311f028d7e5", + "7408f985-047d-4313-b3c8-8e12bef01d12"}, + }) +} + +func TestUpdateDeH(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + th.Mux.HandleFunc(dehEndpoint+"/"+HostID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "PUT") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + th.TestJSONRequest(t, r, updateRequest) + w.WriteHeader(http.StatusNoContent) + }) + + c := client.ServiceClient() + updateOpts := hosts.UpdateOpts{Name: "Test-2", + AutoPlacement: "off", + } + s := hosts.Update(c, HostID, updateOpts) + th.AssertNoErr(t, s.Err) +} + +func TestDeleteDeH(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc(dehEndpoint+"/"+HostID, func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "DELETE") + th.TestHeader(t, r, "X-Auth-Token", fake.TokenID) + w.WriteHeader(http.StatusAccepted) + }) + + result := hosts.Delete(client.ServiceClient(), HostID) + th.AssertNoErr(t, result.Err) +} From 6309962464ce0aa786b72f95709138bc3700436d Mon Sep 17 00:00:00 2001 From: disha-wani Date: Thu, 5 Jul 2018 12:19:02 +0530 Subject: [PATCH 3/8] add DeH service client method --- openstack/client.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/openstack/client.go b/openstack/client.go index 9e13cd2ef..cab90f221 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -496,17 +496,13 @@ func NewAntiDDoSV2(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) // NewDMSServiceV1 creates a ServiceClient that may be used to access the v1 Distributed Message Service. func NewDMSServiceV1(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*golangsdk.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "network") - sc.Endpoint = strings.Replace(sc.Endpoint, "vpc", "dms", 1) - sc.ResourceBase = sc.Endpoint + "v1.0/" + client.ProjectID + "/" + sc, err := initClientOpts(client, eo, "dms") return sc, err } // NewDCSServiceV1 creates a ServiceClient that may be used to access the v1 Distributed Cache Service. func NewDCSServiceV1(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*golangsdk.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "network") - sc.Endpoint = strings.Replace(sc.Endpoint, "vpc", "dcs", 1) - sc.ResourceBase = sc.Endpoint + "v1.0/" + client.ProjectID + "/" + sc, err := initClientOpts(client, eo, "dcs") return sc, err } @@ -523,3 +519,9 @@ func NewHwSFSV2(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*g sc.Endpoint = strings.Replace(sc.Endpoint, "evs", "sfs", 1) return sc, err } + +// NewDeHServiceV1 creates a ServiceClient that may be used to access the v1 Dedicated Hosts service. +func NewDeHServiceV1(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*golangsdk.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "deh") + return sc, err +} From 0d617217f559479c3b29c0b2a91085df89babfc3 Mon Sep 17 00:00:00 2001 From: disha-wani Date: Thu, 12 Jul 2018 10:50:36 +0530 Subject: [PATCH 4/8] changed client method and name of parameter --- openstack/client.go | 9 ++++++++- openstack/deh/v1/hosts/results.go | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/openstack/client.go b/openstack/client.go index cab90f221..dd98245ea 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -521,7 +521,14 @@ func NewHwSFSV2(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*g } // NewDeHServiceV1 creates a ServiceClient that may be used to access the v1 Dedicated Hosts service. -func NewDeHServiceV1(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*golangsdk.ServiceClient, error) { +func NewDeHOTCServiceV1(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*golangsdk.ServiceClient, error) { sc, err := initClientOpts(client, eo, "deh") return sc, err } + +// NewDeHServiceV1 creates a ServiceClient that may be used to access the v1 Dedicated Hosts service. +func NewDeHServiceV1(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*golangsdk.ServiceClient, error) { + sc, err := initClientOpts(client, eo, "evs") + sc.Endpoint = strings.Replace(sc.Endpoint, "evs", "deh", 1) + return sc, err +} \ No newline at end of file diff --git a/openstack/deh/v1/hosts/results.go b/openstack/deh/v1/hosts/results.go index aaf56d808..2d691132f 100644 --- a/openstack/deh/v1/hosts/results.go +++ b/openstack/deh/v1/hosts/results.go @@ -44,9 +44,9 @@ type HostPropertiesOpts struct { Cores int `json:"cores"` Sockets int `json:"sockets"` Memory int `json:"memory"` - AvailableInstanceCapacities []AvailableInstanceCapacitiesOpts `json:"available_instance_capacities"` + InstanceCapacities []InstanceCapacities `json:"available_instance_capacities"` } -type AvailableInstanceCapacitiesOpts struct { +type InstanceCapacities struct { // Specifies the number of supported flavors. Flavor string `json:"flavor"` } From 510950a00ddb723282ca41c0a58481a2b13ebf02 Mon Sep 17 00:00:00 2001 From: disha-wani Date: Thu, 12 Jul 2018 10:59:11 +0530 Subject: [PATCH 5/8] clean code --- openstack/client.go | 2 +- openstack/deh/v1/hosts/results.go | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openstack/client.go b/openstack/client.go index dd98245ea..9f8d036b7 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -531,4 +531,4 @@ func NewDeHServiceV1(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts sc, err := initClientOpts(client, eo, "evs") sc.Endpoint = strings.Replace(sc.Endpoint, "evs", "deh", 1) return sc, err -} \ No newline at end of file +} diff --git a/openstack/deh/v1/hosts/results.go b/openstack/deh/v1/hosts/results.go index 2d691132f..0e0d6ffa6 100644 --- a/openstack/deh/v1/hosts/results.go +++ b/openstack/deh/v1/hosts/results.go @@ -38,13 +38,13 @@ type Host struct { } type HostPropertiesOpts struct { // Specifies the property of host. - HostType string `json:"host_type"` - HostTypeName string `json:"host_type_name"` - Vcpus int `json:"vcpus"` - Cores int `json:"cores"` - Sockets int `json:"sockets"` - Memory int `json:"memory"` - InstanceCapacities []InstanceCapacities `json:"available_instance_capacities"` + HostType string `json:"host_type"` + HostTypeName string `json:"host_type_name"` + Vcpus int `json:"vcpus"` + Cores int `json:"cores"` + Sockets int `json:"sockets"` + Memory int `json:"memory"` + InstanceCapacities []InstanceCapacities `json:"available_instance_capacities"` } type InstanceCapacities struct { // Specifies the number of supported flavors. From 81896153ab47d0882daa3fc915a9c23d1d5821ab Mon Sep 17 00:00:00 2001 From: zengchen1024 Date: Tue, 10 Jul 2018 16:52:39 +0800 Subject: [PATCH 6/8] support creating token by agency --- auth_options.go | 95 +++++++++++++++++++++--- openstack/client.go | 15 ++++ openstack/identity/v3/tokens/requests.go | 7 +- 3 files changed, 107 insertions(+), 10 deletions(-) diff --git a/auth_options.go b/auth_options.go index b09e139d7..cec66f001 100644 --- a/auth_options.go +++ b/auth_options.go @@ -81,6 +81,15 @@ type AuthOptions struct { // TokenID allows users to authenticate (possibly as another user) with an // authentication token ID. TokenID string `json:"-"` + + // AgencyNmae is the name of agnecy + AgencyName string `json:"xrole_name,omitempty"` + + // AgencyDomainName is the domain name who created the agency + AgencyDomainName string `json:"domain_name,omitempty"` + + // AgencyProjectName is the project name of agency + AgencyProjectName string } // ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder @@ -263,13 +272,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s } func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { - - var scope struct { - ProjectID string - ProjectName string - DomainID string - DomainName string - } + var scope scopeInfo if opts.TenantID != "" { scope.ProjectID = opts.TenantID @@ -278,9 +281,31 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { scope.ProjectName = opts.TenantName scope.DomainID = opts.DomainID scope.DomainName = opts.DomainName + } else { + // support scoping to domain + scope.DomainID = opts.DomainID + scope.DomainName = opts.DomainName } } + return scope.BuildTokenV3ScopeMap() +} +func (opts *AuthOptions) CanReauth() bool { + return opts.AllowReauth +} + +func (opts *AuthOptions) AuthTokenID() string { + return "" +} + +type scopeInfo struct { + ProjectID string + ProjectName string + DomainID string + DomainName string +} + +func (scope *scopeInfo) BuildTokenV3ScopeMap() (map[string]interface{}, error) { if scope.ProjectName != "" { // ProjectName provided: either DomainID or DomainName must also be supplied. // ProjectID may not be supplied. @@ -349,6 +374,58 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { return nil, nil } -func (opts AuthOptions) CanReauth() bool { - return opts.AllowReauth +type AgencyAuthOptions struct { + TokenID string + AgencyName string + AgencyDomainName string + AgencyProjectName string +} + +func (opts *AgencyAuthOptions) CanReauth() bool { + return false +} + +func (opts *AgencyAuthOptions) AuthTokenID() string { + return opts.TokenID +} + +func (opts *AgencyAuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { + scope := scopeInfo{ + ProjectName: opts.AgencyProjectName, + DomainName: opts.AgencyDomainName, + } + + return scope.BuildTokenV3ScopeMap() +} + +func (opts *AgencyAuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) { + type assumeRoleReq struct { + DomainName string `json:"domain_name"` + AgencyName string `json:"xrole_name"` + } + + type identityReq struct { + Methods []string `json:"methods"` + AssumeRole assumeRoleReq `json:"assume_role"` + } + + type authReq struct { + Identity identityReq `json:"identity"` + } + + var req authReq + req.Identity.Methods = []string{"assume_role"} + req.Identity.AssumeRole = assumeRoleReq{ + DomainName: opts.AgencyDomainName, + AgencyName: opts.AgencyName, + } + r, err := BuildRequestBody(req, "auth") + if err != nil { + return r, err + } + + if len(scope) != 0 { + r["auth"].(map[string]interface{})["scope"] = scope + } + return r, nil } diff --git a/openstack/client.go b/openstack/client.go index 9f8d036b7..07d3e43d9 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -196,6 +196,21 @@ func v3auth(client *golangsdk.ProviderClient, endpoint string, opts tokens3.Auth return err } + opts1, ok := opts.(*golangsdk.AuthOptions) + if ok && opts1.AgencyDomainName != "" && opts1.AgencyName != "" { + opts2 := golangsdk.AgencyAuthOptions{ + TokenID: token.ID, + AgencyName: opts1.AgencyName, + AgencyDomainName: opts1.AgencyDomainName, + AgencyProjectName: opts1.AgencyProjectName, + } + result = tokens3.Create(v3Client, &opts2) + token, err = result.ExtractToken() + if err != nil { + return err + } + } + project, err := result.ExtractProject() if err != nil { return err diff --git a/openstack/identity/v3/tokens/requests.go b/openstack/identity/v3/tokens/requests.go index b4b60499b..381ec2689 100644 --- a/openstack/identity/v3/tokens/requests.go +++ b/openstack/identity/v3/tokens/requests.go @@ -18,6 +18,7 @@ type AuthOptionsBuilder interface { ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error) ToTokenV3ScopeMap() (map[string]interface{}, error) CanReauth() bool + AuthTokenID() string } // AuthOptions represents options for authenticating a user. @@ -144,6 +145,10 @@ func (opts *AuthOptions) CanReauth() bool { return opts.AllowReauth } +func (opts *AuthOptions) AuthTokenID() string { + return "" +} + func subjectTokenHeaders(c *golangsdk.ServiceClient, subjectToken string) map[string]string { return map[string]string{ "X-Subject-Token": subjectToken, @@ -166,7 +171,7 @@ func Create(c *golangsdk.ServiceClient, opts AuthOptionsBuilder) (r CreateResult } resp, err := c.Post(tokenURL(c), b, &r.Body, &golangsdk.RequestOpts{ - MoreHeaders: map[string]string{"X-Auth-Token": ""}, + MoreHeaders: map[string]string{"X-Auth-Token": opts.AuthTokenID()}, }) r.Err = err if resp != nil { From abe5139f2d0a0b820938adee112bbe85b48e6771 Mon Sep 17 00:00:00 2001 From: zengchen1024 Date: Wed, 11 Jul 2018 17:52:05 +0800 Subject: [PATCH 7/8] fix panic when retriving the project id --- openstack/client.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openstack/client.go b/openstack/client.go index 07d3e43d9..08fa942ab 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -222,7 +222,9 @@ func v3auth(client *golangsdk.ProviderClient, endpoint string, opts tokens3.Auth } client.TokenID = token.ID - client.ProjectID = project.ID + if project != nil { + client.ProjectID = project.ID + } if opts.CanReauth() { client.ReauthFunc = func() error { From 5337d39058ed120afe57fe2144f177ac09309e48 Mon Sep 17 00:00:00 2001 From: Sapan Date: Thu, 12 Jul 2018 16:10:24 +0800 Subject: [PATCH 8/8] support common deh client and revert other client changes --- openstack/client.go | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/openstack/client.go b/openstack/client.go index 08fa942ab..10e649226 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -513,13 +513,17 @@ func NewAntiDDoSV2(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) // NewDMSServiceV1 creates a ServiceClient that may be used to access the v1 Distributed Message Service. func NewDMSServiceV1(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*golangsdk.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "dms") + sc, err := initClientOpts(client, eo, "network") + sc.Endpoint = strings.Replace(sc.Endpoint, "vpc", "dms", 1) + sc.ResourceBase = sc.Endpoint + "v1.0/" + client.ProjectID + "/" return sc, err } // NewDCSServiceV1 creates a ServiceClient that may be used to access the v1 Distributed Cache Service. func NewDCSServiceV1(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*golangsdk.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "dcs") + sc, err := initClientOpts(client, eo, "network") + sc.Endpoint = strings.Replace(sc.Endpoint, "vpc", "dcs", 1) + sc.ResourceBase = sc.Endpoint + "v1.0/" + client.ProjectID + "/" return sc, err } @@ -537,15 +541,8 @@ func NewHwSFSV2(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*g return sc, err } -// NewDeHServiceV1 creates a ServiceClient that may be used to access the v1 Dedicated Hosts service. -func NewDeHOTCServiceV1(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*golangsdk.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "deh") - return sc, err -} - // NewDeHServiceV1 creates a ServiceClient that may be used to access the v1 Dedicated Hosts service. func NewDeHServiceV1(client *golangsdk.ProviderClient, eo golangsdk.EndpointOpts) (*golangsdk.ServiceClient, error) { - sc, err := initClientOpts(client, eo, "evs") - sc.Endpoint = strings.Replace(sc.Endpoint, "evs", "deh", 1) + sc, err := initClientOpts(client, eo, "deh") return sc, err -} +} \ No newline at end of file