Skip to content

Commit

Permalink
Validate cluster and machine objects
Browse files Browse the repository at this point in the history
  • Loading branch information
wangzhen127 committed Jul 24, 2018
1 parent 754ae19 commit 4f7bdf7
Show file tree
Hide file tree
Showing 10 changed files with 761 additions and 34 deletions.
40 changes: 36 additions & 4 deletions clusterctl/cmd/validate_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,58 @@ limitations under the License.
package cmd

import (
"github.com/golang/glog"
"fmt"
"os"

"github.com/spf13/cobra"
"sigs.k8s.io/cluster-api/pkg/errors"
tcmd "k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/cluster-api/clusterctl/validation"
"sigs.k8s.io/cluster-api/pkg/clientcmd"
)

type ValidateClusterOptions struct {
Kubeconfig string
KubeconfigOverrides tcmd.ConfigOverrides
}

var vco = &ValidateClusterOptions{}

var validateClusterCmd = &cobra.Command{
Use: "cluster",
Short: "Validate a cluster created by cluster API.",
Long: `Validate a cluster created by cluster API.`,
Run: func(cmd *cobra.Command, args []string) {
if err := RunValidateCluster(); err != nil {
glog.Exit(err)
os.Stdout.Sync()
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
os.Exit(1)
}
},
}

func init() {
validateClusterCmd.Flags().StringVarP(
&vco.Kubeconfig, "kubeconfig", "", "",
"The file path of the kubeconfig file for the cluster to validate.. If not specified, $KUBECONFIG environment variable or ${HOME}/.kube/config is used.")
// BindContextFlags will bind the flags cluster, namespace, and user
tcmd.BindContextFlags(&do.KubeconfigOverrides.Context, validateClusterCmd.Flags(), tcmd.RecommendedContextOverrideFlags(""))
validateCmd.AddCommand(validateClusterCmd)
}

func RunValidateCluster() error {
return errors.NotImplementedError
clusterApiClient, err := clientcmd.NewClusterApiClientForDefaultSearchPath(vco.Kubeconfig, vco.KubeconfigOverrides)
if err != nil {
return fmt.Errorf("failed to create cluster API client: %v", err)
}
k8sClient, err := clientcmd.NewCoreClientSetForDefaultSearchPath(vco.Kubeconfig, vco.KubeconfigOverrides)
if err != nil {
return fmt.Errorf("failed to create kubernetes client: %v", err)
}

if err = validation.ValidateClusterAPIObjects(os.Stdout, clusterApiClient, k8sClient, vco.KubeconfigOverrides.Context.Cluster, vco.KubeconfigOverrides.Context.Namespace); err != nil {
return err
}

// TODO(wangzhen127): Also validate the cluster in addition to the cluster API objects. https://github.com/kubernetes-sigs/cluster-api/issues/168
return nil
}
30 changes: 0 additions & 30 deletions clusterctl/cmd/validate_cluster_test.go

This file was deleted.

1 change: 1 addition & 0 deletions clusterctl/main_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func TestEmptyAndInvalidArgs(t *testing.T) {
{"delete cluster with no arguments with invalid flag", []string{"delete", "cluster", "--invalid-flag"}, 1, "delete-cluster-no-args-invalid-flag.golden"},
{"validate with no arguments", []string{"validate"}, 0, "validate-no-args.golden"},
{"validate with no arguments with invalid flag", []string{"validate", "--invalid-flag"}, 1, "validate-no-args-invalid-flag.golden"},
{"validate cluster with no arguments with invalid flag", []string{"validate", "cluster", "--invalid-flag"}, 1, "validate-cluster-no-args-invalid-flag.golden"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
Expand Down
22 changes: 22 additions & 0 deletions clusterctl/testdata/validate-cluster-no-args-invalid-flag.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Error: unknown flag: --invalid-flag
Usage:
clusterctl validate cluster [flags]

Flags:
--cluster string The name of the kubeconfig cluster to use
-h, --help help for cluster
--kubeconfig string The file path of the kubeconfig file for the cluster to validate.. If not specified, $KUBECONFIG environment variable or ${HOME}/.kube/config is used.
-n, --namespace string If present, the namespace scope for this CLI request
--user string The name of the kubeconfig user to use

Global Flags:
--alsologtostderr log to standard error as well as files
--log-flush-frequency duration Maximum number of seconds between log flushes (default 5s)
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
--log_dir string If non-empty, write log files in this directory
--logtostderr log to standard error instead of files (default true)
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
-v, --v Level log level for V logs
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging

unknown flag: --invalid-flag
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Validating Cluster API objects in namespace "validate-cluster-api-object-output"
Checking cluster object "test-cluster"... FAIL
[CreateError]: Failed to create cluster
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Validating Cluster API objects in namespace "validate-cluster-api-object-output"
Checking cluster object "test-cluster"... PASS
Checking machine object "test-machine1"... FAIL
[CreateError]: Failed to create machine
Checking machine object "test-machine2"... FAIL
The corresponding node is missing.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Validating Cluster API objects in namespace "validate-cluster-api-object-output"
Checking cluster object "test-cluster"... PASS
Checking machine object "test-machine1"... FAIL
The corresponding node "test-node-not-ready" is not ready.
Checking machine object "test-machine2"... FAIL
The corresponding node "test-node-not-exist" is not found: nodes "test-node-not-exist" not found
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Validating Cluster API objects in namespace "validate-cluster-api-object-output"
Checking cluster object "test-cluster"... PASS
Checking machine object "test-machine1"... PASS
Checking machine object "test-machine2"... PASS
134 changes: 134 additions & 0 deletions clusterctl/validation/validate_cluster_api_objects.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
Copyright 2018 The Kubernetes 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 validation

import (
"fmt"
"io"

meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/cluster-api/pkg/apis/cluster/common"
"sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1"
"sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset"
"sigs.k8s.io/cluster-api/pkg/controller/noderefutil"
)

func ValidateClusterAPIObjects(w io.Writer, clusterApiClient *clientset.Clientset, k8sClient kubernetes.Interface, clusterName string, namespace string) error {
fmt.Fprintf(w, "Validating Cluster API objects in namespace %q\n", namespace)

cluster, err := getClusterObject(clusterApiClient, clusterName, namespace)
if err != nil {
return err
}
if err := validateClusterObject(w, cluster); err != nil {
return err
}

machines, err := clusterApiClient.ClusterV1alpha1().Machines(namespace).List(meta_v1.ListOptions{})
if err != nil {
return fmt.Errorf("failed to get the machines from the apiserver in namespace %q: %v", namespace, err)
}

return validateMachineObjects(w, machines, k8sClient)
}

func getClusterObject(clusterApiClient *clientset.Clientset, clusterName string, namespace string) (*v1alpha1.Cluster, error) {
if clusterName != "" {
cluster, err := clusterApiClient.ClusterV1alpha1().Clusters(namespace).Get(clusterName, meta_v1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to get the cluster %q from the apiserver in namespace %q: %v", clusterName, namespace, err)
}
return cluster, nil
}

clusters, err := clusterApiClient.ClusterV1alpha1().Clusters(namespace).List(meta_v1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("failed to get the clusters from the apiserver in namespace %q: %v", namespace, err)
}
if numOfClusters := len(clusters.Items); numOfClusters == 0 {
return nil, fmt.Errorf("fail: No cluster exists in namespace %q.", namespace)
} else if numOfClusters > 1 {
return nil, fmt.Errorf("fail: There is more than one cluster in namespace %q. Please specify --cluster-name.", namespace)
}
return &clusters.Items[0], nil
}

func validateClusterObject(w io.Writer, cluster *v1alpha1.Cluster) error {
fmt.Fprintf(w, "Checking cluster object %q... ", cluster.Name)
if cluster.Status.ErrorReason != "" || cluster.Status.ErrorMessage != "" {
fmt.Fprintf(w, "FAIL\n")
fmt.Fprintf(w, "\t[%v]: %s\n", cluster.Status.ErrorReason, cluster.Status.ErrorMessage)
return fmt.Errorf("Cluster %q failed the validation.", cluster.Name)
}
fmt.Fprintf(w, "PASS\n")
return nil
}

func validateMachineObjects(w io.Writer, machines *v1alpha1.MachineList, k8sClient kubernetes.Interface) error {
pass := true
for _, machine := range machines.Items {
if (!validateMachineObject(w, machine, k8sClient)) {
pass = false
}
}
if !pass {
return fmt.Errorf("Machine objects failed the validation.")
}
return nil
}

func validateMachineObject(w io.Writer, machine v1alpha1.Machine, k8sClient kubernetes.Interface) bool {
fmt.Fprintf(w, "Checking machine object %q... ", machine.Name)
if machine.Status.ErrorReason != nil || machine.Status.ErrorMessage != nil {
var reason common.MachineStatusError = ""
if machine.Status.ErrorReason != nil {
reason = *machine.Status.ErrorReason
}
var message string = ""
if machine.Status.ErrorMessage != nil {
message = *machine.Status.ErrorMessage
}
fmt.Fprintf(w, "FAIL\n")
fmt.Fprintf(w, "\t[%v]: %s\n", reason, message)
return false
}
if machine.Status.NodeRef == nil {
fmt.Fprintf(w, "FAIL\n")
fmt.Fprintf(w, "\tThe corresponding node is missing.\n")
return false
}
err := validateReferredNode(w, machine.Status.NodeRef.Name, k8sClient)
if err != nil {
fmt.Fprintf(w, "FAIL\n")
fmt.Fprintf(w, "\t%v\n", err)
return false
}
fmt.Fprintf(w, "PASS\n")
return true
}

func validateReferredNode(w io.Writer, nodeName string, k8sClient kubernetes.Interface) error {
node, err := k8sClient.CoreV1().Nodes().Get(nodeName, meta_v1.GetOptions{})
if err != nil {
return fmt.Errorf("The corresponding node %q is not found: %v", nodeName, err)
}
if !noderefutil.IsNodeReady(node) {
return fmt.Errorf("The corresponding node %q is not ready.", nodeName)
}
return nil
}
Loading

0 comments on commit 4f7bdf7

Please sign in to comment.