pkg/cmd/experimental/ipfailover/ipfailover.go
c3904e81
 package ipfailover
2af8e2d2
 
 import (
 	"fmt"
 	"io"
9cf65112
 	"os"
04e3e875
 	"strings"
2af8e2d2
 
 	"github.com/spf13/cobra"
 
04e3e875
 	kapi "k8s.io/kubernetes/pkg/api"
 	"k8s.io/kubernetes/pkg/api/errors"
 	kclient "k8s.io/kubernetes/pkg/client/unversioned"
9cf65112
 	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
04e3e875
 	"k8s.io/kubernetes/pkg/runtime"
9cf65112
 
04e3e875
 	"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
6267dded
 	"github.com/openshift/origin/pkg/cmd/templates"
9cf65112
 	cmdutil "github.com/openshift/origin/pkg/cmd/util"
2af8e2d2
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
 	"github.com/openshift/origin/pkg/cmd/util/variable"
9cf65112
 	configcmd "github.com/openshift/origin/pkg/config/cmd"
de67a260
 	"github.com/openshift/origin/pkg/ipfailover"
c3904e81
 	"github.com/openshift/origin/pkg/ipfailover/keepalived"
2af8e2d2
 )
 
6267dded
 var (
 	ipFailover_long = templates.LongDesc(`
 		Configure or view IP Failover configuration
2af8e2d2
 
6267dded
 		This command helps to setup an IP failover configuration for the
 		cluster. An administrator can configure IP failover on an entire
 		cluster or on a subset of nodes (as defined via a labeled selector).
2af8e2d2
 
6267dded
 		If an IP failover configuration does not exist with the given name,
 		the --create flag can be passed to create a deployment configuration that
 		will provide IP failover capability. If you are running in production, it is
 		recommended that the labeled selector for the nodes matches at least 2 nodes
 		to ensure you have failover protection, and that you provide a --replicas=<n>
 		value that matches the number of nodes for the given labeled selector.`)
2af8e2d2
 
6267dded
 	ipFailover_example = templates.Examples(`
 		# Check the default IP failover configuration ("ipfailover"):
 	  %[1]s %[2]s
2af8e2d2
 
6267dded
 	  # See what the IP failover configuration would look like if it is created:
 	  %[1]s %[2]s -o json
2af8e2d2
 
6267dded
 	  # Create an IP failover configuration if it does not already exist:
 	  %[1]s %[2]s ipf --virtual-ips="10.1.1.1-4" --create
2af8e2d2
 
6267dded
 	  # Create an IP failover configuration on a selection of nodes labeled
 	  # "router=us-west-ha" (on 4 nodes with 7 virtual IPs monitoring a service
 	  # listening on port 80, such as the router process).
 	  %[1]s %[2]s ipfailover --selector="router=us-west-ha" --virtual-ips="1.2.3.4,10.1.1.100-104,5.6.7.8" --watch-port=80 --replicas=4 --create
2af8e2d2
 
6267dded
 	  # Use a different IP failover config image and see the configuration:
 	  %[1]s %[2]s ipf-alt --selector="hagroup=us-west-ha" --virtual-ips="1.2.3.4" -o yaml --images=myrepo/myipfailover:mytag`)
1558f2d9
 )
2af8e2d2
 
9cf65112
 func NewCmdIPFailoverConfig(f *clientcmd.Factory, parentName, name string, out, errout io.Writer) *cobra.Command {
de67a260
 	options := &ipfailover.IPFailoverConfigCmdOptions{
9cf65112
 		Action: configcmd.BulkAction{
 			Out:    out,
 			ErrOut: errout,
 		},
2af8e2d2
 		ImageTemplate:    variable.NewDefaultImageTemplate(),
04e3e875
 		ServiceAccount:   "ipfailover",
de67a260
 		Selector:         ipfailover.DefaultSelector,
 		ServicePort:      ipfailover.DefaultServicePort,
 		WatchPort:        ipfailover.DefaultWatchPort,
 		NetworkInterface: ipfailover.DefaultInterface,
b908bb07
 		VRRPIDOffset:     0,
2af8e2d2
 		Replicas:         1,
 	}
 
 	cmd := &cobra.Command{
1558f2d9
 		Use:     fmt.Sprintf("%s [NAME]", name),
20b11602
 		Short:   "Install an IP failover group to a set of nodes",
1558f2d9
 		Long:    ipFailover_long,
 		Example: fmt.Sprintf(ipFailover_example, parentName, name),
2af8e2d2
 		Run: func(cmd *cobra.Command, args []string) {
9cf65112
 			err := Run(f, options, cmd, args)
 			if err == cmdutil.ErrExit {
 				os.Exit(1)
 			}
 			kcmdutil.CheckErr(err)
2af8e2d2
 		},
 	}
 
de67a260
 	cmd.Flags().StringVar(&options.Type, "type", ipfailover.DefaultType, "The type of IP failover configurator to use.")
c3904e81
 	cmd.Flags().StringVar(&options.ImageTemplate.Format, "images", options.ImageTemplate.Format, "The image to base this IP failover configurator on - ${component} will be replaced based on --type.")
2af8e2d2
 	cmd.Flags().BoolVar(&options.ImageTemplate.Latest, "latest-images", options.ImageTemplate.Latest, "If true, attempt to use the latest images instead of the current release")
 	cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter nodes on.")
 	cmd.Flags().StringVar(&options.Credentials, "credentials", "", "Path to a .kubeconfig file that will contain the credentials the router should use to contact the master.")
68925e88
 	cmd.Flags().StringVar(&options.ServiceAccount, "service-account", options.ServiceAccount, "Name of the service account to use to run the ipfailover pod.")
2af8e2d2
 
 	cmd.Flags().BoolVar(&options.Create, "create", options.Create, "Create the configuration if it does not exist.")
 
 	cmd.Flags().StringVar(&options.VirtualIPs, "virtual-ips", "", "A set of virtual IP ranges and/or addresses that the routers bind and serve on and provide IP failover capability for.")
 	cmd.Flags().StringVarP(&options.NetworkInterface, "interface", "i", "", "Network interface bound by VRRP to use for the set of virtual IP ranges/addresses specified.")
 
de67a260
 	cmd.Flags().IntVarP(&options.WatchPort, "watch-port", "w", ipfailover.DefaultWatchPort, "Port to monitor or watch for resource availability.")
b908bb07
 	cmd.Flags().IntVar(&options.VRRPIDOffset, "vrrp-id-offset", options.VRRPIDOffset, "Offset to use for setting ids of VRRP instances (default offset is 0). This allows multiple ipfailover instances to run within the same cluster.")
2429a35c
 	cmd.Flags().Int32VarP(&options.Replicas, "replicas", "r", options.Replicas, "The replication factor of this IP failover configuration; commonly 2 when high availability is desired. Please ensure this matches the number of nodes that satisfy the selector (or default selector) specified.")
2af8e2d2
 
ea725ace
 	// autocompletion hints
 	cmd.MarkFlagFilename("credentials", "kubeconfig")
04e3e875
 	cmd.Flags().MarkDeprecated("credentials", "use --service-account to specify the service account the ipfailover pod will use to make API calls")
ea725ace
 
9cf65112
 	options.Action.BindForOutput(cmd.Flags())
 	cmd.Flags().String("output-version", "", "The preferred API versions of the output objects")
 
2af8e2d2
 	return cmd
 }
 
de67a260
 //  Get configuration name - argv[1].
 func getConfigurationName(args []string) (string, error) {
 	name := ipfailover.DefaultName
8979efc8
 
 	switch len(args) {
 	case 0:
 		// Do nothing - use default name.
 	case 1:
 		name = args[0]
 	default:
de67a260
 		return "", fmt.Errorf("Please pass zero or one arguments to provide a name for this configuration.")
8979efc8
 	}
 
de67a260
 	return name, nil
8979efc8
 }
 
de67a260
 //  Get the configurator based on the ipfailover type.
9cf65112
 func getPlugin(name string, f *clientcmd.Factory, options *ipfailover.IPFailoverConfigCmdOptions) (ipfailover.IPFailoverConfiguratorPlugin, error) {
714a0078
 	if options.Type == ipfailover.DefaultType {
 		plugin, err := keepalived.NewIPFailoverConfiguratorPlugin(name, f, options)
 		if err != nil {
 			return nil, fmt.Errorf("IPFailoverConfigurator %q plugin error: %v", options.Type, err)
 		}
8979efc8
 
714a0078
 		return plugin, nil
8979efc8
 	}
 
714a0078
 	return nil, fmt.Errorf("No plugins available to handle type %q", options.Type)
8979efc8
 }
 
9cf65112
 // Run runs the ipfailover command.
 func Run(f *clientcmd.Factory, options *ipfailover.IPFailoverConfigCmdOptions, cmd *cobra.Command, args []string) error {
 	name, err := getConfigurationName(args)
2af8e2d2
 	if err != nil {
9cf65112
 		return err
2af8e2d2
 	}
 
04e3e875
 	if len(options.ServiceAccount) == 0 {
 		return fmt.Errorf("you must specify a service account for the ipfailover pod with --service-account, it cannot be blank")
 	}
 
9cf65112
 	options.Action.Bulk.Mapper = clientcmd.ResourceMapper(f)
 	options.Action.Bulk.Op = configcmd.Create
2af8e2d2
 
9cf65112
 	if err := ipfailover.ValidateCmdOptions(options); err != nil {
 		return err
de67a260
 	}
 
9cf65112
 	p, err := getPlugin(name, f, options)
de67a260
 	if err != nil {
 		return err
 	}
 
9cf65112
 	list, err := p.Generate()
de67a260
 	if err != nil {
 		return err
 	}
2af8e2d2
 
9cf65112
 	namespace, _, err := f.DefaultNamespace()
 	if err != nil {
de67a260
 		return err
2af8e2d2
 	}
04e3e875
 	_, kClient, err := f.Clients()
 	if err != nil {
 		return fmt.Errorf("error getting client: %v", err)
 	}
 	if err := validateServiceAccount(kClient, namespace, options.ServiceAccount); err != nil {
 		return fmt.Errorf("ipfailover could not be created; %v", err)
 	}
 
 	configList := []runtime.Object{
 		&kapi.ServiceAccount{ObjectMeta: kapi.ObjectMeta{Name: options.ServiceAccount}},
 	}
 
 	list.Items = append(configList, list.Items...)
 
 	if options.Action.ShouldPrint() {
 		mapper, _ := f.Object(false)
 		return cmdutil.VersionedPrintObject(f.PrintObject, cmd, mapper, options.Action.Out)(list)
 	}
2af8e2d2
 
9cf65112
 	if errs := options.Action.WithMessage(fmt.Sprintf("Creating IP failover %s", name), "created").Run(list, namespace); len(errs) > 0 {
 		return cmdutil.ErrExit
2af8e2d2
 	}
de67a260
 	return nil
2af8e2d2
 }
04e3e875
 
 func validateServiceAccount(client *kclient.Client, ns string, serviceAccount string) error {
 
 	sccList, err := client.SecurityContextConstraints().List(kapi.ListOptions{})
 	if err != nil {
 		if !errors.IsUnauthorized(err) {
 			return fmt.Errorf("could not retrieve list of security constraints to verify service account %q: %v", serviceAccount, err)
 		}
 	}
 
 	for _, scc := range sccList.Items {
 		if scc.AllowPrivilegedContainer {
 			for _, user := range scc.Users {
 				if strings.Contains(user, serviceAccount) {
 					return nil
 				}
 			}
 		}
 	}
 	errMsg := "service account %q does not have sufficient privileges, grant access with oadm policy add-scc-to-user %s -z %s"
 	return fmt.Errorf(errMsg, serviceAccount, bootstrappolicy.SecurityContextConstraintPrivileged, serviceAccount)
 }