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 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
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 @@ -296,7 +297,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 @@ -306,7 +307,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 @@ -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@"
}]
}
]
}
Loading