Skip to content

Commit

Permalink
Merge pull request #266 from linkernetworks/hwchiu/support-app
Browse files Browse the repository at this point in the history
[Task] Support the new type App
  • Loading branch information
John-Lin authored Aug 22, 2018
2 parents b3d00c5 + 946f235 commit 9c13cab
Show file tree
Hide file tree
Showing 10 changed files with 363 additions and 2 deletions.
5 changes: 3 additions & 2 deletions src/deployment/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var allCapabilities = []corev1.Capability{"NET_ADMIN", "SYS_ADMIN", "NET_RAW"}

// VolumeNamePrefix will set prefix of volumename
const VolumeNamePrefix = "volume-"
const DefaultLabel = "vortex"

// CheckDeploymentParameter will Check Deployment's Parameter
func CheckDeploymentParameter(sp *serviceprovider.Container, deploy *entity.Deployment) error {
Expand Down Expand Up @@ -298,7 +299,7 @@ func CreateDeployment(sp *serviceprovider.Container, deploy *entity.Deployment)
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": deploy.Name,
DefaultLabel: deploy.Name,
},
},
Replicas: &deploy.Replicas,
Expand All @@ -308,7 +309,7 @@ func CreateDeployment(sp *serviceprovider.Container, deploy *entity.Deployment)
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": deploy.Name,
DefaultLabel: deploy.Name,
},
},
Spec: corev1.PodSpec{
Expand Down
7 changes: 7 additions & 0 deletions src/entity/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package entity

// Deployment is the structure for deployment info
type Application struct {
Deployment Deployment `bson:"deployment" json:"deployment" validate:"required"`
Service Service `bson:"service" json:"service" validate:"required"`
}
96 changes: 96 additions & 0 deletions src/server/handler_app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package server

import (
"fmt"
"net/http"

"github.com/linkernetworks/utils/timeutils"
"github.com/linkernetworks/vortex/src/deployment"
"github.com/linkernetworks/vortex/src/entity"
response "github.com/linkernetworks/vortex/src/net/http"
"github.com/linkernetworks/vortex/src/service"
"github.com/linkernetworks/vortex/src/web"
"k8s.io/apimachinery/pkg/api/errors"

mgo "gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)

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

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

if err := sp.Validator.Struct(p); err != nil {
response.BadRequest(req.Request, resp.ResponseWriter, err)
return
}

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

p.Deployment.ID = bson.NewObjectId()
p.Service.ID = bson.NewObjectId()

time := timeutils.Now()
p.Deployment.CreatedAt = time
p.Service.CreatedAt = time
if err := deployment.CheckDeploymentParameter(sp, &p.Deployment); err != nil {
response.BadRequest(req.Request, resp.ResponseWriter, err)
return
}

//Bond to same namespace
p.Service.Namespace = p.Deployment.Namespace

//We use the application label for deployment and service
p.Service.Selector[deployment.DefaultLabel] = p.Deployment.Name
if err := deployment.CreateDeployment(sp, &p.Deployment); err != nil {
if errors.IsAlreadyExists(err) {
response.Conflict(req.Request, resp.ResponseWriter, fmt.Errorf("Deployment Name: %s already existed", p.Deployment.Name))
} else {
response.InternalServerError(req.Request, resp.ResponseWriter, err)
}
return
}

if err := service.CreateService(sp, &p.Service); err != nil {
if errors.IsAlreadyExists(err) {
response.Conflict(req.Request, resp.ResponseWriter, fmt.Errorf("Service Name: %s already existed", p.Service.Name))
} else {
response.InternalServerError(req.Request, resp.ResponseWriter, err)
}
return
}

if err := session.Insert(entity.DeploymentCollectionName, &p.Deployment); err != nil {
if mgo.IsDup(err) {
response.Conflict(req.Request, resp.ResponseWriter, fmt.Errorf("Deployment Name: %s already existed", p.Deployment.Name))
} else {
response.InternalServerError(req.Request, resp.ResponseWriter, err)
}
return
}

if err := session.Insert(entity.ServiceCollectionName, &p.Service); err != nil {
if mgo.IsDup(err) {
response.Conflict(req.Request, resp.ResponseWriter, fmt.Errorf("Service Name: %s already existed", p.Service.Name))
} else {
response.InternalServerError(req.Request, resp.ResponseWriter, err)
}
return
}
resp.WriteHeaderAndEntity(http.StatusCreated, p)
}
133 changes: 133 additions & 0 deletions src/server/handler_app_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package server

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

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/moby/moby/pkg/namesgenerator"
"github.com/stretchr/testify/suite"
"gopkg.in/mgo.v2/bson"
)

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

type AppTestSuite struct {
suite.Suite
sp *serviceprovider.Container
wc *restful.Container
session *mongo.Session
}

func (suite *AppTestSuite) SetupSuite() {
cf := config.MustRead("../../config/testing.json")
sp := serviceprovider.NewForTesting(cf)

suite.sp = sp
//init session
suite.session = sp.Mongo.NewSession()
//init restful container
suite.wc = restful.NewContainer()
service := newAppService(suite.sp)
suite.wc.Add(service)
}

func (suite *AppTestSuite) TearDownSuite() {}

func TestAppSuite(t *testing.T) {
suite.Run(t, new(AppTestSuite))
}

func (suite *AppTestSuite) TestCreateApp() {
namespace := "default"
containers := []entity.Container{
{
Name: namesgenerator.GetRandomName(0),
Image: "busybox",
Command: []string{"sleep", "3600"},
},
}
tName := namesgenerator.GetRandomName(0)
deploy := entity.Deployment{
Name: tName,
Namespace: namespace,
Labels: map[string]string{},
EnvVars: map[string]string{},
Containers: containers,
Volumes: []entity.DeploymentVolume{},
Networks: []entity.DeploymentNetwork{},
Capability: true,
NetworkType: entity.DeploymentHostNetwork,
NodeAffinity: []string{},
Replicas: 1,
}

ports := []entity.ServicePort{
{
Name: namesgenerator.GetRandomName(0),
Port: int32(80),
TargetPort: 80,
NodePort: int32(30000),
},
}

serviceName := namesgenerator.GetRandomName(0)
service := entity.Service{
ID: bson.NewObjectId(),
Name: serviceName,
Namespace: "default",
Type: "NodePort",
Selector: map[string]string{},
Ports: ports,
}

app := entity.Application{
Deployment: deploy,
Service: service,
}
bodyBytes, err := json.MarshalIndent(app, "", " ")
suite.NoError(err)

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

httpRequest.Header.Add("Content-Type", "application/json")
httpWriter := httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusCreated, httpWriter)
defer suite.session.Remove(entity.DeploymentCollectionName, "name", app.Deployment.Name)
defer suite.session.Remove(entity.ServiceCollectionName, "name", app.Service.Name)

//load data to check
retDeployment := entity.Deployment{}
err = suite.session.FindOne(entity.DeploymentCollectionName, bson.M{"name": deploy.Name}, &retDeployment)
suite.NoError(err)
suite.NotEqual("", retDeployment.ID)
suite.Equal(deploy.Name, retDeployment.Name)
suite.Equal(len(deploy.Containers), len(retDeployment.Containers))

//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/apps", bodyReader)
suite.NoError(err)
httpRequest.Header.Add("Content-Type", "application/json")
httpWriter = httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusConflict, httpWriter)
}
8 changes: 8 additions & 0 deletions src/server/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func (a *App) AppRoute() *mux.Router {
container.Add(newServiceService(a.ServiceProvider))
container.Add(newNamespaceService(a.ServiceProvider))
container.Add(newMonitoringService(a.ServiceProvider))
container.Add(newAppService(a.ServiceProvider))

router.PathPrefix("/v1/").Handler(container)
return router
Expand Down Expand Up @@ -118,6 +119,13 @@ func newDeploymentService(sp *serviceprovider.Container) *restful.WebService {
return webService
}

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

func newServiceService(sp *serviceprovider.Container) *restful.WebService {
webService := new(restful.WebService)
webService.Path("/v1/services").Consumes(restful.MIME_JSON, restful.MIME_JSON).Produces(restful.MIME_JSON, restful.MIME_JSON)
Expand Down
1 change: 1 addition & 0 deletions tests/05-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.json
38 changes: 38 additions & 0 deletions tests/05-app/app.info
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"deployment": {
"name": "@APPNAME@",
"labels":{},
"envVars":{
"myip":"1.2.3.4"
},
"namespace":"default",
"containers":[
{
"name":"first-container",
"image":"busybox",
"command":["sleep","3600"]
}
],
"networks":[
],
"volumes":[],
"restartPolicy":"Always",
"capability": true,
"networkType": "host",
"nodeAffinity": [],
"replicas":2
},
"service":{
"name": "@APPNAME@",
"namespace":"default",
"type":"ClusterIP",
"selector":{},
"ports":[
{
"protocol": "TCP",
"port": 27017,
"targetPort": 27017
}
]
}
}
8 changes: 8 additions & 0 deletions tests/05-app/init.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
if [ -z "$appName" ]; then
export name=$(date | md5sum | cut -b 1-19)
export appName="test-app-$name"

rm -rf app.json
cp app.info app.json
sed -i "s/@APPNAME@/${appName}/" app.json
fi
15 changes: 15 additions & 0 deletions tests/05-app/networks.info
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"type":"system",
"isDPDKPort":false,
"name":"@NETWORKNAME@",
"vlanTags":[],
"bridgeName":"br0",
"nodes":[
{
"name": "@NODENAME@",
"physicalInterfaces": [{
"name":"@ETHNAME@"
}]
}
]
}
Loading

0 comments on commit 9c13cab

Please sign in to comment.