mirror of https://github.com/linkerd/linkerd2.git
Run extension checks when linkerd check is invoked (#5647)
* Run extension checks when linkerd check is invoked This change allows the linkerd check command to also run any known linkerd extension commands that have been installed in the cluster. It does this by first querying for any namespace that has the label selector `linkerd.io/extension` and then runs the subcommands for either `jaeger`, `multicluster` or `viz`. This change runs basic namespace healthchecks for extensions that aren't part of the Linkerd extension suite. Fixes #5233
This commit is contained in:
parent
96e078421c
commit
e4069b47e0
145
cli/cmd/check.go
145
cli/cmd/check.go
|
|
@ -10,9 +10,14 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/linkerd/linkerd2/cli/flag"
|
||||
jaegerCmd "github.com/linkerd/linkerd2/jaeger/cmd"
|
||||
mcCmd "github.com/linkerd/linkerd2/multicluster/cmd"
|
||||
charts "github.com/linkerd/linkerd2/pkg/charts/linkerd2"
|
||||
"github.com/linkerd/linkerd2/pkg/healthcheck"
|
||||
"github.com/linkerd/linkerd2/pkg/k8s"
|
||||
"github.com/linkerd/linkerd2/pkg/version"
|
||||
vizCmd "github.com/linkerd/linkerd2/viz/cmd"
|
||||
vizHealthCheck "github.com/linkerd/linkerd2/viz/pkg/healthcheck"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
valuespkg "helm.sh/helm/v3/pkg/cli/values"
|
||||
|
|
@ -95,7 +100,7 @@ code.`,
|
|||
Example: ` # Check that the Linkerd cluster-wide resource are installed correctly
|
||||
linkerd check config`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return configureAndRunChecks(cmd.Context(), stdout, stderr, configStage, options)
|
||||
return configureAndRunChecks(cmd, stdout, stderr, configStage, options)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +134,7 @@ non-zero exit code.`,
|
|||
# Check that the Linkerd data plane proxies in the "app" namespace are up and running
|
||||
linkerd check --proxy --namespace app`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return configureAndRunChecks(cmd.Context(), stdout, stderr, "", options)
|
||||
return configureAndRunChecks(cmd, stdout, stderr, "", options)
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +146,7 @@ non-zero exit code.`,
|
|||
return cmd
|
||||
}
|
||||
|
||||
func configureAndRunChecks(ctx context.Context, wout io.Writer, werr io.Writer, stage string, options *checkOptions) error {
|
||||
func configureAndRunChecks(cmd *cobra.Command, wout io.Writer, werr io.Writer, stage string, options *checkOptions) error {
|
||||
err := options.validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Validation error when executing check command: %v", err)
|
||||
|
|
@ -165,7 +170,7 @@ func configureAndRunChecks(ctx context.Context, wout io.Writer, werr io.Writer,
|
|||
} else {
|
||||
checks = append(checks, healthcheck.LinkerdPreInstallCapabilityChecks)
|
||||
}
|
||||
installManifest, err = renderInstallManifest(ctx)
|
||||
installManifest, err = renderInstallManifest(cmd.Context())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error rendering install manifest: %v", err)
|
||||
}
|
||||
|
|
@ -211,9 +216,141 @@ func configureAndRunChecks(ctx context.Context, wout io.Writer, werr io.Writer,
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = runExtensionChecks(cmd, wout, werr, options)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to run extensions checks: %s", err)
|
||||
fmt.Fprintln(werr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runExtensionChecks(cmd *cobra.Command, wout io.Writer, werr io.Writer, opts *checkOptions) error {
|
||||
kubeAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namespaces, err := kubeAPI.GetAllNamespacesWithExtensionLabel(cmd.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// no extensions to check
|
||||
if len(namespaces) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if opts.output != healthcheck.JSONOutput {
|
||||
headerTxt := "Linkerd extensions checks"
|
||||
fmt.Fprintln(wout)
|
||||
fmt.Fprintln(wout, headerTxt)
|
||||
fmt.Fprintln(wout, strings.Repeat("=", len(headerTxt)))
|
||||
}
|
||||
|
||||
for i, ns := range namespaces {
|
||||
if opts.output != healthcheck.JSONOutput && i < len(namespaces) {
|
||||
// add a new line to space out each check output
|
||||
fmt.Fprintln(wout)
|
||||
}
|
||||
|
||||
switch ns.Labels[k8s.LinkerdExtensionLabel] {
|
||||
case jaegerCmd.JaegerExtensionName:
|
||||
jaegerCheckCmd := jaegerCmd.NewCmdCheck()
|
||||
err = executeExtensionCheck(cmd, "jaeger", jaegerCheckCmd.Flags())
|
||||
case vizHealthCheck.VizExtensionName:
|
||||
vizCmd := vizCmd.NewCmdCheck()
|
||||
err = executeExtensionCheck(cmd, "viz", vizCmd.Flags())
|
||||
case mcCmd.MulticlusterExtensionName:
|
||||
mcCheckCmd := mcCmd.NewCmdCheck()
|
||||
err = executeExtensionCheck(cmd, "multicluster", mcCheckCmd.Flags())
|
||||
default:
|
||||
// Since we don't have checks for extensions we don't support
|
||||
// create a healthchecker that checks if the namespace exists
|
||||
categoryID := healthcheck.CategoryID(ns.Labels[k8s.LinkerdExtensionLabel])
|
||||
checkers := []healthcheck.Checker{}
|
||||
checkers = append(checkers,
|
||||
*healthcheck.NewChecker(fmt.Sprintf("%s extension Namespace exists", categoryID)).
|
||||
WithHintAnchor(fmt.Sprintf("%s-ns-exists", categoryID)).
|
||||
Fatal().
|
||||
WithCheck(func(ctx context.Context) error {
|
||||
_, err := kubeAPI.GetNamespaceWithExtensionLabel(ctx, string(categoryID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
hc := healthcheck.NewHealthChecker([]healthcheck.CategoryID{categoryID},
|
||||
&healthcheck.Options{
|
||||
ControlPlaneNamespace: controlPlaneNamespace,
|
||||
CNINamespace: cniNamespace,
|
||||
DataPlaneNamespace: opts.namespace,
|
||||
KubeConfig: kubeconfigPath,
|
||||
KubeContext: kubeContext,
|
||||
Impersonate: impersonate,
|
||||
ImpersonateGroup: impersonateGroup,
|
||||
APIAddr: apiAddr,
|
||||
VersionOverride: opts.versionOverride,
|
||||
RetryDeadline: time.Now().Add(opts.wait),
|
||||
CNIEnabled: opts.cniEnabled,
|
||||
})
|
||||
hc.AppendCategories(*healthcheck.NewCategory(categoryID, checkers, true))
|
||||
success := healthcheck.RunChecks(wout, werr, hc, opts.output)
|
||||
if !success {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getGlobalFlags(lf *pflag.FlagSet) []string {
|
||||
cmdLineFlags := []string{}
|
||||
lf.VisitAll(func(f *pflag.Flag) {
|
||||
val := f.Value.String()
|
||||
if val != "" {
|
||||
cmdLineFlags = append(cmdLineFlags, fmt.Sprintf("--%s=%s", f.Name, val))
|
||||
}
|
||||
})
|
||||
|
||||
return cmdLineFlags
|
||||
}
|
||||
|
||||
func getSharedFlags(lf *pflag.FlagSet, lf2 *pflag.FlagSet) []string {
|
||||
cmdLineFlags := []string{}
|
||||
lf.VisitAll(func(f *pflag.Flag) {
|
||||
val := f.Value.String()
|
||||
|
||||
if val == "" {
|
||||
// skip processing empty flags
|
||||
return
|
||||
}
|
||||
|
||||
if lf2.Lookup(f.Name) != nil {
|
||||
cmdLineFlags = append(cmdLineFlags, fmt.Sprintf("--%s=%s", f.Name, val))
|
||||
}
|
||||
})
|
||||
|
||||
return cmdLineFlags
|
||||
}
|
||||
|
||||
func executeExtensionCheck(currentCmd *cobra.Command, extension string, lf *pflag.FlagSet) error {
|
||||
rootCmd := currentCmd.Root()
|
||||
globalFlags := getGlobalFlags(rootCmd.PersistentFlags())
|
||||
localFlags := getSharedFlags(currentCmd.Flags(), lf)
|
||||
|
||||
args := []string{extension, "check"}
|
||||
args = append(args, globalFlags...)
|
||||
args = append(args, localFlags...)
|
||||
|
||||
rootCmd.SetArgs(args)
|
||||
return rootCmd.Execute()
|
||||
}
|
||||
|
||||
func renderInstallManifest(ctx context.Context) (string, error) {
|
||||
values, err := charts.NewValues()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ import (
|
|||
|
||||
const (
|
||||
|
||||
// jaegerExtensionName is the name of jaeger extension
|
||||
jaegerExtensionName = "linkerd-jaeger"
|
||||
// JaegerExtensionName is the name of jaeger extension
|
||||
JaegerExtensionName = "linkerd-jaeger"
|
||||
|
||||
// linkerdJaegerExtensionCheck adds checks related to the jaeger extension
|
||||
linkerdJaegerExtensionCheck healthcheck.CategoryID = jaegerExtensionName
|
||||
linkerdJaegerExtensionCheck healthcheck.CategoryID = JaegerExtensionName
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -40,7 +40,7 @@ func jaegerCategory(hc *healthcheck.HealthChecker) *healthcheck.Category {
|
|||
Fatal().
|
||||
WithCheck(func(ctx context.Context) error {
|
||||
// Get jaeger Extension Namespace
|
||||
ns, err := hc.KubeAPIClient().GetNamespaceWithExtensionLabel(ctx, jaegerExtensionName)
|
||||
ns, err := hc.KubeAPIClient().GetNamespaceWithExtensionLabel(ctx, JaegerExtensionName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -146,7 +146,8 @@ func (options *checkOptions) validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func newCmdCheck() *cobra.Command {
|
||||
// NewCmdCheck generates a new cobra command for the jaeger extension.
|
||||
func NewCmdCheck() *cobra.Command {
|
||||
options := newCheckOptions()
|
||||
cmd := &cobra.Command{
|
||||
Use: "check [flags]",
|
||||
|
|
@ -165,8 +166,8 @@ code.`,
|
|||
},
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().StringVarP(&options.output, "output", "o", options.output, "Output format. One of: basic, json")
|
||||
cmd.PersistentFlags().DurationVar(&options.wait, "wait", options.wait, "Maximum allowed time for all tests to pass")
|
||||
cmd.Flags().StringVarP(&options.output, "output", "o", options.output, "Output format. One of: basic, json")
|
||||
cmd.Flags().DurationVar(&options.wait, "wait", options.wait, "Maximum allowed time for all tests to pass")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
@ -178,8 +179,6 @@ func configureAndRunChecks(wout io.Writer, werr io.Writer, options *checkOptions
|
|||
}
|
||||
|
||||
checks := []healthcheck.CategoryID{
|
||||
healthcheck.KubernetesAPIChecks,
|
||||
healthcheck.LinkerdControlPlaneExistenceChecks,
|
||||
linkerdJaegerExtensionCheck,
|
||||
}
|
||||
|
||||
|
|
@ -193,6 +192,13 @@ func configureAndRunChecks(wout io.Writer, werr io.Writer, options *checkOptions
|
|||
RetryDeadline: time.Now().Add(options.wait),
|
||||
})
|
||||
|
||||
err = hc.InitializeKubeAPIClient()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error initializing k8s API client: %s", err)
|
||||
fmt.Fprintln(werr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
hc.AppendCategories(*jaegerCategory(hc))
|
||||
success := healthcheck.RunChecks(wout, werr, hc, options.output)
|
||||
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ func newCmdDashboard() *cobra.Command {
|
|||
return err
|
||||
}
|
||||
|
||||
jaegerNamespace, err := k8sAPI.GetNamespaceWithExtensionLabel(cmd.Context(), jaegerExtensionName)
|
||||
jaegerNamespace, err := k8sAPI.GetNamespaceWithExtensionLabel(cmd.Context(), JaegerExtensionName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ func NewCmdJaeger() *cobra.Command {
|
|||
jaegerCmd.PersistentFlags().StringVar(&apiAddr, "api-addr", "", "Override kubeconfig and communicate directly with the control plane at host:port (mostly for testing)")
|
||||
jaegerCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Turn on debug logging")
|
||||
jaegerCmd.AddCommand(newCmdInstall())
|
||||
jaegerCmd.AddCommand(newCmdCheck())
|
||||
jaegerCmd.AddCommand(NewCmdCheck())
|
||||
jaegerCmd.AddCommand(newCmdUninstall())
|
||||
jaegerCmd.AddCommand(newCmdDashboard())
|
||||
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// multiclusterExtensionName is the name of the multicluster extension
|
||||
multiclusterExtensionName = "linkerd-multicluster"
|
||||
// MulticlusterExtensionName is the name of the multicluster extension
|
||||
MulticlusterExtensionName = "linkerd-multicluster"
|
||||
|
||||
// linkerdMulticlusterExtensionCheck adds checks related to the multicluster extension
|
||||
linkerdMulticlusterExtensionCheck healthcheck.CategoryID = multiclusterExtensionName
|
||||
linkerdMulticlusterExtensionCheck healthcheck.CategoryID = MulticlusterExtensionName
|
||||
|
||||
linkerdServiceMirrorServiceAccountName = "linkerd-service-mirror-%s"
|
||||
linkerdServiceMirrorComponentName = "service-mirror"
|
||||
|
|
@ -67,7 +67,8 @@ func newHealthChecker(linkerdHC *healthcheck.HealthChecker) *healthChecker {
|
|||
}
|
||||
}
|
||||
|
||||
func newCmdCheck() *cobra.Command {
|
||||
// NewCmdCheck generates a new cobra command for the multicluster extension.
|
||||
func NewCmdCheck() *cobra.Command {
|
||||
options := newCheckOptions()
|
||||
cmd := &cobra.Command{
|
||||
Use: "check [flags]",
|
||||
|
|
@ -84,7 +85,7 @@ non-zero exit code.`,
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Get the multicluster extension namespace
|
||||
kubeAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
|
||||
_, err = kubeAPI.GetNamespaceWithExtensionLabel(context.Background(), multiclusterExtensionName)
|
||||
_, err = kubeAPI.GetNamespaceWithExtensionLabel(context.Background(), MulticlusterExtensionName)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%w; install by running `linkerd multicluster install | kubectl apply -f -`", err)
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
|
|
@ -93,8 +94,8 @@ non-zero exit code.`,
|
|||
return configureAndRunChecks(stdout, stderr, options)
|
||||
},
|
||||
}
|
||||
cmd.PersistentFlags().StringVarP(&options.output, "output", "o", options.output, "Output format. One of: basic, json")
|
||||
cmd.PersistentFlags().DurationVar(&options.wait, "wait", options.wait, "Maximum allowed time for all tests to pass")
|
||||
cmd.Flags().StringVarP(&options.output, "output", "o", options.output, "Output format. One of: basic, json")
|
||||
cmd.Flags().DurationVar(&options.wait, "wait", options.wait, "Maximum allowed time for all tests to pass")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
@ -104,8 +105,6 @@ func configureAndRunChecks(wout io.Writer, werr io.Writer, options *checkOptions
|
|||
return fmt.Errorf("Validation error when executing check command: %v", err)
|
||||
}
|
||||
checks := []healthcheck.CategoryID{
|
||||
healthcheck.KubernetesAPIChecks,
|
||||
healthcheck.LinkerdControlPlaneExistenceChecks,
|
||||
linkerdMulticlusterExtensionCheck,
|
||||
}
|
||||
linkerdHC := healthcheck.NewHealthChecker(checks, &healthcheck.Options{
|
||||
|
|
@ -117,6 +116,21 @@ func configureAndRunChecks(wout io.Writer, werr io.Writer, options *checkOptions
|
|||
APIAddr: apiAddr,
|
||||
RetryDeadline: time.Now().Add(options.wait),
|
||||
})
|
||||
|
||||
err = linkerdHC.InitializeKubeAPIClient()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error initializing k8s API client: %s", err)
|
||||
fmt.Fprintln(werr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = linkerdHC.InitializeLinkerdGlobalConfig(context.Background())
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to fetch linkerd config: %s", err)
|
||||
fmt.Fprintln(werr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
hc := newHealthChecker(linkerdHC)
|
||||
category := multiclusterCategory(hc)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ components on a cluster, manage credentials and link clusters together.`,
|
|||
multiclusterCmd.AddCommand(newLinkCommand())
|
||||
multiclusterCmd.AddCommand(newUnlinkCommand())
|
||||
multiclusterCmd.AddCommand(newMulticlusterInstallCommand())
|
||||
multiclusterCmd.AddCommand(newCmdCheck())
|
||||
multiclusterCmd.AddCommand(NewCmdCheck())
|
||||
multiclusterCmd.AddCommand(newMulticlusterUninstallCommand())
|
||||
multiclusterCmd.AddCommand(newGatewaysCommand())
|
||||
multiclusterCmd.AddCommand(newAllowCommand())
|
||||
|
|
|
|||
|
|
@ -442,6 +442,37 @@ func NewHealthChecker(categoryIDs []CategoryID, options *Options) *HealthChecker
|
|||
return hc
|
||||
}
|
||||
|
||||
// InitializeKubeAPIClient creates a client for the HealthChecker. It avoids
|
||||
// having to require the KubernetesAPIChecks check to run in order for the
|
||||
// HealthChecker to run other checks.
|
||||
func (hc *HealthChecker) InitializeKubeAPIClient() error {
|
||||
k8sAPI, err := k8s.NewAPI(hc.KubeConfig, hc.KubeContext, hc.Impersonate, hc.ImpersonateGroup, RequestTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hc.kubeAPI = k8sAPI
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitializeLinkerdGlobalConfig populates the linkerd config object in the
|
||||
// healthchecker. It avoids having to require the LinkerdControlPlaneExistenceChecks
|
||||
// check to run before running other checks
|
||||
func (hc *HealthChecker) InitializeLinkerdGlobalConfig(ctx context.Context) error {
|
||||
uuid, l5dConfig, err := hc.checkLinkerdConfigConfigMap(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if l5dConfig != nil {
|
||||
hc.CNIEnabled = l5dConfig.GetGlobal().CNIEnabled
|
||||
}
|
||||
hc.uuid = uuid
|
||||
hc.linkerdConfig = l5dConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AppendCategories returns a HealthChecker instance appending the provided Categories
|
||||
func (hc *HealthChecker) AppendCategories(categories ...Category) *HealthChecker {
|
||||
hc.categories = append(hc.categories, categories...)
|
||||
|
|
@ -474,7 +505,7 @@ func (hc *HealthChecker) allCategories() []Category {
|
|||
hintAnchor: "k8s-api",
|
||||
fatal: true,
|
||||
check: func(context.Context) (err error) {
|
||||
hc.kubeAPI, err = k8s.NewAPI(hc.KubeConfig, hc.KubeContext, hc.Impersonate, hc.ImpersonateGroup, RequestTimeout)
|
||||
err = hc.InitializeKubeAPIClient()
|
||||
return
|
||||
},
|
||||
},
|
||||
|
|
@ -667,11 +698,7 @@ func (hc *HealthChecker) allCategories() []Category {
|
|||
hintAnchor: "l5d-existence-linkerd-config",
|
||||
fatal: true,
|
||||
check: func(ctx context.Context) (err error) {
|
||||
hc.uuid, hc.linkerdConfig, err = hc.checkLinkerdConfigConfigMap(ctx)
|
||||
if hc.linkerdConfig == nil {
|
||||
return errors.New("failed to load linkerd-config")
|
||||
}
|
||||
hc.CNIEnabled = hc.linkerdConfig.GetGlobal().CNIEnabled
|
||||
err = hc.InitializeLinkerdGlobalConfig(ctx)
|
||||
return
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -166,13 +166,24 @@ func (kubeAPI *KubernetesAPI) GetReplicaSets(ctx context.Context, namespace stri
|
|||
return replicaSetList.Items, nil
|
||||
}
|
||||
|
||||
// GetNamespaceWithExtensionLabel gets the namespace with the LinkerdExtensionLabel label value of `value`
|
||||
func (kubeAPI *KubernetesAPI) GetNamespaceWithExtensionLabel(ctx context.Context, value string) (*corev1.Namespace, error) {
|
||||
// GetAllNamespacesWithExtensionLabel gets all namespaces with the linkerd.io/extension label key
|
||||
func (kubeAPI *KubernetesAPI) GetAllNamespacesWithExtensionLabel(ctx context.Context) ([]corev1.Namespace, error) {
|
||||
namespaces, err := kubeAPI.CoreV1().Namespaces().List(ctx, metav1.ListOptions{LabelSelector: LinkerdExtensionLabel})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, ns := range namespaces.Items {
|
||||
|
||||
return namespaces.Items, nil
|
||||
}
|
||||
|
||||
// GetNamespaceWithExtensionLabel gets the namespace with the LinkerdExtensionLabel label value of `value`
|
||||
func (kubeAPI *KubernetesAPI) GetNamespaceWithExtensionLabel(ctx context.Context, value string) (*corev1.Namespace, error) {
|
||||
namespaces, err := kubeAPI.GetAllNamespacesWithExtensionLabel(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ns := range namespaces {
|
||||
if ns.Labels[LinkerdExtensionLabel] == value {
|
||||
return &ns, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,9 @@ var (
|
|||
|
||||
//skippedInboundPorts lists some ports to be marked as skipped, which will
|
||||
// be verified in test/integration/inject
|
||||
skippedInboundPorts = "1234,5678"
|
||||
skippedInboundPorts = "1234,5678"
|
||||
multiclusterExtensionName = "linkerd-multicluster"
|
||||
vizExtensionName = "linkerd-viz"
|
||||
)
|
||||
|
||||
//////////////////////
|
||||
|
|
@ -537,6 +539,7 @@ func TestInstallMulticluster(t *testing.T) {
|
|||
testutil.AnnotatedFatalf(t, "'helm install' command failed",
|
||||
"'helm install' command failed\n%s\n%s", stdout, stderr)
|
||||
}
|
||||
TestHelper.AddInstalledExtension(multiclusterExtensionName)
|
||||
} else if TestHelper.Multicluster() {
|
||||
exec := append([]string{"multicluster"}, []string{
|
||||
"install",
|
||||
|
|
@ -552,6 +555,7 @@ func TestInstallMulticluster(t *testing.T) {
|
|||
testutil.AnnotatedFatalf(t, "'kubectl apply' command failed",
|
||||
"'kubectl apply' command failed\n%s", out)
|
||||
}
|
||||
TestHelper.AddInstalledExtension(multiclusterExtensionName)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -625,6 +629,7 @@ func TestUpgradeHelm(t *testing.T) {
|
|||
testutil.AnnotatedFatalf(t, "'helm install' command failed",
|
||||
"'helm install' command failed\n%s\n%s", stdout, stderr)
|
||||
}
|
||||
TestHelper.AddInstalledExtension(vizExtensionName)
|
||||
}
|
||||
|
||||
func TestRetrieveUidPostUpgrade(t *testing.T) {
|
||||
|
|
@ -730,7 +735,8 @@ func TestVersionPostInstall(t *testing.T) {
|
|||
func testCheckCommand(t *testing.T, stage string, expectedVersion string, namespace string, cliVersionOverride string, compareOutput bool) {
|
||||
var cmd []string
|
||||
var golden string
|
||||
if stage == "proxy" {
|
||||
proxyStage := "proxy"
|
||||
if stage == proxyStage {
|
||||
cmd = []string{"check", "--proxy", "--expected-version", expectedVersion, "--namespace", namespace, "--wait=0"}
|
||||
// if TestHelper.GetMulticlusterHelmReleaseName() != "" || TestHelper.Multicluster() {
|
||||
// golden = "check.multicluster.proxy.golden"
|
||||
|
|
@ -771,11 +777,34 @@ func testCheckCommand(t *testing.T, stage string, expectedVersion string, namesp
|
|||
return nil
|
||||
}
|
||||
|
||||
err = TestHelper.ValidateOutput(out, golden)
|
||||
err = TestHelper.ContainsOutput(out, golden)
|
||||
if err != nil {
|
||||
return fmt.Errorf("received unexpected output\n%s", err.Error())
|
||||
}
|
||||
|
||||
for _, ext := range TestHelper.GetInstalledExtensions() {
|
||||
if ext == multiclusterExtensionName {
|
||||
// multicluster check --proxy and multicluster check have the same output
|
||||
// so use the same golden file.
|
||||
err = TestHelper.ContainsOutput(out, "check.multicluster.golden")
|
||||
if err != nil {
|
||||
return fmt.Errorf("received unexpected output\n%s", err.Error())
|
||||
}
|
||||
} else if ext == vizExtensionName {
|
||||
if stage == proxyStage {
|
||||
err = TestHelper.ContainsOutput(out, "check.viz.proxy.golden")
|
||||
if err != nil {
|
||||
return fmt.Errorf("received unexpected output\n%s", err.Error())
|
||||
}
|
||||
} else {
|
||||
err = TestHelper.ContainsOutput(out, "check.viz.golden")
|
||||
if err != nil {
|
||||
return fmt.Errorf("received unexpected output\n%s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,3 @@
|
|||
kubernetes-api
|
||||
--------------
|
||||
√ can initialize the client
|
||||
√ can query the Kubernetes API
|
||||
|
||||
linkerd-existence
|
||||
-----------------
|
||||
√ 'linkerd-config' config map exists
|
||||
√ heartbeat ServiceAccount exist
|
||||
√ control plane replica sets are ready
|
||||
√ no unschedulable pods
|
||||
√ controller pod is running
|
||||
√ can initialize the client
|
||||
√ can query the control plane API
|
||||
|
||||
linkerd-multicluster
|
||||
--------------------
|
||||
√ Link CRD exists
|
||||
|
|
|
|||
|
|
@ -1,18 +1,3 @@
|
|||
kubernetes-api
|
||||
--------------
|
||||
√ can initialize the client
|
||||
√ can query the Kubernetes API
|
||||
|
||||
linkerd-existence
|
||||
-----------------
|
||||
√ 'linkerd-config' config map exists
|
||||
√ heartbeat ServiceAccount exist
|
||||
√ control plane replica sets are ready
|
||||
√ no unschedulable pods
|
||||
√ controller pod is running
|
||||
√ can initialize the client
|
||||
√ can query the control plane API
|
||||
|
||||
linkerd-viz
|
||||
-----------
|
||||
√ linkerd-viz Namespace exists
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
linkerd-viz
|
||||
-----------
|
||||
√ linkerd-viz Namespace exists
|
||||
√ linkerd-viz ClusterRoles exist
|
||||
√ linkerd-viz ClusterRoleBindings exist
|
||||
√ tap API server has valid cert
|
||||
√ tap API server cert is valid for at least 60 days
|
||||
√ tap API service is running
|
||||
√ linkerd-viz pods are injected
|
||||
√ viz extension pods are running
|
||||
√ prometheus is installed and configured correctly
|
||||
√ grafana is installed and configured correctly
|
||||
√ can initialize the client
|
||||
√ viz extension self-check
|
||||
|
||||
linkerd-viz-data-plane
|
||||
----------------------
|
||||
√ data plane namespace exists
|
||||
√ data plane proxy metrics are present in Prometheus
|
||||
√ data-plane pods have tap enabled
|
||||
|
||||
Status check results are √
|
||||
|
|
@ -1,18 +1,3 @@
|
|||
kubernetes-api
|
||||
--------------
|
||||
√ can initialize the client
|
||||
√ can query the Kubernetes API
|
||||
|
||||
linkerd-existence
|
||||
-----------------
|
||||
√ 'linkerd-config' config map exists
|
||||
√ heartbeat ServiceAccount exist
|
||||
√ control plane replica sets are ready
|
||||
√ no unschedulable pods
|
||||
√ controller pod is running
|
||||
√ can initialize the client
|
||||
√ can query the control plane API
|
||||
|
||||
linkerd-jaeger
|
||||
--------------
|
||||
√ linkerd-jaeger extension Namespace exists
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ type TestHelper struct {
|
|||
httpClient http.Client
|
||||
KubernetesHelper
|
||||
helm
|
||||
installedExtensions []string
|
||||
}
|
||||
|
||||
type helm struct {
|
||||
|
|
@ -354,6 +355,18 @@ func (h *TestHelper) Calico() bool {
|
|||
return h.calico
|
||||
}
|
||||
|
||||
// AddInstalledExtension adds an extension name to installedExtensions to
|
||||
// track the currently installed linkerd extensions.
|
||||
func (h *TestHelper) AddInstalledExtension(extensionName string) {
|
||||
h.installedExtensions = append(h.installedExtensions, extensionName)
|
||||
}
|
||||
|
||||
// GetInstalledExtensions gets a list currently installed extensions
|
||||
// in a test run.
|
||||
func (h *TestHelper) GetInstalledExtensions() []string {
|
||||
return h.installedExtensions
|
||||
}
|
||||
|
||||
// CreateTLSSecret creates a TLS Kubernetes secret
|
||||
func (h *TestHelper) CreateTLSSecret(name, root, cert, key string) error {
|
||||
secret := fmt.Sprintf(`
|
||||
|
|
@ -527,6 +540,22 @@ func (h *TestHelper) ValidateOutput(out, fixtureFile string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ContainsOutput validates that a string is a substring in the contents
|
||||
// of a file in the test's testdata directory.
|
||||
func (h *TestHelper) ContainsOutput(out, fixtureFile string) error {
|
||||
expected, err := ReadFile("testdata/" + fixtureFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !strings.Contains(out, expected) {
|
||||
return fmt.Errorf(
|
||||
"Expected:\n%s\nActual:\n%s", expected, out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckVersion validates the output of the "linkerd version" command.
|
||||
func (h *TestHelper) CheckVersion(serverVersion string) error {
|
||||
out, err := h.LinkerdRun("version")
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ func (options *checkOptions) validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func newCmdCheck() *cobra.Command {
|
||||
// NewCmdCheck generates a new cobra command for the viz extension.
|
||||
func NewCmdCheck() *cobra.Command {
|
||||
options := newCheckOptions()
|
||||
cmd := &cobra.Command{
|
||||
Use: "check [flags]",
|
||||
|
|
@ -52,10 +53,10 @@ code.`,
|
|||
},
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().StringVarP(&options.output, "output", "o", options.output, "Output format. One of: basic, json")
|
||||
cmd.PersistentFlags().BoolVar(&options.proxy, "proxy", options.proxy, "Also run data-plane checks, to determine if the data plane is healthy")
|
||||
cmd.PersistentFlags().DurationVar(&options.wait, "wait", options.wait, "Maximum allowed time for all tests to pass")
|
||||
cmd.PersistentFlags().StringVarP(&options.namespace, "namespace", "n", options.namespace, "Namespace to use for --proxy checks (default: all namespaces)")
|
||||
cmd.Flags().StringVarP(&options.output, "output", "o", options.output, "Output format. One of: basic, json")
|
||||
cmd.Flags().BoolVar(&options.proxy, "proxy", options.proxy, "Also run data-plane checks, to determine if the data plane is healthy")
|
||||
cmd.Flags().DurationVar(&options.wait, "wait", options.wait, "Maximum allowed time for all tests to pass")
|
||||
cmd.Flags().StringVarP(&options.namespace, "namespace", "n", options.namespace, "Namespace to use for --proxy checks (default: all namespaces)")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
@ -65,12 +66,7 @@ func configureAndRunChecks(wout io.Writer, werr io.Writer, options *checkOptions
|
|||
return fmt.Errorf("Validation error when executing check command: %v", err)
|
||||
}
|
||||
|
||||
checks := []healthcheck.CategoryID{
|
||||
healthcheck.KubernetesAPIChecks,
|
||||
healthcheck.LinkerdControlPlaneExistenceChecks,
|
||||
}
|
||||
|
||||
hc := vizHealthCheck.NewHealthChecker(checks, &healthcheck.Options{
|
||||
hc := vizHealthCheck.NewHealthChecker([]healthcheck.CategoryID{}, &healthcheck.Options{
|
||||
ControlPlaneNamespace: controlPlaneNamespace,
|
||||
KubeConfig: kubeconfigPath,
|
||||
KubeContext: kubeContext,
|
||||
|
|
@ -80,6 +76,12 @@ func configureAndRunChecks(wout io.Writer, werr io.Writer, options *checkOptions
|
|||
RetryDeadline: time.Now().Add(options.wait),
|
||||
DataPlaneNamespace: options.namespace,
|
||||
})
|
||||
err = hc.InitializeKubeAPIClient()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error initializing k8s API client: %s", err)
|
||||
fmt.Fprintln(werr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
hc.AppendCategories(hc.VizCategory())
|
||||
if options.proxy {
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@ func NewCmdViz() *cobra.Command {
|
|||
vizCmd.AddCommand(NewCmdEdges())
|
||||
vizCmd.AddCommand(NewCmdDashboard())
|
||||
vizCmd.AddCommand(newCmdUninstall())
|
||||
vizCmd.AddCommand(newCmdCheck())
|
||||
vizCmd.AddCommand(newCmdProfile())
|
||||
vizCmd.AddCommand(NewCmdCheck())
|
||||
|
||||
return vizCmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// VizExtensionName is the name of the viz extension
|
||||
VizExtensionName = "linkerd-viz"
|
||||
|
||||
// LinkerdVizExtensionCheck adds checks related to the Linkerd Viz extension
|
||||
LinkerdVizExtensionCheck healthcheck.CategoryID = "linkerd-viz"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue