Recorder CRD, Records API, Redis deployment (#818)
This commit is contained in:
committed by
Ta-Ching Chen
parent
07ca5df8d0
commit
74a3a54543
@@ -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},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user