Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a restful to create the volume object. #37

Merged
merged 4 commits into from
Jun 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/entity/volume.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package entity

import (
"time"

"gopkg.in/mgo.v2/bson"
corev1 "k8s.io/api/core/v1"
)

const (
VolumeCollectionName string = "volume"
)

/*
Users will create the Volume from the storageProvider and they can use those volumes in their containers
In the kubernetes implementation, it's PVC
So the Volume will create a PVC type and connect to a known StorageClass
*/
type Volume struct {
ID bson.ObjectId `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
StorageProviderName string `bson:"storageProviderName" json:"storageProviderName"`
AccessMode corev1.PersistentVolumeAccessMode `bson:"accessMode" json:"accessMode"`
Capacity string `bson:"capacity" json:"capacity"`
MetaName string `bson:"metaName" json:"metaName"` //For PVC metaname
CreatedAt *time.Time `bson:"createdAt,omitempty" json:"createdAt,omitempty"`
}

//GetCollection - get model mongo collection name.
func (m Volume) GetCollection() string {
return VolumeCollectionName
}

//GenerateMetaName - Generate a metaname for kubernetes PVC object
func (m Volume) GenerateMetaName() string {
return "PVC-" + m.ID.Hex()
}
58 changes: 58 additions & 0 deletions src/server/handler_volume.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package server

import (
"fmt"

"github.com/linkernetworks/utils/timeutils"
"github.com/linkernetworks/vortex/src/entity"
response "github.com/linkernetworks/vortex/src/net/http"
"github.com/linkernetworks/vortex/src/web"
mgo "gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)

func createVolume(ctx *web.Context) {
sp, req, resp := ctx.ServiceProvider, ctx.Request, ctx.Response

volume := entity.Volume{}
if err := req.ReadEntity(&volume); err != nil {
response.BadRequest(req.Request, resp.ResponseWriter, err)
return
}

session := sp.Mongo.NewSession()
session.C(entity.VolumeCollectionName).EnsureIndex(mgo.Index{
Key: []string{"name"},
Unique: true,
})
defer session.Close()

// Check the storageClass is existed
couunt, err := session.Count(entity.StorageProviderCollectionName, bson.M{"displayName": volume.StorageProviderName})
if err != nil {
response.InternalServerError(req.Request, resp.ResponseWriter, err)
return
} else if couunt < 1 {
response.BadRequest(req.Request, resp.ResponseWriter, fmt.Errorf("The reference storage provider %s doesn't exist", volume.StorageProviderName))
return
}

// Check whether this displayname has been used
volume.ID = bson.NewObjectId()
volume.CreatedAt = timeutils.Now()
//Generate the metaName for PVC meta name and we will use it future
volume.MetaName = volume.GenerateMetaName()
if err := session.Insert(entity.VolumeCollectionName, &volume); err != nil {
if mgo.IsDup(err) {
response.Conflict(req.Request, resp.ResponseWriter, fmt.Errorf("Storage Provider Name: %s already existed", volume.Name))
} else {
response.InternalServerError(req.Request, resp.ResponseWriter, err)
}
return
}

resp.WriteEntity(ActionResponse{
Error: false,
Message: "Create success",
})
}
136 changes: 136 additions & 0 deletions src/server/handler_volume_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package server

import (
"encoding/json"
"math/rand"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

"github.com/docker/docker/pkg/namesgenerator"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use github.com/moby/moby/pkg/namesgenerator

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I just copy the line here lol.
https://github.com/moby/moby/blob/master/pkg/namesgenerator/names-generator.go#L1
I will replace all from docker to moby in another PR.
Since all PR use the docker now (including the vendor.json).

restful "github.com/emicklei/go-restful"
"github.com/linkernetworks/mongo"
"github.com/linkernetworks/vortex/src/config"
"github.com/linkernetworks/vortex/src/entity"
"github.com/linkernetworks/vortex/src/serviceprovider"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"gopkg.in/mgo.v2/bson"
corev1 "k8s.io/api/core/v1"
)

func init() {
rand.Seed(time.Now().UnixNano())
}

type VolumeTestSuite struct {
suite.Suite
wc *restful.Container
session *mongo.Session
storageProvider entity.StorageProvider
}

func (suite *VolumeTestSuite) SetupTest() {
cf := config.MustRead("../../config/testing.json")
sp := serviceprovider.New(cf)

//init session
suite.session = sp.Mongo.NewSession()
//init restful container
suite.wc = restful.NewContainer()
service := newVolumeService(sp)
suite.wc.Add(service)
//init a StorageProvider
suite.storageProvider = entity.StorageProvider{
ID: bson.NewObjectId(),
Type: "nfs",
DisplayName: namesgenerator.GetRandomName(0),
}
err := suite.session.Insert(entity.StorageProviderCollectionName, suite.storageProvider)
assert.NoError(suite.T(), err)
}

func (suite *VolumeTestSuite) TearDownTest() {
suite.session.Remove(entity.StorageProviderCollectionName, "_id", suite.storageProvider.ID)
}

func TestVolumeSuite(t *testing.T) {
suite.Run(t, new(VolumeTestSuite))
}

func (suite *VolumeTestSuite) TestCreateVolume() {
tName := namesgenerator.GetRandomName(0)
tAccessMode := corev1.PersistentVolumeAccessMode("ReadOnlyMany")
tCapacity := "500G"
volume := entity.Volume{
Name: tName,
StorageProviderName: suite.storageProvider.DisplayName,
Capacity: tCapacity,
AccessMode: tAccessMode,
}

bodyBytes, err := json.MarshalIndent(volume, "", " ")
assert.NoError(suite.T(), err)

bodyReader := strings.NewReader(string(bodyBytes))
httpRequest, err := http.NewRequest("POST", "http://localhost:7890/v1/volume", bodyReader)
assert.NoError(suite.T(), err)

httpRequest.Header.Add("Content-Type", "application/json")
httpWriter := httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusOK, httpWriter)
defer suite.session.Remove(entity.VolumeCollectionName, "name", volume.Name)

//load data to check
retVolume := entity.Volume{}
err = suite.session.FindOne(entity.VolumeCollectionName, bson.M{"name": volume.Name}, &retVolume)
assert.NoError(suite.T(), err)
assert.NotEqual(suite.T(), "", retVolume.ID)
assert.Equal(suite.T(), volume.Name, retVolume.Name)
assert.Equal(suite.T(), volume.StorageProviderName, retVolume.StorageProviderName)
assert.Equal(suite.T(), volume.AccessMode, retVolume.AccessMode)
assert.Equal(suite.T(), volume.Capacity, retVolume.Capacity)
assert.NotEqual(suite.T(), "", retVolume.MetaName)

//We use the new write but empty input which will cause the readEntity Error
httpWriter = httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusBadRequest, httpWriter)
//Create again and it should fail since the name exist
bodyReader = strings.NewReader(string(bodyBytes))
httpRequest, err = http.NewRequest("POST", "http://localhost:7890/v1/volume", bodyReader)
assert.NoError(suite.T(), err)
httpRequest.Header.Add("Content-Type", "application/json")
httpWriter = httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusConflict, httpWriter)
}

func (suite *VolumeTestSuite) TestCreateVolumeWithInvalidParameter() {
tName := namesgenerator.GetRandomName(0)
tAccessMode := corev1.PersistentVolumeAccessMode("ReadOnlyMany")
tCapacity := "500G"
volume := entity.Volume{
Name: tName,
StorageProviderName: namesgenerator.GetRandomName(0),
Capacity: tCapacity,
AccessMode: tAccessMode,
}

bodyBytes, err := json.MarshalIndent(volume, "", " ")
assert.NoError(suite.T(), err)

bodyReader := strings.NewReader(string(bodyBytes))
httpRequest, err := http.NewRequest("POST", "http://localhost:7890/v1/volume", bodyReader)
assert.NoError(suite.T(), err)

httpRequest.Header.Add("Content-Type", "application/json")
httpWriter := httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusBadRequest, httpWriter)
defer suite.session.Remove(entity.VolumeCollectionName, "name", volume.Name)

}
8 changes: 8 additions & 0 deletions src/server/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func (a *App) AppRoute() *mux.Router {
container.Add(newVersionService(a.ServiceProvider))
container.Add(newNetworkService(a.ServiceProvider))
container.Add(newStorageProviderService(a.ServiceProvider))
container.Add(newVolumeService(a.ServiceProvider))

router.PathPrefix("/v1/").Handler(container)
return router
Expand Down Expand Up @@ -46,3 +47,10 @@ func newStorageProviderService(sp *serviceprovider.Container) *restful.WebServic
webService.Route(webService.POST("/").To(handler.RESTfulServiceHandler(sp, CreateStorageProvider)))
return webService
}

func newVolumeService(sp *serviceprovider.Container) *restful.WebService {
webService := new(restful.WebService)
webService.Path("/v1/volume").Consumes(restful.MIME_JSON, restful.MIME_JSON).Produces(restful.MIME_JSON, restful.MIME_JSON)
webService.Route(webService.POST("/").To(handler.RESTfulServiceHandler(sp, createVolume)))
return webService
}