Merge pull request #40927 from soltysh/deployment_logs

Automatic merge from submit-queue (batch tested with PRs 42053, 41282, 42056, 41663, 40927)

Allow getting logs directly from deployment, job and statefulset

**Special notes for your reviewer**:
@smarterclayton you asked for it in OpenShift


```release-note
kubectl logs allows getting logs directly from deployment, job and statefulset
```
pull/6/head
Kubernetes Submit Queue 2017-02-27 12:45:36 -08:00 committed by GitHub
commit 6d9e2afeda
15 changed files with 289 additions and 74 deletions

View File

@ -377,7 +377,7 @@ func (i *initFederation) Run(cmdOut io.Writer, config util.AdminConfig) error {
return err
}
func createNamespace(clientset *client.Clientset, namespace string, dryRun bool) (*api.Namespace, error) {
func createNamespace(clientset client.Interface, namespace string, dryRun bool) (*api.Namespace, error) {
ns := &api.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
@ -391,7 +391,7 @@ func createNamespace(clientset *client.Clientset, namespace string, dryRun bool)
return clientset.Core().Namespaces().Create(ns)
}
func createService(clientset *client.Clientset, namespace, svcName, apiserverAdvertiseAddress string, apiserverServiceType v1.ServiceType, dryRun bool) (*api.Service, []string, []string, error) {
func createService(clientset client.Interface, namespace, svcName, apiserverAdvertiseAddress string, apiserverServiceType v1.ServiceType, dryRun bool) (*api.Service, []string, []string, error) {
svc := &api.Service{
ObjectMeta: metav1.ObjectMeta{
Name: svcName,
@ -436,12 +436,12 @@ func createService(clientset *client.Clientset, namespace, svcName, apiserverAdv
return svc, ips, hostnames, err
}
func getClusterNodeIPs(clientset *client.Clientset) ([]string, error) {
func getClusterNodeIPs(clientset client.Interface) ([]string, error) {
preferredAddressTypes := []api.NodeAddressType{
api.NodeExternalIP,
api.NodeLegacyHostIP,
}
nodeList, err := clientset.Nodes().List(metav1.ListOptions{})
nodeList, err := clientset.Core().Nodes().List(metav1.ListOptions{})
if err != nil {
return nil, err
}
@ -461,7 +461,7 @@ func getClusterNodeIPs(clientset *client.Clientset) ([]string, error) {
return nodeAddresses, nil
}
func waitForLoadBalancerAddress(clientset *client.Clientset, svc *api.Service, dryRun bool) ([]string, []string, error) {
func waitForLoadBalancerAddress(clientset client.Interface, svc *api.Service, dryRun bool) ([]string, []string, error) {
ips := []string{}
hostnames := []string{}
@ -521,7 +521,7 @@ func genCerts(svcNamespace, name, svcName, localDNSZoneName string, ips, hostnam
}, nil
}
func createAPIServerCredentialsSecret(clientset *client.Clientset, namespace, credentialsName string, entKeyPairs *entityKeyPairs, dryRun bool) (*api.Secret, error) {
func createAPIServerCredentialsSecret(clientset client.Interface, namespace, credentialsName string, entKeyPairs *entityKeyPairs, dryRun bool) (*api.Secret, error) {
// Build the secret object with API server credentials.
secret := &api.Secret{
ObjectMeta: metav1.ObjectMeta{
@ -542,7 +542,7 @@ func createAPIServerCredentialsSecret(clientset *client.Clientset, namespace, cr
return clientset.Core().Secrets(namespace).Create(secret)
}
func createControllerManagerKubeconfigSecret(clientset *client.Clientset, namespace, name, svcName, kubeconfigName string, entKeyPairs *entityKeyPairs, dryRun bool) (*api.Secret, error) {
func createControllerManagerKubeconfigSecret(clientset client.Interface, namespace, name, svcName, kubeconfigName string, entKeyPairs *entityKeyPairs, dryRun bool) (*api.Secret, error) {
config := kubeconfigutil.CreateWithCerts(
fmt.Sprintf("https://%s", svcName),
name,
@ -555,7 +555,7 @@ func createControllerManagerKubeconfigSecret(clientset *client.Clientset, namesp
return util.CreateKubeconfigSecret(clientset, config, namespace, kubeconfigName, dryRun)
}
func createPVC(clientset *client.Clientset, namespace, svcName, etcdPVCapacity string, dryRun bool) (*api.PersistentVolumeClaim, error) {
func createPVC(clientset client.Interface, namespace, svcName, etcdPVCapacity string, dryRun bool) (*api.PersistentVolumeClaim, error) {
capacity, err := resource.ParseQuantity(etcdPVCapacity)
if err != nil {
return nil, err
@ -589,7 +589,7 @@ func createPVC(clientset *client.Clientset, namespace, svcName, etcdPVCapacity s
return clientset.Core().PersistentVolumeClaims(namespace).Create(pvc)
}
func createAPIServer(clientset *client.Clientset, namespace, name, image, credentialsName, advertiseAddress, storageBackend string, argOverrides map[string]string, pvc *api.PersistentVolumeClaim, dryRun bool) (*extensions.Deployment, error) {
func createAPIServer(clientset client.Interface, namespace, name, image, credentialsName, advertiseAddress, storageBackend string, argOverrides map[string]string, pvc *api.PersistentVolumeClaim, dryRun bool) (*extensions.Deployment, error) {
command := []string{
"/hyperkube",
"federation-apiserver",
@ -704,7 +704,7 @@ func createAPIServer(clientset *client.Clientset, namespace, name, image, creden
return clientset.Extensions().Deployments(namespace).Create(dep)
}
func createControllerManagerSA(clientset *client.Clientset, namespace string, dryRun bool) (*api.ServiceAccount, error) {
func createControllerManagerSA(clientset client.Interface, namespace string, dryRun bool) (*api.ServiceAccount, error) {
sa := &api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: ControllerManagerSA,
@ -718,7 +718,7 @@ func createControllerManagerSA(clientset *client.Clientset, namespace string, dr
return clientset.Core().ServiceAccounts(namespace).Create(sa)
}
func createRoleBindings(clientset *client.Clientset, namespace, saName string, dryRun bool) (*rbac.Role, *rbac.RoleBinding, error) {
func createRoleBindings(clientset client.Interface, namespace, saName string, dryRun bool) (*rbac.Role, *rbac.RoleBinding, error) {
roleName := "federation-system:federation-controller-manager"
role := &rbac.Role{
// a role to use for bootstrapping the federation-controller-manager so it can access
@ -752,7 +752,7 @@ func createRoleBindings(clientset *client.Clientset, namespace, saName string, d
return newRole, newRolebinding, err
}
func createControllerManager(clientset *client.Clientset, namespace, name, svcName, cmName, image, kubeconfigName, dnsZoneName, dnsProvider, saName string, dnsProviderSecret *api.Secret, argOverrides map[string]string, dryRun bool) (*extensions.Deployment, error) {
func createControllerManager(clientset client.Interface, namespace, name, svcName, cmName, image, kubeconfigName, dnsZoneName, dnsProvider, saName string, dnsProviderSecret *api.Secret, argOverrides map[string]string, dryRun bool) (*extensions.Deployment, error) {
command := []string{
"/hyperkube",
"federation-controller-manager",
@ -879,7 +879,7 @@ func argMapsToArgStrings(argsMap, overrides map[string]string) []string {
return args
}
func waitForPods(clientset *client.Clientset, fedPods []string, namespace string) error {
func waitForPods(clientset client.Interface, fedPods []string, namespace string) error {
err := wait.PollInfinite(podWaitInterval, func() (bool, error) {
podCheck := len(fedPods)
podList, err := clientset.Core().Pods(namespace).List(metav1.ListOptions{})
@ -979,7 +979,7 @@ func updateKubeconfig(config util.AdminConfig, name, endpoint, kubeConfigPath st
return nil
}
func createDNSProviderConfigSecret(clientset *client.Clientset, namespace, name string, dnsProviderConfigBytes []byte, dryRun bool) (*api.Secret, error) {
func createDNSProviderConfigSecret(clientset client.Interface, namespace, name string, dnsProviderConfigBytes []byte, dryRun bool) (*api.Secret, error) {
if dnsProviderConfigBytes == nil {
return nil, nil
}

View File

@ -213,7 +213,7 @@ func minifyConfig(clientConfig *clientcmdapi.Config, context string) (*clientcmd
// createSecret extracts the kubeconfig for a given cluster and populates
// a secret with that kubeconfig.
func createSecret(clientset *internalclientset.Clientset, clientConfig *clientcmdapi.Config, namespace, contextName, secretName string, dryRun bool) (runtime.Object, error) {
func createSecret(clientset internalclientset.Interface, clientConfig *clientcmdapi.Config, namespace, contextName, secretName string, dryRun bool) (runtime.Object, error) {
// Minify the kubeconfig to ensure that there is only information
// relevant to the cluster we are registering.
newClientConfig, err := minifyConfig(clientConfig, contextName)
@ -236,7 +236,7 @@ func createSecret(clientset *internalclientset.Clientset, clientConfig *clientcm
// createConfigMap creates a configmap with name kube-dns in the joining cluster
// which stores the information about this federation zone name.
// If the configmap with this name already exists, its updated with this information.
func createConfigMap(hostClientSet *internalclientset.Clientset, config util.AdminConfig, fedSystemNamespace, targetClusterContext, kubeconfigPath string, dryRun bool) (*api.ConfigMap, error) {
func createConfigMap(hostClientSet internalclientset.Interface, config util.AdminConfig, fedSystemNamespace, targetClusterContext, kubeconfigPath string, dryRun bool) (*api.ConfigMap, error) {
cmDep, err := getCMDeployment(hostClientSet, fedSystemNamespace)
if err != nil {
return nil, err
@ -337,7 +337,7 @@ func extractScheme(url string) string {
return scheme
}
func getCMDeployment(hostClientSet *internalclientset.Clientset, fedNamespace string) (*extensions.Deployment, error) {
func getCMDeployment(hostClientSet internalclientset.Interface, fedNamespace string) (*extensions.Deployment, error) {
depList, err := hostClientSet.Extensions().Deployments(fedNamespace).List(metav1.ListOptions{})
if err != nil {
return nil, err

View File

@ -162,7 +162,7 @@ func popCluster(f cmdutil.Factory, name string) (*federationapi.Cluster, error)
return cluster, rh.Delete("", name)
}
func deleteConfigMapFromCluster(hostClientset *internalclientset.Clientset, secret *api.Secret, cluster *federationapi.Cluster, fedSystemNamespace string) error {
func deleteConfigMapFromCluster(hostClientset internalclientset.Interface, secret *api.Secret, cluster *federationapi.Cluster, fedSystemNamespace string) error {
clientset, err := getClientsetFromCluster(secret, cluster)
if err != nil {
return err
@ -197,7 +197,7 @@ func deleteConfigMapFromCluster(hostClientset *internalclientset.Clientset, secr
// deleteSecret deletes the secret with the given name from the host
// cluster.
func deleteSecret(clientset *internalclientset.Clientset, name, namespace string) error {
func deleteSecret(clientset internalclientset.Interface, name, namespace string) error {
return clientset.Core().Secrets(namespace).Delete(name, &metav1.DeleteOptions{})
}

View File

@ -136,7 +136,7 @@ func (o *SubcommandOptions) SetName(cmd *cobra.Command, args []string) error {
return nil
}
func CreateKubeconfigSecret(clientset *client.Clientset, kubeconfig *clientcmdapi.Config, namespace, name string, dryRun bool) (*api.Secret, error) {
func CreateKubeconfigSecret(clientset client.Interface, kubeconfig *clientcmdapi.Config, namespace, name string, dryRun bool) (*api.Secret, error) {
configBytes, err := clientcmd.Write(*kubeconfig)
if err != nil {
return nil, err

View File

@ -438,7 +438,7 @@ func getRESTMappings(mapper meta.RESTMapper, pruneResources *[]pruneResource) (n
type pruner struct {
mapper meta.RESTMapper
clientFunc resource.ClientMapperFunc
clientsetFunc func() (*internalclientset.Clientset, error)
clientsetFunc func() (internalclientset.Interface, error)
visitedUids sets.String
selector labels.Selector
@ -500,7 +500,7 @@ func (p *pruner) delete(namespace, name string, mapping *meta.RESTMapping, c res
return runDelete(namespace, name, mapping, c, nil, p.cascade, p.gracePeriod, p.clientsetFunc)
}
func runDelete(namespace, name string, mapping *meta.RESTMapping, c resource.RESTClient, helper *resource.Helper, cascade bool, gracePeriod int, clientsetFunc func() (*internalclientset.Clientset, error)) error {
func runDelete(namespace, name string, mapping *meta.RESTMapping, c resource.RESTClient, helper *resource.Helper, cascade bool, gracePeriod int, clientsetFunc func() (internalclientset.Interface, error)) error {
if !cascade {
if helper == nil {
helper = resource.NewHelper(c, mapping)
@ -538,7 +538,7 @@ type patcher struct {
mapping *meta.RESTMapping
helper *resource.Helper
clientsetFunc func() (*internalclientset.Clientset, error)
clientsetFunc func() (internalclientset.Interface, error)
overwrite bool
backOff clockwork.Clock

View File

@ -47,7 +47,7 @@ import (
)
type DrainOptions struct {
client *internalclientset.Clientset
client internalclientset.Interface
restClient *restclient.RESTClient
factory cmdutil.Factory
Force bool
@ -583,7 +583,7 @@ func (o *DrainOptions) waitForDelete(pods []api.Pod, interval, timeout time.Dura
// SupportEviction uses Discovery API to find out if the server support eviction subresource
// If support, it will return its groupVersion; Otherwise, it will return ""
func SupportEviction(clientset *internalclientset.Clientset) (string, error) {
func SupportEviction(clientset internalclientset.Interface) (string, error) {
discoveryClient := clientset.Discovery()
groupList, err := discoveryClient.ServerGroups()
if err != nil {

View File

@ -54,13 +54,19 @@ var (
kubectl logs --tail=20 nginx
# Show all logs from pod nginx written in the last hour
kubectl logs --since=1h nginx`)
kubectl logs --since=1h nginx
# Return snapshot logs from first container of a job named hello
kubectl logs job/hello
# Return snapshot logs from container nginx-1 of a deployment named nginx
kubectl logs deployment/nginx -c nginx-1`)
selectorTail int64 = 10
)
const (
logsUsageStr = "expected 'logs POD_NAME [CONTAINER_NAME]'.\nPOD_NAME is a required argument for the logs command"
logsUsageStr = "expected 'logs (POD | TYPE/NAME) [CONTAINER_NAME]'.\nPOD or TYPE/NAME is a required argument for the logs command"
)
type LogsOptions struct {
@ -83,9 +89,9 @@ type LogsOptions struct {
func NewCmdLogs(f cmdutil.Factory, out io.Writer) *cobra.Command {
o := &LogsOptions{}
cmd := &cobra.Command{
Use: "logs [-f] [-p] POD [-c CONTAINER]",
Use: "logs [-f] [-p] (POD | TYPE/NAME) [-c CONTAINER]",
Short: i18n.T("Print the logs for a container in a pod"),
Long: "Print the logs for a container in a pod. If the pod has only one container, the container name is optional.",
Long: "Print the logs for a container in a pod or specified resource. If the pod has only one container, the container name is optional.",
Example: logs_example,
PreRun: func(cmd *cobra.Command, args []string) {
if len(os.Args) > 1 && os.Args[1] == "log" {
@ -94,9 +100,7 @@ func NewCmdLogs(f cmdutil.Factory, out io.Writer) *cobra.Command {
},
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, out, cmd, args))
if err := o.Validate(); err != nil {
cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error()))
}
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.RunLogs())
},
Aliases: []string{"log"},

View File

@ -245,7 +245,7 @@ func RunRollingUpdate(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args
// than the old rc. This selector is the hash of the rc, with a suffix to provide uniqueness for
// same-image updates.
if len(image) != 0 {
codec := api.Codecs.LegacyCodec(clientset.CoreClient.RESTClient().APIVersion())
codec := api.Codecs.LegacyCodec(v1.SchemeGroupVersion)
keepOldName = len(args) == 1
newName := findNewName(args, oldRc)
if newRc, err = kubectl.LoadExistingNextReplicationController(coreClient, cmdNamespace, newName); err != nil {

View File

@ -286,7 +286,7 @@ func (f *FakeFactory) RESTClient() (*restclient.RESTClient, error) {
return nil, nil
}
func (f *FakeFactory) ClientSet() (*internalclientset.Clientset, error) {
func (f *FakeFactory) ClientSet() (internalclientset.Interface, error) {
return nil, nil
}
@ -311,7 +311,7 @@ func (f *FakeFactory) FederationClientSetForVersion(version *schema.GroupVersion
func (f *FakeFactory) FederationClientForVersion(version *schema.GroupVersion) (*restclient.RESTClient, error) {
return nil, nil
}
func (f *FakeFactory) ClientSetForVersion(requiredVersion *schema.GroupVersion) (*internalclientset.Clientset, error) {
func (f *FakeFactory) ClientSetForVersion(requiredVersion *schema.GroupVersion) (internalclientset.Interface, error) {
return nil, nil
}
func (f *FakeFactory) ClientConfigForVersion(requiredVersion *schema.GroupVersion) (*restclient.Config, error) {
@ -536,7 +536,7 @@ func (f *fakeAPIFactory) JSONEncoder() runtime.Encoder {
return testapi.Default.Codec()
}
func (f *fakeAPIFactory) ClientSet() (*internalclientset.Clientset, error) {
func (f *fakeAPIFactory) ClientSet() (internalclientset.Interface, error) {
// Swap the HTTP client out of the REST client with the fake
// version.
fakeClient := f.tf.Client.(*fake.RESTClient)

View File

@ -75,6 +75,7 @@ go_test(
name = "go_default_test",
srcs = [
"cached_discovery_test.go",
"factory_object_mapping_test.go",
"factory_test.go",
"helpers_test.go",
"shortcut_restmapper_test.go",
@ -90,7 +91,10 @@ go_test(
"//pkg/api/testing:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/api/validation:go_default_library",
"//pkg/apis/apps:go_default_library",
"//pkg/apis/batch:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/kubectl:go_default_library",
@ -104,6 +108,7 @@ go_test(
"//vendor:k8s.io/apimachinery/pkg/labels",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/util/diff",
"//vendor:k8s.io/apimachinery/pkg/util/validation/field",
"//vendor:k8s.io/apimachinery/pkg/version",
"//vendor:k8s.io/apimachinery/pkg/watch",

View File

@ -32,7 +32,7 @@ import (
func NewClientCache(loader clientcmd.ClientConfig, discoveryClientFactory DiscoveryClientFactory) *ClientCache {
return &ClientCache{
clientsets: make(map[schema.GroupVersion]*internalclientset.Clientset),
clientsets: make(map[schema.GroupVersion]internalclientset.Interface),
configs: make(map[schema.GroupVersion]*restclient.Config),
fedClientSets: make(map[schema.GroupVersion]fedclientset.Interface),
loader: loader,
@ -44,7 +44,7 @@ func NewClientCache(loader clientcmd.ClientConfig, discoveryClientFactory Discov
// is invoked only once
type ClientCache struct {
loader clientcmd.ClientConfig
clientsets map[schema.GroupVersion]*internalclientset.Clientset
clientsets map[schema.GroupVersion]internalclientset.Interface
fedClientSets map[schema.GroupVersion]fedclientset.Interface
configs map[schema.GroupVersion]*restclient.Config
@ -144,7 +144,7 @@ func copyConfig(in *restclient.Config) *restclient.Config {
// ClientSetForVersion initializes or reuses a clientset for the specified version, or returns an
// error if that is not possible
func (c *ClientCache) ClientSetForVersion(requiredVersion *schema.GroupVersion) (*internalclientset.Clientset, error) {
func (c *ClientCache) ClientSetForVersion(requiredVersion *schema.GroupVersion) (internalclientset.Interface, error) {
if requiredVersion != nil {
if clientset, ok := c.clientsets[*requiredVersion]; ok {
return clientset, nil

View File

@ -86,7 +86,7 @@ type ClientAccessFactory interface {
DiscoveryClientFactory
// ClientSet gives you back an internal, generated clientset
ClientSet() (*internalclientset.Clientset, error)
ClientSet() (internalclientset.Interface, error)
// Returns a RESTClient for accessing Kubernetes resources or an error.
RESTClient() (*restclient.RESTClient, error)
// Returns a client.Config for accessing the Kubernetes server.
@ -101,7 +101,7 @@ type ClientAccessFactory interface {
// TODO remove this should be rolled into restclient with the right version
FederationClientForVersion(version *schema.GroupVersion) (*restclient.RESTClient, error)
// TODO remove. This should be rolled into `ClientSet`
ClientSetForVersion(requiredVersion *schema.GroupVersion) (*internalclientset.Clientset, error)
ClientSetForVersion(requiredVersion *schema.GroupVersion) (internalclientset.Interface, error)
// TODO remove. This should be rolled into `ClientConfig`
ClientConfigForVersion(requiredVersion *schema.GroupVersion) (*restclient.Config, error)

View File

@ -168,11 +168,11 @@ func (f *ring0Factory) DiscoveryClient() (discovery.CachedDiscoveryInterface, er
return f.discoveryFactory.DiscoveryClient()
}
func (f *ring0Factory) ClientSet() (*internalclientset.Clientset, error) {
func (f *ring0Factory) ClientSet() (internalclientset.Interface, error) {
return f.clientCache.ClientSetForVersion(nil)
}
func (f *ring0Factory) ClientSetForVersion(requiredVersion *schema.GroupVersion) (*internalclientset.Clientset, error) {
func (f *ring0Factory) ClientSetForVersion(requiredVersion *schema.GroupVersion) (internalclientset.Interface, error) {
return f.clientCache.ClientSetForVersion(requiredVersion)
}

View File

@ -213,51 +213,48 @@ func (f *ring1Factory) LogsForObject(object, options runtime.Object) (*restclien
if err != nil {
return nil, err
}
opts, ok := options.(*api.PodLogOptions)
if !ok {
return nil, errors.New("provided options object is not a PodLogOptions")
}
var selector labels.Selector
var namespace string
switch t := object.(type) {
case *api.Pod:
opts, ok := options.(*api.PodLogOptions)
if !ok {
return nil, errors.New("provided options object is not a PodLogOptions")
}
return clientset.Core().Pods(t.Namespace).GetLogs(t.Name, opts), nil
case *api.ReplicationController:
opts, ok := options.(*api.PodLogOptions)
if !ok {
return nil, errors.New("provided options object is not a PodLogOptions")
}
selector := labels.SelectorFromSet(t.Spec.Selector)
sortBy := func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) }
pod, numPods, err := GetFirstPod(clientset.Core(), t.Namespace, selector, 20*time.Second, sortBy)
if err != nil {
return nil, err
}
if numPods > 1 {
fmt.Fprintf(os.Stderr, "Found %v pods, using pod/%v\n", numPods, pod.Name)
}
return clientset.Core().Pods(pod.Namespace).GetLogs(pod.Name, opts), nil
namespace = t.Namespace
selector = labels.SelectorFromSet(t.Spec.Selector)
case *extensions.ReplicaSet:
opts, ok := options.(*api.PodLogOptions)
if !ok {
return nil, errors.New("provided options object is not a PodLogOptions")
}
selector, err := metav1.LabelSelectorAsSelector(t.Spec.Selector)
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("invalid label selector: %v", err)
}
sortBy := func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) }
pod, numPods, err := GetFirstPod(clientset.Core(), t.Namespace, selector, 20*time.Second, sortBy)
case *extensions.Deployment:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return nil, err
}
if numPods > 1 {
fmt.Fprintf(os.Stderr, "Found %v pods, using pod/%v\n", numPods, pod.Name)
return nil, fmt.Errorf("invalid label selector: %v", err)
}
return clientset.Core().Pods(pod.Namespace).GetLogs(pod.Name, opts), nil
case *batch.Job:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("invalid label selector: %v", err)
}
case *apps.StatefulSet:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("invalid label selector: %v", err)
}
default:
gvks, _, err := api.Scheme.ObjectKinds(object)
@ -266,6 +263,16 @@ func (f *ring1Factory) LogsForObject(object, options runtime.Object) (*restclien
}
return nil, fmt.Errorf("cannot get the logs from %v", gvks[0])
}
sortBy := func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) }
pod, numPods, err := GetFirstPod(clientset.Core(), namespace, selector, 20*time.Second, sortBy)
if err != nil {
return nil, err
}
if numPods > 1 {
fmt.Fprintf(os.Stderr, "Found %v pods, using pod/%v\n", numPods, pod.Name)
}
return clientset.Core().Pods(pod.Namespace).GetLogs(pod.Name, opts), nil
}
func (f *ring1Factory) Scaler(mapping *meta.RESTMapping) (kubectl.Scaler, error) {
@ -329,29 +336,35 @@ func (f *ring1Factory) AttachablePodForObject(object runtime.Object) (*api.Pod,
case *extensions.ReplicaSet:
namespace = t.Namespace
selector = labels.SelectorFromSet(t.Spec.Selector.MatchLabels)
case *api.ReplicationController:
namespace = t.Namespace
selector = labels.SelectorFromSet(t.Spec.Selector)
case *apps.StatefulSet:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("invalid label selector: %v", err)
}
case *extensions.Deployment:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("invalid label selector: %v", err)
}
case *batch.Job:
namespace = t.Namespace
selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector)
if err != nil {
return nil, fmt.Errorf("invalid label selector: %v", err)
}
case *api.Pod:
return t, nil
default:
gvks, _, err := api.Scheme.ObjectKinds(object)
if err != nil {
@ -359,6 +372,7 @@ func (f *ring1Factory) AttachablePodForObject(object runtime.Object) (*api.Pod,
}
return nil, fmt.Errorf("cannot attach to %v: not implemented", gvks[0])
}
sortBy := func(pods []*v1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) }
pod, _, err := GetFirstPod(clientset.Core(), namespace, selector, 1*time.Minute, sortBy)
return pod, err

View File

@ -0,0 +1,192 @@
/*
Copyright 2017 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 util
import (
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
testclient "k8s.io/client-go/testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
)
type fakeClientAccessFactory struct {
ClientAccessFactory
fakeClientset *fake.Clientset
}
func (f *fakeClientAccessFactory) ClientSetForVersion(requiredVersion *schema.GroupVersion) (internalclientset.Interface, error) {
return f.fakeClientset, nil
}
func newFakeClientAccessFactory(objs []runtime.Object) *fakeClientAccessFactory {
return &fakeClientAccessFactory{
fakeClientset: fake.NewSimpleClientset(objs...),
}
}
var (
podsResource = schema.GroupVersionResource{Resource: "pods"}
)
func TestLogsForObject(t *testing.T) {
tests := []struct {
name string
obj runtime.Object
opts *api.PodLogOptions
pods []runtime.Object
actions []testclient.Action
}{
{
name: "pod logs",
obj: &api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
getLogsAction("test", nil),
},
},
{
name: "replication controller logs",
obj: &api.ReplicationController{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: api.ReplicationControllerSpec{
Selector: map[string]string{"foo": "bar"},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
{
name: "replica set logs",
obj: &extensions.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: extensions.ReplicaSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
{
name: "deployment logs",
obj: &extensions.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: extensions.DeploymentSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
{
name: "job logs",
obj: &batch.Job{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: batch.JobSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
{
name: "stateful set logs",
obj: &apps.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: apps.StatefulSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
},
},
pods: []runtime.Object{testPod()},
actions: []testclient.Action{
testclient.NewListAction(podsResource, "test", metav1.ListOptions{LabelSelector: "foo=bar"}),
getLogsAction("test", nil),
},
},
}
for _, test := range tests {
caf := newFakeClientAccessFactory(test.pods)
omf := NewObjectMappingFactory(caf)
_, err := omf.LogsForObject(test.obj, test.opts)
if err != nil {
t.Errorf("%s: unexpected error: %v", test.name, err)
continue
}
for i := range test.actions {
if len(caf.fakeClientset.Actions()) < i {
t.Errorf("%s: action %d does not exists in actual actions: %#v",
test.name, i, caf.fakeClientset.Actions())
continue
}
got := caf.fakeClientset.Actions()[i]
want := test.actions[i]
if !reflect.DeepEqual(got, want) {
t.Errorf("%s: unexpected action: %s", test.name, diff.ObjectDiff(got, want))
}
}
}
}
func testPod() runtime.Object {
return &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "test",
Labels: map[string]string{"foo": "bar"},
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "c1"}},
},
}
}
func getLogsAction(namespace string, opts *api.PodLogOptions) testclient.Action {
action := testclient.GenericActionImpl{}
action.Verb = "get"
action.Namespace = namespace
action.Resource = podsResource
action.Subresource = "logs"
action.Value = opts
return action
}