Recorder CRD, Records API, Redis deployment (#818)

This commit is contained in:
Nafisa Shazia
2018-08-15 05:28:40 +08:00
committed by Ta-Ching Chen
parent 07ca5df8d0
commit 74a3a54543
38 changed files with 2770 additions and 123 deletions
+39
View File
@@ -0,0 +1,39 @@
apiVersion: v1
kind: Service
metadata:
name: redis
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
ports:
- port: 6379
targetPort: 6379
protocol: TCP
name: redis
selector:
app: redis
---
apiVersion: apps/v1beta2
kind: StatefulSet
metadata:
name: redis
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
spec:
selector:
matchLabels:
app: redis
serviceName: redis
replicas: 1
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:3.2-alpine
imagePullPolicy: Always
ports:
- containerPort: 6379
name: redis
+11
View File
@@ -237,6 +237,17 @@ func (api *API) Serve(port int) {
r.HandleFunc("/v2/triggers/messagequeue/{mqTrigger}", api.MessageQueueTriggerApiUpdate).Methods("PUT") r.HandleFunc("/v2/triggers/messagequeue/{mqTrigger}", api.MessageQueueTriggerApiUpdate).Methods("PUT")
r.HandleFunc("/v2/triggers/messagequeue/{mqTrigger}", api.MessageQueueTriggerApiDelete).Methods("DELETE") r.HandleFunc("/v2/triggers/messagequeue/{mqTrigger}", api.MessageQueueTriggerApiDelete).Methods("DELETE")
r.HandleFunc("/v2/recorders", api.RecorderApiList).Methods("GET")
r.HandleFunc("/v2/recorders", api.RecorderApiCreate).Methods("POST")
r.HandleFunc("/v2/recorders/{recorder}", api.RecorderApiGet).Methods("GET")
r.HandleFunc("/v2/recorders/{recorder}", api.RecorderApiUpdate).Methods("PUT")
r.HandleFunc("/v2/recorders/{recorder}", api.RecorderApiDelete).Methods("DELETE")
r.HandleFunc("/v2/records", api.RecordsApiListAll).Methods("GET")
r.HandleFunc("/v2/records/function/{function}", api.RecordsApiFilterByFunction).Methods("GET")
r.HandleFunc("/v2/records/trigger/{trigger}", api.RecordsApiFilterByTrigger).Methods("GET")
r.HandleFunc("/v2/records/time", api.RecordsApiFilterByTime).Methods("GET")
r.HandleFunc("/v2/secrets/{secret}", api.SecretGet).Methods("GET") r.HandleFunc("/v2/secrets/{secret}", api.SecretGet).Methods("GET")
r.HandleFunc("/v2/configmaps/{configmap}", api.ConfigMapGet).Methods("GET") r.HandleFunc("/v2/configmaps/{configmap}", api.ConfigMapGet).Methods("GET")
+239
View File
@@ -0,0 +1,239 @@
/*
Copyright 2018 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"
"fmt"
"net/http"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fission/fission/crd"
fv1 "github.com/fission/fission/pkg/apis/fission.io/v1"
"github.com/fission/fission/redis/build/gen"
)
func (c *Client) RecorderCreate(r *crd.Recorder) (*metav1.ObjectMeta, error) {
err := r.Validate()
if err != nil {
return nil, fv1.AggregateValidationErrors("Recorder", err)
}
reqbody, err := json.Marshal(r)
if err != nil {
return nil, err
}
resp, err := http.Post(c.url("recorders"), "application/json", bytes.NewReader(reqbody))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleCreateResponse(resp)
if err != nil {
return nil, err
}
var m metav1.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) RecorderGet(m *metav1.ObjectMeta) (*crd.Recorder, error) {
relativeUrl := fmt.Sprintf("recorders/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
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 r crd.Recorder
err = json.Unmarshal(body, &r)
if err != nil {
return nil, err
}
return &r, nil
}
func (c *Client) RecorderUpdate(recorder *crd.Recorder) (*metav1.ObjectMeta, error) {
err := recorder.Validate()
if err != nil {
return nil, fv1.AggregateValidationErrors("Recorder", err)
}
reqbody, err := json.Marshal(recorder)
if err != nil {
return nil, err
}
relativeUrl := fmt.Sprintf("recorders/%v", recorder.Metadata.Name)
resp, err := c.put(relativeUrl, "application/json", reqbody)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := c.handleResponse(resp)
if err != nil {
return nil, err
}
var m metav1.ObjectMeta
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
return &m, nil
}
func (c *Client) RecorderDelete(m *metav1.ObjectMeta) error {
relativeUrl := fmt.Sprintf("recorders/%v", m.Name)
relativeUrl += fmt.Sprintf("?namespace=%v", m.Namespace)
return c.delete(relativeUrl)
}
func (c *Client) RecorderList(ns string) ([]crd.Recorder, error) {
relativeUrl := "recorders"
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
}
recorders := make([]crd.Recorder, 0)
err = json.Unmarshal(body, &recorders)
if err != nil {
return nil, err
}
return recorders, nil
}
// TODO: Move to different file?
func (c *Client) RecordsByFunction(function string) ([]*redisCache.RecordedEntry, error) {
relativeUrl := fmt.Sprintf("records/function/%v", function)
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
}
records := make([]*redisCache.RecordedEntry, 0)
err = json.Unmarshal(body, &records)
if err != nil {
return nil, err
}
return records, nil
}
func (c *Client) RecordsAll() ([]*redisCache.RecordedEntry, error) {
relativeUrl := "records"
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
}
records := make([]*redisCache.RecordedEntry, 0)
err = json.Unmarshal(body, &records)
if err != nil {
return nil, err
}
return records, nil
}
func (c *Client) RecordsByTrigger(trigger string) ([]*redisCache.RecordedEntry, error) {
relativeUrl := fmt.Sprintf("records/trigger/%v", trigger)
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
}
records := make([]*redisCache.RecordedEntry, 0)
err = json.Unmarshal(body, &records)
if err != nil {
return nil, err
}
return records, nil
}
func (c *Client) RecordsByTime(from string, to string) ([]*redisCache.RecordedEntry, error) {
relativeUrl := "records/time"
relativeUrl += fmt.Sprintf("?from=%v&to=%v", from, to)
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
}
records := make([]*redisCache.RecordedEntry, 0)
err = json.Unmarshal(body, &records)
if err != nil {
return nil, err
}
return records, nil
}
+148
View File
@@ -0,0 +1,148 @@
/*
Copyright 2018 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"
"github.com/gorilla/mux"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fission/fission"
"github.com/fission/fission/crd"
)
func (a *API) RecorderApiList(w http.ResponseWriter, r *http.Request) {
recorders, err := a.fissionClient.Recorders(metav1.NamespaceAll).List(metav1.ListOptions{})
if err != nil {
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(recorders.Items)
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
func (a *API) RecorderApiCreate(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
a.respondWithError(w, err)
return
}
var recorder crd.Recorder
err = json.Unmarshal(body, &recorder)
if err != nil {
a.respondWithError(w, err)
return
}
tnew, err := a.fissionClient.Recorders(recorder.Metadata.Namespace).Create(&recorder)
if err != nil {
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(tnew.Metadata)
if err != nil {
a.respondWithError(w, err)
return
}
w.WriteHeader(http.StatusCreated)
a.respondWithSuccess(w, resp)
}
func (a *API) RecorderApiGet(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["recorder"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
recorder, err := a.fissionClient.Recorders(ns).Get(name)
if err != nil {
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(recorder)
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
func (a *API) RecorderApiUpdate(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["recorder"]
body, err := ioutil.ReadAll(r.Body)
if err != nil {
a.respondWithError(w, err)
return
}
var recorder crd.Recorder
err = json.Unmarshal(body, &recorder)
if err != nil {
a.respondWithError(w, err)
return
}
if name != recorder.Metadata.Name {
err = fission.MakeError(fission.ErrorInvalidArgument, "Recorder name doesn't match URL")
a.respondWithError(w, err)
return
}
rnew, err := a.fissionClient.Recorders(recorder.Metadata.Namespace).Update(&recorder)
if err != nil {
a.respondWithError(w, err)
return
}
resp, err := json.Marshal(rnew.Metadata)
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
func (a *API) RecorderApiDelete(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["recorder"]
ns := a.extractQueryParamFromRequest(r, "namespace")
if len(ns) == 0 {
ns = metav1.NamespaceDefault
}
err := a.fissionClient.Recorders(ns).Delete(name, &metav1.DeleteOptions{})
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, []byte(""))
}
+95
View File
@@ -0,0 +1,95 @@
/*
Copyright 2018 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 (
"net/http"
"github.com/gorilla/mux"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fission/fission/redis"
)
func (a *API) RecordsApiListAll(w http.ResponseWriter, r *http.Request) {
resp, err := redis.RecordsListAll()
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
func (a *API) RecordsApiFilterByFunction(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
query := vars["function"]
recorders, err := a.fissionClient.Recorders(metav1.NamespaceAll).List(metav1.ListOptions{})
if err != nil {
a.respondWithError(w, err)
return
}
triggers, err := a.fissionClient.HTTPTriggers(metav1.NamespaceAll).List(metav1.ListOptions{})
if err != nil {
a.respondWithError(w, err)
return
}
resp, err := redis.RecordsFilterByFunction(query, recorders, triggers)
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
func (a *API) RecordsApiFilterByTrigger(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
query := vars["trigger"]
recorders, err := a.fissionClient.Recorders(metav1.NamespaceAll).List(metav1.ListOptions{})
if err != nil {
a.respondWithError(w, err)
return
}
triggers, err := a.fissionClient.HTTPTriggers(metav1.NamespaceAll).List(metav1.ListOptions{})
if err != nil {
a.respondWithError(w, err)
return
}
resp, err := redis.RecordsFilterByTrigger(query, recorders, triggers)
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
func (a *API) RecordsApiFilterByTime(w http.ResponseWriter, r *http.Request) {
from := r.FormValue("from")
to := r.FormValue("to")
resp, err := redis.RecordsFilterByTime(from, to)
if err != nil {
a.respondWithError(w, err)
return
}
a.respondWithSuccess(w, resp)
}
+10 -1
View File
@@ -153,6 +153,13 @@ func configureClient(config *rest.Config) {
&metav1.ListOptions{}, &metav1.ListOptions{},
&metav1.DeleteOptions{}, &metav1.DeleteOptions{},
) )
scheme.AddKnownTypes(
groupversion,
&Recorder{},
&RecorderList{},
&metav1.ListOptions{},
&metav1.DeleteOptions{},
)
return nil return nil
}) })
schemeBuilder.AddToScheme(scheme.Scheme) schemeBuilder.AddToScheme(scheme.Scheme)
@@ -208,10 +215,12 @@ func (fc *FissionClient) TimeTriggers(ns string) TimeTriggerInterface {
func (fc *FissionClient) MessageQueueTriggers(ns string) MessageQueueTriggerInterface { func (fc *FissionClient) MessageQueueTriggers(ns string) MessageQueueTriggerInterface {
return MakeMessageQueueTriggerInterface(fc.crdClient, ns) return MakeMessageQueueTriggerInterface(fc.crdClient, ns)
} }
func (fc *FissionClient) Recorders(ns string) RecorderInterface {
return MakeRecorderInterface(fc.crdClient, ns)
}
func (fc *FissionClient) Packages(ns string) PackageInterface { func (fc *FissionClient) Packages(ns string) PackageInterface {
return MakePackageInterface(fc.crdClient, ns) return MakePackageInterface(fc.crdClient, ns)
} }
func (fc *FissionClient) WaitForCRDs() error { func (fc *FissionClient) WaitForCRDs() error {
return waitForCRDs(fc.crdClient) return waitForCRDs(fc.crdClient)
} }
+16
View File
@@ -160,6 +160,22 @@ func EnsureFissionCRDs(clientset *apiextensionsclient.Clientset) error {
}, },
}, },
}, },
// Recorders
{
ObjectMeta: metav1.ObjectMeta{
Name: "recorders.fission.io",
},
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
Group: crdGroupName,
Version: crdVersion,
Scope: apiextensionsv1beta1.NamespaceScoped,
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
Kind: "Recorder",
Plural: "recorders",
Singular: "recorder",
},
},
},
// Packages: archives containing source or binaries for one or more functions // Packages: archives containing source or binaries for one or more functions
{ {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
+121
View File
@@ -0,0 +1,121 @@
/*
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 crd
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
)
type (
RecorderInterface interface {
Create(*Recorder) (*Recorder, error)
Get(name string) (*Recorder, error)
Update(*Recorder) (*Recorder, error)
Delete(name string, opts *metav1.DeleteOptions) error
List(opts metav1.ListOptions) (*RecorderList, error)
Watch(opts metav1.ListOptions) (watch.Interface, error)
}
recorderClient struct {
client *rest.RESTClient
namespace string
}
)
func MakeRecorderInterface(crdClient *rest.RESTClient, namespace string) RecorderInterface {
return &recorderClient{
client: crdClient,
namespace: namespace,
}
}
func (rc *recorderClient) Create(r *Recorder) (*Recorder, error) {
var result Recorder
err := rc.client.Post().
Resource("recorders").
Namespace("default").
Body(r).
Do().Into(&result)
if err != nil {
return nil, err
}
return &result, nil
}
func (rc *recorderClient) Get(name string) (*Recorder, error) {
var result Recorder
err := rc.client.Get().
Resource("recorders").
Namespace(rc.namespace).
Name(name).
Do().Into(&result)
if err != nil {
return nil, err
}
return &result, nil
}
func (rc *recorderClient) Update(r *Recorder) (*Recorder, error) {
var result Recorder
err := rc.client.Put().
Resource("recorders").
Namespace(rc.namespace).
Name(r.Metadata.Name).
Body(r).
Do().Into(&result)
if err != nil {
return nil, err
}
return &result, nil
}
func (rc *recorderClient) Delete(name string, opts *metav1.DeleteOptions) error {
return rc.client.Delete().
Namespace(rc.namespace).
Resource("recorders").
Name(name).
Body(opts).
Do().
Error()
}
func (rc *recorderClient) List(opts metav1.ListOptions) (*RecorderList, error) {
var result RecorderList
err := rc.client.Get().
Namespace(rc.namespace).
Resource("recorders").
VersionedParams(&opts, scheme.ParameterCodec).
Do().
Into(&result)
if err != nil {
return nil, err
}
return &result, nil
}
func (rc *recorderClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
return rc.client.Get().
Prefix("watch").
Namespace(rc.namespace).
Resource("recorders").
VersionedParams(&opts, scheme.ParameterCodec).
Watch()
}
+2
View File
@@ -35,4 +35,6 @@ type (
TimeTriggerList = fv1.TimeTriggerList TimeTriggerList = fv1.TimeTriggerList
MessageQueueTrigger = fv1.MessageQueueTrigger MessageQueueTrigger = fv1.MessageQueueTrigger
MessageQueueTriggerList = fv1.MessageQueueTriggerList MessageQueueTriggerList = fv1.MessageQueueTriggerList
Recorder = fv1.Recorder
RecorderList = fv1.RecorderList
) )
+30
View File
@@ -103,6 +103,7 @@ func main() {
envNamespaceFlag := cli.StringFlag{Name: "envNamespace, envns", Value: metav1.NamespaceDefault, Usage: "Namespace for environment object"} envNamespaceFlag := cli.StringFlag{Name: "envNamespace, envns", Value: metav1.NamespaceDefault, Usage: "Namespace for environment object"}
pkgNamespaceFlag := cli.StringFlag{Name: "pkgNamespace, pkgns", Value: metav1.NamespaceDefault, Usage: "Namespace for package object"} pkgNamespaceFlag := cli.StringFlag{Name: "pkgNamespace, pkgns", Value: metav1.NamespaceDefault, Usage: "Namespace for package object"}
triggerNamespaceFlag := cli.StringFlag{Name: "triggerNamespace, triggerns", Value: metav1.NamespaceDefault, Usage: "Namespace for trigger object"} triggerNamespaceFlag := cli.StringFlag{Name: "triggerNamespace, triggerns", Value: metav1.NamespaceDefault, Usage: "Namespace for trigger object"}
recorderNamespaceFlag := cli.StringFlag{Name: "recorderNamespace, recorderns", Value: metav1.NamespaceDefault, Usage: "Namespace for recorder object"}
// trigger method and url flags (used in function and route CLIs) // trigger method and url flags (used in function and route CLIs)
htMethodFlag := cli.StringFlag{Name: "method", Value: "GET", Usage: "HTTP Method: GET|POST|PUT|DELETE|HEAD"} htMethodFlag := cli.StringFlag{Name: "method", Value: "GET", Usage: "HTTP Method: GET|POST|PUT|DELETE|HEAD"}
@@ -197,6 +198,33 @@ func main() {
{Name: "list", Usage: "List message queue triggers", Flags: []cli.Flag{mqtMQTypeFlag, triggerNamespaceFlag}, Action: mqtList}, {Name: "list", Usage: "List message queue triggers", Flags: []cli.Flag{mqtMQTypeFlag, triggerNamespaceFlag}, Action: mqtList},
} }
// Recorders
recNameFlag := cli.StringFlag{Name: "name", Usage: "Recorder name"}
recFnFlag := cli.StringFlag{Name: "function", Usage: "Record Function name(s): --function=fnA"}
recTriggersFlag := cli.StringSliceFlag{Name: "trigger", Usage: "Record Trigger name(s): --trigger=trigger1,trigger2,trigger3"}
//recRetentionPolFlag := cli.StringFlag{Name: "retention", Usage: "Retention policy (number of days)"}
//recEvictionPolFlag := cli.StringFlag{Name: "eviction", Usage: "Eviction policy (default LRU)"}
recEnabled := cli.BoolFlag{Name: "enable", Usage: "Enable recorder"}
recDisabled := cli.BoolFlag{Name: "disable", Usage: "Disable recorder"}
recSubcommands := []cli.Command{
{Name: "create", Aliases: []string{"add"}, Usage: "Create recorder", Flags: []cli.Flag{recNameFlag, recFnFlag, recTriggersFlag, specSaveFlag}, Action: recorderCreate},
{Name: "get", Usage: "Get recorder", Flags: []cli.Flag{recNameFlag}, Action: recorderGet},
{Name: "update", Usage: "Update recorder", Flags: []cli.Flag{recNameFlag, recFnFlag, recTriggersFlag, recEnabled, recDisabled}, Action: recorderUpdate},
{Name: "delete", Usage: "Delete recorder", Flags: []cli.Flag{recNameFlag, recorderNamespaceFlag}, Action: recorderDelete},
{Name: "list", Usage: "List recorders", Flags: []cli.Flag{}, Action: recorderList},
}
// View records
filterTimeFrom := cli.StringFlag{Name: "from", Usage: "Filter records by time interval; specify start of interval"}
filterTimeTo := cli.StringFlag{Name: "to", Usage: "Filter records by time interval; specify end of interval"}
filterFunction := cli.StringFlag{Name: "function", Usage: "Filter records by function"}
filterTrigger := cli.StringFlag{Name: "trigger", Usage: "Filter records by trigger"}
verbosityFlag := cli.BoolFlag{Name: "v", Usage: "Toggle verbosity -- view more detailed requests/responses"}
vvFlag := cli.BoolFlag{Name: "vv", Usage: "Toggle verbosity -- view raw requests/responses"}
recViewSubcommands := []cli.Command{
{Name: "view", Usage: "View existing records", Flags: []cli.Flag{filterTimeTo, filterTimeFrom, filterFunction, filterTrigger, verbosityFlag, vvFlag}, Action: recordsView},
}
// environments // environments
envNameFlag := cli.StringFlag{Name: "name", Usage: "Environment name"} envNameFlag := cli.StringFlag{Name: "name", Usage: "Environment name"}
envPoolsizeFlag := cli.IntFlag{Name: "poolsize", Value: 3, Usage: "Size of the pool"} envPoolsizeFlag := cli.IntFlag{Name: "poolsize", Value: 3, Usage: "Size of the pool"}
@@ -275,6 +303,8 @@ func main() {
{Name: "httptrigger", Aliases: []string{"ht", "route"}, Usage: "Manage HTTP triggers (routes) for functions", Subcommands: htSubcommands}, {Name: "httptrigger", Aliases: []string{"ht", "route"}, Usage: "Manage HTTP triggers (routes) for functions", Subcommands: htSubcommands},
{Name: "timetrigger", Aliases: []string{"tt", "timer"}, Usage: "Manage Time triggers (timers) for functions", Subcommands: ttSubcommands}, {Name: "timetrigger", Aliases: []string{"tt", "timer"}, Usage: "Manage Time triggers (timers) for functions", Subcommands: ttSubcommands},
{Name: "mqtrigger", Aliases: []string{"mqt", "messagequeue"}, Usage: "Manage message queue triggers for functions", Subcommands: mqtSubcommands}, {Name: "mqtrigger", Aliases: []string{"mqt", "messagequeue"}, Usage: "Manage message queue triggers for functions", Subcommands: mqtSubcommands},
{Name: "recorder", Usage: "Manage recorders for functions", Subcommands: recSubcommands, Hidden: true},
{Name: "records", Usage: "View records with optional filters", Subcommands: recViewSubcommands, Hidden: true},
{Name: "environment", Aliases: []string{"env"}, Usage: "Manage environments", Subcommands: envSubcommands}, {Name: "environment", Aliases: []string{"env"}, Usage: "Manage environments", Subcommands: envSubcommands},
{Name: "watch", Aliases: []string{"w"}, Usage: "Manage watches", Subcommands: wSubCommands}, {Name: "watch", Aliases: []string{"w"}, Usage: "Manage watches", Subcommands: wSubCommands},
{Name: "package", Aliases: []string{"pkg"}, Usage: "Manage packages", Subcommands: pkgSubCommands}, {Name: "package", Aliases: []string{"pkg"}, Usage: "Manage packages", Subcommands: pkgSubCommands},
+238
View File
@@ -0,0 +1,238 @@
/*
Copyright 2018 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
tttp://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 main
import (
"fmt"
"log"
"os"
"strings"
"text/tabwriter"
"github.com/satori/go.uuid"
"github.com/urfave/cli"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fission/fission"
"github.com/fission/fission/crd"
)
func recorderCreate(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
recName := c.String("name")
if len(recName) == 0 {
recName = uuid.NewV4().String()
}
fnName := c.String("function")
triggersOriginal := c.StringSlice("trigger")
// Function XOR triggers can be given
if len(fnName) == 0 && len(triggersOriginal) == 0 {
log.Fatal("Need to specify at least one function or one trigger, use --function, --trigger")
}
if len(fnName) != 0 && len(triggersOriginal) != 0 {
log.Fatal("Can specify either one function or one or more triggers, but not both")
}
// TODO: Validate here or elsewhere that all triggers belong to the same namespace
var triggers []string
if len(triggersOriginal) != 0 {
ts := strings.Split(triggersOriginal[0], ",")
for _, name := range ts {
triggers = append(triggers, name)
}
}
// TODO: Define appropriate set of policies and defaults
//retPolicy := c.String("retention")
//evictPolicy := c.String("eviction")
recorder := &crd.Recorder{
Metadata: metav1.ObjectMeta{
Name: recName,
Namespace: "default",
},
Spec: fission.RecorderSpec{
Name: recName,
Function: fnName,
Triggers: triggers,
RetentionPolicy: "Permanent", // TODO: Implement customizable policies for expiration of records
EvictionPolicy: "None",
Enabled: true,
},
}
// If we're writing a spec, don't call the API
if c.Bool("spec") {
specFile := fmt.Sprintf("recorder-%v.yaml", recName)
err := specSave(*recorder, specFile)
checkErr(err, "create recorder spec")
return nil
}
_, err := client.RecorderCreate(recorder)
checkErr(err, "create recorder")
fmt.Printf("recorder '%s' created\n", recName)
return err
}
func recorderGet(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
recName := c.String("name")
recorder, err := client.RecorderGet(&metav1.ObjectMeta{
Name: recName,
Namespace: "default",
})
checkErr(err, "get recorder")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\n",
"NAME", "ENABLED", "FUNCTION", "TRIGGERS", "RETENTION_POLICY", "EVICTION_POLICY")
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\n",
recorder.Metadata.Name, recorder.Spec.Enabled, recorder.Spec.Function, recorder.Spec.Triggers, recorder.Spec.RetentionPolicy, recorder.Spec.EvictionPolicy)
w.Flush()
return nil
}
func recorderUpdate(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
recName := c.String("name")
enable := c.Bool("enable")
disable := c.Bool("disable")
//retPolicy := c.String("retention")
//evictPolicy := c.String("eviction")
triggers := c.StringSlice("trigger")
function := c.String("function")
if enable && disable {
log.Fatal("Cannot enable and disable a recorder simultaneously.")
}
// Prevent enable or disable while trying to update other fields. These flags must be standalone.
if enable || disable {
if len(triggers) > 0 || len(function) > 0 {
log.Fatal("Enabling or disabling a recorder with other (non-name) flags set is not supported.")
}
} else if len(triggers) == 0 && len(function) == 0 {
log.Fatal("Need to specify either a function or trigger(s) for this recorder")
}
if len(recName) == 0 {
log.Fatal("Need name of recorder, use --name")
}
recorder, err := client.RecorderGet(&metav1.ObjectMeta{
Name: recName,
Namespace: "default",
})
updated := false
// TODO: Additional validation on type of supported retention policy, eviction policy
//if len(retPolicy) > 0 {
// recorder.Spec.RetentionPolicy = retPolicy
// updated = true
//}
//if len(evictPolicy) > 0 {
// recorder.Spec.EvictionPolicy = evictPolicy
// updated = true
//}
if enable {
recorder.Spec.Enabled = true
updated = true
}
if disable {
recorder.Spec.Enabled = false
updated = true
}
if len(triggers) > 0 {
var newTriggers []string
triggs := strings.Split(triggers[0], ",")
for _, name := range triggs {
newTriggers = append(newTriggers, name)
}
recorder.Spec.Triggers = newTriggers
updated = true
}
if len(function) > 0 {
recorder.Spec.Function = function
updated = true
}
if !updated {
log.Fatal("Nothing to update. Use --function, --triggers, --enable or --disable")
}
_, err = client.RecorderUpdate(recorder)
checkErr(err, "update recorder")
fmt.Printf("recorder '%v' updated\n", recName)
return nil
}
func recorderDelete(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
recName := c.String("name")
if len(recName) == 0 {
log.Fatal("Need name of recorder to delete, use --name")
}
recNs := c.String("recorderns")
err := client.RecorderDelete(&metav1.ObjectMeta{
Name: recName,
Namespace: recNs,
})
checkErr(err, "delete recorder")
fmt.Printf("recorder '%v' deleted\n", recName)
return nil
}
func recorderList(c *cli.Context) error {
client := getClient(c.GlobalString("server"))
recorders, err := client.RecorderList("default")
checkErr(err, "list recorders")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\n",
"NAME", "ENABLED", "FUNCTIONS", "TRIGGERS", "RETENTION_POLICY", "EVICTION_POLICY")
for _, r := range recorders {
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\n",
r.Metadata.Name, r.Spec.Enabled, r.Spec.Function, r.Spec.Triggers, r.Spec.RetentionPolicy, r.Spec.EvictionPolicy)
}
w.Flush()
return nil
}
+144
View File
@@ -0,0 +1,144 @@
/*
Copyright 2018 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
tttp://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 main
import (
"fmt"
"log"
"os"
"text/tabwriter"
"github.com/urfave/cli"
"github.com/fission/fission/redis/build/gen"
)
func recordsView(c *cli.Context) error {
var verbosity int
if c.Bool("v") && c.Bool("vv") {
log.Fatal("Conflicting verbosity levels, use either --v or --vv")
}
if c.Bool("v") {
verbosity = 1
}
if c.Bool("vv") {
verbosity = 2
}
function := c.String("function")
trigger := c.String("trigger")
from := c.String("from")
to := c.String("to")
//Refuse multiple filters for now
if multipleFiltersSpecified(function, trigger, from+to) {
log.Fatal("Maximum of one filter is currently supported, either --function, --trigger, or --from,--to")
}
if len(function) != 0 {
return recordsByFunction(function, verbosity, c)
}
if len(trigger) != 0 {
return recordsByTrigger(trigger, verbosity, c)
}
if len(from) != 0 && len(to) != 0 {
return recordsByTime(from, to, verbosity, c)
}
err := recordsAll(verbosity, c)
checkErr(err, "view records")
return nil
}
func recordsAll(verbosity int, c *cli.Context) error {
fc := getClient(c.GlobalString("server"))
records, err := fc.RecordsAll()
checkErr(err, "view records")
showRecords(records, verbosity)
return nil
}
func recordsByTrigger(trigger string, verbosity int, c *cli.Context) error {
fc := getClient(c.GlobalString("server"))
records, err := fc.RecordsByTrigger(trigger)
checkErr(err, "view records")
showRecords(records, verbosity)
return nil
}
// TODO: More accurate function name (function filter)
func recordsByFunction(function string, verbosity int, c *cli.Context) error {
fc := getClient(c.GlobalString("server"))
records, err := fc.RecordsByFunction(function)
checkErr(err, "view records")
showRecords(records, verbosity)
return nil
}
func recordsByTime(from string, to string, verbosity int, c *cli.Context) error {
fc := getClient(c.GlobalString("server"))
records, err := fc.RecordsByTime(from, to)
checkErr(err, "view records")
showRecords(records, verbosity)
return nil
}
func showRecords(records []*redisCache.RecordedEntry, verbosity int) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
if verbosity == 1 {
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\n",
"REQUID", "REQUEST METHOD", "FUNCTION", "RESPONSE STATUS", "TRIGGER")
for _, record := range records {
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\n",
record.ReqUID, record.Req.Method, record.Req.Header["X-Fission-Function-Name"], record.Resp.Status, record.Trigger)
}
} else if verbosity == 2 {
for _, record := range records {
fmt.Println(record)
}
} else {
fmt.Fprintf(w, "%v\n",
"REQUID")
for _, record := range records {
fmt.Fprintf(w, "%v\n",
record.ReqUID)
}
}
w.Flush()
}
func multipleFiltersSpecified(entries ...string) bool {
var specified int
for _, entry := range entries {
if len(entry) > 0 {
specified += 1
}
}
return specified > 1
}
+4
View File
@@ -1723,6 +1723,10 @@ func specSave(resource interface{}, specFile string) error {
typedres.TypeMeta.APIVersion = SPEC_API_VERSION typedres.TypeMeta.APIVersion = SPEC_API_VERSION
typedres.TypeMeta.Kind = "TimeTrigger" typedres.TypeMeta.Kind = "TimeTrigger"
data, err = yaml.Marshal(typedres) data, err = yaml.Marshal(typedres)
case crd.Recorder:
typedres.TypeMeta.APIVersion = SPEC_API_VERSION
typedres.TypeMeta.Kind = "Recorder"
data, err = yaml.Marshal(typedres)
default: default:
return fmt.Errorf("can't save resource %#v", resource) return fmt.Errorf("can't save resource %#v", resource)
} }
Generated
+41 -54
View File
@@ -1,5 +1,5 @@
hash: 1f595f5cdf8dc629d9046f82869cb77811dac21ce5f58e4f81e89d21181f6c34 hash: b26f975bf145378e939db833fa4b6a142fa04b884463d8173622b30f95191d42
updated: 2018-05-24T22:39:34.860206+08:00 updated: 2018-08-13T13:12:57.58715-07:00
imports: imports:
- name: cloud.google.com/go - name: cloud.google.com/go
version: 3b1ae45394a234c385be014e9a488f2bb6eef821 version: 3b1ae45394a234c385be014e9a488f2bb6eef821
@@ -22,7 +22,7 @@ imports:
subpackages: subpackages:
- quantile - quantile
- name: github.com/coreos/etcd - name: github.com/coreos/etcd
version: 6a265731e10a5137b991c1aa3a83ecefdd149d50 version: f87b566248bb0713a56dc55bc545aa5aad17ace0
subpackages: subpackages:
- client - client
- name: github.com/davecgh/go-spew - name: github.com/davecgh/go-spew
@@ -47,8 +47,10 @@ imports:
- internal - internal
- internal/errors - internal/errors
- internal/prefix - internal/prefix
- name: github.com/dustin/go-humanize
version: 9f541cc9db5d55bce703bd99987c9d5cb8eea45e
- name: github.com/fsnotify/fsnotify - name: github.com/fsnotify/fsnotify
version: 4da3e2cfbabc9f751898f250b49f2439785783a1 version: c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9
- name: github.com/ghodss/yaml - name: github.com/ghodss/yaml
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
- name: github.com/gogo/protobuf - name: github.com/gogo/protobuf
@@ -58,15 +60,10 @@ imports:
- proto - proto
- protoc-gen-gogo/descriptor - protoc-gen-gogo/descriptor
- sortkeys - sortkeys
- name: github.com/golang/freetype
version: e2365dfdc4a05e4b8299a783240d4a7d5a65d4e4
subpackages:
- raster
- truetype
- name: github.com/golang/glog - name: github.com/golang/glog
version: 44145f04b68cf362d9c4df2182967c2275eaefed version: 44145f04b68cf362d9c4df2182967c2275eaefed
- name: github.com/golang/protobuf - name: github.com/golang/protobuf
version: 1643683e1b54a9e88ad26d98f81400c8c9d9f4f9 version: b4deda0973fb4c70b50d226b1af49f3da59f5265
subpackages: subpackages:
- proto - proto
- ptypes - ptypes
@@ -74,7 +71,12 @@ imports:
- ptypes/duration - ptypes/duration
- ptypes/timestamp - ptypes/timestamp
- name: github.com/golang/snappy - name: github.com/golang/snappy
version: 553a641470496b2327abcac10b36396bd98e45c9 version: 2e65f85255dbc3072edf28d6b5b8efc472979f5a
- name: github.com/gomodule/redigo
version: 2cd21d9966bf7ff9ae091419744f0b3fb0fecace
subpackages:
- internal
- redis
- name: github.com/google/gofuzz - name: github.com/google/gofuzz
version: 44d81051d367757e1c7c6a5a86423ece9afcf63c version: 44d81051d367757e1c7c6a5a86423ece9afcf63c
- name: github.com/googleapis/gnostic - name: github.com/googleapis/gnostic
@@ -95,17 +97,17 @@ imports:
- name: github.com/gorilla/context - name: github.com/gorilla/context
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
- name: github.com/gorilla/handlers - name: github.com/gorilla/handlers
version: 90663712d74cb411cbef281bc1e08c19d1a76145 version: 7e0847f9db758cdebd26c149d0ae9d5d0b9c98ce
- name: github.com/gorilla/mux - name: github.com/gorilla/mux
version: e3702bed27f0d39777b0b37b664b6280e8ef8fbf version: e3702bed27f0d39777b0b37b664b6280e8ef8fbf
- name: github.com/graymeta/stow - name: github.com/graymeta/stow
version: abb68c488872b06c5453865fa59f4818b4ea13a4 version: 7b5498c561bbfeb17e413eb96c9edaf88af3855f
subpackages: subpackages:
- local - local
- name: github.com/hashicorp/errwrap - name: github.com/hashicorp/errwrap
version: 7554cd9344cec97297fa6649b055a8c98c2a1e55 version: d6c0cd88035724dd42e0f335ae30161c20575ecc
- name: github.com/hashicorp/go-multierror - name: github.com/hashicorp/go-multierror
version: b7773ae218740a7be65057fc60b366a49b538a44 version: 3d5d8f294aa03d8e98859feac328afbdf1ae0703
- name: github.com/hashicorp/golang-lru - name: github.com/hashicorp/golang-lru
version: a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4 version: a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4
subpackages: subpackages:
@@ -113,7 +115,7 @@ imports:
- name: github.com/howeyc/gopass - name: github.com/howeyc/gopass
version: bf9dde6d0d2c004a008c27aaee91170c786f6db8 version: bf9dde6d0d2c004a008c27aaee91170c786f6db8
- name: github.com/imdario/mergo - name: github.com/imdario/mergo
version: 9d5f1277e9a8ed20c3684bda8fde67c05628518c version: 33882c6bfe701aca0ff1472aa8b4ebd6135a560d
- name: github.com/influxdata/influxdb - name: github.com/influxdata/influxdb
version: b7bb7e8359642b6e071735b50ae41f5eb343fd42 version: b7bb7e8359642b6e071735b50ae41f5eb343fd42
subpackages: subpackages:
@@ -121,7 +123,7 @@ imports:
- models - models
- pkg/escape - pkg/escape
- name: github.com/json-iterator/go - name: github.com/json-iterator/go
version: 13f86432b882000a51c6e610c620974462691a97 version: f2b4162afba35581b6d4a50d3b8f34e33c144682
- name: github.com/marstr/guid - name: github.com/marstr/guid
version: 8bdf7d1a087ccc975cf37dd6507da50698fd19ca version: 8bdf7d1a087ccc975cf37dd6507da50698fd19ca
- name: github.com/matttproud/golang_protobuf_extensions - name: github.com/matttproud/golang_protobuf_extensions
@@ -129,33 +131,35 @@ imports:
subpackages: subpackages:
- pbutil - pbutil
- name: github.com/mholt/archiver - name: github.com/mholt/archiver
version: 26cf5bb32d07aa4e8d0de15f56ce516f4641d7df version: e4ef56d48eb029648b0e895bb0b6a393ef0829c3
- name: github.com/modern-go/concurrent
version: bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94
- name: github.com/modern-go/reflect2
version: 05fbef0ca5da472bbf96c9322b84a53edc03c9fd
- name: github.com/nats-io/go-nats - name: github.com/nats-io/go-nats
version: 2485387d6ede89c1c8c3445cd1935fd41a2e9ee9 version: 2485387d6ede89c1c8c3445cd1935fd41a2e9ee9
subpackages: subpackages:
- encoders/builtin - encoders/builtin
- util - util
- name: github.com/nats-io/go-nats-streaming - name: github.com/nats-io/go-nats-streaming
version: 6e620057a207bd61e992c1c5b6a2de7b6a4cb010 version: e15a53f85e4932540600a16b56f6c4f65f58176f
subpackages: subpackages:
- pb - pb
- name: github.com/nats-io/nats-streaming-server - name: github.com/nats-io/nats-streaming-server
version: 6026da1b7c444bf9f1d1b1d3ed14e435aabf02bc version: 63e2c334b66dba3edade0047625a25c0d8f18f80
subpackages: subpackages:
- spb - spb
- util - util
- name: github.com/nats-io/nuid - name: github.com/nats-io/nuid
version: 289cccf02c178dc782430d534e3c1f5b72af807f version: 3024a71c3cbe30667286099921591e6fcc328230
- name: github.com/nwaples/rardecode - name: github.com/nwaples/rardecode
version: e06696f847aeda6f39a8f0b7cdff193b7690aef6 version: e06696f847aeda6f39a8f0b7cdff193b7690aef6
- name: github.com/pierrec/lz4 - name: github.com/pierrec/lz4
version: ed8d4cc3b461464e69798080a0092bd028910298 version: 6b9367c9ff401dbc54fabce3fb8d972e799b702d
- name: github.com/pierrec/xxHash
version: a0006b13c722f7f12368c00a3d3c2ae8a999a0c6
subpackages: subpackages:
- xxHash32 - internal/xxh32
- name: github.com/pkg/errors - name: github.com/pkg/errors
version: f15c970de5b76fac0b59abb32d62c17cc7bed265 version: 816c9085562cd7ee03e7f8188a1cfd942858cded
- name: github.com/prometheus/client_golang - name: github.com/prometheus/client_golang
version: c5b7fccd204277076155f10851dad72b76a49317 version: c5b7fccd204277076155f10851dad72b76a49317
subpackages: subpackages:
@@ -176,46 +180,31 @@ imports:
subpackages: subpackages:
- xfs - xfs
- name: github.com/robfig/cron - name: github.com/robfig/cron
version: 2315d5715e36303a941d907f038da7f7c44c773b version: b41be1df696709bb6395fe435af20370037c0b4c
- name: github.com/satori/go.uuid - name: github.com/satori/go.uuid
version: f58768cc1a7a7e77a3bd49e98cdd21419399b6a3 version: f58768cc1a7a7e77a3bd49e98cdd21419399b6a3
- name: github.com/sirupsen/logrus - name: github.com/sirupsen/logrus
version: 68cec9f21fbf3ea8d8f98c044bc6ce05f17b267a version: 68cec9f21fbf3ea8d8f98c044bc6ce05f17b267a
- name: github.com/spf13/pflag - name: github.com/spf13/pflag
version: 4c012f6dcd9546820e378d0bdda4d8fc772cdfea version: 583c0c0531f06d5278b7d917446061adc344b5cd
- name: github.com/stretchr/testify - name: github.com/stretchr/testify
version: 12b6f73e6084dad08a7c6e575284b177ecafbc71 version: f35b8ab0b5a2cef36673838d662e249dd9c94686
subpackages: subpackages:
- assert - assert
- mock - mock
- require - require
- name: github.com/ulikunitz/xz - name: github.com/ulikunitz/xz
version: 0c6b41e72360850ca4f98dc341fd999726ea007f version: 636d36a76670e6c700f22fd5f4588679ff2896c4
subpackages: subpackages:
- internal/hash - internal/hash
- internal/xlog - internal/xlog
- lzma - lzma
- name: github.com/urfave/cli - name: github.com/urfave/cli
version: cfb38830724cc34fedffe9a2a29fb54fa9169cd1 version: cfb38830724cc34fedffe9a2a29fb54fa9169cd1
- name: github.com/wcharczuk/go-chart
version: 9e3a080aa3e7573281cf8d65a55305e1148d857d
subpackages:
- drawing
- matrix
- roboto
- seq
- util
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: 81e90905daefcd6fd217b62423c0908922eadb30 version: 81e90905daefcd6fd217b62423c0908922eadb30
subpackages: subpackages:
- ssh/terminal - ssh/terminal
- name: golang.org/x/image
version: f315e440302883054d0c2bd85486878cb4f8572c
subpackages:
- draw
- font
- math/f64
- math/fixed
- name: golang.org/x/net - name: golang.org/x/net
version: 1c05540f6879653db88113bc4a2b70aec4bd491f version: 1c05540f6879653db88113bc4a2b70aec4bd491f
subpackages: subpackages:
@@ -249,7 +238,7 @@ imports:
subpackages: subpackages:
- rate - rate
- name: golang.org/x/tools - name: golang.org/x/tools
version: 1937f90a1bb43667aff4059b1bab13eb15121e8e version: 87c7dcbd5db6be1a938380ce9944ed2299806701
subpackages: subpackages:
- imports - imports
- name: google.golang.org/appengine - name: google.golang.org/appengine
@@ -271,7 +260,7 @@ imports:
- name: gopkg.in/yaml.v2 - name: gopkg.in/yaml.v2
version: 670d4cfef0544295bc27a114dbac37980d83185a version: 670d4cfef0544295bc27a114dbac37980d83185a
- name: k8s.io/api - name: k8s.io/api
version: 590a9173e3b65d74e907fcfd94b78465cf314760 version: 0f11257a8a25954878633ebdc9841c67d8f83bdb
subpackages: subpackages:
- admissionregistration/v1alpha1 - admissionregistration/v1alpha1
- admissionregistration/v1beta1 - admissionregistration/v1beta1
@@ -302,7 +291,7 @@ imports:
- storage/v1alpha1 - storage/v1alpha1
- storage/v1beta1 - storage/v1beta1
- name: k8s.io/apiextensions-apiserver - name: k8s.io/apiextensions-apiserver
version: 7fbced0db3c3378efac13578ef9d75512c582b03 version: f584b16eb23bd2a3fd292a027d698d95db427c5d
subpackages: subpackages:
- pkg/apis/apiextensions - pkg/apis/apiextensions
- pkg/apis/apiextensions/v1beta1 - pkg/apis/apiextensions/v1beta1
@@ -310,7 +299,7 @@ imports:
- pkg/client/clientset/clientset/scheme - pkg/client/clientset/clientset/scheme
- pkg/client/clientset/clientset/typed/apiextensions/v1beta1 - pkg/client/clientset/clientset/typed/apiextensions/v1beta1
- name: k8s.io/apimachinery - name: k8s.io/apimachinery
version: 31dade610c053669d8054bfd847da657251e8c1a version: e386b2658ed20923da8cc9250e552f082899a1ee
subpackages: subpackages:
- pkg/api/errors - pkg/api/errors
- pkg/api/meta - pkg/api/meta
@@ -420,15 +409,13 @@ imports:
- util/jsonpath - util/jsonpath
- util/retry - util/retry
- name: k8s.io/code-generator - name: k8s.io/code-generator
version: d9b16e114e8c31761e26efcdee0a42cf066fbf58 version: caff7734d7c07b6211025f0898aa6168e5bc874d
- name: k8s.io/gengo - name: k8s.io/gengo
version: 01a732e01d00cb9a81bb0ca050d3e6d2b947927b version: c42f3cdacc394f43077ff17e327d1b351c0304e4
testImports: testImports:
- name: github.com/pmezard/go-difflib - name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages: subpackages:
- difflib - difflib
- name: github.com/stretchr/objx - name: github.com/stretchr/objx
version: 8a3f7159479fbc75b30357fbc48f380b7320f08e version: b8b73a35e9830ae509858c10dec5866b4d5c8bff
- name: github.com/dustin/go-humanize
version: 02af3965c54e8cacf948b97fef38925c4120652c
+3 -1
View File
@@ -62,9 +62,11 @@ import:
- package: github.com/davecgh/go-spew - package: github.com/davecgh/go-spew
version: ~1.1.0 version: ~1.1.0
- package: github.com/imdario/mergo - package: github.com/imdario/mergo
version: ~0.3.2 version: v0.3.3
- package: github.com/hashicorp/go-multierror - package: github.com/hashicorp/go-multierror
- package: github.com/hashicorp/errwrap - package: github.com/hashicorp/errwrap
- package: github.com/prometheus/client_golang - package: github.com/prometheus/client_golang
version: v0.8.0 version: v0.8.0
- package: github.com/dustin/go-humanize - package: github.com/dustin/go-humanize
- package: github.com/golang/protobuf/proto
version: v1.1.0
+11
View File
@@ -311,6 +311,17 @@ type (
ContentType string `json:"contentType"` ContentType string `json:"contentType"`
} }
// RecorderSpec defines a policy for recording requests and responses
// to a function, that can be later inspected or replayed.
RecorderSpec struct {
Name string `json:"name"`
Function string `json:"function"`
Triggers []string `json:"triggers"`
RetentionPolicy string `json:"retentionPolicy"`
EvictionPolicy string `json:"evictionPolicy"`
Enabled bool `json:"enabled"`
}
// TimeTrigger invokes the specific function at a time or // TimeTrigger invokes the specific function at a time or
// times specified by a cron string. // times specified by a cron string.
TimeTriggerSpec struct { TimeTriggerSpec struct {
+39
View File
@@ -152,6 +152,21 @@ type (
Items []MessageQueueTrigger `json:"items"` Items []MessageQueueTrigger `json:"items"`
} }
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
Recorder struct {
metav1.TypeMeta `json:",inline"`
Metadata metav1.ObjectMeta `json:"metadata"`
Spec RecorderSpec `json:"spec"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
RecorderList struct {
metav1.TypeMeta `json:",inline"`
Metadata metav1.ListMeta `json:"metadata"`
Items []Recorder `json:"items"`
}
) )
// Each CRD type needs: // Each CRD type needs:
@@ -185,6 +200,10 @@ func (p *Package) GetObjectKind() schema.ObjectKind {
return &p.TypeMeta return &p.TypeMeta
} }
func (r *Recorder) GetObjectKind() schema.ObjectKind {
return &r.TypeMeta
}
func (f *Function) GetObjectMeta() metav1.Object { func (f *Function) GetObjectMeta() metav1.Object {
return &f.Metadata return &f.Metadata
} }
@@ -207,6 +226,10 @@ func (p *Package) GetObjectMeta() metav1.Object {
return &p.Metadata return &p.Metadata
} }
func (r *Recorder) GetObjectMeta() metav1.Object {
return &r.Metadata
}
func (fl *FunctionList) GetObjectKind() schema.ObjectKind { func (fl *FunctionList) GetObjectKind() schema.ObjectKind {
return &fl.TypeMeta return &fl.TypeMeta
} }
@@ -228,6 +251,9 @@ func (ml *MessageQueueTriggerList) GetObjectKind() schema.ObjectKind {
func (pl *PackageList) GetObjectKind() schema.ObjectKind { func (pl *PackageList) GetObjectKind() schema.ObjectKind {
return &pl.TypeMeta return &pl.TypeMeta
} }
func (rl *RecorderList) GetObjectKind() schema.ObjectKind {
return &rl.TypeMeta
}
func (fl *FunctionList) GetListMeta() metav1.ListInterface { func (fl *FunctionList) GetListMeta() metav1.ListInterface {
return &fl.Metadata return &fl.Metadata
@@ -250,6 +276,9 @@ func (ml *MessageQueueTriggerList) GetListMeta() metav1.ListInterface {
func (pl *PackageList) GetListMeta() metav1.ListInterface { func (pl *PackageList) GetListMeta() metav1.ListInterface {
return &pl.Metadata return &pl.Metadata
} }
func (rl *RecorderList) GetListMeta() metav1.ListInterface {
return &rl.Metadata
}
func validateMetadata(field string, m metav1.ObjectMeta) error { func validateMetadata(field string, m metav1.ObjectMeta) error {
return ValidateKubeReference(field, m.Name, m.Namespace) return ValidateKubeReference(field, m.Name, m.Namespace)
@@ -382,3 +411,13 @@ func (ml *MessageQueueTriggerList) Validate() error {
} }
return result.ErrorOrNil() return result.ErrorOrNil()
} }
func (r *Recorder) Validate() error {
var result *multierror.Error
result = multierror.Append(result,
validateMetadata("Recorder", r.Metadata),
r.Spec.Validate())
return result.ErrorOrNil()
}
+27
View File
@@ -446,6 +446,33 @@ func (spec MessageQueueTriggerSpec) Validate() error {
return result.ErrorOrNil() return result.ErrorOrNil()
} }
func (spec RecorderSpec) Validate() error {
var result *multierror.Error
// TODO: Function validation
//if len(spec.Function.Name) != 0 {
// result = multierror.Append(result, spec.Function.Validate())
//}
// TODO: Triggers validation
//for _, trigger := range spec.Triggers {
// result = multierror.Append(result, trigger.Validate())
//}
if len(spec.Name) == 0 {
result = multierror.Append(result, MakeValidationErr(ErrorInvalidValue, "RecorderSpec.Name", spec.Name, "not a valid name"))
}
//if len(spec.RetentionPolicy) == 0 {
// result = multierror.Append(result, MakeValidationErr(ErrorInvalidValue, "RecorderSpec.RetentionPolicy", spec.Name, "not a valid retention policy"))
//}
//if len(spec.EvictionPolicy) == 0 {
// result = multierror.Append(result, MakeValidationErr(ErrorInvalidValue, "RecorderSpec.EvictionPolicy", spec.Name, "not a valid eviction policy"))
//}
//log.Info("This is the RecorderSpec validation result: %v", result)
return result.ErrorOrNil()
}
func (spec TimeTriggerSpec) Validate() error { func (spec TimeTriggerSpec) Validate() error {
var result *multierror.Error var result *multierror.Error
@@ -705,6 +705,87 @@ func (in *PackageStatus) DeepCopy() *PackageStatus {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Recorder) DeepCopyInto(out *Recorder) {
*out = *in
out.TypeMeta = in.TypeMeta
in.Metadata.DeepCopyInto(&out.Metadata)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Recorder.
func (in *Recorder) DeepCopy() *Recorder {
if in == nil {
return nil
}
out := new(Recorder)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Recorder) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RecorderList) DeepCopyInto(out *RecorderList) {
*out = *in
out.TypeMeta = in.TypeMeta
out.Metadata = in.Metadata
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Recorder, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RecorderList.
func (in *RecorderList) DeepCopy() *RecorderList {
if in == nil {
return nil
}
out := new(RecorderList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *RecorderList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RecorderSpec) DeepCopyInto(out *RecorderSpec) {
*out = *in
if in.Triggers != nil {
in, out := &in.Triggers, &out.Triggers
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RecorderSpec.
func (in *RecorderSpec) DeepCopy() *RecorderSpec {
if in == nil {
return nil
}
out := new(RecorderSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Runtime) DeepCopyInto(out *Runtime) { func (in *Runtime) DeepCopyInto(out *Runtime) {
*out = *in *out = *in
+5
View File
@@ -0,0 +1,5 @@
To compile protocol buffer definitions, you need to have installed the standard C++ implementation of protocol buffers and the Go compiler plugin, protoc-gen-go.
See [here](https://github.com/golang/protobuf) for installation instructions.
Run the following command within this directory:
`protoc --go_out=build/gen *.proto`
+305
View File
@@ -0,0 +1,305 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: spec.proto
package redisCache
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Request struct {
Method string `protobuf:"bytes,1,opt,name=Method,proto3" json:"Method,omitempty"`
URL map[string]string `protobuf:"bytes,2,rep,name=URL,proto3" json:"URL,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Header map[string]string `protobuf:"bytes,3,rep,name=Header,proto3" json:"Header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Host string `protobuf:"bytes,4,opt,name=Host,proto3" json:"Host,omitempty"`
Form map[string]string `protobuf:"bytes,5,rep,name=Form,proto3" json:"Form,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
PostForm map[string]string `protobuf:"bytes,6,rep,name=PostForm,proto3" json:"PostForm,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Request) Reset() { *m = Request{} }
func (m *Request) String() string { return proto.CompactTextString(m) }
func (*Request) ProtoMessage() {}
func (*Request) Descriptor() ([]byte, []int) {
return fileDescriptor_spec_ce9436f0047fa204, []int{0}
}
func (m *Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Request.Unmarshal(m, b)
}
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
}
func (dst *Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_Request.Merge(dst, src)
}
func (m *Request) XXX_Size() int {
return xxx_messageInfo_Request.Size(m)
}
func (m *Request) XXX_DiscardUnknown() {
xxx_messageInfo_Request.DiscardUnknown(m)
}
var xxx_messageInfo_Request proto.InternalMessageInfo
func (m *Request) GetMethod() string {
if m != nil {
return m.Method
}
return ""
}
func (m *Request) GetURL() map[string]string {
if m != nil {
return m.URL
}
return nil
}
func (m *Request) GetHeader() map[string]string {
if m != nil {
return m.Header
}
return nil
}
func (m *Request) GetHost() string {
if m != nil {
return m.Host
}
return ""
}
func (m *Request) GetForm() map[string]string {
if m != nil {
return m.Form
}
return nil
}
func (m *Request) GetPostForm() map[string]string {
if m != nil {
return m.PostForm
}
return nil
}
type Response struct {
Status string `protobuf:"bytes,7,opt,name=Status,proto3" json:"Status,omitempty"`
StatusCode int32 `protobuf:"varint,8,opt,name=StatusCode,proto3" json:"StatusCode,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) {
return fileDescriptor_spec_ce9436f0047fa204, []int{1}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
}
func (dst *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(dst, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func (m *Response) GetStatus() string {
if m != nil {
return m.Status
}
return ""
}
func (m *Response) GetStatusCode() int32 {
if m != nil {
return m.StatusCode
}
return 0
}
type UniqueRequest struct {
Req *Request `protobuf:"bytes,10,opt,name=Req,proto3" json:"Req,omitempty"`
Resp *Response `protobuf:"bytes,11,opt,name=Resp,proto3" json:"Resp,omitempty"`
Trigger string `protobuf:"bytes,12,opt,name=Trigger,proto3" json:"Trigger,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *UniqueRequest) Reset() { *m = UniqueRequest{} }
func (m *UniqueRequest) String() string { return proto.CompactTextString(m) }
func (*UniqueRequest) ProtoMessage() {}
func (*UniqueRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_spec_ce9436f0047fa204, []int{2}
}
func (m *UniqueRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_UniqueRequest.Unmarshal(m, b)
}
func (m *UniqueRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_UniqueRequest.Marshal(b, m, deterministic)
}
func (dst *UniqueRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_UniqueRequest.Merge(dst, src)
}
func (m *UniqueRequest) XXX_Size() int {
return xxx_messageInfo_UniqueRequest.Size(m)
}
func (m *UniqueRequest) XXX_DiscardUnknown() {
xxx_messageInfo_UniqueRequest.DiscardUnknown(m)
}
var xxx_messageInfo_UniqueRequest proto.InternalMessageInfo
func (m *UniqueRequest) GetReq() *Request {
if m != nil {
return m.Req
}
return nil
}
func (m *UniqueRequest) GetResp() *Response {
if m != nil {
return m.Resp
}
return nil
}
func (m *UniqueRequest) GetTrigger() string {
if m != nil {
return m.Trigger
}
return ""
}
// We need this because we don't store the ReqUID in the above Message (UniqueRequest)
// and we don't store the UID there because the key in Redis is the ReqUID -- including it in the value would be
// unnecessary duplication
// but we need it here because we want to display to the user the ReqResponses for a given ReqUID
type RecordedEntry struct {
ReqUID string `protobuf:"bytes,13,opt,name=ReqUID,proto3" json:"ReqUID,omitempty"`
Req *Request `protobuf:"bytes,14,opt,name=Req,proto3" json:"Req,omitempty"`
Resp *Response `protobuf:"bytes,15,opt,name=Resp,proto3" json:"Resp,omitempty"`
Trigger string `protobuf:"bytes,16,opt,name=Trigger,proto3" json:"Trigger,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RecordedEntry) Reset() { *m = RecordedEntry{} }
func (m *RecordedEntry) String() string { return proto.CompactTextString(m) }
func (*RecordedEntry) ProtoMessage() {}
func (*RecordedEntry) Descriptor() ([]byte, []int) {
return fileDescriptor_spec_ce9436f0047fa204, []int{3}
}
func (m *RecordedEntry) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RecordedEntry.Unmarshal(m, b)
}
func (m *RecordedEntry) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RecordedEntry.Marshal(b, m, deterministic)
}
func (dst *RecordedEntry) XXX_Merge(src proto.Message) {
xxx_messageInfo_RecordedEntry.Merge(dst, src)
}
func (m *RecordedEntry) XXX_Size() int {
return xxx_messageInfo_RecordedEntry.Size(m)
}
func (m *RecordedEntry) XXX_DiscardUnknown() {
xxx_messageInfo_RecordedEntry.DiscardUnknown(m)
}
var xxx_messageInfo_RecordedEntry proto.InternalMessageInfo
func (m *RecordedEntry) GetReqUID() string {
if m != nil {
return m.ReqUID
}
return ""
}
func (m *RecordedEntry) GetReq() *Request {
if m != nil {
return m.Req
}
return nil
}
func (m *RecordedEntry) GetResp() *Response {
if m != nil {
return m.Resp
}
return nil
}
func (m *RecordedEntry) GetTrigger() string {
if m != nil {
return m.Trigger
}
return ""
}
func init() {
proto.RegisterType((*Request)(nil), "redisCache.Request")
proto.RegisterMapType((map[string]string)(nil), "redisCache.Request.FormEntry")
proto.RegisterMapType((map[string]string)(nil), "redisCache.Request.HeaderEntry")
proto.RegisterMapType((map[string]string)(nil), "redisCache.Request.PostFormEntry")
proto.RegisterMapType((map[string]string)(nil), "redisCache.Request.URLEntry")
proto.RegisterType((*Response)(nil), "redisCache.Response")
proto.RegisterType((*UniqueRequest)(nil), "redisCache.UniqueRequest")
proto.RegisterType((*RecordedEntry)(nil), "redisCache.RecordedEntry")
}
func init() { proto.RegisterFile("spec.proto", fileDescriptor_spec_ce9436f0047fa204) }
var fileDescriptor_spec_ce9436f0047fa204 = []byte{
// 392 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x93, 0x5f, 0xab, 0xd3, 0x30,
0x18, 0xc6, 0xe9, 0xda, 0x75, 0xdd, 0x5b, 0xab, 0x23, 0x8e, 0x11, 0x86, 0x7f, 0x6a, 0x41, 0xe8,
0x55, 0xc1, 0x09, 0xce, 0x3f, 0x78, 0xe3, 0x54, 0x26, 0x4c, 0x90, 0x68, 0x3f, 0x40, 0x5d, 0x5f,
0xb6, 0xa1, 0x2e, 0x6b, 0x92, 0x0a, 0x3b, 0x1f, 0xe3, 0x7c, 0xdb, 0x73, 0x77, 0x68, 0xd2, 0xee,
0x6c, 0xd0, 0x8b, 0xd3, 0xbb, 0xf7, 0x4d, 0x9e, 0xdf, 0x9b, 0xe7, 0x49, 0x5a, 0x00, 0x79, 0xc0,
0x75, 0x72, 0x10, 0x5c, 0x71, 0x02, 0x02, 0xf3, 0x9d, 0x5c, 0x64, 0xeb, 0x2d, 0x46, 0x37, 0x36,
0x0c, 0x18, 0x16, 0x25, 0x4a, 0x45, 0x26, 0xe0, 0x7e, 0x47, 0xb5, 0xe5, 0x39, 0xb5, 0x42, 0x2b,
0x1e, 0xb2, 0xba, 0x23, 0x09, 0xd8, 0x29, 0x5b, 0xd1, 0x5e, 0x68, 0xc7, 0xfe, 0xec, 0x49, 0x72,
0x47, 0x27, 0x35, 0x99, 0xa4, 0x6c, 0xf5, 0x65, 0xaf, 0xc4, 0x91, 0x55, 0x42, 0x32, 0x07, 0x77,
0x89, 0x59, 0x8e, 0x82, 0xda, 0x1a, 0x79, 0xde, 0x86, 0x18, 0x85, 0xa1, 0x6a, 0x39, 0x21, 0xe0,
0x2c, 0xb9, 0x54, 0xd4, 0xd1, 0xc7, 0xeb, 0x9a, 0xbc, 0x02, 0xe7, 0x2b, 0x17, 0xff, 0x68, 0x5f,
0x8f, 0x7a, 0xda, 0x36, 0xaa, 0xda, 0x37, 0x83, 0xb4, 0x94, 0x7c, 0x04, 0xef, 0x07, 0x97, 0x4a,
0x63, 0xae, 0xc6, 0x5e, 0xb4, 0x61, 0x8d, 0xc6, 0xa0, 0x27, 0x64, 0xfa, 0x06, 0xbc, 0x26, 0x0f,
0x19, 0x81, 0xfd, 0x07, 0x8f, 0xf5, 0x7d, 0x54, 0x25, 0x19, 0x43, 0xff, 0x7f, 0xf6, 0xb7, 0x44,
0xda, 0xd3, 0x6b, 0xa6, 0x79, 0xdf, 0x7b, 0x6b, 0x4d, 0xdf, 0x81, 0x7f, 0x16, 0xaa, 0x13, 0x3a,
0x87, 0xe1, 0xc9, 0x49, 0x27, 0xf0, 0x03, 0x04, 0x17, 0x31, 0xba, 0xc0, 0xd1, 0x27, 0xf0, 0x18,
0xca, 0x03, 0xdf, 0x4b, 0xac, 0xde, 0xfe, 0xa7, 0xca, 0x54, 0x29, 0xe9, 0xc0, 0xbc, 0xbd, 0xe9,
0xc8, 0x33, 0x00, 0x53, 0x2d, 0x78, 0x8e, 0xd4, 0x0b, 0xad, 0xb8, 0xcf, 0xce, 0x56, 0xa2, 0x2b,
0x08, 0xd2, 0xfd, 0xae, 0x28, 0xb1, 0xf9, 0x88, 0x5e, 0x82, 0xcd, 0xb0, 0xa0, 0x10, 0x5a, 0xb1,
0x3f, 0x7b, 0xdc, 0x72, 0xef, 0xac, 0xda, 0x27, 0x31, 0x38, 0xd5, 0xd9, 0xd4, 0xd7, 0xba, 0xf1,
0xa5, 0xce, 0x78, 0x62, 0x5a, 0x41, 0x28, 0x0c, 0x7e, 0x89, 0xdd, 0x66, 0x83, 0x82, 0x3e, 0xd0,
0xd6, 0x9a, 0x36, 0xba, 0xb6, 0x20, 0x60, 0xb8, 0xe6, 0x22, 0xc7, 0xdc, 0xa4, 0x9f, 0x80, 0xcb,
0xb0, 0x48, 0xbf, 0x7d, 0xa6, 0x81, 0x49, 0x61, 0xba, 0xc6, 0xd4, 0xc3, 0x7b, 0x9a, 0x7a, 0xd4,
0xc5, 0xd4, 0xe8, 0xc2, 0xd4, 0x6f, 0x57, 0xff, 0x63, 0xaf, 0x6f, 0x03, 0x00, 0x00, 0xff, 0xff,
0x0f, 0xb0, 0x49, 0xaf, 0x71, 0x03, 0x00, 0x00,
}
+123
View File
@@ -0,0 +1,123 @@
/*
Copyright 2018 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 redis
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"github.com/golang/protobuf/proto"
"github.com/gomodule/redigo/redis"
log "github.com/sirupsen/logrus"
"github.com/fission/fission/redis/build/gen"
)
func NewClient() redis.Conn {
redisIP := os.Getenv("REDIS_SERVICE_HOST") // TODO: Do this here or somewhere earlier?
redisPort := os.Getenv("REDIS_SERVICE_PORT")
redisUrl := fmt.Sprintf("%s:%s", redisIP, redisPort)
if len(redisUrl) == 0 {
log.Error("Could not reach Redis in cluster at IP ", redisUrl)
return nil
}
c, err := redis.Dial("tcp", redisUrl)
if err != nil {
log.Error("Could not connect to Redis: %v\n", err)
return nil
}
return c
}
func Record(triggerName string, recorderName string, reqUID string, request *http.Request, originalUrl url.URL, payload string, response *http.Response, namespace string, timestamp int64) {
// Case where the function should not have been recorded
if len(reqUID) == 0 {
return
}
fullPath := originalUrl.String()
escPayload := string(json.RawMessage(payload))
client := NewClient()
if client == nil {
return
}
url := make(map[string]string)
url["Host"] = request.URL.Host
url["Path"] = fullPath
url["Payload"] = escPayload
header := make(map[string]string)
for key, value := range request.Header {
header[key] = strings.Join(value, ",")
}
form := make(map[string]string)
for key, value := range request.Form {
form[key] = strings.Join(value, ",")
}
postForm := make(map[string]string)
for key, value := range request.PostForm {
postForm[key] = strings.Join(value, ",")
}
req := &redisCache.Request{
Method: request.Method,
URL: url,
Header: header,
Host: request.Host, // Proxied host?
Form: form,
PostForm: postForm,
}
resp := &redisCache.Response{
Status: response.Status,
StatusCode: int32(response.StatusCode),
}
ureq := &redisCache.UniqueRequest{
Req: req,
Resp: resp,
Trigger: triggerName,
}
data, err := proto.Marshal(ureq)
if err != nil {
log.Error("Error marshalling request: ", err)
return
}
_, err = client.Do("HMSET", reqUID, "ReqResponse", data, "Timestamp", timestamp, "Trigger", triggerName)
if err != nil {
log.Error("Error saving request: ", err)
return
}
_, err = client.Do("LPUSH", recorderName, reqUID)
if err != nil {
log.Error("Error saving recorder-request pair: ", err)
return
}
}
+338
View File
@@ -0,0 +1,338 @@
/*
Copyright 2018 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 redis
import (
"encoding/json"
"errors"
"strconv"
"strings"
"time"
"github.com/golang/protobuf/proto"
"github.com/gomodule/redigo/redis"
log "github.com/sirupsen/logrus"
"github.com/fission/fission/crd"
"github.com/fission/fission/redis/build/gen"
)
func RecordsListAll() ([]byte, error) {
client := NewClient()
if client == nil {
return []byte{}, errors.New("failed to create redis client")
}
iter := 0
var filtered []*redisCache.RecordedEntry
for {
// Each scan yields only a subset of all keys which is why we keep an iter. When iter == 0,
// Redis tells us there are no keys left to traverse.
arr, err := redis.Values(client.Do("SCAN", iter))
if err != nil {
return []byte{}, err
}
// SCAN return value is an array of two values: the first value is the new cursor to use in the next call,
// the second value is an array of elements.
iter, _ = redis.Int(arr[0], nil)
keys, _ := redis.Strings(arr[1], nil)
for _, key := range keys {
if strings.HasPrefix(key, "REQ") {
val, err := redis.Bytes(client.Do("HGET", key, "ReqResponse"))
if err != nil {
log.Error("Error retrieving request from Redis: ", err)
return []byte{}, err
}
entry, err := deserializeReqResponse(val, key)
if err != nil {
log.Error("Error deserializing request: ", err)
return []byte{}, err
}
filtered = append(filtered, entry)
}
}
if iter == 0 {
break
}
}
resp, err := json.Marshal(filtered)
if err != nil {
return []byte{}, err
}
return resp, nil
}
// Input: `from` (hours ago, between 0 [today] and 5) and `to` (same units)
// Note: Fractional values don't seem to work -- document that for the user
func RecordsFilterByTime(from string, to string) ([]byte, error) {
rangeStart, rangeEnd, err := obtainInterval(from, to)
log.Debug("Interval inferred: ", rangeStart, rangeEnd)
if rangeStart >= rangeEnd {
log.Error("Invalid chronology")
return []byte{}, err
}
client := NewClient()
if client == nil {
return []byte{}, errors.New("failed to create redis client")
}
iter := 0
var filtered []*redisCache.RecordedEntry
for {
arr, err := redis.Values(client.Do("SCAN", iter))
if err != nil {
return []byte{}, err
}
// SCAN return value is an array of two values: the first value is the new cursor to use in the next call,
// the second value is an array of elements.
iter, _ = redis.Int(arr[0], nil)
keys, _ := redis.Strings(arr[1], nil)
for _, key := range keys {
if strings.HasPrefix(key, "REQ") {
val, err := redis.Strings(client.Do("HMGET", key, "Timestamp"))
if err != nil {
log.Error("Error retrieving timestamp from Redis: ", err)
return []byte{}, err
}
tsO, err := strconv.Atoi(val[0])
if err != nil {
log.Error("Error converting timestamp to int: ", err)
return []byte{}, err
}
ts := int64(tsO)
if ts >= rangeStart && ts <= rangeEnd {
val2, err := redis.Bytes(client.Do("HGET", key, "ReqResponse"))
if err != nil {
log.Error("Error retrieving request from Redis: ", err)
return []byte{}, err
}
entry, err := deserializeReqResponse(val2, key)
if err != nil {
log.Error("Error deserializing request: ", err)
return []byte{}, err
}
filtered = append(filtered, entry)
}
}
}
if iter == 0 {
break
}
}
resp, err := json.Marshal(filtered)
if err != nil {
return []byte{}, err
}
return resp, nil
}
func RecordsFilterByTrigger(queriedTriggerName string, recorders *crd.RecorderList, triggers *crd.HTTPTriggerList) ([]byte, error) {
matchingRecorders := make(map[string]bool)
// Implicit triggers:
// Sometimes triggers are not explicitly attached to recorders but we still want to be able to
// filter records by those triggers; we do so by identifying the functionReference the queriedTriggerName trigger has
// and finding recorder(s) for that function
var correspFunction string
for _, trigger := range triggers.Items {
if trigger.Metadata.Name == queriedTriggerName {
correspFunction = trigger.Spec.FunctionReference.Name
break
}
}
for _, recorder := range recorders.Items {
if len(recorder.Spec.Triggers) > 0 {
if includesTrigger(recorder.Spec.Triggers, queriedTriggerName) {
matchingRecorders[recorder.Spec.Name] = true
}
}
if recorder.Spec.Function == correspFunction {
matchingRecorders[recorder.Spec.Name] = true
}
}
client := NewClient()
if client == nil {
return []byte{}, errors.New("failed to create redis client")
}
var filtered []*redisCache.RecordedEntry
// TODO: Account for old/not-yet-deleted entries in the recorder lists
for key := range matchingRecorders {
val, err := redis.Strings(client.Do("LRANGE", key, "0", "-1")) // TODO: Prefix that distinguishes recorder lists
if err != nil {
// TODO: Handle deleted recorder? Or is this a non-issue because our list of recorders is up to date?
return []byte{}, err
}
for _, reqUID := range val {
val, err := redis.Strings(client.Do("HMGET", reqUID, "Trigger")) // 1-to-1 reqUID - trigger?
if err != nil {
log.Error("Error retrieving trigger for a request from Redis: ", err)
return []byte{}, err
}
if val[0] == queriedTriggerName {
// TODO: Reconsider multiple commands
val, err := redis.Bytes(client.Do("HGET", reqUID, "ReqResponse"))
if err != nil {
log.Error("Error retrieving request from Redis: ", err)
return []byte{}, err
}
entry, err := deserializeReqResponse(val, reqUID)
if err != nil {
log.Error("Error deserializing request: ", err)
return []byte{}, err
}
filtered = append(filtered, entry)
}
}
}
resp, err := json.Marshal(filtered)
if err != nil {
return []byte{}, err
}
return resp, nil
}
func RecordsFilterByFunction(queriedFunctionName string, recorders *crd.RecorderList, triggers *crd.HTTPTriggerList) ([]byte, error) {
// Implicit functions:
// Sometimes functions are not explicitly attached to recorders but we still want to be able to
// filter records by those functions; we do so by identifying all triggers recorders are associated with
// and checking functionReferences for those triggers.
triggerMap := make(map[string]crd.HTTPTrigger)
for _, trigger := range triggers.Items {
triggerMap[trigger.Metadata.Name] = trigger
}
matchingRecorders := make(map[string]bool)
for _, recorder := range recorders.Items {
if len(recorder.Spec.Function) > 0 && recorder.Spec.Function == queriedFunctionName {
matchingRecorders[recorder.Spec.Name] = true
} else {
for _, trigger := range recorder.Spec.Triggers {
validTrigger, ok := triggerMap[trigger]
if ok {
if validTrigger.Spec.FunctionReference.Name == queriedFunctionName {
matchingRecorders[recorder.Spec.Name] = true
}
}
}
}
}
client := NewClient()
if client == nil {
return []byte{}, errors.New("failed to create redis client")
}
var filtered []*redisCache.RecordedEntry
for key := range matchingRecorders {
val, err := redis.Strings(client.Do("LRANGE", key, "0", "-1")) // TODO: Prefix that distinguishes recorder lists
if err != nil {
return []byte{}, err
}
for _, reqUID := range val {
// TODO: Check if it still exists, else clean up this value from the cache
exists, err := redis.Int(client.Do("EXISTS", reqUID))
if err != nil {
continue
}
if exists > 0 {
val, err := redis.Bytes(client.Do("HGET", reqUID, "ReqResponse"))
if err != nil {
log.Error("Error retrieving request from Redis: ", err)
return []byte{}, err
}
entry, err := deserializeReqResponse(val, reqUID)
if err != nil {
log.Error("Error deserializing request: ", err)
return []byte{}, err
}
filtered = append(filtered, entry)
}
}
}
resp, err := json.Marshal(filtered)
if err != nil {
return []byte{}, err
}
return resp, nil
}
// TODO: Discuss this approach of using two different protobuf message formats
func deserializeReqResponse(value []byte, reqUID string) (*redisCache.RecordedEntry, error) {
data := &redisCache.UniqueRequest{}
err := proto.Unmarshal(value, data)
if err != nil {
log.Error("Error unmarshalling request: ", err)
return nil, err
}
log.Info("Parsed protobuf bytes: ", data)
transformed := &redisCache.RecordedEntry{
ReqUID: reqUID,
Req: data.Req,
Resp: data.Resp,
Trigger: data.Trigger,
}
return transformed, nil
}
func obtainInterval(from string, to string) (int64, int64, error) {
now := time.Now()
parsedFrom, err := time.ParseDuration(from)
if err != nil {
return -1, -1, err
}
parsedTo, err := time.ParseDuration(to)
if err != nil {
return -1, -1, err
}
then := now.Add(-1 * parsedFrom) // Start search interval
rangeStart := then.UnixNano()
until := now.Add(-1 * parsedTo) // End search interval
rangeEnd := until.UnixNano()
return rangeStart, rangeEnd, nil
}
func includesTrigger(triggers []string, query string) bool {
for _, trigger := range triggers {
if trigger == query {
return true
}
}
return false
}
+51
View File
@@ -0,0 +1,51 @@
/*
Copyright 2018 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.
*/
syntax = 'proto3';
package redisCache;
message Request {
string Method = 1;
map<string, string> URL = 2;
map<string, string> Header = 3;
string Host = 4;
map<string, string> Form = 5;
map<string, string> PostForm = 6;
}
message Response {
string Status = 7;
int32 StatusCode = 8;
}
message UniqueRequest {
Request Req = 10;
Response Resp = 11;
string Trigger = 12;
}
// We need this because we don't store the ReqUID in the above Message (UniqueRequest)
// and we don't store the UID there because the key in Redis is the ReqUID -- including it in the value would be
// unnecessary duplication
// but we need it here because we want to display to the user the ReqResponses for a given ReqUID
message RecordedEntry {
string ReqUID = 13;
Request Req = 14;
Response Resp = 15;
string Trigger = 16;
}
+59
View File
@@ -17,20 +17,26 @@ limitations under the License.
package router package router
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
"strings"
"time" "time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/satori/go.uuid"
"github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fission/fission" "github.com/fission/fission"
"github.com/fission/fission/crd" "github.com/fission/fission/crd"
executorClient "github.com/fission/fission/executor/client" executorClient "github.com/fission/fission/executor/client"
"github.com/fission/fission/redis"
) )
type tsRoundTripperParams struct { type tsRoundTripperParams struct {
@@ -42,10 +48,13 @@ type tsRoundTripperParams struct {
type functionHandler struct { type functionHandler struct {
fmap *functionServiceMap fmap *functionServiceMap
frmap *functionRecorderMap
trmap *triggerRecorderMap
executor *executorClient.Client executor *executorClient.Client
function *metav1.ObjectMeta function *metav1.ObjectMeta
httpTrigger *crd.HTTPTrigger httpTrigger *crd.HTTPTrigger
tsRoundTripperParams *tsRoundTripperParams tsRoundTripperParams *tsRoundTripperParams
recorderName string
} }
// A layer on top of http.DefaultTransport, with retries. // A layer on top of http.DefaultTransport, with retries.
@@ -83,6 +92,27 @@ func (roundTripper RetryingRoundTripper) RoundTrip(req *http.Request) (resp *htt
var needExecutor, serviceUrlFromExecutor bool var needExecutor, serviceUrlFromExecutor bool
var serviceUrl *url.URL var serviceUrl *url.URL
// TODO: Keep? --> Needed for queries encoded in URL before they're stripped by the proxy
var originalUrl url.URL
originalUrl = *req.URL
// Iff this request needs to be recorded, we save the body
var postedBody string
if len(roundTripper.funcHandler.recorderName) > 0 {
if req.ContentLength > 0 {
p := make([]byte, req.ContentLength)
buf, _ := ioutil.ReadAll(req.Body)
// We need two io readers because a single reader will drain the buffer, hence we keep a replacement copy
rdr1 := ioutil.NopCloser(bytes.NewBuffer(buf))
rdr2 := ioutil.NopCloser(bytes.NewBuffer(buf))
rdr1.Read(p)
postedBody = string(p)
logrus.Info(fmt.Sprintf("%v", postedBody))
req.Body = rdr2
}
}
// Metrics stuff // Metrics stuff
startTime := time.Now() startTime := time.Now()
funcMetricLabels := &functionLabels{ funcMetricLabels := &functionLabels{
@@ -177,6 +207,27 @@ func (roundTripper RetryingRoundTripper) RoundTrip(req *http.Request) (resp *htt
functionCallCompleted(funcMetricLabels, httpMetricLabels, functionCallCompleted(funcMetricLabels, httpMetricLabels,
overhead, time.Since(startTime), resp.ContentLength) overhead, time.Since(startTime), resp.ContentLength)
// if transport.RoundTrip succeeds and it was a cached entry, then tapService
if !serviceUrlFromExecutor {
go roundTripper.funcHandler.tapService(serviceUrl)
}
trigger := ""
if roundTripper.funcHandler.httpTrigger != nil {
trigger = roundTripper.funcHandler.httpTrigger.Metadata.Name
} else {
log.Println("No trigger attached.") // Wording?
}
if len(roundTripper.funcHandler.recorderName) > 0 {
redis.Record(
trigger,
roundTripper.funcHandler.recorderName,
req.Header.Get("X-Fission-ReqUID"), req, originalUrl, postedBody, resp, roundTripper.funcHandler.function.Namespace,
time.Now().UnixNano(),
)
}
// return response back to user // return response back to user
return resp, nil return resp, nil
} }
@@ -227,6 +278,14 @@ func (fh *functionHandler) handler(responseWriter http.ResponseWriter, request *
request.Header.Add(fmt.Sprintf("X-Fission-Params-%v", k), v) request.Header.Add(fmt.Sprintf("X-Fission-Params-%v", k), v)
} }
var reqUID string
if len(fh.recorderName) > 0 {
UID := strings.ToLower(uuid.NewV4().String())
reqUID = "REQ" + UID
request.Header.Add("X-Fission-ReqUID", reqUID)
log.Print("Record request with ReqUID: ", reqUID)
}
// system params // system params
MetadataToHeaders(HEADERS_FISSION_FUNCTION_PREFIX, fh.function, request) MetadataToHeaders(HEADERS_FISSION_FUNCTION_PREFIX, fh.function, request)
+63
View File
@@ -0,0 +1,63 @@
/*
Copyright 2018 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 router
import (
"log"
"time"
"github.com/fission/fission"
"github.com/fission/fission/cache"
"github.com/fission/fission/crd"
)
type (
functionRecorderMap struct {
cache *cache.Cache // map[string]*crd.Recorder
}
)
// Why do we need an expiry?
func makeFunctionRecorderMap(expiry time.Duration) *functionRecorderMap {
return &functionRecorderMap{
cache: cache.MakeCache(expiry, 0),
}
}
func (frmap *functionRecorderMap) lookup(function string) (*crd.Recorder, error) {
item, err := frmap.cache.Get(function)
if err != nil {
return nil, err
}
u := item.(*crd.Recorder)
return u, nil
}
func (frmap *functionRecorderMap) assign(function string, recorder *crd.Recorder) {
err, _ := frmap.cache.Set(function, recorder)
if err != nil {
if e, ok := err.(fission.Error); ok && e.Code == fission.ErrorNameExists {
return
}
log.Printf("error caching recorder for function name with a different value: %v", err)
}
}
func (frmap *functionRecorderMap) remove(function string) error {
return frmap.cache.Delete(function)
}
+69 -4
View File
@@ -49,12 +49,12 @@ type HTTPTriggerSet struct {
functions []crd.Function functions []crd.Function
funcStore k8sCache.Store funcStore k8sCache.Store
funcController k8sCache.Controller funcController k8sCache.Controller
recorderSet *RecorderSet
updateRouterRequestChannel chan struct{} updateRouterRequestChannel chan struct{}
tsRoundTripperParams *tsRoundTripperParams
tsRoundTripperParams *tsRoundTripperParams
} }
func makeHTTPTriggerSet(fmap *functionServiceMap, fissionClient *crd.FissionClient, func makeHTTPTriggerSet(fmap *functionServiceMap, frmap *functionRecorderMap, trmap *triggerRecorderMap, fissionClient *crd.FissionClient,
kubeClient *kubernetes.Clientset, executor *executorClient.Client, crdClient *rest.RESTClient, params *tsRoundTripperParams) (*HTTPTriggerSet, k8sCache.Store, k8sCache.Store) { kubeClient *kubernetes.Clientset, executor *executorClient.Client, crdClient *rest.RESTClient, params *tsRoundTripperParams) (*HTTPTriggerSet, k8sCache.Store, k8sCache.Store) {
httpTriggerSet := &HTTPTriggerSet{ httpTriggerSet := &HTTPTriggerSet{
functionServiceMap: fmap, functionServiceMap: fmap,
@@ -66,8 +66,9 @@ func makeHTTPTriggerSet(fmap *functionServiceMap, fissionClient *crd.FissionClie
updateRouterRequestChannel: make(chan struct{}), updateRouterRequestChannel: make(chan struct{}),
tsRoundTripperParams: params, tsRoundTripperParams: params,
} }
var tStore, fnStore k8sCache.Store var tStore, fnStore, rStore k8sCache.Store
var tController, fnController k8sCache.Controller var tController, fnController k8sCache.Controller
var recorderSet *RecorderSet
if httpTriggerSet.crdClient != nil { if httpTriggerSet.crdClient != nil {
tStore, tController = httpTriggerSet.initTriggerController() tStore, tController = httpTriggerSet.initTriggerController()
httpTriggerSet.triggerStore = tStore httpTriggerSet.triggerStore = tStore
@@ -76,6 +77,8 @@ func makeHTTPTriggerSet(fmap *functionServiceMap, fissionClient *crd.FissionClie
httpTriggerSet.funcStore = fnStore httpTriggerSet.funcStore = fnStore
httpTriggerSet.funcController = fnController httpTriggerSet.funcController = fnController
} }
recorderSet = MakeRecorderSet(httpTriggerSet, crdClient, rStore, frmap, trmap)
httpTriggerSet.recorderSet = recorderSet
return httpTriggerSet, tStore, fnStore return httpTriggerSet, tStore, fnStore
} }
@@ -92,6 +95,11 @@ func (ts *HTTPTriggerSet) subscribeRouter(ctx context.Context, mr *mutableRouter
go ts.updateRouter() go ts.updateRouter()
go ts.runWatcher(ctx, ts.funcController) go ts.runWatcher(ctx, ts.funcController)
go ts.runWatcher(ctx, ts.triggerController) go ts.runWatcher(ctx, ts.triggerController)
if ts.recorderSet.recController != nil {
go ts.runWatcher(ctx, ts.recorderSet.recController)
} else {
log.Fatal("Failed to run recorder Controller")
}
} }
func defaultHomeHandler(w http.ResponseWriter, r *http.Request) { func defaultHomeHandler(w http.ResponseWriter, r *http.Request) {
@@ -121,6 +129,14 @@ func (ts *HTTPTriggerSet) getRouter() *mux.Router {
continue continue
} }
var recorderName string
recorder, err := ts.recorderSet.triggerRecorderMap.lookup(trigger.Metadata.Name)
if err == nil && recorder != nil {
recorderName = recorder.Spec.Name
}
//log.Printf("The trigger %v should be recorded: %v", trigger.Metadata.Name, doRecord)
if rr.resolveResultType != resolveResultSingleFunction { if rr.resolveResultType != resolveResultSingleFunction {
// not implemented yet // not implemented yet
log.Panicf("resolve result type not implemented (%v)", rr.resolveResultType) log.Panicf("resolve result type not implemented (%v)", rr.resolveResultType)
@@ -128,10 +144,13 @@ func (ts *HTTPTriggerSet) getRouter() *mux.Router {
fh := &functionHandler{ fh := &functionHandler{
fmap: ts.functionServiceMap, fmap: ts.functionServiceMap,
frmap: ts.recorderSet.functionRecorderMap,
trmap: ts.recorderSet.triggerRecorderMap,
function: rr.functionMetadata, function: rr.functionMetadata,
executor: ts.executor, executor: ts.executor,
httpTrigger: &trigger, httpTrigger: &trigger,
tsRoundTripperParams: ts.tsRoundTripperParams, tsRoundTripperParams: ts.tsRoundTripperParams,
recorderName: recorderName,
} }
ht := muxRouter.HandleFunc(trigger.Spec.RelativeURL, fh.handler) ht := muxRouter.HandleFunc(trigger.Spec.RelativeURL, fh.handler)
@@ -158,11 +177,21 @@ func (ts *HTTPTriggerSet) getRouter() *mux.Router {
// triggers route into these. // triggers route into these.
for _, function := range ts.functions { for _, function := range ts.functions {
m := function.Metadata m := function.Metadata
var recorderName string
recorder, err := ts.recorderSet.functionRecorderMap.lookup(m.Name)
if err == nil && recorder != nil {
recorderName = recorder.Spec.Name
}
fh := &functionHandler{ fh := &functionHandler{
fmap: ts.functionServiceMap, fmap: ts.functionServiceMap,
frmap: ts.recorderSet.functionRecorderMap,
trmap: ts.recorderSet.triggerRecorderMap,
function: &m, function: &m,
executor: ts.executor, executor: ts.executor,
tsRoundTripperParams: ts.tsRoundTripperParams, tsRoundTripperParams: ts.tsRoundTripperParams,
recorderName: recorderName,
} }
muxRouter.HandleFunc(fission.UrlForFunction(function.Metadata.Name, function.Metadata.Namespace), fh.handler) muxRouter.HandleFunc(fission.UrlForFunction(function.Metadata.Name, function.Metadata.Namespace), fh.handler)
} }
@@ -186,11 +215,22 @@ func (ts *HTTPTriggerSet) initTriggerController() (k8sCache.Store, k8sCache.Cont
trigger := obj.(*crd.HTTPTrigger) trigger := obj.(*crd.HTTPTrigger)
go createIngress(trigger, ts.kubeClient) go createIngress(trigger, ts.kubeClient)
ts.syncTriggers() ts.syncTriggers()
// Check if this trigger's function needs to be recorded
fnRef := trigger.Spec.FunctionReference.Name
recorder, err := ts.recorderSet.functionRecorderMap.lookup(fnRef)
if err == nil && recorder != nil {
if len(recorder.Spec.Triggers) == 0 {
ts.recorderSet.triggerRecorderMap.assign(trigger.Metadata.Name, recorder)
}
} else {
log.Print("Unable to lookup function in functionRecorderMap")
}
}, },
DeleteFunc: func(obj interface{}) { DeleteFunc: func(obj interface{}) {
ts.syncTriggers() ts.syncTriggers()
trigger := obj.(*crd.HTTPTrigger) trigger := obj.(*crd.HTTPTrigger)
go deleteIngress(trigger, ts.kubeClient) go deleteIngress(trigger, ts.kubeClient)
go ts.recorderSet.DeleteTriggerFromRecorderMap(trigger)
}, },
UpdateFunc: func(oldObj interface{}, newObj interface{}) { UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldTrigger := oldObj.(*crd.HTTPTrigger) oldTrigger := oldObj.(*crd.HTTPTrigger)
@@ -216,7 +256,9 @@ func (ts *HTTPTriggerSet) initFunctionController() (k8sCache.Store, k8sCache.Con
ts.syncTriggers() ts.syncTriggers()
}, },
DeleteFunc: func(obj interface{}) { DeleteFunc: func(obj interface{}) {
function := obj.(*crd.Function)
ts.syncTriggers() ts.syncTriggers()
go ts.recorderSet.DeleteFunctionFromRecorderMap(function)
}, },
UpdateFunc: func(oldObj interface{}, newObj interface{}) { UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldFn := oldObj.(*crd.Function) oldFn := oldObj.(*crd.Function)
@@ -243,6 +285,29 @@ func (ts *HTTPTriggerSet) initFunctionController() (k8sCache.Store, k8sCache.Con
return store, controller return store, controller
} }
func (ts *HTTPTriggerSet) initRecorderController() (k8sCache.Store, k8sCache.Controller) {
resyncPeriod := 30 * time.Second
listWatch := k8sCache.NewListWatchFromClient(ts.crdClient, "recorders", metav1.NamespaceAll, fields.Everything())
store, controller := k8sCache.NewInformer(listWatch, &crd.Recorder{}, resyncPeriod,
k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
recorder := obj.(*crd.Recorder)
ts.recorderSet.newRecorder(recorder)
},
DeleteFunc: func(obj interface{}) {
recorder := obj.(*crd.Recorder)
ts.recorderSet.disableRecorder(recorder)
},
UpdateFunc: func(oldObj, newObj interface{}) {
oldRecorder := oldObj.(*crd.Recorder)
newRecorder := newObj.(*crd.Recorder)
ts.recorderSet.updateRecorder(oldRecorder, newRecorder)
},
},
)
return store, controller
}
func (ts *HTTPTriggerSet) runWatcher(ctx context.Context, controller k8sCache.Controller) { func (ts *HTTPTriggerSet) runWatcher(ctx context.Context, controller k8sCache.Controller) {
go func() { go func() {
controller.Run(ctx.Done()) controller.Run(ctx.Done())
+118
View File
@@ -0,0 +1,118 @@
package router
import (
"github.com/fission/fission/crd"
log "github.com/sirupsen/logrus"
"k8s.io/client-go/rest"
k8sCache "k8s.io/client-go/tools/cache"
)
type RecorderSet struct {
httpTriggerSet *HTTPTriggerSet
crdClient *rest.RESTClient
recStore k8sCache.Store
recController k8sCache.Controller
functionRecorderMap *functionRecorderMap
triggerRecorderMap *triggerRecorderMap
}
func MakeRecorderSet(httpTriggerSet *HTTPTriggerSet, crdClient *rest.RESTClient, rStore k8sCache.Store, frmap *functionRecorderMap, trmap *triggerRecorderMap) *RecorderSet {
recorderSet := &RecorderSet{
httpTriggerSet: httpTriggerSet,
crdClient: crdClient,
recStore: rStore,
functionRecorderMap: frmap,
triggerRecorderMap: trmap,
}
recorderSet.recStore, recorderSet.recController = httpTriggerSet.initRecorderController()
return recorderSet
}
// All new recorders are by default enabled
func (rs *RecorderSet) newRecorder(r *crd.Recorder) {
function := r.Spec.Function
triggers := r.Spec.Triggers
// If triggers are not explicitly specified during the creation of this recorder,
// keep track of those associated with the function specified [implicitly added triggers]
needTrackByFunction := len(triggers) == 0
rs.functionRecorderMap.assign(function, r)
if needTrackByFunction {
for _, t := range rs.httpTriggerSet.triggerStore.List() {
trigger := *t.(*crd.HTTPTrigger)
if trigger.Spec.FunctionReference.Name == function {
rs.triggerRecorderMap.assign(trigger.Metadata.Name, r)
}
}
} else {
for _, trigger := range triggers {
rs.triggerRecorderMap.assign(trigger, r)
}
}
rs.httpTriggerSet.syncTriggers()
}
// TODO: Delete or disable?
func (rs *RecorderSet) disableRecorder(r *crd.Recorder) {
function := r.Spec.Function
triggers := r.Spec.Triggers
log.Info("Disabling recorder ", r.Metadata.Name)
// Account for function
err := rs.functionRecorderMap.remove(function)
if err != nil {
log.Error("Error disabling recorder (failed to remove function from functionRecorderMap): ", err)
}
// Account for explicitly added triggers
if len(triggers) != 0 {
for _, trigger := range triggers {
err := rs.triggerRecorderMap.remove(trigger)
if err != nil {
log.Error("Error disabling recorder (failed to remove triggers from triggerRecorderMap): ", err)
}
}
} else {
// Account for implicitly added triggers
for _, t := range rs.httpTriggerSet.triggerStore.List() {
trigger := *t.(*crd.HTTPTrigger)
if trigger.Spec.FunctionReference.Name == function {
err := rs.triggerRecorderMap.remove(trigger.Metadata.Name)
if err != nil {
log.Error("Failed to remove trigger from triggerRecorderMap: ", err)
}
}
}
}
rs.httpTriggerSet.syncTriggers()
}
func (rs *RecorderSet) updateRecorder(old *crd.Recorder, newer *crd.Recorder) {
if newer.Spec.Enabled == true {
rs.newRecorder(newer) // TODO: Test this
} else {
rs.disableRecorder(old)
}
}
func (rs *RecorderSet) DeleteTriggerFromRecorderMap(trigger *crd.HTTPTrigger) {
err := rs.triggerRecorderMap.remove(trigger.Metadata.Name)
if err != nil {
log.Error("Failed to remove trigger from triggerRecorderMap: ", err)
}
}
func (rs *RecorderSet) DeleteFunctionFromRecorderMap(function *crd.Function) {
err := rs.functionRecorderMap.remove(function.Metadata.Name)
if err != nil {
log.Error("Failed to remove function from functionRecorderMap: ", err)
}
}
+6 -1
View File
@@ -86,6 +86,10 @@ func Start(port int, executorUrl string) {
fmap := makeFunctionServiceMap(time.Minute) fmap := makeFunctionServiceMap(time.Minute)
frmap := makeFunctionRecorderMap(time.Minute)
trmap := makeTriggerRecorderMap(time.Minute)
fissionClient, kubeClient, _, err := crd.MakeFissionClient() fissionClient, kubeClient, _, err := crd.MakeFissionClient()
if err != nil { if err != nil {
log.Fatalf("Error connecting to kubernetes API: %v", err) log.Fatalf("Error connecting to kubernetes API: %v", err)
@@ -120,13 +124,14 @@ func Start(port int, executorUrl string) {
log.Fatalf("Failed to parse max retry times: %v", err) log.Fatalf("Failed to parse max retry times: %v", err)
} }
triggers, _, fnStore := makeHTTPTriggerSet(fmap, fissionClient, kubeClient, executor, restClient, triggers, _, fnStore := makeHTTPTriggerSet(fmap, frmap, trmap, fissionClient, kubeClient, executor, restClient,
&tsRoundTripperParams{ &tsRoundTripperParams{
timeout: timeout, timeout: timeout,
timeoutExponent: timeoutExponent, timeoutExponent: timeoutExponent,
keepAlive: keepAlive, keepAlive: keepAlive,
maxRetries: maxRetries, maxRetries: maxRetries,
}) })
resolver := makeFunctionReferenceResolver(fnStore) resolver := makeFunctionReferenceResolver(fnStore)
go serveMetric() go serveMetric()
+5 -1
View File
@@ -58,8 +58,12 @@ func TestRouter(t *testing.T) {
} }
frr.refCache.Set(nfr, rr) frr.refCache.Set(nfr, rr)
frmap := makeFunctionRecorderMap(time.Minute)
trmap := makeTriggerRecorderMap(time.Minute)
// HTTP trigger set with a trigger for this function // HTTP trigger set with a trigger for this function
triggers, _, _ := makeHTTPTriggerSet(fmap, nil, nil, nil, nil, triggers, _, _ := makeHTTPTriggerSet(fmap, frmap, trmap, nil, nil, nil, nil,
&tsRoundTripperParams{ &tsRoundTripperParams{
timeout: 50 * time.Millisecond, timeout: 50 * time.Millisecond,
timeoutExponent: 2, timeoutExponent: 2,
+62
View File
@@ -0,0 +1,62 @@
/*
Copyright 2018 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 router
import (
"log"
"time"
"github.com/fission/fission"
"github.com/fission/fission/cache"
"github.com/fission/fission/crd"
)
type (
triggerRecorderMap struct {
cache *cache.Cache // map[string]*crd.Recorder
}
)
func makeTriggerRecorderMap(expiry time.Duration) *triggerRecorderMap {
return &triggerRecorderMap{
cache: cache.MakeCache(expiry, 0),
}
}
func (trmap *triggerRecorderMap) lookup(trigger string) (*crd.Recorder, error) {
item, err := trmap.cache.Get(trigger)
if err != nil {
return nil, err
}
u := item.(*crd.Recorder)
return u, nil
}
func (trmap *triggerRecorderMap) assign(trigger string, recorder *crd.Recorder) {
err, _ := trmap.cache.Set(trigger, recorder)
if err != nil {
if e, ok := err.(fission.Error); ok && e.Code == fission.ErrorNameExists {
return
}
log.Printf("error caching recorder for function name with a different value: %v", err)
}
}
func (trmap *triggerRecorderMap) remove(trigger string) error {
return trmap.cache.Delete(trigger)
}
-61
View File
@@ -1,61 +0,0 @@
#!/bin/bash
#
# Create a function and trigger it using NATS
#
set -euo pipefail
set +x
ROOT=$(dirname $0)/../..
DIR=$(dirname $0)
clusterID="fissionMQTrigger"
topic="foo.bar"
resptopic="foo.foo"
expectedRespOutput="[foo.foo]: 'Hello, World!'"
log "Pre-test cleanup"
fission env delete --name nodejs || true
log "Creating nodejs env"
fission env create --name nodejs --image fission/node-env
trap "fission env delete --name nodejs" EXIT
log "Creating function"
fn=hello-$(date +%s)
fission fn create --name $fn --env nodejs --code $DIR/main.js --method GET
trap "fission fn delete --name $fn" EXIT
log "Creating message queue trigger"
mqt=mqt-$(date +%s)
fission mqtrigger create --name $mqt --function $fn --mqtype "nats-streaming" --topic $topic --resptopic $resptopic
trap "fission mqtrigger delete --name $mqt" EXIT
# wait until nats trigger is created
sleep 5
#
# Send a message
#
log "Sending message"
go run $DIR/stan-pub.go -s $FISSION_NATS_STREAMING_URL -c $clusterID -id clientPub $topic ""
#
# Wait for message on response topic
#
log "Waiting for response"
TIMEOUT=timeout
if [ $(uname -s) == 'Darwin' ]
then
# If this fails on mac os, do "brew install coreutils".
TIMEOUT=gtimeout
fi
response=$($TIMEOUT 120s go run $DIR/stan-sub.go --last -s $FISSION_NATS_STREAMING_URL -c $clusterID -id clientSub $resptopic 2>&1)
if [[ "$response" != "$expectedRespOutput" ]]; then
log "$response is not equal to $expectedRespOutput"
exit 1
fi
log "Subscriber received expected response: $response"
+9
View File
@@ -0,0 +1,9 @@
from flask import request
def main():
content = request.get_json(force=True)
title = content['title']
name = content['name']
item = content['item']
g = "Greetings, {} {}. May I take your {}?\n".format(title, name, item)
return g
+7
View File
@@ -0,0 +1,7 @@
from flask import request
def main():
time = request.args.get('time')
date = request.args.get('date')
r = "We'll meet at {} on {}.\n".format(time, date)
return r
+55
View File
@@ -0,0 +1,55 @@
#!/bin/bash
#
# Simple end-to-end test of record with POST
# Two recorders tested: by function, by trigger (TODO)
#
set -euo pipefail
set +x
ROOT=$(dirname $0)/../..
DIR=$(dirname $0)
echo "Pre-test cleanup"
fission env delete --name python || true
echo "Creating python env"
fission env create --name python --image fission/python-env
# trap "fission env delete --name python" EXIT
echo "Creating function"
fn=greetings-$(date +%s)
fission fn create --name $fn --env python --code $DIR/greetings.py --method GET
# trap "fission function delete --name $fn" EXIT
echo "Creating http trigger"
generated=$(fission route create --function $fn --method POST --url greetings | awk '{print $2}'| tr -d "'")
# Wait until trigger is created
sleep 5
echo "Creating recorder"
recName="gacrux"
fission recorder create --name $recName --function $fn
fission recorder get --name $recName
# trap "fission recorder delete --name $recName" EXIT
# Wait until recorder is created
sleep 5
echo "Issuing cURL request:"
resp=$(curl -X POST "http://$FISSION_ROUTER/greetings" -d "{\"title\":\"Madam\",\"name\":\"Thanh\",\"item\":\"coat\"}")
expectedR="Greetings, Madam Thanh. May I take your coat?"
recordedStatus="$(fission records view --from 15s --to 0s -v | awk 'FNR == 2 {print $4$5}')"
expectedS="200OK"
trap "fission recorder delete --name $recName && fission ht delete --name $generated && fission function delete --name $fn && fission env delete --name python" EXIT
if [ "$resp" != "$expectedR" ] || [ "$recordedStatus" != "$expectedS" ]; then
echo "Response is not equal to expected response."
exit 1
fi
echo "Passed."
exit 0
+95
View File
@@ -0,0 +1,95 @@
#!/bin/bash
#
# Simple end-to-end test of record with GET
# 0) Setup: Two triggers created for a function (different urls, urlA and urlB)
# 1) One recorder created for that function, two cURL requests made to both urls, check both are recorded
# 2) One recorder created for a particular trigger (urlB), both requests repeated, check only the one for urlB was recorded
#
set -euo pipefail
set +x
ROOT=$(dirname $0)/../..
DIR=$(dirname $0)
expectedR="We'll meet at 9 on Tuesday."
echo "Pre-test cleanup"
fission env delete --name python || true
echo "Creating python env"
fission env create --name python --image fission/python-env
echo "Creating function"
fn=rv-$(date +%s)
fission fn create --name $fn --env python --code $DIR/rendezvous.py --method GET
echo "Creating trigger A"
generatedA=$(fission route create --function $fn --method GET --url rvA | awk '{print $2}'| tr -d "'")
echo "Creating trigger B"
generatedB=$(fission route create --function $fn --method GET --url rvB | awk '{print $2}'| tr -d "'")
# Wait until triggers are created
sleep 5
echo "Creating recorder by function"
recName="regulus"
fission recorder create --name $recName --function $fn
fission recorder get --name $recName
# Wait until recorder is created
sleep 5
echo "Issuing cURL request to urlA:"
respA=$(curl -X GET "http://$FISSION_ROUTER/rvA?time=9&date=Tuesday")
recordedStatusA="$(fission records view --from 5s --to 0s -v | awk 'FNR == 2 {print $4$5}')"
expectedSA="200OK"
# Separate records
sleep 5
echo "Issuing cURL request to urlB:"
respB=$(curl -X GET "http://$FISSION_ROUTER/rvB?time=9&date=Tuesday")
recordedStatusB="$(fission records view --from 5s --to 0s -v | awk 'FNR == 2 {print $4$5}')"
expectedSB="200OK"
if [ "$respA" != "$expectedR" ] || [ "$recordedStatusA" != "$expectedSA" ] || [ "$recordedStatusB" != "$expectedSB" ]; then
echo "Failed at test case 1."
exit 1
fi
echo "Test case 1) Passed."
# Delete first recorder
fission recorder delete --name $recName
sleep 5
echo "Creating recorder by trigger"
recName2="regulus2"
fission recorder create --name $recName2 --trigger $generatedB
fission recorder get --name $recName2
echo "Issuing cURL request to urlA:"
respA=$(curl -X GET "http://$FISSION_ROUTER/rvA?time=9&date=Tuesday")
recordedStatusA="$(fission records view --from 5s --to 0s -v | awk 'FNR == 2 {print $4$5}')"
expectedSA=""
# Separate records
sleep 5
echo "Issuing cURL request to urlB:"
respB=$(curl -X GET "http://$FISSION_ROUTER/rvB?time=9&date=Tuesday")
recordedStatusB="$(fission records view --from 5s --to 0s -v | awk 'FNR == 2 {print $4$5}')"
expectedSB="200OK"
if [ "$respA" != "$expectedR" ] || [ "$recordedStatusA" != "$expectedSA" ] || [ "$recordedStatusB" != "$expectedSB" ]; then
echo "Failed at test case 2."
exit 1
fi
trap "fission recorder delete --name $recName2 && fission ht delete --name $generatedA && fission ht delete --name $generatedB && fission fn delete --name $fn && fission env delete --name python" EXIT
echo "All passed."
exit 0
+100
View File
@@ -0,0 +1,100 @@
#!/bin/bash
#
# Simple test of recorder updates
# 0) Setup: One recorder created for function with trigger
# 1) Recorder disabled, cURL request made, check not saved.
# 2) Recorder re-enabled, request repeated, check now saved.
# 3) New trigger created for same function recorded w/ different url, recorder updated to observe requests for this trigger
# Request repeated at new url, check saved.
#
set -euo pipefail
set +x
ROOT=$(dirname $0)/../..
DIR=$(dirname $0)
echo "Pre-test cleanup"
fission env delete --name python || true
echo "Creating python env"
fission env create --name python --image fission/python-env
echo "Creating function"
fn=rv-$(date +%s)
fission fn create --name $fn --env python --code $DIR/rendezvous.py --method GET
echo "Creating http trigger"
generated=$(fission route create --function $fn --method GET --url rv | awk '{print $2}'| tr -d "'")
# Wait until trigger is created
sleep 5
echo "Creating recorder"
recName="regulus"
fission recorder create --name $recName --function $fn
fission recorder get --name $recName
# Wait until recorder is created
sleep 5
# Disable recorder
fission recorder update --name $recName --disable
sleep 5
echo "Issuing cURL request that should not be recorded:"
resp=$(curl -X GET "http://$FISSION_ROUTER/rv?time=9&date=Tuesday")
recordedStatus="$(fission records view --from 15s --to 0s -v | awk 'FNR == 2 {print $4$5}')"
expectedR="We'll meet at 9 on Tuesday."
expectedS=""
if [ "$resp" != "$expectedR" ] || [ "$recordedStatus" != "$expectedS" ]; then
echo "Response is not equal to expected response."
exit 1
fi
echo "Test case 1) Passed."
# Reenable recorder
fission recorder update --name $recName --enable
sleep 5
echo "Issuing cURL request that should be recorded:"
resp=$(curl -X GET "http://$FISSION_ROUTER/rv?time=9&date=Tuesday")
expectedR="We'll meet at 9 on Tuesday."
recordedStatus="$(fission records view --from 15s --to 0s -v | awk 'FNR == 2 {print $4$5}')"
expectedS="200OK"
if [ "$resp" != "$expectedR" ] || [ "$recordedStatus" != "$expectedS" ]; then
echo "Response is not equal to expected response."
exit 1
fi
echo "Test case 2) Passed."
# Create new trigger for same function recorded w/ different url
generated2=$(fission route create --function $fn --method GET --url rv2 | awk '{print $2}'| tr -d "'")
echo "New trigger: $generated2"
# Update recorder to observe new trigger
fission recorder update --name $recName --trigger $generated2
fission recorder list
echo "Issuing cURL request that should be recorded:"
resp=$(curl -X GET "http://$FISSION_ROUTER/rv2?time=9&date=Tuesday")
expectedR="We'll meet at 9 on Tuesday."
recordedStatus="$(fission records view --from 15s --to 0s -v | awk 'FNR == 2 {print $4$5}')"
expectedS="200OK"
if [ "$resp" != "$expectedR" ] || [ "$recordedStatus" != "$expectedS" ]; then
echo "Response is not equal to expected response."
exit 1
fi
echo "Test case 3) Passed."
trap "fission recorder delete --name $recName && fission ht delete --name $generated && fission ht delete --name $generated2 && fission function delete --name $fn && fission env delete --name python" EXIT
echo "All passed."
exit 0
+1
View File
@@ -51,6 +51,7 @@ type (
MessageQueueType = fv1.MessageQueueType MessageQueueType = fv1.MessageQueueType
MessageQueueTriggerSpec = fv1.MessageQueueTriggerSpec MessageQueueTriggerSpec = fv1.MessageQueueTriggerSpec
TimeTriggerSpec = fv1.TimeTriggerSpec TimeTriggerSpec = fv1.TimeTriggerSpec
RecorderSpec = fv1.RecorderSpec
) )
type ( type (