Skip to content

Commit

Permalink
Zun Capsule Creation Method (#954)
Browse files Browse the repository at this point in the history
* Add capsule creation method

Also add unit test and accept test

Depends-On: #944

Change-Id: I29f9c56b0aa71ef4d20917cbc216b909a253f473
Signed-off-by: Kevin Zhao <kevin.zhao@arm.com>

* Remove TE and Validate method

Change-Id: I06f3295970f5945998ca75a2b5dc40b427eef8be
Signed-off-by: Kevin Zhao <kevin.zhao@arm.com>

* Remove the unused error type

Change-Id: Ib6f016107d3c5c367ceeb968c699c981c3238643
Signed-off-by: Kevin Zhao <kevin.zhao@arm.com>

* Remove the invalid change in orchestration

Change-Id: Ib7a0e4da4c69f416e425229910b48d128facc010
Signed-off-by: Kevin Zhao <kevin.zhao@arm.com>
  • Loading branch information
kevinzs2048 authored and jtopjian committed Apr 27, 2018
1 parent 27dfd3c commit a3ac253
Show file tree
Hide file tree
Showing 10 changed files with 411 additions and 5 deletions.
56 changes: 56 additions & 0 deletions acceptance/openstack/container/v1/capsules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,59 @@ func TestCapsuleGet(t *testing.T) {
th.AssertEquals(t, capsule.MetaName, "template")
th.AssertEquals(t, capsule.CPU, float64(2.0))
}

func TestCapsuleCreate(t *testing.T) {
client, err := clients.NewContainerV1Client()
if err != nil {
t.Fatalf("Unable to create an container v1 client: %v", err)
}
th.AssertNoErr(t, err)
template := new(capsules.Template)
template.Bin = []byte(`{
"capsuleVersion": "beta",
"kind": "capsule",
"metadata": {
"labels": {
"app": "web",
"app1": "web1"
},
"name": "template"
},
"restartPolicy": "Always",
"spec": {
"containers": [
{
"command": [
"/bin/bash"
],
"env": {
"ENV1": "/usr/local/bin",
"ENV2": "/usr/bin"
},
"image": "ubuntu",
"imagePullPolicy": "ifnotpresent",
"ports": [
{
"containerPort": 80,
"hostPort": 80,
"name": "nginx-port",
"protocol": "TCP"
}
],
"resources": {
"requests": {
"cpu": 1,
"memory": 1024
}
},
"workDir": "/root"
}
]
}
}`)
createOpts := capsules.CreateOpts{
TemplateOpts: template,
}
err = capsules.Create(client, createOpts).ExtractErr()
th.AssertNoErr(t, err)
}
15 changes: 15 additions & 0 deletions openstack/container/v1/capsules/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package capsules

import (
"fmt"

"github.com/gophercloud/gophercloud"
)

type ErrInvalidDataFormat struct {
gophercloud.BaseError
}

func (e ErrInvalidDataFormat) Error() string {
return fmt.Sprintf("Data in neither json nor yaml format.")
}
166 changes: 166 additions & 0 deletions openstack/container/v1/capsules/fixtures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package capsules

// ValidJSONTemplate is a valid OpenStack Capsule template in JSON format
const ValidJSONTemplate = `
{
"capsuleVersion": "beta",
"kind": "capsule",
"metadata": {
"labels": {
"app": "web",
"app1": "web1"
},
"name": "template"
},
"restartPolicy": "Always",
"spec": {
"containers": [
{
"command": [
"/bin/bash"
],
"env": {
"ENV1": "/usr/local/bin",
"ENV2": "/usr/bin"
},
"image": "ubuntu",
"imagePullPolicy": "ifnotpresent",
"ports": [
{
"containerPort": 80,
"hostPort": 80,
"name": "nginx-port",
"protocol": "TCP"
}
],
"resources": {
"requests": {
"cpu": 1,
"memory": 1024
}
},
"workDir": "/root"
}
]
}
}
`

// ValidYAMLTemplate is a valid OpenStack Capsule template in YAML format
const ValidYAMLTemplate = `
capsuleVersion: beta
kind: capsule
metadata:
name: template
labels:
app: web
app1: web1
restartPolicy: Always
spec:
containers:
- image: ubuntu
command:
- "/bin/bash"
imagePullPolicy: ifnotpresent
workDir: /root
ports:
- name: nginx-port
containerPort: 80
hostPort: 80
protocol: TCP
resources:
requests:
cpu: 1
memory: 1024
env:
ENV1: /usr/local/bin
ENV2: /usr/bin
`

// ValidJSONTemplateParsed is the expected parsed version of ValidJSONTemplate
var ValidJSONTemplateParsed = map[string]interface{}{
"capsuleVersion": "beta",
"kind": "capsule",
"restartPolicy": "Always",
"metadata": map[string]interface{}{
"name": "template",
"labels": map[string]string{
"app": "web",
"app1": "web1",
},
},
"spec": map[string]interface{}{
"containers": []map[string]interface{}{
map[string]interface{}{
"image": "ubuntu",
"command": []interface{}{
"/bin/bash",
},
"imagePullPolicy": "ifnotpresent",
"workDir": "/root",
"ports": []interface{}{
map[string]interface{}{
"name": "nginx-port",
"containerPort": float64(80),
"hostPort": float64(80),
"protocol": "TCP",
},
},
"resources": map[string]interface{}{
"requests": map[string]interface{}{
"cpu": float64(1),
"memory": float64(1024),
},
},
"env": map[string]interface{}{
"ENV1": "/usr/local/bin",
"ENV2": "/usr/bin",
},
},
},
},
}

// ValidYAMLTemplateParsed is the expected parsed version of ValidYAMLTemplate
var ValidYAMLTemplateParsed = map[string]interface{}{
"capsuleVersion": "beta",
"kind": "capsule",
"restartPolicy": "Always",
"metadata": map[string]interface{}{
"name": "template",
"labels": map[string]string{
"app": "web",
"app1": "web1",
},
},
"spec": map[interface{}]interface{}{
"containers": []map[interface{}]interface{}{
map[interface{}]interface{}{
"image": "ubuntu",
"command": []interface{}{
"/bin/bash",
},
"imagePullPolicy": "ifnotpresent",
"workDir": "/root",
"ports": []interface{}{
map[interface{}]interface{}{
"name": "nginx-port",
"containerPort": 80,
"hostPort": 80,
"protocol": "TCP",
},
},
"resources": map[interface{}]interface{}{
"requests": map[interface{}]interface{}{
"cpu": 1,
"memory": 1024,
},
},
"env": map[interface{}]interface{}{
"ENV1": "/usr/local/bin",
"ENV2": "/usr/bin",
},
},
},
},
}
43 changes: 43 additions & 0 deletions openstack/container/v1/capsules/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,53 @@ import (
"github.com/gophercloud/gophercloud"
)

// CreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type CreateOptsBuilder interface {
ToCapsuleCreateMap() (map[string]interface{}, error)
}

// 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
}

// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts struct {
// A structure that contains either the template file or url. Call the
// associated methods to extract the information relevant to send in a create request.
TemplateOpts *Template `json:"-" required:"true"`
}

// ToCapsuleCreateMap assembles a request body based on the contents of
// a CreateOpts.
func (opts CreateOpts) ToCapsuleCreateMap() (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "")
if err != nil {
return nil, err
}

if err := opts.TemplateOpts.Parse(); err != nil {
return nil, err
}
b["template"] = string(opts.TemplateOpts.Bin)

return b, nil
}

// Create implements create capsule request.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToCapsuleCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{202}})
return
}
6 changes: 6 additions & 0 deletions openstack/container/v1/capsules/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ type GetResult struct {
commonResult
}

// CreateResult is the response from a Create operation. Call its Extract
// method to interpret it as a Server.
type CreateResult struct {
gophercloud.ErrResult
}

// Represents a Container Orchestration Engine Bay, i.e. a cluster
type Capsule struct {
// UUID for the capsule
Expand Down
27 changes: 27 additions & 0 deletions openstack/container/v1/capsules/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package capsules

import (
"encoding/json"

"gopkg.in/yaml.v2"
)

// Template is a structure that represents OpenStack Zun Capsule templates
type Template struct {
// Bin stores the contents of the template or environment.
Bin []byte
// Parsed contains a parsed version of Bin. Since there are 2 different
// fields referring to the same value, you must be careful when accessing
// this filed.
Parsed map[string]interface{}
}

// Parse will parse the contents and then validate. The contents MUST be either JSON or YAML.
func (t *Template) Parse() error {
if jerr := json.Unmarshal(t.Bin, &t.Parsed); jerr != nil {
if yerr := yaml.Unmarshal(t.Bin, &t.Parsed); yerr != nil {
return ErrInvalidDataFormat{}
}
}
return nil
}
28 changes: 28 additions & 0 deletions openstack/container/v1/capsules/template_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package capsules

import (
"testing"

th "github.com/gophercloud/gophercloud/testhelper"
)

func TestTemplateParsing(t *testing.T) {
templateJSON := new(Template)
templateJSON.Bin = []byte(ValidJSONTemplate)
err := templateJSON.Parse()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, ValidJSONTemplateParsed, templateJSON.Parsed)

templateYAML := new(Template)
templateYAML.Bin = []byte(ValidYAMLTemplate)
err = templateYAML.Parse()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, ValidYAMLTemplateParsed, templateYAML.Parsed)

templateInvalid := new(Template)
templateInvalid.Bin = []byte("Keep Austin Weird")
err = templateInvalid.Parse()
if err == nil {
t.Error("Template parsing did not catch invalid template")
}
}
17 changes: 12 additions & 5 deletions openstack/container/v1/capsules/testing/fixtures.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ import (
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) {
Expand Down Expand Up @@ -66,3 +61,15 @@ func HandleCapsuleGetSuccessfully(t *testing.T) {
}`)
})
}

// HandleCapsuleCreateSuccessfully creates an HTTP handler at `/capsules` on the test handler mux
// that responds with a `Create` response.
func HandleCapsuleCreateSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/capsules", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID)
th.TestHeader(t, r, "Accept", "application/json")
w.WriteHeader(http.StatusAccepted)
fmt.Fprintf(w, `{}`)
})
}

0 comments on commit a3ac253

Please sign in to comment.