Merge pull request #4107 from chrislovecnm/validate-ouput

kops validate cluster can output YAML or JSON
This commit is contained in:
k8s-ci-robot 2018-01-08 02:47:11 -08:00 committed by GitHub
commit b8a701b2da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 121 additions and 29 deletions

View File

@ -17,12 +17,14 @@ limitations under the License.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
"runtime"
"strings"
"github.com/ghodss/yaml"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/api/core/v1"
@ -45,11 +47,16 @@ func init() {
}
type ValidateClusterOptions struct {
// No options yet
output string
}
func (o *ValidateClusterOptions) InitDefaults() {
o.output = OutputTable
}
func NewCmdValidateCluster(f *util.Factory, out io.Writer) *cobra.Command {
options := &ValidateClusterOptions{}
options.InitDefaults()
cmd := &cobra.Command{
Use: "cluster",
@ -64,6 +71,8 @@ func NewCmdValidateCluster(f *util.Factory, out io.Writer) *cobra.Command {
},
}
cmd.Flags().StringVarP(&options.output, "output", "o", options.output, "Ouput format. One of json|yaml|table.")
return cmd
}
@ -88,7 +97,9 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
return fmt.Errorf("cannot get InstanceGroups for %q: %v", cluster.ObjectMeta.Name, err)
}
fmt.Fprintf(out, "Validating cluster %v\n\n", cluster.ObjectMeta.Name)
if options.output == OutputTable {
fmt.Fprintf(out, "Validating cluster %v\n\n", cluster.ObjectMeta.Name)
}
var instanceGroups []api.InstanceGroup
for _, ig := range list.Items {
@ -116,30 +127,60 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
// Do not use if we are running gossip
if !dns.IsGossipHostname(cluster.ObjectMeta.Name) {
// TODO we may want to return validation.ValidationCluster instead of building it later on
hasPlaceHolderIPAddress, err := validation.HasPlaceHolderIP(contextName)
if err != nil {
return err
}
if hasPlaceHolderIPAddress {
fmt.Println(
"Validation Failed\n\n" +
"The dns-controller Kubernetes deployment has not updated the Kubernetes cluster's API DNS entry to the correct IP address." +
" The API DNS IP address is the placeholder address that kops creates: 203.0.113.123." +
" Please wait about 5-10 minutes for a master to start, dns-controller to launch, and DNS to propagate." +
" The protokube container and dns-controller deployment logs may contain more diagnostic information." +
" Etcd and the API DNS entries must be updated for a kops Kubernetes cluster to start.")
return fmt.Errorf("\nCannot reach cluster's API server: unable to Validate Cluster: %s", cluster.ObjectMeta.Name)
message := "Validation Failed\n\n" +
"The dns-controller Kubernetes deployment has not updated the Kubernetes cluster's API DNS entry to the correct IP address." +
" The API DNS IP address is the placeholder address that kops creates: 203.0.113.123." +
" Please wait about 5-10 minutes for a master to start, dns-controller to launch, and DNS to propagate." +
" The protokube container and dns-controller deployment logs may contain more diagnostic information." +
" Etcd and the API DNS entries must be updated for a kops Kubernetes cluster to start."
validationCluster := &validation.ValidationCluster{
ClusterName: cluster.ObjectMeta.Name,
ErrorMessage: message,
Status: validation.ClusterValidationFailed,
}
validationFailed := fmt.Errorf("\nCannot reach cluster's API server: unable to Validate Cluster: %s", cluster.ObjectMeta.Name)
switch options.output {
case OutputTable:
fmt.Println(message)
return validationFailed
case OutputYaml:
return validateClusterOutputYAML(validationCluster, validationFailed, out)
case OutputJSON:
return validateClusterOutputJSON(validationCluster, validationFailed, out)
default:
return fmt.Errorf("Unknown output format: %q", options.output)
}
}
}
validationCluster, validationFailed := validation.ValidateCluster(cluster.ObjectMeta.Name, list, k8sClient)
if validationCluster == nil || validationCluster.NodeList == nil || validationCluster.NodeList.Items == nil {
// validationFailed error is already formatted
return validationFailed
}
switch options.output {
case OutputTable:
return validateClusterOutputTable(validationCluster, validationFailed, instanceGroups, out)
case OutputYaml:
return validateClusterOutputYAML(validationCluster, validationFailed, out)
case OutputJSON:
return validateClusterOutputJSON(validationCluster, validationFailed, out)
default:
return fmt.Errorf("Unknown output format: %q", options.output)
}
}
func validateClusterOutputTable(validationCluster *validation.ValidationCluster, validationFailed error, instanceGroups []api.InstanceGroup, out io.Writer) error {
t := &tables.Table{}
t.AddColumn("NAME", func(c api.InstanceGroup) string {
return c.ObjectMeta.Name
@ -161,10 +202,10 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
})
fmt.Fprintln(out, "INSTANCE GROUPS")
err = t.Render(instanceGroups, out, "NAME", "ROLE", "MACHINETYPE", "MIN", "MAX", "SUBNETS")
err := t.Render(instanceGroups, out, "NAME", "ROLE", "MACHINETYPE", "MIN", "MAX", "SUBNETS")
if err != nil {
return fmt.Errorf("cannot render nodes for %q: %v", cluster.ObjectMeta.Name, err)
return fmt.Errorf("cannot render nodes for %q: %v", validationCluster.ClusterName, err)
}
nodeTable := &tables.Table{}
@ -191,7 +232,7 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
err = nodeTable.Render(validationCluster.NodeList.Items, out, "NAME", "ROLE", "READY")
if err != nil {
return fmt.Errorf("cannot render nodes for %q: %v", cluster.ObjectMeta.Name, err)
return fmt.Errorf("cannot render nodes for %q: %v", validationCluster.ClusterName, err)
}
if len(validationCluster.ComponentFailures) != 0 {
@ -204,7 +245,7 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
err = componentFailuresTable.Render(validationCluster.ComponentFailures, out, "NAME")
if err != nil {
return fmt.Errorf("cannot render components for %q: %v", cluster.ObjectMeta.Name, err)
return fmt.Errorf("cannot render components for %q: %v", validationCluster.ClusterName, err)
}
}
@ -218,12 +259,12 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
err = podFailuresTable.Render(validationCluster.PodFailures, out, "NAME")
if err != nil {
return fmt.Errorf("cannot render pods for %q: %v", cluster.ObjectMeta.Name, err)
return fmt.Errorf("cannot render pods for %q: %v", validationCluster.ClusterName, err)
}
}
if validationFailed == nil {
fmt.Fprintf(out, "\nYour cluster %s is ready\n", cluster.ObjectMeta.Name)
fmt.Fprintf(out, "\nYour cluster %s is ready\n", validationCluster.ClusterName)
return nil
} else {
// do we need to print which instance group is not ready?
@ -234,3 +275,26 @@ func RunValidateCluster(f *util.Factory, cmd *cobra.Command, args []string, out
return validationFailed
}
}
func validateClusterOutputYAML(validationCluster *validation.ValidationCluster, validationFailed error, out io.Writer) error {
y, err := yaml.Marshal(validationCluster)
if err != nil {
return fmt.Errorf("unable to marshall YAML: %v\n", err)
}
return validateOutput(y, validationFailed, out)
}
func validateClusterOutputJSON(validationCluster *validation.ValidationCluster, validationFailed error, out io.Writer) error {
j, err := json.Marshal(validationCluster)
if err != nil {
return fmt.Errorf("unable to marshall JSON: %v\n", err)
}
return validateOutput(j, validationFailed, out)
}
func validateOutput(b []byte, validationFailed error, out io.Writer) error {
if _, err := out.Write(b); err != nil {
return fmt.Errorf("unable to print data: %v\n", err)
}
return validationFailed
}

View File

@ -28,6 +28,12 @@ kops validate cluster
kops validate cluster
```
### Options
```
-o, --output string Ouput format. One of json|yaml|table. (default "table")
```
### Options inherited from parent commands
```

View File

@ -31,7 +31,7 @@ import (
"k8s.io/kops/pkg/apis/kops/util"
)
// A cluster to validate
// ValidationCluster a cluster to validate.
type ValidationCluster struct {
MastersReady bool `json:"mastersReady,omitempty"`
MastersReadyArray []*ValidationNode `json:"mastersReadyArray,omitempty"`
@ -45,11 +45,15 @@ type ValidationCluster struct {
NodeList *v1.NodeList `json:"nodeList,omitempty"`
ComponentFailures []string `json:"componentFailures,omitempty"`
PodFailures []string `json:"podFailures,omitempty"`
ComponentFailures []string `json:"componentFailures,omitempty"`
PodFailures []string `json:"podFailures,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
Status string `json:"status"`
ClusterName string `json:"clusterName"`
InstanceGroups []*kops.InstanceGroup `json:"instanceGroups,omitempty"`
}
// A K8s node to be validated
// ValidationNode is A K8s node to be validated.
type ValidationNode struct {
Zone string `json:"zone,omitempty"`
Role string `json:"role,omitempty"`
@ -57,7 +61,12 @@ type ValidationNode struct {
Status v1.ConditionStatus `json:"status,omitempty"`
}
// HasPlaceHolderIP checks if the API DNS has been updated
const (
ClusterValidationFailed = "FAILED"
ClusterValidationPassed = "PASSED"
)
// HasPlaceHolderIP checks if the API DNS has been updated.
func HasPlaceHolderIP(clusterName string) (bool, error) {
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
@ -86,7 +95,6 @@ func HasPlaceHolderIP(clusterName string) (bool, error) {
// ValidateCluster validate a k8s cluster with a provided instance group list
func ValidateCluster(clusterName string, instanceGroupList *kops.InstanceGroupList, clusterKubernetesClient kubernetes.Interface) (*ValidationCluster, error) {
var instanceGroups []*kops.InstanceGroup
validationCluster := &ValidationCluster{}
for i := range instanceGroupList.Items {
ig := &instanceGroupList.Items[i]
@ -94,7 +102,7 @@ func ValidateCluster(clusterName string, instanceGroupList *kops.InstanceGroupLi
}
if len(instanceGroups) == 0 {
return validationCluster, fmt.Errorf("no InstanceGroup objects found")
return nil, fmt.Errorf("no InstanceGroup objects found")
}
timeout, err := time.ParseDuration("10s")
@ -107,6 +115,12 @@ func ValidateCluster(clusterName string, instanceGroupList *kops.InstanceGroupLi
return nil, fmt.Errorf("error building node adapter for %q: %v", clusterName, err)
}
validationCluster := &ValidationCluster{
ClusterName: clusterName,
ErrorMessage: ClusterValidationPassed,
InstanceGroups: instanceGroups,
}
validationCluster.NodeList, err = nodeAA.GetAllNodes()
if err != nil {
return nil, fmt.Errorf("cannot get nodes for %q: %v", clusterName, err)
@ -161,7 +175,7 @@ func validateTheNodes(clusterName string, validationCluster *ValidationCluster)
nodes := validationCluster.NodeList
if nodes == nil || len(nodes.Items) == 0 {
return validationCluster, fmt.Errorf("No nodes found in validationCluster")
return nil, fmt.Errorf("No nodes found in validationCluster")
}
// Needed for when NodesCount and MastersCounts are predefined, i.e tests
presetNodeCount := validationCluster.NodesCount == 0
@ -217,19 +231,27 @@ func validateTheNodes(clusterName string, validationCluster *ValidationCluster)
}
if !validationCluster.MastersReady {
return validationCluster, fmt.Errorf("your masters are NOT ready %s", clusterName)
validationCluster.Status = ClusterValidationFailed
validationCluster.ErrorMessage = fmt.Sprintf("your masters are NOT ready %s", clusterName)
return validationCluster, fmt.Errorf(validationCluster.ErrorMessage)
}
if !validationCluster.NodesReady {
return validationCluster, fmt.Errorf("your nodes are NOT ready %s", clusterName)
validationCluster.Status = ClusterValidationFailed
validationCluster.ErrorMessage = fmt.Sprintf("your nodes are NOT ready %s", clusterName)
return validationCluster, fmt.Errorf(validationCluster.ErrorMessage)
}
if len(validationCluster.ComponentFailures) != 0 {
return validationCluster, fmt.Errorf("your components are NOT healthy %s", clusterName)
validationCluster.Status = ClusterValidationFailed
validationCluster.ErrorMessage = fmt.Sprintf("your components are NOT healthy %s", clusterName)
return validationCluster, fmt.Errorf(validationCluster.ErrorMessage)
}
if len(validationCluster.PodFailures) != 0 {
return validationCluster, fmt.Errorf("your kube-system pods are NOT healthy %s", clusterName)
validationCluster.Status = ClusterValidationFailed
validationCluster.ErrorMessage = fmt.Sprintf("your kube-system pods are NOT healthy %s", clusterName)
return validationCluster, fmt.Errorf(validationCluster.ErrorMessage)
}
return validationCluster, nil