@@ -0,0 +1,102 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
)
|
||||
|
||||
type API struct {
|
||||
FunctionStore
|
||||
HTTPTriggerStore
|
||||
EnvironmentStore
|
||||
}
|
||||
|
||||
func (api *API) respondWithSuccess(w http.ResponseWriter, resp []byte) {
|
||||
_, err := w.Write(resp)
|
||||
if err != nil {
|
||||
// this will probably fail too, but try anyway
|
||||
api.respondWithError(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) respondWithError(w http.ResponseWriter, err error) {
|
||||
var code int
|
||||
var msg string
|
||||
|
||||
fe, ok := err.(fission.Error)
|
||||
if ok {
|
||||
msg = fe.Message
|
||||
switch fe.Code {
|
||||
case fission.ErrorNotFound:
|
||||
code = 404
|
||||
case fission.ErrorInvalidArgument:
|
||||
code = 400
|
||||
case fission.ErrorNoSpace:
|
||||
code = 500
|
||||
case fission.ErrorNotAuthorized:
|
||||
code = 403
|
||||
default:
|
||||
code = 500
|
||||
}
|
||||
} else {
|
||||
code = 500
|
||||
msg = err.Error()
|
||||
}
|
||||
log.Printf("Error: %v: %v", code, msg)
|
||||
http.Error(w, msg, code)
|
||||
}
|
||||
|
||||
func (api *API) HomeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Fission API")
|
||||
}
|
||||
|
||||
func (api *API) serve(port int) {
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/", api.HomeHandler)
|
||||
|
||||
r.HandleFunc("/functions", api.FunctionApiList).Methods("GET")
|
||||
r.HandleFunc("/functions", api.FunctionApiCreate).Methods("POST")
|
||||
r.HandleFunc("/functions/{function}", api.FunctionApiGet).Methods("GET")
|
||||
r.HandleFunc("/functions/{function}", api.FunctionApiUpdate).Methods("PUT")
|
||||
r.HandleFunc("/functions/{function}", api.FunctionApiDelete).Methods("DELETE")
|
||||
|
||||
// r.HandleFunc("/triggers/http", api.HTTPTriggerApiList).Methods("GET")
|
||||
// r.HandleFunc("/triggers/http", api.HTTPTriggerApiCreate).Methods("POST")
|
||||
// r.HandleFunc("/triggers/http/{httpTrigger}", api.HTTPTriggerApiGet).Methods("GET")
|
||||
// r.HandleFunc("/triggers/http/{httpTrigger}", api.HTTPTriggerApiUpdate).Methods("PUT")
|
||||
// r.HandleFunc("/triggers/http/{httpTrigger}", api.HTTPTriggerApiDelete).Methods("DELETE")
|
||||
|
||||
// r.HandleFunc("/environments", api.EnvironmentApiList).Methods("GET")
|
||||
// r.HandleFunc("/environments", api.EnvironmentApiCreate).Methods("POST")
|
||||
// r.HandleFunc("/environments/{environment}", api.EnvironmentApiGet).Methods("GET")
|
||||
// r.HandleFunc("/environments/{environment}", api.EnvironmentApiUpdate).Methods("PUT")
|
||||
// r.HandleFunc("/environments/{environment}", api.EnvironmentApiDelete).Methods("DELETE")
|
||||
|
||||
address := fmt.Sprintf(":%v", port)
|
||||
|
||||
log.WithFields(log.Fields{"port": port}).Info("Server started")
|
||||
log.Fatal(http.ListenAndServe(address, handlers.LoggingHandler(os.Stdout, r)))
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
etcdClient "github.com/coreos/etcd/client"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
"github.com/platform9/fission/controller/client"
|
||||
)
|
||||
|
||||
func TestFunctionApi(t *testing.T) {
|
||||
log.SetFormatter(&log.TextFormatter{DisableColors: true})
|
||||
|
||||
_, ks, rs := getTestResourceStore()
|
||||
fs := &FunctionStore{resourceStore: *rs}
|
||||
hts := &HTTPTriggerStore{resourceStore: *rs}
|
||||
es := &EnvironmentStore{resourceStore: *rs}
|
||||
|
||||
api := &API{
|
||||
FunctionStore: *fs,
|
||||
HTTPTriggerStore: *hts,
|
||||
EnvironmentStore: *es,
|
||||
}
|
||||
|
||||
testFunc := &fission.Function{
|
||||
Metadata: fission.Metadata{
|
||||
Name: "foo",
|
||||
Uid: "",
|
||||
},
|
||||
Environment: fission.Metadata{
|
||||
Name: "nodejs",
|
||||
Uid: "xxx",
|
||||
},
|
||||
Code: "code1",
|
||||
}
|
||||
|
||||
go api.serve(8888)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
resp, err := http.Get("http://localhost:8888/")
|
||||
panicIf(err)
|
||||
_, err = ioutil.ReadAll(resp.Body)
|
||||
panicIf(err)
|
||||
|
||||
client := client.New("http://localhost:8888")
|
||||
|
||||
_, err = ks.Delete(context.Background(), "Function", &etcdClient.DeleteOptions{Recursive: true})
|
||||
if err != nil {
|
||||
log.Printf("failed to delete: %v", err)
|
||||
}
|
||||
|
||||
m, err := client.FunctionCreate(testFunc)
|
||||
panicIf(err)
|
||||
uid1 := m.Uid
|
||||
log.Printf("Created function %v: %v", m.Name, m.Uid)
|
||||
|
||||
testFunc.Code = "code2"
|
||||
m, err = client.FunctionUpdate(testFunc)
|
||||
panicIf(err)
|
||||
uid2 := m.Uid
|
||||
log.Printf("Updated function %v: %v", m.Name, m.Uid)
|
||||
|
||||
m.Uid = uid1
|
||||
testFunc.Code = "code1"
|
||||
f, err := client.FunctionGet(m)
|
||||
panicIf(err)
|
||||
|
||||
testFunc.Metadata.Uid = m.Uid
|
||||
log.Printf("f = %#v", f)
|
||||
log.Printf("testFunc = %#v", testFunc)
|
||||
assert(*f == *testFunc, "first version should match when read by uid")
|
||||
|
||||
m.Uid = uid2
|
||||
testFunc.Metadata.Uid = m.Uid
|
||||
testFunc.Code = "code2"
|
||||
f, err = client.FunctionGet(m)
|
||||
panicIf(err)
|
||||
|
||||
assert(*f == *testFunc, "second version should match when read by uid")
|
||||
|
||||
m.Uid = ""
|
||||
testFunc.Metadata.Uid = uid2
|
||||
testFunc.Code = "code2"
|
||||
f, err = client.FunctionGet(m)
|
||||
panicIf(err)
|
||||
|
||||
assert(*f == *testFunc, "second version should match when read as latest")
|
||||
|
||||
testFunc.Metadata.Name = "bar"
|
||||
m, err = client.FunctionCreate(testFunc)
|
||||
panicIf(err)
|
||||
|
||||
funcs, err := client.FunctionList()
|
||||
panicIf(err)
|
||||
assert(len(funcs) == 2,
|
||||
"created two functions, but didn't find them")
|
||||
|
||||
err = client.FunctionDelete(&fission.Metadata{Name: "foo"})
|
||||
panicIf(err)
|
||||
err = client.FunctionDelete(&fission.Metadata{Name: "bar"})
|
||||
panicIf(err)
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
)
|
||||
|
||||
type (
|
||||
Client struct {
|
||||
Url string
|
||||
}
|
||||
)
|
||||
|
||||
func New(serverUrl string) *Client {
|
||||
return &Client{Url: serverUrl}
|
||||
}
|
||||
|
||||
func (c *Client) delete(relativeUrl string) error {
|
||||
req, err := http.NewRequest("DELETE", c.url(relativeUrl), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.New("Delete failed")
|
||||
} else {
|
||||
return errors.New("Delete failed: " + string(body))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) put(relativeUrl string, contentType string, body []byte) (*http.Response, error) {
|
||||
req, err := http.NewRequest("PUT", c.url(relativeUrl), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-type", contentType)
|
||||
return http.DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) url(relativeUrl string) string {
|
||||
return c.Url + "/" + relativeUrl
|
||||
}
|
||||
|
||||
func (c *Client) FunctionCreate(f *fission.Function) (*fission.Metadata, error) {
|
||||
reqbody, err := json.Marshal(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := http.Post(c.url("functions"), "application/json", bytes.NewReader(reqbody))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
log.WithFields(log.Fields{
|
||||
"function": f.Metadata.Name,
|
||||
"status": resp.StatusCode,
|
||||
}).Error("Failed to create function")
|
||||
return nil, errors.New("failed to create function")
|
||||
}
|
||||
|
||||
var m fission.Metadata
|
||||
err = json.Unmarshal(body, &m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func (c *Client) FunctionGet(m *fission.Metadata) (*fission.Function, error) {
|
||||
relativeUrl := fmt.Sprintf("functions/%v", m.Name)
|
||||
if len(m.Uid) > 0 {
|
||||
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
|
||||
}
|
||||
|
||||
resp, err := http.Get(c.url(relativeUrl))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var f fission.Function
|
||||
err = json.Unmarshal(body, &f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &f, nil
|
||||
}
|
||||
|
||||
func (c *Client) FunctionUpdate(f *fission.Function) (*fission.Metadata, error) {
|
||||
reqbody, err := json.Marshal(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
relativeUrl := fmt.Sprintf("functions/%v", f.Metadata.Name)
|
||||
|
||||
resp, err := c.put(relativeUrl, "application/json", reqbody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var m fission.Metadata
|
||||
err = json.Unmarshal(body, &m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func (c *Client) FunctionDelete(m *fission.Metadata) error {
|
||||
relativeUrl := fmt.Sprintf("functions/%v", m.Name)
|
||||
if len(m.Uid) > 0 {
|
||||
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
|
||||
}
|
||||
err := c.delete(relativeUrl)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) FunctionList() ([]fission.Function, error) {
|
||||
resp, err := http.Get(c.url("functions"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
funcs := make([]fission.Function, 0)
|
||||
err = json.Unmarshal(body, &funcs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return funcs, nil
|
||||
}
|
||||
|
||||
// func (c *Client) HTTPTriggerCreate(f *fission.Function) (string, error) {
|
||||
// }
|
||||
// func (c *Client) HTTPTriggerGet(m *fission.Metadata) (*fission.HTTPTrigger, error) {
|
||||
// }
|
||||
// func (c *Client) HTTPTriggerUpdate(f *fission.HTTPTrigger) (string, error) {
|
||||
// }
|
||||
// func (c *Client) HTTPTriggerDelete(m *fission.Metadata) error {
|
||||
// }
|
||||
// func (c *Client) HTTPTriggerList() ([]fission.HTTPTrigger, error) {
|
||||
// }
|
||||
|
||||
// func (c *Client) EnvironmentCreate(f *fission.Environment) (string, error) {
|
||||
// }
|
||||
// func (c *Client) EnvironmentGet(m *fission.Metadata) (*fission.Environment, error) {
|
||||
// }
|
||||
// func (c *Client) EnvironmentUpdate(f *fission.Environment) (string, error) {
|
||||
// }
|
||||
// func (c *Client) EnvironmentDelete(m *fission.Metadata) error {
|
||||
// }
|
||||
// func (c *Client) EnvironmentList() ([]fission.Environment, error) {
|
||||
// }
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/satori/go.uuid"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
)
|
||||
|
||||
type EnvironmentStore struct {
|
||||
resourceStore
|
||||
}
|
||||
|
||||
func (es *EnvironmentStore) create(e *fission.Environment) error {
|
||||
e.Metadata.Uid = uuid.NewV4().String()
|
||||
return es.resourceStore.create(e)
|
||||
}
|
||||
|
||||
func (es *EnvironmentStore) read(m fission.Metadata) (*fission.Environment, error) {
|
||||
var e fission.Environment
|
||||
err := es.resourceStore.read(m.Name, &e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (es *EnvironmentStore) update(e *fission.Environment) error {
|
||||
e.Metadata.Uid = uuid.NewV4().String()
|
||||
return es.resourceStore.update(e)
|
||||
}
|
||||
|
||||
func (es *EnvironmentStore) delete(m fission.Metadata) error {
|
||||
typeName, err := getTypeName(fission.Environment{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return es.resourceStore.delete(typeName, m.Name)
|
||||
}
|
||||
|
||||
func (es *EnvironmentStore) list() ([]fission.Environment, error) {
|
||||
typeName, err := getTypeName(fission.Environment{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bufs, err := es.resourceStore.getAll(typeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
triggers := make([]fission.Environment, 0, len(bufs))
|
||||
js := JsonSerializer{}
|
||||
for _, buf := range bufs {
|
||||
var e fission.Environment
|
||||
err = js.deserialize([]byte(buf), &e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
triggers = append(triggers, e)
|
||||
}
|
||||
|
||||
return triggers, nil
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
type requestType int
|
||||
|
||||
const (
|
||||
READ requestType = iota
|
||||
WRITE
|
||||
DELETE
|
||||
)
|
||||
|
||||
type (
|
||||
fileStore struct {
|
||||
root string // abs path of root of filestore
|
||||
requestChannel chan fileStoreRequest
|
||||
}
|
||||
|
||||
fileStoreRequest struct {
|
||||
requestType
|
||||
fileName string // relative path
|
||||
fileContents []byte
|
||||
responseChannel chan fileStoreResponse
|
||||
}
|
||||
|
||||
fileStoreResponse struct {
|
||||
error
|
||||
fileContents []byte
|
||||
}
|
||||
)
|
||||
|
||||
func makeFileStore(path string) *fileStore {
|
||||
fileStore := &fileStore{
|
||||
root: path,
|
||||
requestChannel: make(chan fileStoreRequest),
|
||||
}
|
||||
go fileStore.fileStoreService()
|
||||
return fileStore
|
||||
}
|
||||
|
||||
func (fs *fileStore) fileStoreService() {
|
||||
for {
|
||||
req := <-fs.requestChannel
|
||||
response := &fileStoreResponse{}
|
||||
|
||||
log.WithFields(log.Fields{"file": req.fileName, "type": req.requestType}).Debug("fileStore request")
|
||||
switch req.requestType {
|
||||
case READ:
|
||||
response.fileContents, response.error = ioutil.ReadFile(path.Join(fs.root, req.fileName))
|
||||
case WRITE:
|
||||
response.error = ioutil.WriteFile(path.Join(fs.root, req.fileName), req.fileContents, 0600)
|
||||
case DELETE:
|
||||
response.error = os.Remove(path.Join(fs.root, req.fileName))
|
||||
default:
|
||||
log.Panic("bad request")
|
||||
}
|
||||
req.responseChannel <- *response
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *fileStore) read(fileName string) ([]byte, error) {
|
||||
req := fileStoreRequest{
|
||||
requestType: READ,
|
||||
fileName: fileName,
|
||||
responseChannel: make(chan fileStoreResponse),
|
||||
}
|
||||
fs.requestChannel <- req
|
||||
response := <-req.responseChannel
|
||||
return response.fileContents, response.error
|
||||
}
|
||||
|
||||
func (fs *fileStore) write(fileName string, contents []byte) error {
|
||||
req := fileStoreRequest{
|
||||
requestType: WRITE,
|
||||
fileName: fileName,
|
||||
fileContents: contents,
|
||||
responseChannel: make(chan fileStoreResponse),
|
||||
}
|
||||
fs.requestChannel <- req
|
||||
response := <-req.responseChannel
|
||||
return response.error
|
||||
}
|
||||
|
||||
func (fs *fileStore) delete(fileName string) error {
|
||||
req := fileStoreRequest{
|
||||
requestType: DELETE,
|
||||
fileName: fileName,
|
||||
responseChannel: make(chan fileStoreResponse),
|
||||
}
|
||||
fs.requestChannel <- req
|
||||
response := <-req.responseChannel
|
||||
return response.error
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFileStore(t *testing.T) {
|
||||
// tmp dir
|
||||
dir, err := ioutil.TempDir("", "testFileStore")
|
||||
if err != nil {
|
||||
t.Fatalf("error creating temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
log.Printf("temp dir at %v", dir)
|
||||
|
||||
// file store
|
||||
fs := makeFileStore(dir)
|
||||
|
||||
_, err = fs.read("nonexistent")
|
||||
if err == nil {
|
||||
t.Fatalf("expected an error")
|
||||
}
|
||||
|
||||
path := "fileStoreTest"
|
||||
contents := []byte("bar")
|
||||
err = fs.write(path, contents)
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
observedContents, err := fs.read(path)
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
if string(observedContents) != string(contents) {
|
||||
t.Fatalf("contents don't match")
|
||||
}
|
||||
|
||||
err = fs.delete(path)
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"encoding/json"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
)
|
||||
|
||||
func (api *API) FunctionApiList(w http.ResponseWriter, r *http.Request) {
|
||||
funcs, err := api.FunctionStore.List()
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := json.Marshal(funcs)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
func (api *API) FunctionApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
}
|
||||
|
||||
var f fission.Function
|
||||
err = json.Unmarshal(body, &f)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
uid, err := api.FunctionStore.Create(&f)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
m := &fission.Metadata{Name: f.Name, Uid: uid}
|
||||
resp, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
func (api *API) FunctionApiGet(w http.ResponseWriter, r *http.Request) {
|
||||
var m fission.Metadata
|
||||
|
||||
vars := mux.Vars(r)
|
||||
m.Name = vars["function"]
|
||||
m.Uid = r.FormValue("uid") // empty if uid is absent
|
||||
|
||||
f, err := api.FunctionStore.Get(&m)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := json.Marshal(f)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
func (api *API) FunctionApiUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
funcName := vars["function"]
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
}
|
||||
|
||||
var f fission.Function
|
||||
err = json.Unmarshal(body, &f)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if funcName != f.Metadata.Name {
|
||||
err = fission.MakeError(fission.ErrorInvalidArgument, "Function name doesn't match URL")
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
uid, err := api.FunctionStore.Update(&f)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
m := &fission.Metadata{Name: f.Name, Uid: uid}
|
||||
resp, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
func (api *API) FunctionApiDelete(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
var m fission.Metadata
|
||||
m.Name = vars["function"]
|
||||
|
||||
m.Uid = r.FormValue("uid") // empty if uid is absent
|
||||
if len(m.Uid) == 0 {
|
||||
log.WithFields(log.Fields{"function": m.Name}).Info("Deleting all versions")
|
||||
}
|
||||
|
||||
err := api.FunctionStore.Delete(m)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.respondWithSuccess(w, []byte(""))
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
)
|
||||
|
||||
type FunctionStore struct {
|
||||
resourceStore
|
||||
}
|
||||
|
||||
func (fs *FunctionStore) Create(f *fission.Function) (string, error) {
|
||||
code := []byte(f.Code)
|
||||
_, uid, err := fs.resourceStore.writeFile(f.Key(), code)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
f.Metadata.Uid = uid
|
||||
f.Code = ""
|
||||
|
||||
err = fs.resourceStore.create(f)
|
||||
if err != nil {
|
||||
fs.resourceStore.deleteFile(f.Key(), uid) // ignore errors
|
||||
return "", err
|
||||
}
|
||||
return f.Metadata.Uid, nil
|
||||
}
|
||||
|
||||
func (fs *FunctionStore) Get(m *fission.Metadata) (*fission.Function, error) {
|
||||
var f fission.Function
|
||||
err := fs.resourceStore.read(m.Name, &f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var code []byte
|
||||
if len(m.Uid) > 0 {
|
||||
log.WithFields(log.Fields{"Uid": m.Uid}).Info("fetching by uid")
|
||||
code, err = fs.resourceStore.readFile(m.Name, &m.Uid)
|
||||
f.Metadata = *m
|
||||
} else {
|
||||
code, err = fs.resourceStore.readFile(m.Name, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f.Code = string(code)
|
||||
return &f, nil
|
||||
}
|
||||
|
||||
func (fs *FunctionStore) Update(f *fission.Function) (string, error) {
|
||||
code := []byte(f.Code)
|
||||
_, uid, err := fs.resourceStore.writeFile(f.Key(), code)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var fnew fission.Function
|
||||
err = fs.resourceStore.read(f.Metadata.Name, &fnew)
|
||||
if err != nil {
|
||||
fs.resourceStore.deleteFile(f.Key(), uid) // ignore err
|
||||
return "", err
|
||||
}
|
||||
|
||||
fnew.Metadata.Uid = uid
|
||||
fnew.Environment = f.Environment
|
||||
|
||||
err = fs.resourceStore.update(fnew)
|
||||
if err != nil {
|
||||
fs.resourceStore.deleteFile(f.Key(), uid) // ignore err
|
||||
return "", err
|
||||
}
|
||||
return uid, err
|
||||
}
|
||||
|
||||
func (fs *FunctionStore) Delete(m fission.Metadata) error {
|
||||
if len(m.Uid) == 0 {
|
||||
err := fs.resourceStore.deleteAllFiles(m.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := fs.resourceStore.deleteFile(m.Name, m.Uid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
typeName, err := getTypeName(fission.Function{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.resourceStore.delete(typeName, m.Name)
|
||||
}
|
||||
|
||||
func (fs *FunctionStore) List() ([]fission.Function, error) {
|
||||
typeName, err := getTypeName(fission.Function{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bufs, err := fs.resourceStore.getAll(typeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
js := JsonSerializer{}
|
||||
functions := make([]fission.Function, 0, len(bufs))
|
||||
for _, buf := range bufs {
|
||||
var f fission.Function
|
||||
err = js.deserialize([]byte(buf), &f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
functions = append(functions, f)
|
||||
}
|
||||
|
||||
return functions, nil
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/satori/go.uuid"
|
||||
|
||||
"github.com/platform9/fission"
|
||||
)
|
||||
|
||||
type HTTPTriggerStore struct {
|
||||
resourceStore
|
||||
}
|
||||
|
||||
func (hts *HTTPTriggerStore) create(ht *fission.HTTPTrigger) error {
|
||||
ht.Metadata.Uid = uuid.NewV4().String()
|
||||
return hts.resourceStore.create(ht)
|
||||
}
|
||||
|
||||
func (hts *HTTPTriggerStore) read(m fission.Metadata) (*fission.HTTPTrigger, error) {
|
||||
var ht fission.HTTPTrigger
|
||||
err := hts.resourceStore.read(m.Name, &ht)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ht, nil
|
||||
}
|
||||
|
||||
func (hts *HTTPTriggerStore) update(ht *fission.HTTPTrigger) error {
|
||||
err := validateHTTPTrigger(ht)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ht.Metadata.Uid = uuid.NewV4().String()
|
||||
return hts.resourceStore.update(ht)
|
||||
}
|
||||
|
||||
func (hts *HTTPTriggerStore) delete(m fission.Metadata) error {
|
||||
typeName, err := getTypeName(fission.HTTPTrigger{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return hts.resourceStore.delete(typeName, m.Name)
|
||||
}
|
||||
|
||||
func (hts *HTTPTriggerStore) list() ([]fission.HTTPTrigger, error) {
|
||||
typeName, err := getTypeName(fission.HTTPTrigger{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bufs, err := hts.resourceStore.getAll(typeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
triggers := make([]fission.HTTPTrigger, 0, len(bufs))
|
||||
js := JsonSerializer{}
|
||||
for _, buf := range bufs {
|
||||
var ht fission.HTTPTrigger
|
||||
err = js.deserialize([]byte(buf), &ht)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
triggers = append(triggers, ht)
|
||||
}
|
||||
|
||||
return triggers, nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type JsonSerializer struct {
|
||||
}
|
||||
|
||||
func (j JsonSerializer) serialize(r resource) ([]byte, error) {
|
||||
return json.Marshal(r)
|
||||
}
|
||||
|
||||
func (j JsonSerializer) deserialize(buf []byte, r resource) error {
|
||||
return json.Unmarshal(buf, r)
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/coreos/etcd/client"
|
||||
"github.com/satori/go.uuid"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type (
|
||||
resourceStore struct {
|
||||
*fileStore
|
||||
client.KeysAPI
|
||||
serializer
|
||||
}
|
||||
)
|
||||
|
||||
func makeResourceStore(fs *fileStore, ks client.KeysAPI, s serializer) *resourceStore {
|
||||
return &resourceStore{fileStore: fs, KeysAPI: ks, serializer: s}
|
||||
}
|
||||
|
||||
func getTypeName(r resource) (string, error) {
|
||||
typ := reflect.TypeOf(r)
|
||||
if typ.Kind().String() == "ptr" {
|
||||
typ = typ.Elem()
|
||||
}
|
||||
typName := typ.Name()
|
||||
if len(typName) == 0 {
|
||||
return "", errors.New("Failed to get type")
|
||||
}
|
||||
return typName, nil
|
||||
}
|
||||
|
||||
func getKey(r resource) (string, error) {
|
||||
typName, err := getTypeName(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rkey := r.Key()
|
||||
return (typName + "/" + rkey), nil
|
||||
}
|
||||
|
||||
func (rs *resourceStore) create(r resource) error {
|
||||
key, err := getKey(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serialized, err := rs.serializer.serialize(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = rs.KeysAPI.Set(context.Background(), key, string(serialized),
|
||||
&client.SetOptions{PrevExist: client.PrevNoExist})
|
||||
return err
|
||||
}
|
||||
|
||||
func (rs *resourceStore) read(rkey string, res resource) error {
|
||||
typName, err := getTypeName(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := typName + "/" + rkey
|
||||
|
||||
resp, err := rs.KeysAPI.Get(context.Background(), key, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return rs.serializer.deserialize([]byte(resp.Node.Value), res)
|
||||
}
|
||||
|
||||
func (rs *resourceStore) update(r resource) error {
|
||||
key, err := getKey(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serialized, err := rs.serializer.serialize(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = rs.KeysAPI.Set(context.Background(), key, string(serialized),
|
||||
&client.SetOptions{PrevExist: client.PrevExist})
|
||||
return err
|
||||
}
|
||||
|
||||
func (rs *resourceStore) delete(typename, rkey string) error {
|
||||
key := typename + "/" + rkey
|
||||
_, err := rs.KeysAPI.Delete(context.Background(), key, nil) // ignore response
|
||||
return err
|
||||
}
|
||||
|
||||
func (rs *resourceStore) getAll(key string) ([]string, error) {
|
||||
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Recursive: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]string, 0, len(resp.Node.Nodes))
|
||||
for _, n := range resp.Node.Nodes {
|
||||
res = append(res, n.Value)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (rs *resourceStore) writeFile(parentKey string, contents []byte) (string, string, error) {
|
||||
uid := uuid.NewV4().String()
|
||||
|
||||
err := rs.fileStore.write(uid, contents)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
parentKey = "file/" + parentKey
|
||||
resp, err := rs.KeysAPI.CreateInOrder(context.Background(), parentKey, uid, nil)
|
||||
if err != nil {
|
||||
_ = rs.fileStore.delete(uid)
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return resp.Node.Key, uid, nil
|
||||
}
|
||||
|
||||
func (rs *resourceStore) readFile(key string, uid *string) ([]byte, error) {
|
||||
key = "file/" + key
|
||||
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if uid == nil {
|
||||
// get latest
|
||||
n := resp.Node.Nodes
|
||||
uid = &n[len(n)-1].Value
|
||||
} else {
|
||||
// validate uid is in the list
|
||||
found := false
|
||||
for _, u := range resp.Node.Nodes {
|
||||
if *uid == u.Value {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, errors.New("Invalid UID " + *uid)
|
||||
}
|
||||
}
|
||||
|
||||
contents, err := rs.fileStore.read(*uid)
|
||||
return contents, err
|
||||
}
|
||||
|
||||
func (rs *resourceStore) deleteFile(key string, uid string) error {
|
||||
key = "file/" + key
|
||||
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var node *client.Node
|
||||
for _, u := range resp.Node.Nodes {
|
||||
if u.Value == uid {
|
||||
node = u
|
||||
}
|
||||
}
|
||||
if node == nil {
|
||||
log.WithFields(log.Fields{"key": key, "uid": uid}).Error("unreferenced file")
|
||||
return errors.New("won't delete unreferenced file")
|
||||
}
|
||||
|
||||
err = rs.fileStore.delete(node.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = rs.KeysAPI.Delete(context.Background(), node.Key, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(resp.Node.Nodes) == 1 {
|
||||
_, err = rs.KeysAPI.Delete(context.Background(), key, &client.DeleteOptions{Dir: true})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rs *resourceStore) deleteAllFiles(key string) error {
|
||||
key = "file/" + key
|
||||
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, u := range resp.Node.Nodes {
|
||||
err = rs.fileStore.delete(u.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = rs.KeysAPI.Delete(context.Background(), u.Key, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = rs.KeysAPI.Delete(context.Background(), key, &client.DeleteOptions{Dir: true})
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/etcd/client"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type TestResource struct {
|
||||
A string
|
||||
B int
|
||||
}
|
||||
|
||||
func (tr TestResource) Key() string {
|
||||
return tr.A
|
||||
}
|
||||
|
||||
func panicIf(err error) {
|
||||
if err != nil {
|
||||
log.Panicf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func assert(b bool, msg string) {
|
||||
if !b {
|
||||
log.Panic("assertion failed: " + msg)
|
||||
}
|
||||
}
|
||||
|
||||
func getTestResourceStore() (*fileStore, client.KeysAPI, *resourceStore) {
|
||||
// make a tmp dir
|
||||
dir, err := ioutil.TempDir("", "testFileStore")
|
||||
panicIf(err)
|
||||
fs := makeFileStore(dir)
|
||||
|
||||
// assume etcd is running, connect to it
|
||||
ks := getEtcdKeyAPI([]string{"http://localhost:2379"})
|
||||
|
||||
s := JsonSerializer{}
|
||||
rs := makeResourceStore(fs, ks, s)
|
||||
|
||||
return fs, ks, rs
|
||||
}
|
||||
|
||||
func TestResourceStore(t *testing.T) {
|
||||
fs, ks, rs := getTestResourceStore()
|
||||
defer os.RemoveAll(fs.root)
|
||||
|
||||
s := JsonSerializer{}
|
||||
|
||||
tr := TestResource{A: "hello", B: 1}
|
||||
|
||||
// Delete the key first, in case of a panic'd previous test run; ignore errors
|
||||
_ = rs.delete("TestResource", tr.Key())
|
||||
|
||||
// Create
|
||||
err := rs.create(tr)
|
||||
panicIf(err)
|
||||
defer ks.Delete(context.Background(), "/TestResource", &client.DeleteOptions{Dir: true})
|
||||
defer rs.delete("TestResource", tr.Key())
|
||||
|
||||
// Etcd key /TestResource/hello should exist
|
||||
_, err = ks.Get(context.Background(), "TestResource/hello", nil)
|
||||
panicIf(err)
|
||||
|
||||
// Read
|
||||
tr1 := TestResource{}
|
||||
err = rs.read(tr.Key(), &tr1)
|
||||
panicIf(err)
|
||||
assert(tr1 == tr, "retrieved value must equal created value")
|
||||
|
||||
// Update and Read
|
||||
tr.B += 1
|
||||
err = rs.update(tr)
|
||||
panicIf(err)
|
||||
err = rs.read(tr.Key(), &tr1)
|
||||
panicIf(err)
|
||||
assert(tr1 == tr, "retrieved value must equal updated value")
|
||||
|
||||
// Get list
|
||||
results, err := rs.getAll("TestResource")
|
||||
panicIf(err)
|
||||
res := make([]TestResource, 0, 0)
|
||||
for _, r := range results {
|
||||
tmp := TestResource{}
|
||||
err = s.deserialize([]byte(r), &tmp)
|
||||
panicIf(err)
|
||||
res = append(res, tmp)
|
||||
}
|
||||
assert(res[0] == tr, "value from retrieved list must equal updated value")
|
||||
|
||||
// file tests
|
||||
fileKey := "resourceStoreTest"
|
||||
fileContents1 := []byte("hello")
|
||||
fileContents2 := []byte("world")
|
||||
key, uid1, err := rs.writeFile(fileKey, fileContents1)
|
||||
panicIf(err)
|
||||
defer rs.deleteFile(fileKey, uid1)
|
||||
log.Printf("key = %v, uid = %v", key, uid1)
|
||||
|
||||
// read latest
|
||||
contents, err := rs.readFile(fileKey, nil)
|
||||
panicIf(err)
|
||||
assert(string(contents) == string(fileContents1), "retrieved file contents must match written value")
|
||||
|
||||
// update-- same key new contents
|
||||
_, uid2, err := rs.writeFile(fileKey, fileContents2)
|
||||
panicIf(err)
|
||||
defer rs.deleteFile(fileKey, uid2)
|
||||
|
||||
// read latest
|
||||
contents, err = rs.readFile(fileKey, nil)
|
||||
panicIf(err)
|
||||
assert(string(contents) == string(fileContents2), "retrieved file contents must match updated value")
|
||||
|
||||
// read by uid
|
||||
// 1
|
||||
contents, err = rs.readFile(fileKey, &uid1)
|
||||
panicIf(err)
|
||||
assert(string(contents) == string(fileContents1), "retrieved file contents must match updated value")
|
||||
|
||||
// 2
|
||||
contents, err = rs.readFile(fileKey, &uid2)
|
||||
panicIf(err)
|
||||
assert(string(contents) == string(fileContents2), "retrieved file contents must match updated value")
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
type (
|
||||
resource interface {
|
||||
Key() string
|
||||
}
|
||||
|
||||
serializer interface {
|
||||
serialize(r resource) ([]byte, error)
|
||||
deserialize(buf []byte, r resource) error
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2016 The Fission Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package fission
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("(Error %v) %v", e.Code, e.Message)
|
||||
}
|
||||
|
||||
func MakeError(code int, msg string) Error {
|
||||
return Error{Code: errorCode(code), Message: msg}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package fission
|
||||
|
||||
func (f Function) Key() string {
|
||||
return f.Metadata.Name
|
||||
}
|
||||
|
||||
func (e Environment) Key() string {
|
||||
return e.Metadata.Name
|
||||
}
|
||||
|
||||
func (ht HTTPTrigger) Key() string {
|
||||
return ht.Metadata.Name
|
||||
}
|
||||
@@ -18,22 +18,19 @@ package fission
|
||||
|
||||
type (
|
||||
// Metadata is used as the general identifier for all kinds of
|
||||
// resources managed by the controller. In general, when a
|
||||
// resource is updated, the Name remains the same, but the UID
|
||||
// changes. In other words, the UID identifies a particular
|
||||
// value of a resource.
|
||||
// resources managed by the controller.
|
||||
Metadata struct {
|
||||
Name string
|
||||
Uid string
|
||||
Name string `json:"name"`
|
||||
Uid string `json:"uid,omitempty"`
|
||||
}
|
||||
|
||||
// Function is a unit of executable code. Though it's called
|
||||
// a function, the code may have more than one function; it's
|
||||
// usually some sort of module or package.
|
||||
Function struct {
|
||||
Metadata
|
||||
Environment Metadata
|
||||
Code string
|
||||
Metadata `json:"metadata"`
|
||||
Environment Metadata `json:"environment"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// Environment identifies the language and OS specific
|
||||
@@ -42,16 +39,33 @@ type (
|
||||
// this will also include build containers, as well as support
|
||||
// tools like debuggers, profilers, etc.
|
||||
Environment struct {
|
||||
Metadata
|
||||
RunContainerImageUrl string
|
||||
Metadata `json:"metadata"`
|
||||
RunContainerImageUrl string `json:"runContainerImageUrl"`
|
||||
}
|
||||
|
||||
// HTTPTrigger maps URL patterns to functions. Function.UID
|
||||
// is optional; if absent, the latest version of the function
|
||||
// will automatically be selected.
|
||||
HTTPTrigger struct {
|
||||
Metadata
|
||||
UrlPattern string
|
||||
Function Metadata
|
||||
Metadata `json:"metadata"`
|
||||
UrlPattern string `json:"urlpattern"`
|
||||
Function Metadata `json:"function"`
|
||||
}
|
||||
|
||||
// Errors returned by the Fission API.
|
||||
Error struct {
|
||||
Code errorCode `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
errorCode int
|
||||
)
|
||||
|
||||
const (
|
||||
ErrorInternal = iota
|
||||
|
||||
ErrorNotAuthorized
|
||||
ErrorNotFound
|
||||
ErrorNameExists
|
||||
ErrorInvalidArgument
|
||||
ErrorNoSpace
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user