diff --git a/controller/api.go b/controller/api.go index f26abc29..132fd85f 100644 --- a/controller/api.go +++ b/controller/api.go @@ -33,6 +33,17 @@ type API struct { FunctionStore HTTPTriggerStore EnvironmentStore + WatchStore +} + +func MakeAPI(rs *ResourceStore) *API { + api := &API{ + FunctionStore: FunctionStore{ResourceStore: *rs}, + HTTPTriggerStore: HTTPTriggerStore{ResourceStore: *rs}, + EnvironmentStore: EnvironmentStore{ResourceStore: *rs}, + WatchStore: WatchStore{ResourceStore: *rs}, + } + return api } func (api *API) respondWithSuccess(w http.ResponseWriter, resp []byte) { @@ -76,6 +87,12 @@ func (api *API) Serve(port int) { r.HandleFunc("/v1/environments/{environment}", api.EnvironmentApiUpdate).Methods("PUT") r.HandleFunc("/v1/environments/{environment}", api.EnvironmentApiDelete).Methods("DELETE") + r.HandleFunc("/v1/watches", api.WatchApiList).Methods("GET") + r.HandleFunc("/v1/watches", api.WatchApiCreate).Methods("POST") + r.HandleFunc("/v1/watches/{watch}", api.WatchApiGet).Methods("GET") + r.HandleFunc("/v1/watches/{watch}", api.WatchApiUpdate).Methods("PUT") + r.HandleFunc("/v1/watches/{watch}", api.WatchApiDelete).Methods("DELETE") + address := fmt.Sprintf(":%v", port) log.WithFields(log.Fields{"port": port}).Info("Server started") diff --git a/controller/api_test.go b/controller/api_test.go index 8b218c26..d61528ad 100644 --- a/controller/api_test.go +++ b/controller/api_test.go @@ -185,22 +185,55 @@ func TestEnvironmentApi(t *testing.T) { assert(len(ts) == 2, "created two envs, but didn't find them") } +func TestWatchApi(t *testing.T) { + testWatch := &fission.Watch{ + Metadata: fission.Metadata{ + Name: "xxx", + Uid: "yyy", + }, + Namespace: "default", + ObjType: "pod", + LabelSelector: "", + FieldSelector: "", + Function: fission.Metadata{ + Name: "foo", + Uid: "", + }, + Url: "", + } + m, err := g.client.WatchCreate(testWatch) + panicIf(err) + defer g.client.WatchDelete(m) + + w, err := g.client.WatchGet(m) + panicIf(err) + testWatch.Metadata.Uid = m.Uid + w.Url = "" + assert(*testWatch == *w, "watch should match after reading") + + testWatch.Metadata.Name = "yyy" + m2, err := g.client.WatchCreate(testWatch) + panicIf(err) + defer g.client.WatchDelete(m2) + + ws, err := g.client.WatchList() + panicIf(err) + assert(len(ws) == 2, "created two envs, but didn't find them") +} + func TestMain(m *testing.M) { flag.Parse() fileStore, ks, rs := getTestResourceStore() defer os.RemoveAll(fileStore.root) - api := &API{ - FunctionStore: FunctionStore{ResourceStore: *rs}, - HTTPTriggerStore: HTTPTriggerStore{ResourceStore: *rs}, - EnvironmentStore: EnvironmentStore{ResourceStore: *rs}, - } + api := MakeAPI(rs) g.client = client.MakeClient("http://localhost:8888") ks.Delete(context.Background(), "Function", &etcdClient.DeleteOptions{Recursive: true}) ks.Delete(context.Background(), "HTTPTrigger", &etcdClient.DeleteOptions{Recursive: true}) ks.Delete(context.Background(), "Environment", &etcdClient.DeleteOptions{Recursive: true}) + ks.Delete(context.Background(), "Watch", &etcdClient.DeleteOptions{Recursive: true}) go api.Serve(8888) time.Sleep(500 * time.Millisecond) diff --git a/controller/client/client.go b/controller/client/client.go index 82bd7906..e73eb95a 100644 --- a/controller/client/client.go +++ b/controller/client/client.go @@ -459,3 +459,90 @@ func (c *Client) EnvironmentList() ([]fission.Environment, error) { return envs, nil } + +func (c *Client) WatchCreate(w *fission.Watch) (*fission.Metadata, error) { + reqbody, err := json.Marshal(w) + if err != nil { + return nil, err + } + + resp, err := http.Post(c.url("watches"), "application/json", bytes.NewReader(reqbody)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := c.handleResponse(resp) + 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) WatchGet(m *fission.Metadata) (*fission.Watch, error) { + relativeUrl := fmt.Sprintf("watches/%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 := c.handleResponse(resp) + if err != nil { + return nil, err + } + + var w fission.Watch + err = json.Unmarshal(body, &w) + if err != nil { + return nil, err + } + + return &w, nil +} + +func (c *Client) WatchUpdate(w *fission.Watch) (*fission.Metadata, error) { + return nil, fission.MakeError(fission.ErrorNotImplmented, + "watch update not implemented") +} + +func (c *Client) WatchDelete(m *fission.Metadata) error { + relativeUrl := fmt.Sprintf("watches/%v", m.Name) + if len(m.Uid) > 0 { + relativeUrl += fmt.Sprintf("?uid=%v", m.Uid) + } + err := c.delete(relativeUrl) + return err + +} + +func (c *Client) WatchList() ([]fission.Watch, error) { + resp, err := http.Get(c.url("watches")) + if err != nil { + return nil, err + } + + body, err := c.handleResponse(resp) + if err != nil { + return nil, err + } + + watches := make([]fission.Watch, 0) + err = json.Unmarshal(body, &watches) + if err != nil { + return nil, err + } + + return watches, err +} diff --git a/controller/watchApi.go b/controller/watchApi.go new file mode 100644 index 00000000..34e4a4d9 --- /dev/null +++ b/controller/watchApi.go @@ -0,0 +1,120 @@ +/* +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" + "io/ioutil" + "net/http" + + log "github.com/Sirupsen/logrus" + "github.com/gorilla/mux" + + "github.com/platform9/fission" +) + +func (api *API) WatchApiList(w http.ResponseWriter, r *http.Request) { + watches, err := api.WatchStore.List() + if err != nil { + api.respondWithError(w, err) + return + } + + resp, err := json.Marshal(watches) + if err != nil { + api.respondWithError(w, err) + return + } + + api.respondWithSuccess(w, resp) +} + +func (api *API) WatchApiCreate(w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + api.respondWithError(w, err) + } + + var watch fission.Watch + err = json.Unmarshal(body, &watch) + if err != nil { + api.respondWithError(w, err) + return + } + watch.Url = fission.UrlForFunction(&watch.Function) + + uid, err := api.WatchStore.Create(&watch) + if err != nil { + api.respondWithError(w, err) + return + } + + m := &fission.Metadata{Name: watch.Metadata.Name, Uid: uid} + resp, err := json.Marshal(m) + if err != nil { + api.respondWithError(w, err) + return + } + + api.respondWithSuccess(w, resp) +} + +func (api *API) WatchApiGet(w http.ResponseWriter, r *http.Request) { + var m fission.Metadata + + vars := mux.Vars(r) + m.Name = vars["watch"] + m.Uid = r.FormValue("uid") // empty if uid is absent + + watch, err := api.WatchStore.Get(&m) + if err != nil { + api.respondWithError(w, err) + return + } + + resp, err := json.Marshal(watch) + if err != nil { + api.respondWithError(w, err) + return + } + + api.respondWithSuccess(w, resp) +} + +func (api *API) WatchApiUpdate(w http.ResponseWriter, r *http.Request) { + api.respondWithError(w, fission.MakeError(fission.ErrorNotImplmented, + "Not implemented")) +} + +func (api *API) WatchApiDelete(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + var m fission.Metadata + m.Name = vars["watch"] + + m.Uid = r.FormValue("uid") // empty if uid is absent + if len(m.Uid) == 0 { + log.WithFields(log.Fields{"watch": m.Name}).Info("Deleting all versions") + } + + err := api.WatchStore.Delete(m) + if err != nil { + api.respondWithError(w, err) + return + } + + api.respondWithSuccess(w, []byte("")) +} diff --git a/controller/watchStore.go b/controller/watchStore.go new file mode 100644 index 00000000..53f21a6e --- /dev/null +++ b/controller/watchStore.go @@ -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 WatchStore struct { + ResourceStore +} + +func (ws *WatchStore) Create(w *fission.Watch) (string, error) { + w.Metadata.Uid = uuid.NewV4().String() + return w.Metadata.Uid, ws.ResourceStore.create(w) +} + +func (ws *WatchStore) Get(m *fission.Metadata) (*fission.Watch, error) { + var w fission.Watch + err := ws.ResourceStore.read(m.Name, &w) + if err != nil { + return nil, err + } + return &w, err +} + +func (ws *WatchStore) Update(w *fission.Watch) (string, error) { + w.Metadata.Uid = uuid.NewV4().String() + return w.Metadata.Uid, ws.ResourceStore.update(w) +} + +func (ws *WatchStore) Delete(m fission.Metadata) error { + typeName, err := getTypeName(fission.Watch{}) + if err != nil { + return err + } + return ws.ResourceStore.delete(typeName, m.Name) +} + +func (ws *WatchStore) List() ([]fission.Watch, error) { + typeName, err := getTypeName(fission.Watch{}) + if err != nil { + return nil, err + } + + bufs, err := ws.ResourceStore.getAll(typeName) + if err != nil { + return nil, err + } + + watches := make([]fission.Watch, 0, len(bufs)) + js := JsonSerializer{} + for _, buf := range bufs { + var w fission.Watch + err = js.deserialize([]byte(buf), &w) + if err != nil { + return nil, err + } + watches = append(watches, w) + } + + return watches, nil +}