From 5ba5808722ba19f102f493d4e2b1c096b71f9c17 Mon Sep 17 00:00:00 2001 From: stefanprodan Date: Wed, 20 May 2020 12:59:27 +0300 Subject: [PATCH 1/2] Add cache CRUD API --- README.md | 5 +-- pkg/api/cache.go | 68 ++++++++++++++++++++++++++++----------- pkg/api/docs/docs.go | 59 +++++++++++++++++++-------------- pkg/api/docs/swagger.json | 57 ++++++++++++++++++-------------- pkg/api/docs/swagger.yaml | 26 +++++++++------ pkg/api/server.go | 5 +-- 6 files changed, 139 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 887d174..581b7cb 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,9 @@ Web API: * `POST /token` issues a JWT token valid for one minute `JWT=$(curl -sd 'anon' podinfo:9898/token | jq -r .token)` * `GET /token/validate` validates the JWT token `curl -H "Authorization: Bearer $JWT" podinfo:9898/token/validate` * `GET /configs` returns a JSON with configmaps and/or secrets mounted in the `config` volume -* `POST /cache` saves the posted content to Redis and returns the SHA1 hash of the content -* `GET /cache/{hash}` returns the content from Redis if the key exists +* `POST/PUT /cache/{key}` saves the posted content to Redis +* `GET /cache/{key}` returns the content from Redis if the key exists +* `DELETE /cache/{key}` deletes the key from Redis if exists * `POST /store` writes the posted content to disk at /data/hash and returns the SHA1 hash of the content * `GET /store/{hash}` returns the content of the file /data/hash if exists * `GET /ws/echo` echos content via websockets `podcli ws ws://localhost:9898/ws/echo` diff --git a/pkg/api/cache.go b/pkg/api/cache.go index 298c604..dbeafd4 100644 --- a/pkg/api/cache.go +++ b/pkg/api/cache.go @@ -1,45 +1,74 @@ package api import ( - "github.com/gomodule/redigo/redis" - "github.com/gorilla/mux" - "go.uber.org/zap" "io/ioutil" "net/http" "time" + + "github.com/gomodule/redigo/redis" + "github.com/gorilla/mux" + "go.uber.org/zap" ) // Cache godoc // @Summary Save payload in cache -// @Description writes the posted content in cache and returns the SHA1 hash of the content +// @Description writes the posted content in cache // @Tags HTTP API // @Accept json // @Produce json -// @Router /cache [post] -// @Success 200 {object} api.MapResponse +// @Router /cache/{key} [post] +// @Success 202 func (s *Server) cacheWriteHandler(w http.ResponseWriter, r *http.Request) { if s.pool == nil { s.ErrorResponse(w, r, "cache server is offline", http.StatusBadRequest) return } + key := mux.Vars(r)["key"] body, err := ioutil.ReadAll(r.Body) if err != nil { s.ErrorResponse(w, r, "reading the request body failed", http.StatusBadRequest) return } - hash := hash(string(body)) - conn := s.pool.Get() defer conn.Close() - _, err = conn.Do("SET", hash, string(body)) + _, err = conn.Do("SET", key, string(body)) if err != nil { s.logger.Warn("cache set failed", zap.Error(err)) s.ErrorResponse(w, r, "cache set failed", http.StatusInternalServerError) return } - s.JSONResponseCode(w, r, map[string]string{"hash": hash}, http.StatusAccepted) + + w.WriteHeader(http.StatusAccepted) +} + +// Cache godoc +// @Summary Delete payload from cache +// @Description deletes the key and its value from cache +// @Tags HTTP API +// @Accept json +// @Produce json +// @Router /cache/{key} [delete] +// @Success 202 +func (s *Server) cacheDeleteHandler(w http.ResponseWriter, r *http.Request) { + if s.pool == nil { + s.ErrorResponse(w, r, "cache server is offline", http.StatusBadRequest) + return + } + + key := mux.Vars(r)["key"] + + conn := s.pool.Get() + defer conn.Close() + _, err := conn.Do("DEL", key) + if err != nil { + s.logger.Warn("cache delete failed", zap.Error(err)) + w.WriteHeader(http.StatusBadRequest) + return + } + + w.WriteHeader(http.StatusAccepted) } // Cache godoc @@ -48,32 +77,35 @@ func (s *Server) cacheWriteHandler(w http.ResponseWriter, r *http.Request) { // @Tags HTTP API // @Accept json // @Produce json -// @Router /cache/{hash} [get] -// @Success 200 {string} api.MapResponse +// @Router /cache/{key} [get] +// @Success 200 {string} string value func (s *Server) cacheReadHandler(w http.ResponseWriter, r *http.Request) { if s.pool == nil { s.ErrorResponse(w, r, "cache server is offline", http.StatusBadRequest) return } - hash := mux.Vars(r)["hash"] + key := mux.Vars(r)["key"] + conn := s.pool.Get() defer conn.Close() - ok, err := redis.Bool(conn.Do("EXISTS", hash)) + ok, err := redis.Bool(conn.Do("EXISTS", key)) if err != nil || !ok { - s.ErrorResponse(w, r, "key not found in cache", http.StatusNotFound) + s.logger.Warn("cache key not found", zap.String("key", key)) + w.WriteHeader(http.StatusNotFound) return } - data, err := redis.String(conn.Do("GET", hash)) + data, err := redis.String(conn.Do("GET", key)) if err != nil { s.logger.Warn("cache get failed", zap.Error(err)) - s.ErrorResponse(w, r, "cache get failed", http.StatusInternalServerError) + w.WriteHeader(http.StatusInternalServerError) return } - s.JSONResponseCode(w, r, map[string]string{"data": data}, http.StatusAccepted) + w.WriteHeader(http.StatusOK) + w.Write([]byte(data)) } func (s *Server) startCachePool() { diff --git a/pkg/api/docs/docs.go b/pkg/api/docs/docs.go index e27dbcf..fcf8f01 100644 --- a/pkg/api/docs/docs.go +++ b/pkg/api/docs/docs.go @@ -1,6 +1,6 @@ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag at -// 2020-05-16 09:49:23.920068 +0300 EEST m=+0.052088436 +// 2020-05-20 12:48:10.564627 +0300 EEST m=+0.030136350 package docs @@ -98,30 +98,7 @@ var doc = `{ } } }, - "/cache": { - "post": { - "description": "writes the posted content in cache and returns the SHA1 hash of the content", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "HTTP API" - ], - "summary": "Save payload in cache", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/api.MapResponse" - } - } - } - } - }, - "/cache/{hash}": { + "/cache/{key}": { "get": { "description": "returns the content from cache if key exists", "consumes": [ @@ -142,6 +119,38 @@ var doc = `{ } } } + }, + "post": { + "description": "writes the posted content in cache", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Save payload in cache", + "responses": { + "202": {} + } + }, + "delete": { + "description": "deletes the key and its value from cache", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Delete payload from cache", + "responses": { + "202": {} + } } }, "/chunked/{seconds}": { diff --git a/pkg/api/docs/swagger.json b/pkg/api/docs/swagger.json index dd444d0..343a5ce 100644 --- a/pkg/api/docs/swagger.json +++ b/pkg/api/docs/swagger.json @@ -86,30 +86,7 @@ } } }, - "/cache": { - "post": { - "description": "writes the posted content in cache and returns the SHA1 hash of the content", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "HTTP API" - ], - "summary": "Save payload in cache", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/api.MapResponse" - } - } - } - } - }, - "/cache/{hash}": { + "/cache/{key}": { "get": { "description": "returns the content from cache if key exists", "consumes": [ @@ -130,6 +107,38 @@ } } } + }, + "post": { + "description": "writes the posted content in cache", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Save payload in cache", + "responses": { + "202": {} + } + }, + "delete": { + "description": "deletes the key and its value from cache", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "HTTP API" + ], + "summary": "Delete payload from cache", + "responses": { + "202": {} + } } }, "/chunked/{seconds}": { diff --git a/pkg/api/docs/swagger.yaml b/pkg/api/docs/swagger.yaml index f55e742..84376b5 100644 --- a/pkg/api/docs/swagger.yaml +++ b/pkg/api/docs/swagger.yaml @@ -102,23 +102,18 @@ paths: summary: Runtime information tags: - HTTP API - /cache: - post: + /cache/{key}: + delete: consumes: - application/json - description: writes the posted content in cache and returns the SHA1 hash of - the content + description: deletes the key and its value from cache produces: - application/json responses: - "200": - description: OK - schema: - $ref: '#/definitions/api.MapResponse' - summary: Save payload in cache + "202": {} + summary: Delete payload from cache tags: - HTTP API - /cache/{hash}: get: consumes: - application/json @@ -133,6 +128,17 @@ paths: summary: Get payload from cache tags: - HTTP API + post: + consumes: + - application/json + description: writes the posted content in cache + produces: + - application/json + responses: + "202": {} + summary: Save payload in cache + tags: + - HTTP API /chunked/{seconds}: get: consumes: diff --git a/pkg/api/server.go b/pkg/api/server.go index d43d68e..65afbf3 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -106,8 +106,9 @@ func (s *Server) registerHandlers() { s.router.HandleFunc("/status/{code:[0-9]+}", s.statusHandler).Methods("GET", "POST", "PUT").Name("status") s.router.HandleFunc("/store", s.storeWriteHandler).Methods("POST", "PUT") s.router.HandleFunc("/store/{hash}", s.storeReadHandler).Methods("GET").Name("store") - s.router.HandleFunc("/cache", s.cacheWriteHandler).Methods("POST", "PUT") - s.router.HandleFunc("/cache/{hash}", s.cacheReadHandler).Methods("GET").Name("cache") + s.router.HandleFunc("/cache/{key}", s.cacheWriteHandler).Methods("POST", "PUT") + s.router.HandleFunc("/cache/{key}", s.cacheDeleteHandler).Methods("DELETE") + s.router.HandleFunc("/cache/{key}", s.cacheReadHandler).Methods("GET").Name("cache") s.router.HandleFunc("/configs", s.configReadHandler).Methods("GET") s.router.HandleFunc("/token", s.tokenGenerateHandler).Methods("POST") s.router.HandleFunc("/token/validate", s.tokenValidateHandler).Methods("GET") From 0352a3c8221b9fd880978e94080fa73322a35a46 Mon Sep 17 00:00:00 2001 From: stefanprodan Date: Wed, 20 May 2020 13:00:03 +0300 Subject: [PATCH 2/2] Add Helm test for the cache routes --- charts/podinfo/templates/tests/cache.yaml | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 charts/podinfo/templates/tests/cache.yaml diff --git a/charts/podinfo/templates/tests/cache.yaml b/charts/podinfo/templates/tests/cache.yaml new file mode 100644 index 0000000..e77217d --- /dev/null +++ b/charts/podinfo/templates/tests/cache.yaml @@ -0,0 +1,32 @@ +{{- if .Values.cache }} +apiVersion: v1 +kind: Pod +metadata: + name: {{ template "podinfo.fullname" . }}-cache-test-{{ randAlphaNum 5 | lower }} + labels: + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app: {{ template "podinfo.name" . }} + annotations: + "helm.sh/hook": test + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + sidecar.istio.io/inject: "false" + linkerd.io/inject: disabled + appmesh.k8s.aws/sidecarInjectorWebhook: disabled +spec: + containers: + - name: curl + image: curlimages/curl:7.69.0 + command: + - sh + - -c + - | + curl -sd 'data' ${PODINFO_SVC}/cache/test && + curl -s ${PODINFO_SVC}/cache/test | grep data && + curl -s -XDELETE ${PODINFO_SVC}/cache/test + env: + - name: PODINFO_SVC + value: "{{ template "podinfo.fullname" . }}.{{ .Release.Namespace }}:{{ .Values.service.externalPort }}" + restartPolicy: Never +{{- end }}