Skip to content

Commit

Permalink
Merge pull request #37 from linkernetworks/hwchiu/VX-104
Browse files Browse the repository at this point in the history
Implement a restful to create the volume object.

Former-commit-id: f79b7be85c712cbadc137738f36a334bcb84b344 [formerly 004f168]
Former-commit-id: 92e9310eacd956ac5b009d0991d46ec7be7ad6a3
  • Loading branch information
Hung-Wei Chiu authored Jun 25, 2018
2 parents b88f569 + cabefa8 commit 5b10d57
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 0 deletions.
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"
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.GET("/").To(handler.RESTfulServiceHandler(sp, ListStorageProvider)))
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
}

0 comments on commit 5b10d57

Please sign in to comment.