From 5cb3ebf262f78bc4a834771b07afdd4a7140afcd Mon Sep 17 00:00:00 2001 From: Shubham Nazare Date: Mon, 7 Feb 2022 11:01:20 +0530 Subject: [PATCH] CLI Check (#2341) * added checks for kubernetes-version, fission-services and fission-version * refactor kuberntes and fission version check --- cmd/fission-cli/app/app.go | 16 +- pkg/apis/core/v1/const.go | 4 + pkg/fission-cli/cmd/check/check.go | 50 ++++++ pkg/fission-cli/cmd/check/command.go | 35 ++++ pkg/fission-cli/flag/flag.go | 2 + pkg/fission-cli/flag/key/key.go | 2 + pkg/healthcheck/healthcheck.go | 243 +++++++++++++++++++++++++++ 7 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 pkg/fission-cli/cmd/check/check.go create mode 100644 pkg/fission-cli/cmd/check/command.go create mode 100644 pkg/healthcheck/healthcheck.go diff --git a/cmd/fission-cli/app/app.go b/cmd/fission-cli/app/app.go index 15013705..7e7d721e 100644 --- a/cmd/fission-cli/app/app.go +++ b/cmd/fission-cli/app/app.go @@ -1,3 +1,16 @@ +/* +Copyright 2022 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 app import ( @@ -10,6 +23,7 @@ import ( "github.com/fission/fission/pkg/fission-cli/cliwrapper/driver/cobra/helptemplate" "github.com/fission/fission/pkg/fission-cli/cmd" "github.com/fission/fission/pkg/fission-cli/cmd/canaryconfig" + "github.com/fission/fission/pkg/fission-cli/cmd/check" "github.com/fission/fission/pkg/fission-cli/cmd/environment" "github.com/fission/fission/pkg/fission-cli/cmd/function" "github.com/fission/fission/pkg/fission-cli/cmd/httptrigger" @@ -83,7 +97,7 @@ func App() *cobra.Command { groups = append(groups, helptemplate.CreateCmdGroup("Trigger Commands", httptrigger.Commands(), mqtrigger.Commands(), timetrigger.Commands(), kubewatch.Commands())) groups = append(groups, helptemplate.CreateCmdGroup("Deploy Strategies Commands", canaryconfig.Commands())) groups = append(groups, helptemplate.CreateCmdGroup("Declarative Application Commands", spec.Commands())) - groups = append(groups, helptemplate.CreateCmdGroup("Other Commands", support.Commands(), version.Commands())) + groups = append(groups, helptemplate.CreateCmdGroup("Other Commands", support.Commands(), version.Commands(), check.Commands())) groups.Add(rootCmd) flagExposer := helptemplate.ActsAsRootCommand(rootCmd, nil, groups...) diff --git a/pkg/apis/core/v1/const.go b/pkg/apis/core/v1/const.go index 3b5d5263..ae696d0d 100644 --- a/pkg/apis/core/v1/const.go +++ b/pkg/apis/core/v1/const.go @@ -16,6 +16,10 @@ limitations under the License. package v1 +var ( + MinimumKubernetesVersion = [3]int{1, 19, 0} +) + const ( EXECUTOR_INSTANCEID_LABEL string = "executorInstanceId" DEFAULT_FUNCTION_TIMEOUT int = 60 diff --git a/pkg/fission-cli/cmd/check/check.go b/pkg/fission-cli/cmd/check/check.go new file mode 100644 index 00000000..cfda16e8 --- /dev/null +++ b/pkg/fission-cli/cmd/check/check.go @@ -0,0 +1,50 @@ +/* +Copyright 2022 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 check + +import ( + "github.com/fission/fission/pkg/fission-cli/cliwrapper/cli" + "github.com/fission/fission/pkg/fission-cli/cmd" + flagkey "github.com/fission/fission/pkg/fission-cli/flag/key" + "github.com/fission/fission/pkg/healthcheck" +) + +type CheckSubCommand struct { + cmd.CommandActioner +} + +func Check(input cli.Input) error { + return (&CheckSubCommand{}).do(input) +} + +func (opts *CheckSubCommand) do(input cli.Input) error { + + kubeContext := input.String(flagkey.KubeContext) + + checks := []healthcheck.CategoryID{} + + if input.IsSet(flagkey.PreCheckOnly) { + checks = append(checks, healthcheck.Kubernetes) + } else { + checks = append(checks, healthcheck.FissionServices, healthcheck.FissionVersion) + } + + hc := healthcheck.NewHealthChecker(checks, &healthcheck.Options{ + KubeContext: kubeContext, + FissionClient: opts.Client(), + }) + + healthcheck.RunChecks(hc) + return nil +} diff --git a/pkg/fission-cli/cmd/check/command.go b/pkg/fission-cli/cmd/check/command.go new file mode 100644 index 00000000..9085cad1 --- /dev/null +++ b/pkg/fission-cli/cmd/check/command.go @@ -0,0 +1,35 @@ +/* +Copyright 2022 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 check + +import ( + "github.com/spf13/cobra" + + wrapper "github.com/fission/fission/pkg/fission-cli/cliwrapper/driver/cobra" + "github.com/fission/fission/pkg/fission-cli/flag" +) + +func Commands() *cobra.Command { + command := &cobra.Command{ + Use: "check", + Short: "Check the fission installation for potential problems", + Long: `Check the fission installation for potential problems.`, + RunE: wrapper.Wrapper(Check), + } + wrapper.SetFlags(command, flag.FlagSet{ + Optional: []flag.Flag{flag.PreCheckOnly}, + }) + + return command +} diff --git a/pkg/fission-cli/flag/flag.go b/pkg/fission-cli/flag/flag.go index 5d1ca2cd..ce8f93b8 100644 --- a/pkg/fission-cli/flag/flag.go +++ b/pkg/fission-cli/flag/flag.go @@ -74,6 +74,8 @@ var ( ClientOnly = Flag{Type: Bool, Name: flagkey.ClientOnly, Usage: "If set, the CLI won't connect to remote server"} + PreCheckOnly = Flag{Type: Bool, Name: flagkey.PreCheckOnly, Usage: "Only run pre-installation checks, to determine if fission can be installed"} + KubeContext = Flag{Type: String, Name: flagkey.KubeContext, Usage: "Kubernetes context to be used for the execution of Fission commands", DefaultValue: ""} IgnoreNotFound = Flag{Type: Bool, Name: flagkey.IgnoreNotFound, Usage: "Treat \"resource not found\" as a successful delete.", DefaultValue: false} diff --git a/pkg/fission-cli/flag/key/key.go b/pkg/fission-cli/flag/key/key.go index 42df7c43..3d47da40 100644 --- a/pkg/fission-cli/flag/key/key.go +++ b/pkg/fission-cli/flag/key/key.go @@ -22,6 +22,8 @@ const ( ClientOnly = "client-only" KubeContext = "kube-context" + PreCheckOnly = "pre" + resourceName = "name" force = "force" Output = "output" diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go new file mode 100644 index 00000000..e905a617 --- /dev/null +++ b/pkg/healthcheck/healthcheck.go @@ -0,0 +1,243 @@ +/* +Copyright 2022 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 healthcheck + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/fatih/color" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + fv1 "github.com/fission/fission/pkg/apis/core/v1" + "github.com/fission/fission/pkg/controller/client" + "github.com/fission/fission/pkg/fission-cli/util" +) + +type CategoryID string + +const ( + Kubernetes CategoryID = "kubernetes" + FissionServices CategoryID = "fission-services" + FissionVersion CategoryID = "fission-version" +) + +var ( + okStatus = color.New(color.FgGreen, color.Bold).SprintFunc()("\u221A") // √ + failStatus = color.New(color.FgRed, color.Bold).SprintFunc()("\u00D7") // × +) + +// Category is a group of checkers +type Category struct { + ID CategoryID + checkers []Checker + enabled bool +} + +type Checker struct { + successMsg string + check func() error +} + +type Options struct { + KubeContext string + FissionClient client.Interface +} + +type HealthChecker struct { + categories []*Category + *Options + + kubeAPI *kubernetes.Clientset + fissionNamespace string +} + +func isCompatibleVersion(minimalRequirementVersion [3]int, actualVersion [3]int) bool { + if minimalRequirementVersion[0] < actualVersion[0] { + return true + } + + if (minimalRequirementVersion[0] == actualVersion[0]) && minimalRequirementVersion[1] < actualVersion[1] { + return true + } + + if (minimalRequirementVersion[0] == actualVersion[0]) && (minimalRequirementVersion[1] == actualVersion[1]) && (minimalRequirementVersion[2] <= actualVersion[2]) { + return true + } + + return false +} + +func (hc *HealthChecker) CheckKubeVersion() (err error) { + + version, err := hc.kubeAPI.ServerVersion() + if err != nil { + return err + } + + major, _ := strconv.Atoi(version.Major) + minor, _ := strconv.Atoi(version.Minor) + apiVersion := [3]int{major, minor, 0} + + if !isCompatibleVersion(fv1.MinimumKubernetesVersion, apiVersion) { + return fmt.Errorf("kubernetes is on version %d.%d.%d, but version %d.%d.%d or more recent is required", + apiVersion[0], apiVersion[1], apiVersion[2], + fv1.MinimumKubernetesVersion[0], fv1.MinimumKubernetesVersion[1], fv1.MinimumKubernetesVersion[2]) + } + + return nil +} + +func (hc *HealthChecker) CheckServiceStatus(namespace string, name string) (err error) { + depl, err := hc.kubeAPI.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get %s deployment status", name) + } + + if depl.Status.UnavailableReplicas > 0 || depl.Status.Replicas == 0 { + return fmt.Errorf("%s deployment is not running", name) + } + + _, err = hc.kubeAPI.CoreV1().Services(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get %s service status", name) + } + + return nil +} + +func (hc *HealthChecker) CheckFissionVersion() error { + ver := util.GetVersion(hc.FissionClient) + + clientVersion := ver.Client["fission/core"].Version + serverVersion := ver.Server["fission/core"].Version + + if clientVersion != serverVersion { + return fmt.Errorf("client version %s does not match with server version %s", clientVersion, serverVersion) + } + + return nil +} + +func NewCategory(id CategoryID, checkers []Checker, enabled bool) *Category { + return &Category{ + ID: id, + checkers: checkers, + enabled: enabled, + } +} + +func (hc *HealthChecker) allCategories() []*Category { + return []*Category{ + NewCategory( + Kubernetes, + []Checker{ + { + successMsg: "kubernetes version is compatible", + check: func() (err error) { + return hc.CheckKubeVersion() + }, + }, + }, + false, + ), + NewCategory( + FissionServices, + []Checker{ + { + successMsg: "controller is running fine", + check: func() error { + return hc.CheckServiceStatus(hc.fissionNamespace, "controller") + }, + }, + { + successMsg: "executor is running fine", + check: func() error { + return hc.CheckServiceStatus(hc.fissionNamespace, "executor") + }, + }, + { + successMsg: "router is running fine", + check: func() error { + return hc.CheckServiceStatus(hc.fissionNamespace, "router") + }, + }, + { + successMsg: "storagesvc is running fine", + check: func() error { + return hc.CheckServiceStatus(hc.fissionNamespace, "storagesvc") + }, + }, + }, + false, + ), + NewCategory( + FissionVersion, + []Checker{ + { + successMsg: "fission is up-to-date", + check: func() error { + return hc.CheckFissionVersion() + }, + }, + }, + false, + ), + } +} + +func NewHealthChecker(categoryIDs []CategoryID, options *Options) *HealthChecker { + hc := &HealthChecker{ + Options: options, + } + + _, clientset, _ := util.GetKubernetesClient(hc.KubeContext) + hc.kubeAPI = clientset + hc.fissionNamespace = "fission" + + hc.categories = hc.allCategories() + + checkMap := map[CategoryID]struct{}{} + for _, category := range categoryIDs { + checkMap[category] = struct{}{} + } + for i := range hc.categories { + if _, ok := checkMap[hc.categories[i].ID]; ok { + hc.categories[i].enabled = true + } + } + + return hc +} + +func RunChecks(hc *HealthChecker) { + for _, c := range hc.categories { + if c.enabled { + fmt.Println(c.ID) + fmt.Println(strings.Repeat("-", 20)) + for _, checker := range c.checkers { + err := checker.check() + if err != nil { + fmt.Printf("%s %s\n", failStatus, err) + } else { + fmt.Printf("%s %s\n", okStatus, checker.successMsg) + } + } + fmt.Printf("\n") + } + } +}