Merge pull request #36447 from irfanurrehman/federation-kubefed-todo-2

Automatic merge from submit-queue

[Federation] Implement dry run support in kubefed init

This one implements one of the TODO items pending in the previous set of kubefed PRs.
This one is done on top of another todo PR https://github.com/kubernetes/kubernetes/pull/36310 which is being reviewed separately.
Please review only the last 2 commits in this one.
The design doc PR for kubefed is at https://github.com/kubernetes/kubernetes/pull/34484.

cc  @kubernetes/sig-cluster-federation  @madhusudancs @nikhiljindal



**Release note**:
<!--  Steps to write your release note:
1. Use the release-note-* labels to set the release note state (if you have access) 
2. Enter your extended release note in the below block; leaving it blank means using the PR title as the release note. If no release note is required, just write `NONE`. 
-->
``` 
[Federation] `kubefed init` now supports dry run mode. 
```
This commit is contained in:
Kubernetes Submit Queue 2016-12-08 14:32:08 -08:00 committed by GitHub
commit 8182f178f6
2 changed files with 84 additions and 25 deletions

View File

@ -120,6 +120,7 @@ func NewCmdInit(cmdOut io.Writer, config util.AdminConfig) *cobra.Command {
cmd.Flags().String("image", defaultImage, "Image to use for federation API server and controller manager binaries.")
cmd.Flags().String("dns-provider", "google-clouddns", "Dns provider to be used for this deployment.")
cmd.Flags().String("etcd-pv-capacity", "10Gi", "Size of persistent volume claim to be used for etcd.")
cmd.Flags().Bool("dry-run", false, "dry run without sending commands to server.")
return cmd
}
@ -142,6 +143,7 @@ func initFederation(cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Comman
image := cmdutil.GetFlagString(cmd, "image")
dnsProvider := cmdutil.GetFlagString(cmd, "dns-provider")
etcdPVCapacity := cmdutil.GetFlagString(cmd, "etcd-pv-capacity")
dryRun := cmdutil.GetDryRunFlag(cmd)
hostFactory := config.HostFactory(initFlags.Host, initFlags.Kubeconfig)
hostClientset, err := hostFactory.ClientSet()
@ -155,17 +157,17 @@ func initFederation(cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Comman
cmKubeconfigName := fmt.Sprintf("%s-kubeconfig", cmName)
// 1. Create a namespace for federation system components
_, err = createNamespace(hostClientset, initFlags.FederationSystemNamespace)
_, err = createNamespace(hostClientset, initFlags.FederationSystemNamespace, dryRun)
if err != nil {
return err
}
// 2. Expose a network endpoint for the federation API server
svc, err := createService(hostClientset, initFlags.FederationSystemNamespace, serverName)
svc, err := createService(hostClientset, initFlags.FederationSystemNamespace, serverName, dryRun)
if err != nil {
return err
}
ips, hostnames, err := waitForLoadBalancerAddress(hostClientset, svc)
ips, hostnames, err := waitForLoadBalancerAddress(hostClientset, svc, dryRun)
if err != nil {
return err
}
@ -176,13 +178,13 @@ func initFederation(cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Comman
return err
}
_, err = createAPIServerCredentialsSecret(hostClientset, initFlags.FederationSystemNamespace, serverCredName, entKeyPairs)
_, err = createAPIServerCredentialsSecret(hostClientset, initFlags.FederationSystemNamespace, serverCredName, entKeyPairs, dryRun)
if err != nil {
return err
}
// 4. Create a kubeconfig secret
_, err = createControllerManagerKubeconfigSecret(hostClientset, initFlags.FederationSystemNamespace, initFlags.Name, svc.Name, cmKubeconfigName, entKeyPairs)
_, err = createControllerManagerKubeconfigSecret(hostClientset, initFlags.FederationSystemNamespace, initFlags.Name, svc.Name, cmKubeconfigName, entKeyPairs, dryRun)
if err != nil {
return err
}
@ -190,7 +192,7 @@ func initFederation(cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Comman
// 5. Create a persistent volume and a claim to store the federation
// API server's state. This is where federation API server's etcd
// stores its data.
pvc, err := createPVC(hostClientset, initFlags.FederationSystemNamespace, svc.Name, etcdPVCapacity)
pvc, err := createPVC(hostClientset, initFlags.FederationSystemNamespace, svc.Name, etcdPVCapacity, dryRun)
if err != nil {
return err
}
@ -208,38 +210,46 @@ func initFederation(cmdOut io.Writer, config util.AdminConfig, cmd *cobra.Comman
}
// 6. Create federation API server
_, err = createAPIServer(hostClientset, initFlags.FederationSystemNamespace, serverName, image, serverCredName, pvc.Name, advertiseAddress)
_, err = createAPIServer(hostClientset, initFlags.FederationSystemNamespace, serverName, image, serverCredName, pvc.Name, advertiseAddress, dryRun)
if err != nil {
return err
}
// 7. Create federation controller manager
_, err = createControllerManager(hostClientset, initFlags.FederationSystemNamespace, initFlags.Name, svc.Name, cmName, image, cmKubeconfigName, dnsZoneName, dnsProvider)
_, err = createControllerManager(hostClientset, initFlags.FederationSystemNamespace, initFlags.Name, svc.Name, cmName, image, cmKubeconfigName, dnsZoneName, dnsProvider, dryRun)
if err != nil {
return err
}
// 8. Write the federation API server endpoint info, credentials
// and context to kubeconfig
err = updateKubeconfig(config, initFlags.Name, endpoint, entKeyPairs)
err = updateKubeconfig(config, initFlags.Name, endpoint, entKeyPairs, dryRun)
if err != nil {
return err
}
if !dryRun {
return printSuccess(cmdOut, ips, hostnames)
}
_, err = fmt.Fprintf(cmdOut, "Federation control plane runs (dry run)\n")
return err
}
func createNamespace(clientset *client.Clientset, namespace string) (*api.Namespace, error) {
func createNamespace(clientset *client.Clientset, namespace string, dryRun bool) (*api.Namespace, error) {
ns := &api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: namespace,
},
}
if dryRun {
return ns, nil
}
return clientset.Core().Namespaces().Create(ns)
}
func createService(clientset *client.Clientset, namespace, svcName string) (*api.Service, error) {
func createService(clientset *client.Clientset, namespace, svcName string, dryRun bool) (*api.Service, error) {
svc := &api.Service{
ObjectMeta: api.ObjectMeta{
Name: svcName,
@ -260,13 +270,21 @@ func createService(clientset *client.Clientset, namespace, svcName string) (*api
},
}
if dryRun {
return svc, nil
}
return clientset.Core().Services(namespace).Create(svc)
}
func waitForLoadBalancerAddress(clientset *client.Clientset, svc *api.Service) ([]string, []string, error) {
func waitForLoadBalancerAddress(clientset *client.Clientset, svc *api.Service, dryRun bool) ([]string, []string, error) {
ips := []string{}
hostnames := []string{}
if dryRun {
return ips, hostnames, nil
}
err := wait.PollImmediateInfinite(lbAddrRetryInterval, func() (bool, error) {
pollSvc, err := clientset.Core().Services(svc.Namespace).Get(svc.Name)
if err != nil {
@ -319,7 +337,7 @@ func genCerts(svcNamespace, name, svcName, localDNSZoneName string, ips, hostnam
}, nil
}
func createAPIServerCredentialsSecret(clientset *client.Clientset, namespace, credentialsName string, entKeyPairs *entityKeyPairs) (*api.Secret, error) {
func createAPIServerCredentialsSecret(clientset *client.Clientset, namespace, credentialsName string, entKeyPairs *entityKeyPairs, dryRun bool) (*api.Secret, error) {
// Build the secret object with API server credentials.
secret := &api.Secret{
ObjectMeta: api.ObjectMeta{
@ -333,11 +351,14 @@ func createAPIServerCredentialsSecret(clientset *client.Clientset, namespace, cr
},
}
if dryRun {
return secret, nil
}
// Boilerplate to create the secret in the host cluster.
return clientset.Core().Secrets(namespace).Create(secret)
}
func createControllerManagerKubeconfigSecret(clientset *client.Clientset, namespace, name, svcName, kubeconfigName string, entKeyPairs *entityKeyPairs) (*api.Secret, error) {
func createControllerManagerKubeconfigSecret(clientset *client.Clientset, namespace, name, svcName, kubeconfigName string, entKeyPairs *entityKeyPairs, dryRun bool) (*api.Secret, error) {
basicClientConfig := kubeadmutil.CreateBasicClientConfig(
name,
fmt.Sprintf("https://%s", svcName),
@ -352,10 +373,10 @@ func createControllerManagerKubeconfigSecret(clientset *client.Clientset, namesp
certutil.EncodeCertPEM(entKeyPairs.controllerManager.Cert),
)
return util.CreateKubeconfigSecret(clientset, config, namespace, kubeconfigName, false)
return util.CreateKubeconfigSecret(clientset, config, namespace, kubeconfigName, dryRun)
}
func createPVC(clientset *client.Clientset, namespace, svcName, etcdPVCapacity string) (*api.PersistentVolumeClaim, error) {
func createPVC(clientset *client.Clientset, namespace, svcName, etcdPVCapacity string, dryRun bool) (*api.PersistentVolumeClaim, error) {
capacity, err := resource.ParseQuantity(etcdPVCapacity)
if err != nil {
return nil, err
@ -382,10 +403,14 @@ func createPVC(clientset *client.Clientset, namespace, svcName, etcdPVCapacity s
},
}
if dryRun {
return pvc, nil
}
return clientset.Core().PersistentVolumeClaims(namespace).Create(pvc)
}
func createAPIServer(clientset *client.Clientset, namespace, name, image, credentialsName, pvcName, advertiseAddress string) (*extensions.Deployment, error) {
func createAPIServer(clientset *client.Clientset, namespace, name, image, credentialsName, pvcName, advertiseAddress string, dryRun bool) (*extensions.Deployment, error) {
command := []string{
"/hyperkube",
"federation-apiserver",
@ -480,10 +505,14 @@ func createAPIServer(clientset *client.Clientset, namespace, name, image, creden
},
}
if dryRun {
return dep, nil
}
return clientset.Extensions().Deployments(namespace).Create(dep)
}
func createControllerManager(clientset *client.Clientset, namespace, name, svcName, cmName, image, kubeconfigName, dnsZoneName, dnsProvider string) (*extensions.Deployment, error) {
func createControllerManager(clientset *client.Clientset, namespace, name, svcName, cmName, image, kubeconfigName, dnsZoneName, dnsProvider string, dryRun bool) (*extensions.Deployment, error) {
dep := &extensions.Deployment{
ObjectMeta: api.ObjectMeta{
Name: cmName,
@ -546,6 +575,9 @@ func createControllerManager(clientset *client.Clientset, namespace, name, svcNa
},
}
if dryRun {
return dep, nil
}
return clientset.Extensions().Deployments(namespace).Create(dep)
}
@ -555,7 +587,7 @@ func printSuccess(cmdOut io.Writer, ips, hostnames []string) error {
return err
}
func updateKubeconfig(config util.AdminConfig, name, endpoint string, entKeyPairs *entityKeyPairs) error {
func updateKubeconfig(config util.AdminConfig, name, endpoint string, entKeyPairs *entityKeyPairs, dryRun bool) error {
po := config.PathOptions()
kubeconfig, err := po.GetStartingConfig()
if err != nil {
@ -588,10 +620,12 @@ func updateKubeconfig(config util.AdminConfig, name, endpoint string, entKeyPair
kubeconfig.AuthInfos[name] = authInfo
kubeconfig.Contexts[name] = context
if !dryRun {
// Write the update kubeconfig.
if err := clientcmd.ModifyConfig(po, *kubeconfig, true); err != nil {
return err
}
}
return nil
}

View File

@ -79,6 +79,7 @@ func TestInitFederation(t *testing.T) {
etcdPVCapacity string
expectedErr string
dnsProvider string
dryRun string
}{
{
federation: "union",
@ -90,6 +91,7 @@ func TestInitFederation(t *testing.T) {
etcdPVCapacity: "5Gi",
expectedErr: "",
dnsProvider: "test-dns-provider",
dryRun: "",
},
{
federation: "union",
@ -101,9 +103,24 @@ func TestInitFederation(t *testing.T) {
etcdPVCapacity: "", //test for default value of pvc-size
expectedErr: "",
dnsProvider: "", //test for default value of dns provider
dryRun: "",
},
{
federation: "union",
kubeconfigGlobal: fakeKubeFiles[0],
kubeconfigExplicit: "",
dnsZoneName: "example.test.",
lbIP: "10.20.30.40",
image: "example.test/foo:bar",
etcdPVCapacity: "",
expectedErr: "",
dnsProvider: "test-dns-provider",
dryRun: "valid-run",
},
}
//TODO: implement a negative case for dry run
for i, tc := range testCases {
cmdErrMsg = ""
dnsProvider = ""
@ -130,12 +147,16 @@ func TestInitFederation(t *testing.T) {
cmd.Flags().Set("host-cluster-context", "substrate")
cmd.Flags().Set("dns-zone-name", tc.dnsZoneName)
cmd.Flags().Set("image", tc.image)
if "" != tc.dnsProvider {
if tc.dnsProvider != "" {
cmd.Flags().Set("dns-provider", tc.dnsProvider)
}
if "" != tc.etcdPVCapacity {
if tc.etcdPVCapacity != "" {
cmd.Flags().Set("etcd-pv-capacity", tc.etcdPVCapacity)
}
if tc.dryRun == "valid-run" {
cmd.Flags().Set("dry-run", "true")
}
cmd.Run(cmd, []string{tc.federation})
if tc.expectedErr == "" {
@ -143,6 +164,10 @@ func TestInitFederation(t *testing.T) {
// Actual data passed are tested in the fake secret and cluster
// REST clients.
want := fmt.Sprintf("Federation API server is running at: %s\n", tc.lbIP)
if tc.dryRun != "" {
want = fmt.Sprintf("Federation control plane runs (dry run)\n")
}
if got := buf.String(); got != want {
t.Errorf("[%d] unexpected output: got: %s, want: %s", i, got, want)
if cmdErrMsg != "" {