From 00572705515c9d324dded7f142c8b95cdf6f4d79 Mon Sep 17 00:00:00 2001 From: Ta-Ching Chen Date: Mon, 4 Jun 2018 19:02:54 +0800 Subject: [PATCH] Add time trigger cron spec examination tool (#680) --- builder/builder.go | 2 +- controller/api.go | 2 +- controller/client/client.go | 24 ++++++++++++++ environments/fetcher/fetcher.go | 2 +- fission-bundle/main.go | 2 +- fission/main.go | 42 ++++++++--------------- fission/timetrigger.go | 59 ++++++++++++++++++++++++++++++--- version.go => info.go | 40 +++++++++++++++++++--- 8 files changed, 133 insertions(+), 40 deletions(-) rename version.go => info.go (61%) diff --git a/builder/builder.go b/builder/builder.go index 18891cab..7eaa66a4 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -72,7 +72,7 @@ func MakeBuilder(sharedVolumePath string) *Builder { func (builder *Builder) VersionHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") - fmt.Fprintf(w, fission.VersionInfo().String()) + fmt.Fprintf(w, fission.BuildInfo().String()) } func (builder *Builder) Handler(w http.ResponseWriter, r *http.Request) { diff --git a/controller/api.go b/controller/api.go index cf988be3..75dd2449 100644 --- a/controller/api.go +++ b/controller/api.go @@ -155,7 +155,7 @@ func (api *API) getLogDBConfig(dbType string) logDBConfig { func (api *API) HomeHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") - fmt.Fprintf(w, fission.VersionInfo().String()) + fmt.Fprintf(w, fission.ApiInfo().String()) } func (api *API) ApiVersionMismatchHandler(w http.ResponseWriter, r *http.Request) { diff --git a/controller/client/client.go b/controller/client/client.go index 0cd03e56..f489eada 100644 --- a/controller/client/client.go +++ b/controller/client/client.go @@ -18,7 +18,9 @@ package client import ( "bytes" + "encoding/json" "errors" + "fmt" "io/ioutil" "net/http" "strings" @@ -88,3 +90,25 @@ func (c *Client) handleCreateResponse(resp *http.Response) ([]byte, error) { body, err := ioutil.ReadAll(resp.Body) return body, err } + +func (c *Client) ServerInfo() (*fission.ServerInfo, error) { + url := fmt.Sprintf(c.Url) + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + info := &fission.ServerInfo{} + err = json.Unmarshal(body, info) + if err != nil { + return nil, err + } + + return info, nil +} diff --git a/environments/fetcher/fetcher.go b/environments/fetcher/fetcher.go index 56289f31..9872ec2f 100644 --- a/environments/fetcher/fetcher.go +++ b/environments/fetcher/fetcher.go @@ -165,7 +165,7 @@ func writeSecretOrConfigMap(dataMap map[string][]byte, dirPath string) error { func (fetcher *Fetcher) VersionHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") - fmt.Fprintf(w, fission.VersionInfo().String()) + fmt.Fprintf(w, fission.BuildInfo().String()) } func (fetcher *Fetcher) FetchHandler(w http.ResponseWriter, r *http.Request) { diff --git a/fission-bundle/main.go b/fission-bundle/main.go index 583c0e49..3dc17621 100644 --- a/fission-bundle/main.go +++ b/fission-bundle/main.go @@ -140,7 +140,7 @@ Options: --builderMgr Start builder manager. --version Print version information ` - version := fmt.Sprintf("Fission Bundle Version: %v", fission.VersionInfo().String()) + version := fmt.Sprintf("Fission Bundle Version: %v", fission.BuildInfo().String()) arguments, err := docopt.Parse(usage, nil, true, version, false) if err != nil { log.Fatalf("Error: %v", err) diff --git a/fission/main.go b/fission/main.go index 474c052a..53b649e1 100644 --- a/fission/main.go +++ b/fission/main.go @@ -18,13 +18,10 @@ package main import ( "fmt" - "io/ioutil" - "net/http" "os" "path/filepath" - "strings" - version "github.com/fission/fission" + "github.com/fission/fission" "github.com/urfave/cli" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -47,20 +44,6 @@ func getKubeConfigPath() string { return kubeConfig } -func getFissionAPIVersion(apiUrl string) (string, error) { - resp, err := http.Get(apiUrl) - if err != nil { - return "", err - } - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "", err - } - return strings.TrimRight(string(body), "\n"), nil -} - func getServerUrl() string { var serverUrl string // Use FISSION_URL env variable if set; otherwise, port-forward to controller. @@ -87,16 +70,17 @@ func main() { app := cli.NewApp() app.Name = "fission" app.Usage = "Serverless functions for Kubernetes" - app.Version = version.Version + app.Version = fission.Version cli.VersionPrinter = func(c *cli.Context) { - clientVer := version.VersionInfo().String() + clientVer := fission.BuildInfo().String() fmt.Printf("Client Version: %v\n", clientVer) - serverVer, err := getFissionAPIVersion(getServerUrl()) + serverInfo, err := getClient(getServerUrl()).ServerInfo() if err != nil { fmt.Printf("Error getting Fission API version: %v", err) } else { - fmt.Printf("Server Version: %v", serverVer) + serverVer := serverInfo.Build.String() + fmt.Printf("Server Version: %v\n", serverVer) } } @@ -177,14 +161,16 @@ func main() { // timetriggers ttNameFlag := cli.StringFlag{Name: "name", Usage: "Time Trigger name"} - ttCronFlag := cli.StringFlag{Name: "cron", Usage: "Time Trigger cron spec ('0 30 * * *', '@every 5m', '@hourly')"} + ttCronFlag := cli.StringFlag{Name: "cron", Usage: "Time trigger cron spec with each asterisk representing respectively second, minute, hour, the day of the month, month and day of the week. Also supports readable formats like '@every 5m', '@hourly'"} ttFnNameFlag := cli.StringFlag{Name: "function", Usage: "Function name"} + ttRoundFlag := cli.IntFlag{Name: "round", Value: 1, Usage: "Get next N rounds of invocation time"} ttSubcommands := []cli.Command{ - {Name: "create", Aliases: []string{"add"}, Usage: "Create Time trigger", Flags: []cli.Flag{ttNameFlag, ttFnNameFlag, fnNamespaceFlag, ttCronFlag, specSaveFlag}, Action: ttCreate}, - {Name: "get", Usage: "Get Time trigger", Flags: []cli.Flag{triggerNamespaceFlag}, Action: ttGet}, - {Name: "update", Usage: "Update Time trigger", Flags: []cli.Flag{ttNameFlag, triggerNamespaceFlag, ttCronFlag, ttFnNameFlag}, Action: ttUpdate}, - {Name: "delete", Usage: "Delete Time trigger", Flags: []cli.Flag{ttNameFlag, triggerNamespaceFlag}, Action: ttDelete}, - {Name: "list", Usage: "List Time triggers", Flags: []cli.Flag{triggerNamespaceFlag}, Action: ttList}, + {Name: "create", Aliases: []string{"add"}, Usage: "Create time trigger", Flags: []cli.Flag{ttNameFlag, ttFnNameFlag, fnNamespaceFlag, ttCronFlag, specSaveFlag}, Action: ttCreate}, + {Name: "get", Usage: "Get time trigger", Flags: []cli.Flag{triggerNamespaceFlag}, Action: ttGet}, + {Name: "update", Usage: "Update time trigger", Flags: []cli.Flag{ttNameFlag, triggerNamespaceFlag, ttCronFlag, ttFnNameFlag}, Action: ttUpdate}, + {Name: "delete", Usage: "Delete time trigger", Flags: []cli.Flag{ttNameFlag, triggerNamespaceFlag}, Action: ttDelete}, + {Name: "list", Usage: "List time triggers", Flags: []cli.Flag{triggerNamespaceFlag}, Action: ttList}, + {Name: "showschedule", Aliases: []string{"show"}, Usage: "Show schedule for cron spec", Flags: []cli.Flag{ttCronFlag, ttRoundFlag}, Action: ttTest}, } // Message queue trigger diff --git a/fission/timetrigger.go b/fission/timetrigger.go index f717192e..5d5d1abc 100644 --- a/fission/timetrigger.go +++ b/fission/timetrigger.go @@ -20,15 +20,42 @@ import ( "fmt" "os" "text/tabwriter" + "time" + "github.com/robfig/cron" "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/controller/client" "github.com/fission/fission/crd" ) +func getAPITimeInfo(client *client.Client) time.Time { + serverInfo, err := client.ServerInfo() + if err != nil { + fatal(fmt.Sprintf("Error syncing server time information: %v", err)) + } + return serverInfo.ServerTime.CurrentTime +} + +func getCronNextNActivationTime(cronSpec string, serverTime time.Time, round int) error { + sched, err := cron.Parse(cronSpec) + if err != nil { + return err + } + + fmt.Printf("Current Server Time: \t%v\n", serverTime.Format(time.RFC3339)) + + for i := 0; i < round; i++ { + serverTime = sched.Next(serverTime) + fmt.Printf("Next %v invocation: \t%v\n", i+1, serverTime.Format(time.RFC3339)) + } + + return nil +} + func ttCreate(c *cli.Context) error { client := getClient(c.GlobalString("server")) @@ -40,11 +67,12 @@ func ttCreate(c *cli.Context) error { if len(fnName) == 0 { fatal("Need a function name to create a trigger, use --function") } + fnNamespace := c.String("fnNamespace") - cron := c.String("cron") - if len(cron) == 0 { - fatal("Need a cron spec like '0 30 * * *', '@every 1h30m', or '@hourly'; use --cron") + cronSpec := c.String("cron") + if len(cronSpec) == 0 { + fatal("Need a cron spec like '0 30 * * * *', '@every 1h30m', or '@hourly'; use --cron") } tt := &crd.TimeTrigger{ @@ -53,7 +81,7 @@ func ttCreate(c *cli.Context) error { Namespace: fnNamespace, }, Spec: fission.TimeTriggerSpec{ - Cron: cron, + Cron: cronSpec, FunctionReference: fission.FunctionReference{ Type: fission.FunctionReferenceTypeFunctionName, Name: fnName, @@ -73,6 +101,10 @@ func ttCreate(c *cli.Context) error { checkErr(err, "create Time trigger") fmt.Printf("trigger '%v' created\n", name) + + err = getCronNextNActivationTime(cronSpec, getAPITimeInfo(client), 1) + checkErr(err, "pass cron spec examination") + return err } @@ -118,6 +150,10 @@ func ttUpdate(c *cli.Context) error { checkErr(err, "update Time trigger") fmt.Printf("trigger '%v' updated\n", ttName) + + err = getCronNextNActivationTime(newCron, getAPITimeInfo(client), 1) + checkErr(err, "pass cron spec examination") + return nil } @@ -157,3 +193,18 @@ func ttList(c *cli.Context) error { return nil } + +func ttTest(c *cli.Context) error { + client := getClient(c.GlobalString("server")) + + round := c.Int("round") + cronSpec := c.String("cron") + if len(cronSpec) == 0 { + fatal("Need a cron spec like '0 30 * * * *', '@every 1h30m', or '@hourly'; use --cron") + } + + err := getCronNextNActivationTime(cronSpec, getAPITimeInfo(client), round) + checkErr(err, "pass cron spec examination") + + return nil +} diff --git a/version.go b/info.go similarity index 61% rename from version.go rename to info.go index e3410929..b4271105 100644 --- a/version.go +++ b/info.go @@ -18,6 +18,7 @@ package fission import ( "encoding/json" + "time" ) var ( @@ -27,22 +28,53 @@ var ( ) type ( - Info struct { + BuildMeta struct { GitCommit string `json:"GitCommit,omitempty"` BuildDate string `json:"BuildDate,omitempty"` Version string `json:"Version,omitempty"` } + + Time struct { + Timezone string `json:"Timezone,omitempty"` + CurrentTime time.Time `json:"CurrentTime,omitempty"` + } + + ServerInfo struct { + Build BuildMeta `json:"Build,omitempty"` + ServerTime Time `json:"ServerTime,omitempty"` + } ) -func VersionInfo() Info { - return Info{ +func BuildInfo() BuildMeta { + return BuildMeta{ GitCommit: GitCommit, BuildDate: BuildDate, Version: Version, } } -func (info Info) String() string { +func (info BuildMeta) String() string { + v, _ := json.Marshal(info) + return string(v) +} + +func TimeInfo() Time { + t := time.Now() + zone, _ := t.Local().Zone() + return Time{ + Timezone: zone, + CurrentTime: t, + } +} + +func ApiInfo() ServerInfo { + return ServerInfo{ + Build: BuildInfo(), + ServerTime: TimeInfo(), + } +} + +func (info ServerInfo) String() string { v, _ := json.Marshal(info) return string(v) }