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] YAML upload handler for resources #314

Merged
merged 7 commits into from
Sep 18, 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
6 changes: 5 additions & 1 deletion src/server/handler_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,11 @@ func uploadDeploymentYAMLHandler(ctx *web.Context) {
return
}

deploymentObj := obj.(*v1.Deployment)
deploymentObj, ok := obj.(*v1.Deployment)
if !ok {
response.BadRequest(req.Request, resp.ResponseWriter, fmt.Errorf("The YAML file is not for creating deployment"))
return
}

d := entity.Deployment{
ID: bson.NewObjectId(),
Expand Down
96 changes: 96 additions & 0 deletions src/server/handler_deployment_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package server

import (
"bytes"
"encoding/json"
"fmt"
"io"
"math/rand"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -349,3 +354,94 @@ func (suite *DeploymentTestSuite) TestListDeploymentWithInvalidPage() {
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusInternalServerError, httpWriter)
}

func (suite *DeploymentTestSuite) TestUploadDeploymentYAML() {
filename := "../../testYAMLs/deployment.yaml"

bodyBuf := bytes.NewBufferString("")
bodyWriter := multipart.NewWriter(bodyBuf)

// use the bodyWriter to write the Part headers to the buffer
_, err := bodyWriter.CreateFormFile("file", filename)
suite.NoError(err)

// the file data will be the second part of the body
file, err := os.Open(filename)
suite.NoError(err)

// need to know the boundary to properly close the part myself.
boundary := bodyWriter.Boundary()
//close_string := fmt.Sprintf("\r\n--%s--\r\n", boundary)
closeBuf := bytes.NewBufferString(fmt.Sprintf("\r\n--%s--\r\n", boundary))

// use multi-reader to defer the reading of the file data until
// writing to the socket buffer.
requestReader := io.MultiReader(bodyBuf, file, closeBuf)
fileStat, err := file.Stat()
suite.NoError(err)

httpRequest, err := http.NewRequest("POST", "http://localhost:7890/v1/deployments/upload/yaml", requestReader)
suite.NoError(err)

httpRequest.Header.Add("Content-Type", "multipart/form-data; boundary="+boundary)
httpRequest.Header.Add("Authorization", suite.JWTBearer)
httpRequest.ContentLength = fileStat.Size() + int64(bodyBuf.Len()) + int64(closeBuf.Len())
httpWriter := httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
defer suite.session.Remove(entity.DeploymentCollectionName, "name", "upload-deployment")

assertResponseCode(suite.T(), http.StatusCreated, httpWriter)

//load data to check
retDeployment := entity.Deployment{}
err = suite.session.FindOne(entity.DeploymentCollectionName, bson.M{"name": "upload-deployment"}, &retDeployment)
suite.NoError(err)
suite.NotEqual("", retDeployment.ID)
suite.Equal("upload-deployment", retDeployment.Name)
suite.Equal("default", retDeployment.Namespace)

//Create again and it should fail since the name exist
httpWriter = httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusConflict, httpWriter)

err = p.DeleteDeployment(suite.sp, &retDeployment)
suite.NoError(err)
}

func (suite *DeploymentTestSuite) TestUploadDeploymentYAMLFail() {
filename := "../../testYAMLs/namespace.yaml"

bodyBuf := bytes.NewBufferString("")
bodyWriter := multipart.NewWriter(bodyBuf)

// use the bodyWriter to write the Part headers to the buffer
_, err := bodyWriter.CreateFormFile("file", filename)
suite.NoError(err)

// the file data will be the second part of the body
file, err := os.Open(filename)
suite.NoError(err)

// need to know the boundary to properly close the part myself.
boundary := bodyWriter.Boundary()
//close_string := fmt.Sprintf("\r\n--%s--\r\n", boundary)
closeBuf := bytes.NewBufferString(fmt.Sprintf("\r\n--%s--\r\n", boundary))

// use multi-reader to defer the reading of the file data until
// writing to the socket buffer.
requestReader := io.MultiReader(bodyBuf, file, closeBuf)
fileStat, err := file.Stat()
suite.NoError(err)

httpRequest, err := http.NewRequest("POST", "http://localhost:7890/v1/deployments/upload/yaml", requestReader)
suite.NoError(err)

httpRequest.Header.Add("Content-Type", "multipart/form-data; boundary="+boundary)
httpRequest.Header.Add("Authorization", suite.JWTBearer)
httpRequest.ContentLength = fileStat.Size() + int64(bodyBuf.Len()) + int64(closeBuf.Len())
httpWriter := httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)

assertResponseCode(suite.T(), http.StatusBadRequest, httpWriter)
}
86 changes: 86 additions & 0 deletions src/server/handler_namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ package server

import (
"fmt"
"io/ioutil"
"math"
"net/http"
"strconv"

"github.com/linkernetworks/utils/timeutils"
"github.com/linkernetworks/vortex/src/entity"
"github.com/linkernetworks/vortex/src/kubernetes"
"github.com/linkernetworks/vortex/src/namespace"
response "github.com/linkernetworks/vortex/src/net/http"
"github.com/linkernetworks/vortex/src/net/http/query"
"github.com/linkernetworks/vortex/src/server/backend"
"github.com/linkernetworks/vortex/src/web"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"

mgo "gopkg.in/mgo.v2"
Expand Down Expand Up @@ -189,3 +192,86 @@ func getNamespaceHandler(ctx *web.Context) {
namespace.CreatedBy, _ = backend.FindUserByID(session, namespace.OwnerID)
resp.WriteEntity(namespace)
}

func uploadNamespaceYAMLHandler(ctx *web.Context) {
sp, req, resp := ctx.ServiceProvider, ctx.Request, ctx.Response
userID, ok := req.Attribute("UserID").(string)
if !ok {
response.Unauthorized(req.Request, resp.ResponseWriter, fmt.Errorf("Unauthorized: User ID is empty"))
return
}

if err := req.Request.ParseMultipartForm(_24K); nil != err {
response.InternalServerError(req.Request, resp.ResponseWriter, fmt.Errorf("Failed to read multipart form: %s", err.Error()))
return
}

infile, _, err := req.Request.FormFile("file")
if err != nil {
response.BadRequest(req.Request, resp.ResponseWriter, fmt.Errorf("Error parsing uploaded file %v", err))
return
}

content, err := ioutil.ReadAll(infile)
if err != nil {
response.InternalServerError(req.Request, resp.ResponseWriter, fmt.Errorf("Failed to read data: %s", err.Error()))
return
}

if len(content) == 0 {
response.BadRequest(req.Request, resp.ResponseWriter, fmt.Errorf("Empty content"))
return
}

obj, err := kubernetes.ParseK8SYAML(content)
if err != nil {
response.BadRequest(req.Request, resp.ResponseWriter, err)
return
}

namespaceObj, ok := obj.(*v1.Namespace)
if !ok {
response.BadRequest(req.Request, resp.ResponseWriter, fmt.Errorf("The YAML file is not for creating namespace"))
return
}

d := entity.Namespace{
ID: bson.NewObjectId(),
OwnerID: bson.ObjectIdHex(userID),
Name: namespaceObj.ObjectMeta.Name,
}

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

d.CreatedAt = timeutils.Now()
_, err = sp.KubeCtl.CreateNamespace(namespaceObj)
if err != nil {
if errors.IsAlreadyExists(err) {
response.Conflict(req.Request, resp.ResponseWriter, fmt.Errorf("Namespace Name: %s already existed", d.Name))
} else if errors.IsConflict(err) {
response.Conflict(req.Request, resp.ResponseWriter, fmt.Errorf("Create setting has conflict: %v", err))
} else if errors.IsInvalid(err) {
response.BadRequest(req.Request, resp.ResponseWriter, fmt.Errorf("Create setting is invalid: %v", err))
} else {
response.InternalServerError(req.Request, resp.ResponseWriter, err)
}
return
}

if err := session.Insert(entity.NamespaceCollectionName, &d); err != nil {
if mgo.IsDup(err) {
response.Conflict(req.Request, resp.ResponseWriter, fmt.Errorf("Namespace Name: %s already existed", d.Name))
} else {
response.InternalServerError(req.Request, resp.ResponseWriter, err)
}
return
}
// find owner in user entity
d.CreatedBy, _ = backend.FindUserByID(session, d.OwnerID)
resp.WriteHeaderAndEntity(http.StatusCreated, d)
}
95 changes: 95 additions & 0 deletions src/server/handler_namespace_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package server

import (
"bytes"
"encoding/json"
"fmt"
"io"
"math/rand"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -265,3 +270,93 @@ func (suite *NamespaceTestSuite) TestListNamespaceWithInvalidPage() {
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusInternalServerError, httpWriter)
}

func (suite *NamespaceTestSuite) TestUploadNamespaceYAML() {
filename := "../../testYAMLs/namespace.yaml"

bodyBuf := bytes.NewBufferString("")
bodyWriter := multipart.NewWriter(bodyBuf)

// use the bodyWriter to write the Part headers to the buffer
_, err := bodyWriter.CreateFormFile("file", filename)
suite.NoError(err)

// the file data will be the second part of the body
file, err := os.Open(filename)
suite.NoError(err)

// need to know the boundary to properly close the part myself.
boundary := bodyWriter.Boundary()
//close_string := fmt.Sprintf("\r\n--%s--\r\n", boundary)
closeBuf := bytes.NewBufferString(fmt.Sprintf("\r\n--%s--\r\n", boundary))

// use multi-reader to defer the reading of the file data until
// writing to the socket buffer.
requestReader := io.MultiReader(bodyBuf, file, closeBuf)
fileStat, err := file.Stat()
suite.NoError(err)

httpRequest, err := http.NewRequest("POST", "http://localhost:7890/v1/namespaces/upload/yaml", requestReader)
suite.NoError(err)

httpRequest.Header.Add("Content-Type", "multipart/form-data; boundary="+boundary)
httpRequest.Header.Add("Authorization", suite.JWTBearer)
httpRequest.ContentLength = fileStat.Size() + int64(bodyBuf.Len()) + int64(closeBuf.Len())
httpWriter := httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
defer suite.session.Remove(entity.NamespaceCollectionName, "name", "uploadnamespace")

assertResponseCode(suite.T(), http.StatusCreated, httpWriter)

//load data to check
retNamespace := entity.Namespace{}
err = suite.session.FindOne(entity.NamespaceCollectionName, bson.M{"name": "uploadnamespace"}, &retNamespace)
suite.NoError(err)
suite.NotEqual("", retNamespace.ID)
suite.Equal("uploadnamespace", retNamespace.Name)

//Create again and it should fail since the name exist
httpWriter = httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)
assertResponseCode(suite.T(), http.StatusConflict, httpWriter)

err = ns.DeleteNamespace(suite.sp, &retNamespace)
suite.NoError(err)
}

func (suite *NamespaceTestSuite) TestUploadNamespaceYAMLFail() {
filename := "../../testYAMLs/deployment.yaml"

bodyBuf := bytes.NewBufferString("")
bodyWriter := multipart.NewWriter(bodyBuf)

// use the bodyWriter to write the Part headers to the buffer
_, err := bodyWriter.CreateFormFile("file", filename)
suite.NoError(err)

// the file data will be the second part of the body
file, err := os.Open(filename)
suite.NoError(err)

// need to know the boundary to properly close the part myself.
boundary := bodyWriter.Boundary()
//close_string := fmt.Sprintf("\r\n--%s--\r\n", boundary)
closeBuf := bytes.NewBufferString(fmt.Sprintf("\r\n--%s--\r\n", boundary))

// use multi-reader to defer the reading of the file data until
// writing to the socket buffer.
requestReader := io.MultiReader(bodyBuf, file, closeBuf)
fileStat, err := file.Stat()
suite.NoError(err)

httpRequest, err := http.NewRequest("POST", "http://localhost:7890/v1/namespaces/upload/yaml", requestReader)
suite.NoError(err)

httpRequest.Header.Add("Content-Type", "multipart/form-data; boundary="+boundary)
httpRequest.Header.Add("Authorization", suite.JWTBearer)
httpRequest.ContentLength = fileStat.Size() + int64(bodyBuf.Len()) + int64(closeBuf.Len())
httpWriter := httptest.NewRecorder()
suite.wc.Dispatch(httpWriter, httpRequest)

assertResponseCode(suite.T(), http.StatusBadRequest, httpWriter)
}
Loading