From 14734f64354cde5043a9c30a54962129e8001088 Mon Sep 17 00:00:00 2001 From: Soam Vasani Date: Tue, 20 Sep 2016 00:45:13 -0700 Subject: [PATCH] HTTP API for Functions So far, just Function create/read/update/delete/list. FunctionApi glues HTTP request/response to functionStore. --- controller/api.go | 102 +++++++++++++++++++++++++ controller/functionApi.go | 151 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 controller/api.go create mode 100644 controller/functionApi.go diff --git a/controller/api.go b/controller/api.go new file mode 100644 index 00000000..533daa61 --- /dev/null +++ b/controller/api.go @@ -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))) +} diff --git a/controller/functionApi.go b/controller/functionApi.go new file mode 100644 index 00000000..5319cd35 --- /dev/null +++ b/controller/functionApi.go @@ -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("")) +}