From 29842f990b3db904777ca78e38128196234e680d Mon Sep 17 00:00:00 2001 From: Kevin Zhao Date: Tue, 13 Feb 2018 09:47:36 -0600 Subject: [PATCH 1/2] Add support for Zun experimental API "Capsule"Get (#725) * Add support for Zun experimental API "Capsule" get Also add the test case and acceptance test. Signed-off-by: Kevin Zhao * Add SubnetID to Addresses Signed-off-by: Kevin Zhao --- acceptance/clients/clients.go | 19 ++++ .../container/experimental/capsules_test.go | 28 +++++ openstack/client.go | 5 + .../container/experimental/capsules/doc.go | 4 + .../experimental/capsules/requests.go | 13 +++ .../experimental/capsules/results.go | 107 ++++++++++++++++++ .../experimental/capsules/testing/doc.go | 1 + .../experimental/capsules/testing/fixtures.go | 68 +++++++++++ .../capsules/testing/requests_test.go | 91 +++++++++++++++ .../container/experimental/capsules/urls.go | 7 ++ results.go | 21 ++++ 11 files changed, 364 insertions(+) create mode 100644 acceptance/openstack/container/experimental/capsules_test.go create mode 100644 openstack/container/experimental/capsules/doc.go create mode 100644 openstack/container/experimental/capsules/requests.go create mode 100644 openstack/container/experimental/capsules/results.go create mode 100644 openstack/container/experimental/capsules/testing/doc.go create mode 100644 openstack/container/experimental/capsules/testing/fixtures.go create mode 100644 openstack/container/experimental/capsules/testing/requests_test.go create mode 100644 openstack/container/experimental/capsules/urls.go diff --git a/acceptance/clients/clients.go b/acceptance/clients/clients.go index d664f90109..29adb0a1da 100644 --- a/acceptance/clients/clients.go +++ b/acceptance/clients/clients.go @@ -521,6 +521,25 @@ func NewMessagingV2Client(clientID string) (*gophercloud.ServiceClient, error) { }) } +// NewContainerExperimentalClient returns a *ServiceClient for making calls +// to the OpenStack Container Experimental API. An error will be returned +// if authentication or client creation was not possible. +func NewContainerExperimentalClient() (*gophercloud.ServiceClient, error) { + ao, err := openstack.AuthOptionsFromEnv() + if err != nil { + return nil, err + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return nil, err + } + + return openstack.NewContainerExperimental(client, gophercloud.EndpointOpts{ + Region: os.Getenv("OS_REGION_NAME"), + }) +} + // configureDebug will configure the provider client to print the API // requests and responses if OS_DEBUG is enabled. func configureDebug(client *gophercloud.ProviderClient) *gophercloud.ProviderClient { diff --git a/acceptance/openstack/container/experimental/capsules_test.go b/acceptance/openstack/container/experimental/capsules_test.go new file mode 100644 index 0000000000..5c037aa201 --- /dev/null +++ b/acceptance/openstack/container/experimental/capsules_test.go @@ -0,0 +1,28 @@ +package experimental + +import ( + "testing" + + "github.com/gophercloud/gophercloud/acceptance/clients" + "github.com/gophercloud/gophercloud/openstack/container/experimental/capsules" + th "github.com/gophercloud/gophercloud/testhelper" +) + +func TestCapsuleGet(t *testing.T) { + client, err := clients.NewContainerExperimentalClient() + if err != nil { + t.Fatalf("Unable to create an container experimental client: %v", err) + } + th.AssertNoErr(t, err) + capsuleUUID := "e6c913bb-b4e4-409d-8b71-3e029f196458" + if capsuleUUID == "" { + t.Fatalf("In order to retrieve a capsule, the CapsuleUUID must be set") + } + capsule, err := capsules.Get(client, capsuleUUID).Extract() + // Get a capsule + + th.AssertNoErr(t, err) + th.AssertEquals(t, capsule.Status, "Running") + th.AssertEquals(t, capsule.MetaName, "template") + th.AssertEquals(t, capsule.CPU, float64(2.0)) +} diff --git a/openstack/client.go b/openstack/client.go index b9e187ddaa..2847d8da65 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -408,3 +408,8 @@ func NewMessagingV2(client *gophercloud.ProviderClient, clientID string, eo goph sc.MoreHeaders = map[string]string{"Client-ID": clientID} return sc, err } + +// NewContainerExperimental creates a ServiceClient that may be used with experimental container package +func NewContainerExperimental(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "container-experimental") +} diff --git a/openstack/container/experimental/capsules/doc.go b/openstack/container/experimental/capsules/doc.go new file mode 100644 index 0000000000..5237759eb2 --- /dev/null +++ b/openstack/container/experimental/capsules/doc.go @@ -0,0 +1,4 @@ +// Package capsules contains functionality for working with Zun capsule +// resources. A capsule is a container group, as the co-located and +// co-scheduled unit, is the same like pod in Kubernetes. +package capsules diff --git a/openstack/container/experimental/capsules/requests.go b/openstack/container/experimental/capsules/requests.go new file mode 100644 index 0000000000..25b1ffa875 --- /dev/null +++ b/openstack/container/experimental/capsules/requests.go @@ -0,0 +1,13 @@ +package capsules + +import ( + "github.com/gophercloud/gophercloud" +) + +// Get requests details on a single capsule, by ID. +func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { + _, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{ + OkCodes: []int{200, 203}, + }) + return +} diff --git a/openstack/container/experimental/capsules/results.go b/openstack/container/experimental/capsules/results.go new file mode 100644 index 0000000000..26b56e45e1 --- /dev/null +++ b/openstack/container/experimental/capsules/results.go @@ -0,0 +1,107 @@ +package capsules + +import ( + "encoding/json" + "time" + + "github.com/gophercloud/gophercloud" +) + +type commonResult struct { + gophercloud.Result +} + +// Extract is a function that accepts a result and extracts a capsule resource. +func (r commonResult) Extract() (*Capsule, error) { + var s *Capsule + err := r.ExtractInto(&s) + return s, err +} + +// GetResult represents the result of a get operation. +type GetResult struct { + commonResult +} + +// Represents a Container Orchestration Engine Bay, i.e. a cluster +type Capsule struct { + // UUID for the capsule + UUID string `json:"uuid"` + + // ID for the capsule + ID int `json:"id"` + + // User ID for the capsule + UserID string `json:"user_id"` + + // Project ID for the capsule + ProjectID string `json:"project_id"` + + // cpu for the capsule + CPU float64 `json:"cpu"` + + // Memory for the capsule + Memory string `json:"memory"` + + // The name of the capsule + MetaName string `json:"meta_name"` + + // Indicates whether capsule is currently operational. Possible values include: + // Running, + Status string `json:"status"` + + // The created time of the capsule. + CreatedAt time.Time `json:"-"` + + // The updated time of the capsule. + UpdatedAt time.Time `json:"-"` + + // Links includes HTTP references to the itself, useful for passing along to + // other APIs that might want a server reference. + Links []interface{} `json:"links"` + + // The capsule version + CapsuleVersion string `json:"capsule_version"` + + // The capsule restart policy + RestartPolicy string `json:"restart_policy"` + + // The capsule metadata labels + MetaLabels map[string]string `json:"meta_labels"` + + // The list of containers uuids inside capsule. + ContainersUUIDs []string `json:"containers_uuids"` + + // The capsule IP addresses + Addresses map[string][]Address `json:"addresses"` + + // The capsule volume attached information + VolumesInfo map[string][]string `json:"volumes_info"` +} + +type Address struct { + PreserveOnDelete bool `json:"preserve_on_delete"` + Addr string `json:"addr"` + Port string `json:"port"` + Version float64 `json:"version"` + SubnetID string `json:"subnet_id"` +} + +func (r *Capsule) UnmarshalJSON(b []byte) error { + type tmp Capsule + var s struct { + tmp + CreatedAt gophercloud.JSONRFC3339ZNoT `json:"created_at"` + UpdatedAt gophercloud.JSONRFC3339ZNoT `json:"updated_at"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Capsule(s.tmp) + + r.CreatedAt = time.Time(s.CreatedAt) + r.UpdatedAt = time.Time(s.UpdatedAt) + + return nil +} diff --git a/openstack/container/experimental/capsules/testing/doc.go b/openstack/container/experimental/capsules/testing/doc.go new file mode 100644 index 0000000000..7603f836a0 --- /dev/null +++ b/openstack/container/experimental/capsules/testing/doc.go @@ -0,0 +1 @@ +package testing diff --git a/openstack/container/experimental/capsules/testing/fixtures.go b/openstack/container/experimental/capsules/testing/fixtures.go new file mode 100644 index 0000000000..017d12bc7e --- /dev/null +++ b/openstack/container/experimental/capsules/testing/fixtures.go @@ -0,0 +1,68 @@ +package testing + +import ( + "fmt" + "net/http" + "testing" + + th "github.com/gophercloud/gophercloud/testhelper" + fakeclient "github.com/gophercloud/gophercloud/testhelper/client" +) + +type imageEntry struct { + ID string + JSON string +} + +// HandleImageGetSuccessfully test setup +func HandleCapsuleGetSuccessfully(t *testing.T) { + th.Mux.HandleFunc("/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID) + + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-Type", "application/json") + fmt.Fprintf(w, `{ + "uuid": "cc654059-1a77-47a3-bfcf-715bde5aad9e", + "status": "Running", + "id": 1, + "user_id": "d33b18c384574fd2a3299447aac285f0", + "project_id": "6b8ffef2a0ac42ee87887b9cc98bdf68", + "cpu": 1, + "memory": "1024M", + "meta_name": "test", + "meta_labels": {"web": "app"}, + "created_at": "2018-01-12 09:37:25+00:00", + "updated_at": "2018-01-12 09:37:25+01:00", + "links": [ + { + "href": "http://10.10.10.10/v1/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "self" + }, + { + "href": "http://10.10.10.10/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "bookmark" + } + ], + "capsule_version": "beta", + "restart_policy": "always", + "containers_uuids": ["1739e28a-d391-4fd9-93a5-3ba3f29a4c9b", "d1469e8d-bcbc-43fc-b163-8b9b6a740930"], + "addresses": { + "b1295212-64e1-471d-aa01-25ff46f9818d": [ + { + "version": 4, + "preserve_on_delete": false, + "addr": "172.24.4.11", + "port": "8439060f-381a-4386-a518-33d5a4058636", + "subnet_id": "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a" + } + ] + }, + "volumes_info": { + "67618d54-dd55-4f7e-91b3-39ffb3ba7f5f": [ + "4b725a92-2197-497b-b6b1-fb8caa4cb99b" + ] + } + }`) + }) +} diff --git a/openstack/container/experimental/capsules/testing/requests_test.go b/openstack/container/experimental/capsules/testing/requests_test.go new file mode 100644 index 0000000000..8f7b64b39c --- /dev/null +++ b/openstack/container/experimental/capsules/testing/requests_test.go @@ -0,0 +1,91 @@ +package testing + +import ( + "testing" + "time" + + "github.com/gophercloud/gophercloud" + "github.com/gophercloud/gophercloud/openstack/container/experimental/capsules" + th "github.com/gophercloud/gophercloud/testhelper" + fakeclient "github.com/gophercloud/gophercloud/testhelper/client" +) + +func TestGetCapsule(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleCapsuleGetSuccessfully(t) + + actualCapsule, err := capsules.Get(fakeclient.ServiceClient(), "cc654059-1a77-47a3-bfcf-715bde5aad9e").Extract() + + th.AssertNoErr(t, err) + + uuid := "cc654059-1a77-47a3-bfcf-715bde5aad9e" + status := "Running" + id := 1 + userID := "d33b18c384574fd2a3299447aac285f0" + projectID := "6b8ffef2a0ac42ee87887b9cc98bdf68" + cpu := float64(1) + memory := "1024M" + metaName := "test" + + createdAt, _ := time.Parse(gophercloud.RFC3339ZNoT, "2018-01-12 09:37:25+00:00") + updatedAt, _ := time.Parse(gophercloud.RFC3339ZNoT, "2018-01-12 09:37:25+01:00") + links := []interface{}{ + map[string]interface{}{ + "href": "http://10.10.10.10/v1/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "self", + }, + map[string]interface{}{ + "href": "http://10.10.10.10/capsules/cc654059-1a77-47a3-bfcf-715bde5aad9e", + "rel": "bookmark", + }, + } + capsuleVersion := "beta" + restartPolicy := "always" + metaLabels := map[string]string{ + "web": "app", + } + containersUUIDs := []string{ + "1739e28a-d391-4fd9-93a5-3ba3f29a4c9b", + "d1469e8d-bcbc-43fc-b163-8b9b6a740930", + } + addresses := map[string][]capsules.Address{ + "b1295212-64e1-471d-aa01-25ff46f9818d": []capsules.Address{ + { + PreserveOnDelete: false, + Addr: "172.24.4.11", + Port: "8439060f-381a-4386-a518-33d5a4058636", + Version: float64(4), + SubnetID: "4a2bcd64-93ad-4436-9f48-3a7f9b267e0a", + }, + }, + } + volumesInfo := map[string][]string{ + "67618d54-dd55-4f7e-91b3-39ffb3ba7f5f": []string{ + "4b725a92-2197-497b-b6b1-fb8caa4cb99b", + }, + } + + expectedCapsule := capsules.Capsule{ + UUID: uuid, + ID: id, + UserID: userID, + ProjectID: projectID, + CPU: cpu, + Status: status, + Memory: memory, + MetaName: metaName, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + Links: links, + CapsuleVersion: capsuleVersion, + RestartPolicy: restartPolicy, + MetaLabels: metaLabels, + ContainersUUIDs: containersUUIDs, + Addresses: addresses, + VolumesInfo: volumesInfo, + } + + th.AssertDeepEquals(t, &expectedCapsule, actualCapsule) +} diff --git a/openstack/container/experimental/capsules/urls.go b/openstack/container/experimental/capsules/urls.go new file mode 100644 index 0000000000..c3baf01a84 --- /dev/null +++ b/openstack/container/experimental/capsules/urls.go @@ -0,0 +1,7 @@ +package capsules + +import "github.com/gophercloud/gophercloud" + +func getURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("capsules", id) +} diff --git a/results.go b/results.go index e64feee19e..fdd4830ec1 100644 --- a/results.go +++ b/results.go @@ -345,6 +345,27 @@ func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error { return nil } +// RFC3339ZNoT is the time format used in Zun (Containers Service). +const RFC3339ZNoT = "2006-01-02 15:04:05-07:00" + +type JSONRFC3339ZNoT time.Time + +func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + t, err := time.Parse(RFC3339ZNoT, s) + if err != nil { + return err + } + *jt = JSONRFC3339ZNoT(t) + return nil +} + /* Link is an internal type to be used in packages of collection resources that are paginated in a certain way. From 10a32ceb59cc4f9a28693ec7fadc1e440b590ffe Mon Sep 17 00:00:00 2001 From: Kevin Zhao Date: Tue, 6 Mar 2018 16:04:23 +0800 Subject: [PATCH 2/2] Move capsule from experimental to v1 Refer to: https://review.openstack.org/543878 Signed-off-by: Kevin Zhao --- acceptance/clients/clients.go | 8 ++++---- .../container/{experimental => v1}/capsules_test.go | 8 ++++---- openstack/client.go | 6 +++--- openstack/container/{experimental => v1}/capsules/doc.go | 0 .../container/{experimental => v1}/capsules/requests.go | 0 .../container/{experimental => v1}/capsules/results.go | 0 .../{experimental => v1}/capsules/testing/doc.go | 0 .../{experimental => v1}/capsules/testing/fixtures.go | 0 .../capsules/testing/requests_test.go | 2 +- openstack/container/{experimental => v1}/capsules/urls.go | 0 10 files changed, 12 insertions(+), 12 deletions(-) rename acceptance/openstack/container/{experimental => v1}/capsules_test.go (72%) rename openstack/container/{experimental => v1}/capsules/doc.go (100%) rename openstack/container/{experimental => v1}/capsules/requests.go (100%) rename openstack/container/{experimental => v1}/capsules/results.go (100%) rename openstack/container/{experimental => v1}/capsules/testing/doc.go (100%) rename openstack/container/{experimental => v1}/capsules/testing/fixtures.go (100%) rename openstack/container/{experimental => v1}/capsules/testing/requests_test.go (96%) rename openstack/container/{experimental => v1}/capsules/urls.go (100%) diff --git a/acceptance/clients/clients.go b/acceptance/clients/clients.go index 29adb0a1da..c07fecf22a 100644 --- a/acceptance/clients/clients.go +++ b/acceptance/clients/clients.go @@ -521,10 +521,10 @@ func NewMessagingV2Client(clientID string) (*gophercloud.ServiceClient, error) { }) } -// NewContainerExperimentalClient returns a *ServiceClient for making calls -// to the OpenStack Container Experimental API. An error will be returned +// NewContainerV1Client returns a *ServiceClient for making calls +// to the OpenStack Container V1 API. An error will be returned // if authentication or client creation was not possible. -func NewContainerExperimentalClient() (*gophercloud.ServiceClient, error) { +func NewContainerV1Client() (*gophercloud.ServiceClient, error) { ao, err := openstack.AuthOptionsFromEnv() if err != nil { return nil, err @@ -535,7 +535,7 @@ func NewContainerExperimentalClient() (*gophercloud.ServiceClient, error) { return nil, err } - return openstack.NewContainerExperimental(client, gophercloud.EndpointOpts{ + return openstack.NewContainerV1(client, gophercloud.EndpointOpts{ Region: os.Getenv("OS_REGION_NAME"), }) } diff --git a/acceptance/openstack/container/experimental/capsules_test.go b/acceptance/openstack/container/v1/capsules_test.go similarity index 72% rename from acceptance/openstack/container/experimental/capsules_test.go rename to acceptance/openstack/container/v1/capsules_test.go index 5c037aa201..41dad46339 100644 --- a/acceptance/openstack/container/experimental/capsules_test.go +++ b/acceptance/openstack/container/v1/capsules_test.go @@ -1,17 +1,17 @@ -package experimental +package v1 import ( "testing" "github.com/gophercloud/gophercloud/acceptance/clients" - "github.com/gophercloud/gophercloud/openstack/container/experimental/capsules" + "github.com/gophercloud/gophercloud/openstack/container/v1/capsules" th "github.com/gophercloud/gophercloud/testhelper" ) func TestCapsuleGet(t *testing.T) { - client, err := clients.NewContainerExperimentalClient() + client, err := clients.NewContainerV1Client() if err != nil { - t.Fatalf("Unable to create an container experimental client: %v", err) + t.Fatalf("Unable to create an container v1 client: %v", err) } th.AssertNoErr(t, err) capsuleUUID := "e6c913bb-b4e4-409d-8b71-3e029f196458" diff --git a/openstack/client.go b/openstack/client.go index 2847d8da65..0178d358eb 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -409,7 +409,7 @@ func NewMessagingV2(client *gophercloud.ProviderClient, clientID string, eo goph return sc, err } -// NewContainerExperimental creates a ServiceClient that may be used with experimental container package -func NewContainerExperimental(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { - return initClientOpts(client, eo, "container-experimental") +// NewContainerV1 creates a ServiceClient that may be used with v1 container package +func NewContainerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { + return initClientOpts(client, eo, "container") } diff --git a/openstack/container/experimental/capsules/doc.go b/openstack/container/v1/capsules/doc.go similarity index 100% rename from openstack/container/experimental/capsules/doc.go rename to openstack/container/v1/capsules/doc.go diff --git a/openstack/container/experimental/capsules/requests.go b/openstack/container/v1/capsules/requests.go similarity index 100% rename from openstack/container/experimental/capsules/requests.go rename to openstack/container/v1/capsules/requests.go diff --git a/openstack/container/experimental/capsules/results.go b/openstack/container/v1/capsules/results.go similarity index 100% rename from openstack/container/experimental/capsules/results.go rename to openstack/container/v1/capsules/results.go diff --git a/openstack/container/experimental/capsules/testing/doc.go b/openstack/container/v1/capsules/testing/doc.go similarity index 100% rename from openstack/container/experimental/capsules/testing/doc.go rename to openstack/container/v1/capsules/testing/doc.go diff --git a/openstack/container/experimental/capsules/testing/fixtures.go b/openstack/container/v1/capsules/testing/fixtures.go similarity index 100% rename from openstack/container/experimental/capsules/testing/fixtures.go rename to openstack/container/v1/capsules/testing/fixtures.go diff --git a/openstack/container/experimental/capsules/testing/requests_test.go b/openstack/container/v1/capsules/testing/requests_test.go similarity index 96% rename from openstack/container/experimental/capsules/testing/requests_test.go rename to openstack/container/v1/capsules/testing/requests_test.go index 8f7b64b39c..016ad0d277 100644 --- a/openstack/container/experimental/capsules/testing/requests_test.go +++ b/openstack/container/v1/capsules/testing/requests_test.go @@ -5,7 +5,7 @@ import ( "time" "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack/container/experimental/capsules" + "github.com/gophercloud/gophercloud/openstack/container/v1/capsules" th "github.com/gophercloud/gophercloud/testhelper" fakeclient "github.com/gophercloud/gophercloud/testhelper/client" ) diff --git a/openstack/container/experimental/capsules/urls.go b/openstack/container/v1/capsules/urls.go similarity index 100% rename from openstack/container/experimental/capsules/urls.go rename to openstack/container/v1/capsules/urls.go