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
+30
View File
@@ -103,6 +103,7 @@ func main() {
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"}
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)
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},
}
// 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
envNameFlag := cli.StringFlag{Name: "name", Usage: "Environment name"}
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: "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: "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: "watch", Aliases: []string{"w"}, Usage: "Manage watches", Subcommands: wSubCommands},
{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.Kind = "TimeTrigger"
data, err = yaml.Marshal(typedres)
case crd.Recorder:
typedres.TypeMeta.APIVersion = SPEC_API_VERSION
typedres.TypeMeta.Kind = "Recorder"
data, err = yaml.Marshal(typedres)
default:
return fmt.Errorf("can't save resource %#v", resource)
}