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

[Task] Support the new type App #266

Merged
merged 7 commits into from
Aug 22, 2018
Merged
Show file tree
Hide file tree
Changes from 6 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
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 App struct {
Deployment Deployment `bson:"deployment" json:"deployment" validate:"required"`
Service Service `bson:"service" json:"service" validate:"required"`
}
95 changes: 95 additions & 0 deletions src/server/handler_app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
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.App{}
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()

// Check whether this name has been used
ID := bson.NewObjectId()
p.Deployment.ID = ID
p.Service.ID = ID

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
}

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
}

p.Service.Selector["app"] = p.Deployment.Name
p.Service.Namespace = p.Deployment.Namespace
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("Deployment Name: %s already existed", p.Deployment.Name))
Copy link
Contributor

Choose a reason for hiding this comment

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

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.App{
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 @@ -26,6 +26,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 @@ -109,6 +110,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@"
}]
}
]
}
54 changes: 54 additions & 0 deletions tests/05-app/script.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env bats

load init

@test "Create App" {
http -v --check-status 127.0.0.1:7890/v1/apps < app.json
[ $? = 0 ]
#Wait the Deployment
#jsonpath="{.status.phase}"
NEXT_WAIT_TIME=0
WAIT_LIMIT=40
until kubectl get deployment ${appName} -o jsonpath="{.status.readyReplicas}" | grep "2" || [ $NEXT_WAIT_TIME -eq $WAIT_LIMIT ]; do
sleep 2
kubectl get deployment ${appName}
NEXT_WAIT_TIME=$((NEXT_WAIT_TIME+ 1))
done
[ $NEXT_WAIT_TIME != $WAIT_LIMIT ]
}

@test "List Service" {
run bash -c "http http://127.0.0.1:7890/v1/services 2>/dev/null | jq -r '.[] | select(.name == \"${appName}\").name'"
[ "$output" = "${appName}" ]
[ $status = 0 ]
}

@test "List Deployment" {
run bash -c "http http://127.0.0.1:7890/v1/deployments 2>/dev/null | jq -r '.[] | select(.name == \"${appName}\").name'"
[ "$output" = "${appName}" ]
[ $status = 0 ]
}

@test "Check Deployment Label" {
run kubectl get deployment ${appName} -o jsonpath="{.spec.template.metadata.labels.app}"
[ "$output" = "${appName}" ]
[ $status = 0 ]
}

@test "Check Service Selector" {
run kubectl get svc ${appName} -o jsonpath="{.spec.selector.app}"
[ "$output" = "${appName}" ]
[ $status = 0 ]
}

@test "Delete Deployment" {
run bash -c "http http://127.0.0.1:7890/v1/deployments 2>/dev/null | jq -r '.[] | select(.name == \"${appName}\").id'"
run http DELETE http://127.0.0.1:7890/v1/deployments/${output} 2>/dev/null
[ $status = 0 ]
}

@test "Delete Services" {
run bash -c "http http://127.0.0.1:7890/v1/services 2>/dev/null | jq -r '.[] | select(.name == \"${appName}\").id'"
run http DELETE http://127.0.0.1:7890/v1/services/${output} 2>/dev/null
[ $status = 0 ]
}