diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index a8c9c79e4e7..5295dc6b33b 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -287,6 +287,7 @@ _kubectl_get() must_have_one_flag=() must_have_one_noun=() must_have_one_noun+=("componentstatus") + must_have_one_noun+=("daemonset") must_have_one_noun+=("deployment") must_have_one_noun+=("endpoints") must_have_one_noun+=("event") @@ -328,6 +329,7 @@ _kubectl_describe() must_have_one_flag=() must_have_one_noun=() + must_have_one_noun+=("daemonset") must_have_one_noun+=("limitrange") must_have_one_noun+=("minion") must_have_one_noun+=("namespace") @@ -456,6 +458,7 @@ _kubectl_delete() must_have_one_flag=() must_have_one_noun=() must_have_one_noun+=("componentstatus") + must_have_one_noun+=("daemonset") must_have_one_noun+=("deployment") must_have_one_noun+=("endpoints") must_have_one_noun+=("event") @@ -827,6 +830,7 @@ _kubectl_label() must_have_one_flag=() must_have_one_noun=() must_have_one_noun+=("componentstatus") + must_have_one_noun+=("daemonset") must_have_one_noun+=("deployment") must_have_one_noun+=("endpoints") must_have_one_noun+=("event") diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 3865de98271..3e40a225820 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -112,6 +112,7 @@ __custom_func() { valid_resources = `Valid resource types include: * pods (aka 'po') * replicationcontrollers (aka 'rc') + * daemonsets (aka 'ds') * services (aka 'svc') * events (aka 'ev') * nodes (aka 'no') diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index 65be81d3d40..6a99355758c 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -69,6 +69,7 @@ func describerMap(c *client.Client) map[string]Describer { m := map[string]Describer{ "Pod": &PodDescriber{c}, "ReplicationController": &ReplicationControllerDescriber{c}, + "DaemonSet": &DaemonSetDescriber{c}, "Secret": &SecretDescriber{c}, "Service": &ServiceDescriber{c}, "ServiceAccount": &ServiceAccountDescriber{c}, @@ -128,6 +129,7 @@ func init() { describePod, describeService, describeReplicationController, + describeDaemonSet, describeNode, describeNamespace, ) @@ -823,7 +825,7 @@ func (d *ReplicationControllerDescriber) Describe(namespace, name string) (strin return "", err } - running, waiting, succeeded, failed, err := getPodStatusForReplicationController(pc, controller) + running, waiting, succeeded, failed, err := getPodStatusForController(pc, controller.Spec.Selector) if err != nil { return "", err } @@ -896,6 +898,52 @@ func describeJob(job *experimental.Job, events *api.EventList) (string, error) { }) } +// DaemonSetDescriber generates information about a daemon set and the pods it has created. +type DaemonSetDescriber struct { + client.Interface +} + +func (d *DaemonSetDescriber) Describe(namespace, name string) (string, error) { + dc := d.Experimental().DaemonSets(namespace) + pc := d.Pods(namespace) + + daemon, err := dc.Get(name) + if err != nil { + return "", err + } + + running, waiting, succeeded, failed, err := getPodStatusForController(pc, daemon.Spec.Selector) + if err != nil { + return "", err + } + + events, _ := d.Events(namespace).Search(daemon) + + return describeDaemonSet(daemon, events, running, waiting, succeeded, failed) +} + +func describeDaemonSet(daemon *experimental.DaemonSet, events *api.EventList, running, waiting, succeeded, failed int) (string, error) { + return tabbedString(func(out io.Writer) error { + fmt.Fprintf(out, "Name:\t%s\n", daemon.Name) + if daemon.Spec.Template != nil { + fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(&daemon.Spec.Template.Spec)) + } else { + fmt.Fprintf(out, "Image(s):\t%s\n", "") + } + fmt.Fprintf(out, "Selector:\t%s\n", labels.FormatLabels(daemon.Spec.Selector)) + fmt.Fprintf(out, "Node-Selector:\t%s\n", labels.FormatLabels(daemon.Spec.Template.Spec.NodeSelector)) + fmt.Fprintf(out, "Labels:\t%s\n", labels.FormatLabels(daemon.Labels)) + fmt.Fprintf(out, "Desired Number of Nodes Scheduled: %d\n", daemon.Status.DesiredNumberScheduled) + fmt.Fprintf(out, "Current Number of Nodes Scheduled: %d\n", daemon.Status.CurrentNumberScheduled) + fmt.Fprintf(out, "Number of Nodes Misscheduled: %d\n", daemon.Status.NumberMisscheduled) + fmt.Fprintf(out, "Pods Status:\t%d Running / %d Waiting / %d Succeeded / %d Failed\n", running, waiting, succeeded, failed) + if events != nil { + DescribeEvents(events, out) + } + return nil + }) +} + // SecretDescriber generates information about a secret type SecretDescriber struct { client.Interface @@ -1347,6 +1395,30 @@ func DescribeEvents(el *api.EventList, w io.Writer) { } } +// Get all daemon set whose selectors would match a given set of labels. +// TODO: Move this to pkg/client and ideally implement it server-side (instead +// of getting all DS's and searching through them manually). +// TODO: write an interface for controllers and fuse getReplicationControllersForLabels +// and getDaemonSetsForLabels. +func getDaemonSetsForLabels(c client.DaemonSetInterface, labelsToMatch labels.Labels) ([]experimental.DaemonSet, error) { + // Get all daemon sets + // TODO: this needs a namespace scope as argument + dss, err := c.List(labels.Everything()) + if err != nil { + return nil, fmt.Errorf("error getting daemon set: %v", err) + } + + // Find the ones that match labelsToMatch. + var matchingDaemonSets []experimental.DaemonSet + for _, ds := range dss.Items { + selector := labels.SelectorFromSet(ds.Spec.Selector) + if selector.Matches(labelsToMatch) { + matchingDaemonSets = append(matchingDaemonSets, ds) + } + } + return matchingDaemonSets, nil +} + // Get all replication controllers whose selectors would match a given set of // labels. // TODO Move this to pkg/client and ideally implement it server-side (instead @@ -1384,8 +1456,8 @@ func printReplicationControllersByLabels(matchingRCs []api.ReplicationController return list } -func getPodStatusForReplicationController(c client.PodInterface, controller *api.ReplicationController) (running, waiting, succeeded, failed int, err error) { - rcPods, err := c.List(labels.SelectorFromSet(controller.Spec.Selector), fields.Everything()) +func getPodStatusForController(c client.PodInterface, selector map[string]string) (running, waiting, succeeded, failed int, err error) { + rcPods, err := c.List(labels.SelectorFromSet(selector), fields.Everything()) if err != nil { return } diff --git a/pkg/kubectl/kubectl.go b/pkg/kubectl/kubectl.go index b6cfff4e7cc..d2fc980dc39 100644 --- a/pkg/kubectl/kubectl.go +++ b/pkg/kubectl/kubectl.go @@ -100,6 +100,7 @@ func expandResourceShortcut(resource string) string { "pvc": "persistentvolumeclaims", "quota": "resourcequotas", "rc": "replicationcontrollers", + "ds": "daemonsets", "svc": "services", } if expanded, ok := shortForms[resource]; ok { diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 81f095cd01f..1dac87dd347 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -384,6 +384,7 @@ var jobColumns = []string{"JOB", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "SUCCES var serviceColumns = []string{"NAME", "CLUSTER_IP", "EXTERNAL_IP", "PORT(S)", "SELECTOR", "AGE"} var endpointColumns = []string{"NAME", "ENDPOINTS", "AGE"} var nodeColumns = []string{"NAME", "LABELS", "STATUS", "AGE"} +var daemonSetColumns = []string{"NAME", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "NODE-SELECTOR"} var eventColumns = []string{"FIRSTSEEN", "LASTSEEN", "COUNT", "NAME", "KIND", "SUBOBJECT", "REASON", "SOURCE", "MESSAGE"} var limitRangeColumns = []string{"NAME", "AGE"} var resourceQuotaColumns = []string{"NAME", "AGE"} @@ -406,6 +407,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() { h.Handler(podTemplateColumns, printPodTemplateList) h.Handler(replicationControllerColumns, printReplicationController) h.Handler(replicationControllerColumns, printReplicationControllerList) + h.Handler(daemonSetColumns, printDaemonSet) + h.Handler(daemonSetColumns, printDaemonSetList) h.Handler(jobColumns, printJob) h.Handler(jobColumns, printJobList) h.Handler(serviceColumns, printService) @@ -810,6 +813,60 @@ func printServiceList(list *api.ServiceList, w io.Writer, withNamespace bool, wi return nil } +func printDaemonSet(ds *experimental.DaemonSet, w io.Writer, withNamespace bool, wide bool, showAll bool, columnLabels []string) error { + name := ds.Name + namespace := ds.Namespace + + containers := ds.Spec.Template.Spec.Containers + var firstContainer api.Container + if len(containers) > 0 { + firstContainer, containers = containers[0], containers[1:] + } + + if withNamespace { + if _, err := fmt.Fprintf(w, "%s\t", namespace); err != nil { + return err + } + } + if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s", + name, + firstContainer.Name, + firstContainer.Image, + labels.FormatLabels(ds.Spec.Selector), + labels.FormatLabels(ds.Spec.Template.Spec.NodeSelector), + ); err != nil { + return err + } + if _, err := fmt.Fprint(w, appendLabels(ds.Labels, columnLabels)); err != nil { + return err + } + + // Lay out all the other containers on separate lines. + extraLinePrefix := "\t" + if withNamespace { + extraLinePrefix = "\t\t" + } + for _, container := range containers { + _, err := fmt.Fprintf(w, "%s%s\t%s\t%s\t%s", extraLinePrefix, container.Name, container.Image, "", "") + if err != nil { + return err + } + if _, err := fmt.Fprint(w, appendLabelTabs(columnLabels)); err != nil { + return err + } + } + return nil +} + +func printDaemonSetList(list *experimental.DaemonSetList, w io.Writer, withNamespace bool, wide bool, showAll bool, columnLabels []string) error { + for _, ds := range list.Items { + if err := printDaemonSet(&ds, w, withNamespace, wide, showAll, columnLabels); err != nil { + return err + } + } + return nil +} + func printEndpoints(endpoints *api.Endpoints, w io.Writer, withNamespace bool, wide bool, showAll bool, columnLabels []string) error { name := endpoints.Name namespace := endpoints.Namespace diff --git a/pkg/kubectl/stop.go b/pkg/kubectl/stop.go index bca8ff277d9..fa4a371c659 100644 --- a/pkg/kubectl/stop.go +++ b/pkg/kubectl/stop.go @@ -21,10 +21,13 @@ import ( "strings" "time" + fuzz "github.com/google/gofuzz" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/util/wait" ) const ( @@ -56,6 +59,8 @@ func ReaperFor(kind string, c client.Interface) (Reaper, error) { switch kind { case "ReplicationController": return &ReplicationControllerReaper{c, Interval, Timeout}, nil + case "DaemonSet": + return &DaemonSetReaper{c, Interval, Timeout}, nil case "Pod": return &PodReaper{c}, nil case "Service": @@ -72,6 +77,10 @@ type ReplicationControllerReaper struct { client.Interface pollInterval, timeout time.Duration } +type DaemonSetReaper struct { + client.Interface + pollInterval, timeout time.Duration +} type PodReaper struct { client.Interface } @@ -167,6 +176,53 @@ func (reaper *ReplicationControllerReaper) Stop(namespace, name string, timeout return fmt.Sprintf("%s stopped", name), nil } +func (reaper *DaemonSetReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) (string, error) { + daemon, err := reaper.Experimental().DaemonSets(namespace).Get(name) + if err != nil { + return "", err + } + + // Update the daemon set to select for a non-existent NodeName. + // The daemon set controller will then kill all the daemon pods corresponding to daemon set. + nodes, err := reaper.Nodes().List(labels.Everything(), fields.Everything()) + if err != nil { + return "", err + } + var fuzzer = fuzz.New() + var nameExists bool + + var nodeName string + fuzzer.Fuzz(&nodeName) + nameExists = false + for _, node := range nodes.Items { + nameExists = nameExists || node.Name == nodeName + } + if nameExists { + // Probability of reaching here is extremely low, most likely indicates a programming bug/library error. + return "", fmt.Errorf("Name collision generating an unused node name. Please retry this operation.") + } + + daemon.Spec.Template.Spec.NodeName = nodeName + + reaper.Experimental().DaemonSets(namespace).Update(daemon) + + // Wait for the daemon set controller to kill all the daemon pods. + if err := wait.Poll(reaper.pollInterval, reaper.timeout, func() (bool, error) { + updatedDS, err := reaper.Experimental().DaemonSets(namespace).Get(name) + if err != nil { + return false, nil + } + return updatedDS.Status.CurrentNumberScheduled+updatedDS.Status.NumberMisscheduled == 0, nil + }); err != nil { + return "", err + } + + if err := reaper.Experimental().DaemonSets(namespace).Delete(name); err != nil { + return "", err + } + return fmt.Sprintf("%s stopped", name), nil +} + func (reaper *PodReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) (string, error) { pods := reaper.Pods(namespace) _, err := pods.Get(name) diff --git a/test/e2e/daemon_set.go b/test/e2e/daemon_set.go index 05f412768ce..727959a830f 100644 --- a/test/e2e/daemon_set.go +++ b/test/e2e/daemon_set.go @@ -24,6 +24,7 @@ import ( "k8s.io/kubernetes/pkg/apis/experimental" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/util/wait" @@ -148,6 +149,15 @@ func testDaemonSets(f *Framework) { }, }) Expect(err).NotTo(HaveOccurred()) + defer func() { + Logf("Check that reaper kills all daemon pods for %s", simpleDSName) + dsReaper, err := kubectl.ReaperFor("DaemonSet", c) + Expect(err).NotTo(HaveOccurred()) + _, err = dsReaper.Stop(ns, simpleDSName, 0, nil) + Expect(err).NotTo(HaveOccurred()) + err = wait.Poll(retryInterval, retryTimeout, checkRunningOnNoNodes(f, label)) + Expect(err).NotTo(HaveOccurred(), "error waiting for daemon pod to be reaped") + }() By("Check that daemon pods launch on every node of the cluster.") Expect(err).NotTo(HaveOccurred()) @@ -210,7 +220,7 @@ func testDaemonSets(f *Framework) { err = wait.Poll(retryInterval, retryTimeout, checkDaemonPodOnNodes(f, complexLabel, []string{newNode.Name})) Expect(err).NotTo(HaveOccurred(), "error waiting for daemon pods to be running on new nodes") - By("remove the node selector and wait for") + By("remove the node selector and wait for daemons to be unscheduled") newNode, err = nodeClient.Get(newNode.Name) Expect(err).NotTo(HaveOccurred(), "error getting node") newNode.Labels = map[string]string{} @@ -218,4 +228,7 @@ func testDaemonSets(f *Framework) { Expect(err).NotTo(HaveOccurred()) Expect(wait.Poll(retryInterval, retryTimeout, checkRunningOnNoNodes(f, complexLabel))). NotTo(HaveOccurred(), "error waiting for daemon pod to not be running on nodes") + + By("We should now be able to delete the daemon set.") + Expect(c.DaemonSets(ns).Delete(complexDSName)).NotTo(HaveOccurred()) }