From 6d31bc7ec40608e4eb17cc0fd58136c235c13836 Mon Sep 17 00:00:00 2001 From: ZhiMing Zhang Date: Fri, 3 Apr 2015 08:49:58 +0800 Subject: [PATCH 01/43] Update fedora_manual_config.md when I run into this trouble,I googled a lot and finally mattf helped me out of this ,I think it's better to update this document for someone else who try to follow this guide --- docs/getting-started-guides/fedora/fedora_manual_config.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/getting-started-guides/fedora/fedora_manual_config.md b/docs/getting-started-guides/fedora/fedora_manual_config.md index 428c746f194..12a5787e261 100644 --- a/docs/getting-started-guides/fedora/fedora_manual_config.md +++ b/docs/getting-started-guides/fedora/fedora_manual_config.md @@ -77,6 +77,11 @@ KUBE_SERVICE_ADDRESSES="--portal_net=10.254.0.0/16" KUBE_API_ARGS="" ``` +* Edit /etc/etcd/etcd.conf,let the etcd to listen all the ip instead of 127.0.0.1,if not ,you will get the error like "connection refused" +``` +ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:4001" +``` + * Start the appropriate services on master: ``` From 98db9081a69a8717f92163f867af53b1e161a016 Mon Sep 17 00:00:00 2001 From: Jeff Mendoza Date: Wed, 8 Apr 2015 09:05:07 -0700 Subject: [PATCH 02/43] Azure: Wait for salt completion on cluster initlization Fix for #3177. Add a loop at the end of cluster initlization to wait for salt completion. This may never complete if there was a cluster deployment error. --- cluster/azure/util.sh | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/cluster/azure/util.sh b/cluster/azure/util.sh index b9e95f5b576..f5381bf8d6f 100644 --- a/cluster/azure/util.sh +++ b/cluster/azure/util.sh @@ -460,21 +460,22 @@ EOF "${HOME}/${kube_key}" "${HOME}/${ca_cert}" ) - # Wait for salt on the minions - sleep 30 - echo "Sanity checking cluster..." + echo + echo " This will continually check the minions to ensure docker is" + echo " installed. This is usually a good indicator that salt has" + echo " successfully provisioned. This might loop forever if there was" + echo " some uncaught error during start up." + echo # Basic sanity checking for (( i=0; i<${#MINION_NAMES[@]}; i++)); do # Make sure docker is installed echo "--> Making sure docker is installed on ${MINION_NAMES[$i]}." - ssh -oStrictHostKeyChecking=no -i $AZ_SSH_KEY -p ${ssh_ports[$i]} \ - $AZ_CS.cloudapp.net which docker > /dev/null || { - echo "Docker failed to install on ${MINION_NAMES[$i]}. Your cluster is unlikely" >&2 - echo "to work correctly. Please run ./cluster/kube-down.sh and re-create the" >&2 - echo "cluster. (sorry!)" >&2 - exit 1 - } + until ssh -oStrictHostKeyChecking=no -i $AZ_SSH_KEY -p ${ssh_ports[$i]} \ + $AZ_CS.cloudapp.net which docker > /dev/null 2>&1; do + printf "." + sleep 2 + done done echo From ee54b1d0effa09962dc4473af15af160da204afc Mon Sep 17 00:00:00 2001 From: Christiaan Hees Date: Fri, 17 Apr 2015 13:24:53 +0200 Subject: [PATCH 03/43] Added an example for Meteor with raw documentation --- examples/meteor/README.md | 72 ++++++++++++++++++++++++++ examples/meteor/dockerbase/Dockerfile | 18 +++++++ examples/meteor/dockerbase/README.md | 9 ++++ examples/meteor/meteor-controller.json | 26 ++++++++++ examples/meteor/meteor-service.json | 10 ++++ examples/meteor/mongo-pod.json | 33 ++++++++++++ examples/meteor/mongo-service.json | 13 +++++ 7 files changed, 181 insertions(+) create mode 100644 examples/meteor/README.md create mode 100644 examples/meteor/dockerbase/Dockerfile create mode 100644 examples/meteor/dockerbase/README.md create mode 100644 examples/meteor/meteor-controller.json create mode 100644 examples/meteor/meteor-service.json create mode 100644 examples/meteor/mongo-pod.json create mode 100644 examples/meteor/mongo-service.json diff --git a/examples/meteor/README.md b/examples/meteor/README.md new file mode 100644 index 00000000000..5fb3eab297a --- /dev/null +++ b/examples/meteor/README.md @@ -0,0 +1,72 @@ +Build a container for your Meteor app +------------------------------------- + +To be able to run your Meteor app on Kubernetes you need to build a container for it first. To do that you need to install [Docker](https://www.docker.com) and get an account on [Docker Hub](https://hub.docker.com/). Once you have that you need to add 2 files to your Meteor project "Dockerfile" and ".dockerignore". + +"Dockerfile" should contain this: + + FROM chees/meteor-kubernetes + ENV ROOT_URL http://myawesomeapp.com + +You should replace the ROOT_URL with the actual hostname of your app. + +The .dockerignore file should contain this: + + .meteor/local + packages/*/.build* + +This tells Docker to ignore the files on those directories when it's building your container. + +You can see an example of a Dockerfile in our [meteor-gke-example](https://github.com/Q42/meteor-gke-example) project. + +Now you can build your container by running something like this in your Meteor project directory: + + docker build -t chees/meteor-gke-example:1 . + +Here you should replace "chees" with your own username on Docker Hub, "meteor-gke-example" with the name of your project and "1" with the version name of your build. + +Push the container to your Docker hub account (replace the username and project with your own again): + + docker push chees/meteor-gke-example + + + +Running +------- + +Now that you have containerized your Meteor app it's time to set up your cluster. Edit "meteor-controller.json" and make sure the "image" points to the container you just pushed to the Docker Hub. + +For Mongo we use a Persistent Disk to store the data. If you're using gcloud you can create it once by running: + + gcloud compute disks create --size=200GB mongo-disk + +You also need to format the disk before you can use it: + + gcloud compute instances attach-disk --disk=mongo-disk --device-name temp-data k8s-meteor-master + gcloud compute ssh k8s-meteor-master --command "sudo mkdir /mnt/tmp && sudo /usr/share/google/safe_format_and_mount /dev/disk/by-id/google-temp-data /mnt/tmp" + gcloud compute instances detach-disk --disk mongo-disk k8s-meteor-master + +Now you can start Mongo using that disk: + + kubectl create -f mongo-pod.json + kubectl create -f mongo-service.json + +Wait until Mongo is started completely and then set up Meteor: + + kubectl create -f meteor-controller.json + kubectl create -f meteor-service.json + +Note that meteor-service.json creates an external load balancer, so your app should be available through the IP of that load balancer once the Meteor pods are started. On gcloud you can find the IP of your load balancer by running: + + gcloud compute forwarding-rules list k8s-meteor-default-meteor | grep k8s-meteor-default-meteor | awk '{print $3}' + +You might have to open up port 80 if it's not open yet in your project. For example: + + gcloud compute firewall-rules create meteor-80 --allow=tcp:80 --target-tags k8s-meteor-node + + + + +TODO replace the mongo image with the official mongo? https://registry.hub.docker.com/_/mongo/ +TODO use Kubernetes v1beta3 syntax? + diff --git a/examples/meteor/dockerbase/Dockerfile b/examples/meteor/dockerbase/Dockerfile new file mode 100644 index 00000000000..8ce633c634b --- /dev/null +++ b/examples/meteor/dockerbase/Dockerfile @@ -0,0 +1,18 @@ +FROM node:0.10 +MAINTAINER Christiaan Hees + +ONBUILD WORKDIR /appsrc +ONBUILD COPY . /appsrc + +ONBUILD RUN curl https://install.meteor.com/ | sh && \ + meteor build ../app --directory --architecture os.linux.x86_64 && \ + rm -rf /appsrc +# TODO rm meteor so it doesn't take space in the image? + +ONBUILD WORKDIR /app/bundle + +ONBUILD RUN (cd programs/server && npm install) +EXPOSE 8080 +CMD [] +ENV PORT 8080 +ENTRYPOINT MONGO_URL=mongodb://$MONGO_SERVICE_HOST:$MONGO_SERVICE_PORT /usr/local/bin/node main.js diff --git a/examples/meteor/dockerbase/README.md b/examples/meteor/dockerbase/README.md new file mode 100644 index 00000000000..f0234c9ad04 --- /dev/null +++ b/examples/meteor/dockerbase/README.md @@ -0,0 +1,9 @@ +Building the meteor-kubernetes base image +----------------------------------------- + +As a normal user you don't need to do this since the image is already built and pushed to Docker Hub. You can just use it as a base image. See [this example](https://github.com/Q42/meteor-gke-example/blob/master/Dockerfile). + +To build and push the base meteor-kubernetes image: + + docker build -t chees/meteor-kubernetes . + docker push chees/meteor-kubernetes diff --git a/examples/meteor/meteor-controller.json b/examples/meteor/meteor-controller.json new file mode 100644 index 00000000000..d2a08ad2d7c --- /dev/null +++ b/examples/meteor/meteor-controller.json @@ -0,0 +1,26 @@ +{ + "id": "meteor-controller", + "kind": "ReplicationController", + "apiVersion": "v1beta1", + "desiredState": { + "replicas": 2, + "replicaSelector": {"name": "meteor"}, + "podTemplate": { + "desiredState": { + "manifest": { + "version": "v1beta1", + "id": "meteor-controller", + "containers": [{ + "name": "meteor", + "image": "chees/meteor-gke-example:latest", + "cpu": 1000, + "memory": 500000000, + "ports": [{"name": "http-server", "containerPort": 8080, "hostPort": 80}] + }] + } + }, + "labels": { "name": "meteor" } + } + }, + "labels": {"name": "meteor"} +} diff --git a/examples/meteor/meteor-service.json b/examples/meteor/meteor-service.json new file mode 100644 index 00000000000..11c0ca41da7 --- /dev/null +++ b/examples/meteor/meteor-service.json @@ -0,0 +1,10 @@ +{ + "apiVersion": "v1beta1", + "kind": "Service", + "id": "meteor", + "port": 80, + "containerPort": "http-server", + "selector": { "name": "meteor" }, + "createExternalLoadBalancer": true, + "sessionAffinity": "ClientIP" +} diff --git a/examples/meteor/mongo-pod.json b/examples/meteor/mongo-pod.json new file mode 100644 index 00000000000..c9a2d5051b0 --- /dev/null +++ b/examples/meteor/mongo-pod.json @@ -0,0 +1,33 @@ +{ + "id": "mongo", + "kind": "Pod", + "apiVersion": "v1beta1", + "desiredState": { + "manifest": { + "version": "v1beta1", + "id": "mongo", + "containers": [{ + "name": "mongo", + "image": "mongo", + "cpu": 1000, + "ports": [{ "name": "mongo", "containerPort": 27017 }], + "volumeMounts": [{ + "mountPath": "/data/db", + "name": "mongo-disk" + }] + }], + "volumes": [{ + "name": "mongo-disk", + "source": { + "persistentDisk": { + "pdName": "mongo-disk", + "fsType": "ext4" + } + } + }] + } + }, + "labels": { + "name": "mongo", "role": "mongo" + } +} diff --git a/examples/meteor/mongo-service.json b/examples/meteor/mongo-service.json new file mode 100644 index 00000000000..dde2e5066d2 --- /dev/null +++ b/examples/meteor/mongo-service.json @@ -0,0 +1,13 @@ +{ + "id": "mongo", + "kind": "Service", + "apiVersion": "v1beta1", + "port": 27017, + "containerPort": "mongo", + "selector": { + "name": "mongo", "role": "mongo" + }, + "labels": { + "name": "mongo" + } +} \ No newline at end of file From 75482cabb465844366e5db8aea44d81d32a62181 Mon Sep 17 00:00:00 2001 From: derekwaynecarr Date: Fri, 17 Apr 2015 16:59:54 -0400 Subject: [PATCH 04/43] Reject unbounded cpu and memory pods if quota is restricting it --- .../resource_quota_controller.go | 22 +++++++ .../resource_quota_controller_test.go | 60 +++++++++++++++++ .../pkg/admission/resourcequota/admission.go | 6 ++ .../admission/resourcequota/admission_test.go | 66 +++++++++++++++++++ 4 files changed, 154 insertions(+) diff --git a/pkg/resourcequota/resource_quota_controller.go b/pkg/resourcequota/resource_quota_controller.go index 3d651c12fc8..62ad59f0107 100644 --- a/pkg/resourcequota/resource_quota_controller.go +++ b/pkg/resourcequota/resource_quota_controller.go @@ -226,6 +226,28 @@ func PodCPU(pod *api.Pod) *resource.Quantity { return resource.NewMilliQuantity(int64(val), resource.DecimalSI) } +// IsPodCPUUnbounded returns true if the cpu use is unbounded for any container in pod +func IsPodCPUUnbounded(pod *api.Pod) bool { + for j := range pod.Spec.Containers { + container := pod.Spec.Containers[j] + if container.Resources.Limits.Cpu().MilliValue() == int64(0) { + return true + } + } + return false +} + +// IsPodMemoryUnbounded returns true if the memory use is unbounded for any container in pod +func IsPodMemoryUnbounded(pod *api.Pod) bool { + for j := range pod.Spec.Containers { + container := pod.Spec.Containers[j] + if container.Resources.Limits.Memory().Value() == int64(0) { + return true + } + } + return false +} + // PodMemory computes the memory usage of a pod func PodMemory(pod *api.Pod) *resource.Quantity { val := int64(0) diff --git a/pkg/resourcequota/resource_quota_controller_test.go b/pkg/resourcequota/resource_quota_controller_test.go index 8d03ab44c34..2f112ccbf02 100644 --- a/pkg/resourcequota/resource_quota_controller_test.go +++ b/pkg/resourcequota/resource_quota_controller_test.go @@ -267,3 +267,63 @@ func TestSyncResourceQuotaNoChange(t *testing.T) { t.Errorf("SyncResourceQuota made an unexpected client action when state was not dirty: %v", kubeClient.Actions) } } + +func TestIsPodCPUUnbounded(t *testing.T) { + pod := api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "pod-running"}, + Status: api.PodStatus{Phase: api.PodRunning}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "0")}}, + }, + } + if IsPodCPUUnbounded(&pod) { + t.Errorf("Expected false") + } + pod = api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "pod-running"}, + Status: api.PodStatus{Phase: api.PodRunning}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("0", "0")}}, + }, + } + if !IsPodCPUUnbounded(&pod) { + t.Errorf("Expected true") + } + + pod.Spec.Containers[0].Resources = api.ResourceRequirements{} + if !IsPodCPUUnbounded(&pod) { + t.Errorf("Expected true") + } +} + +func TestIsPodMemoryUnbounded(t *testing.T) { + pod := api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "pod-running"}, + Status: api.PodStatus{Phase: api.PodRunning}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("0", "1Gi")}}, + }, + } + if IsPodMemoryUnbounded(&pod) { + t.Errorf("Expected false") + } + pod = api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "pod-running"}, + Status: api.PodStatus{Phase: api.PodRunning}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("0", "0")}}, + }, + } + if !IsPodMemoryUnbounded(&pod) { + t.Errorf("Expected true") + } + + pod.Spec.Containers[0].Resources = api.ResourceRequirements{} + if !IsPodMemoryUnbounded(&pod) { + t.Errorf("Expected true") + } +} diff --git a/plugin/pkg/admission/resourcequota/admission.go b/plugin/pkg/admission/resourcequota/admission.go index 092832e15a7..3a9469c4106 100644 --- a/plugin/pkg/admission/resourcequota/admission.go +++ b/plugin/pkg/admission/resourcequota/admission.go @@ -169,6 +169,9 @@ func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, cli hardMem, hardMemFound := status.Hard[api.ResourceMemory] if hardMemFound { + if set[api.ResourceMemory] && resourcequota.IsPodMemoryUnbounded(pod) { + return false, fmt.Errorf("Limited to %s memory, but pod has no specified memory limit", hardMem.String()) + } used, usedFound := status.Used[api.ResourceMemory] if !usedFound { return false, fmt.Errorf("Quota usage stats are not yet known, unable to admit resource until an accurate count is completed.") @@ -182,6 +185,9 @@ func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, cli } hardCPU, hardCPUFound := status.Hard[api.ResourceCPU] if hardCPUFound { + if set[api.ResourceCPU] && resourcequota.IsPodCPUUnbounded(pod) { + return false, fmt.Errorf("Limited to %s CPU, but pod has no specified cpu limit", hardCPU.String()) + } used, usedFound := status.Used[api.ResourceCPU] if !usedFound { return false, fmt.Errorf("Quota usage stats are not yet known, unable to admit resource until an accurate count is completed.") diff --git a/plugin/pkg/admission/resourcequota/admission_test.go b/plugin/pkg/admission/resourcequota/admission_test.go index 2b8e9e881c0..31fc7161936 100644 --- a/plugin/pkg/admission/resourcequota/admission_test.go +++ b/plugin/pkg/admission/resourcequota/admission_test.go @@ -195,6 +195,72 @@ func TestIncrementUsageCPU(t *testing.T) { } } +func TestUnboundedCPU(t *testing.T) { + namespace := "default" + client := testclient.NewSimpleFake(&api.PodList{ + Items: []api.Pod{ + { + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}}, + }, + }, + }, + }) + status := &api.ResourceQuotaStatus{ + Hard: api.ResourceList{}, + Used: api.ResourceList{}, + } + r := api.ResourceCPU + status.Hard[r] = resource.MustParse("200m") + status.Used[r] = resource.MustParse("100m") + + newPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("0m", "1Gi")}}, + }} + _, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", "CREATE"), status, client) + if err == nil { + t.Errorf("Expected CPU unbounded usage error") + } +} + +func TestUnboundedMemory(t *testing.T) { + namespace := "default" + client := testclient.NewSimpleFake(&api.PodList{ + Items: []api.Pod{ + { + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("100m", "1Gi")}}, + }, + }, + }, + }) + status := &api.ResourceQuotaStatus{ + Hard: api.ResourceList{}, + Used: api.ResourceList{}, + } + r := api.ResourceMemory + status.Hard[r] = resource.MustParse("10Gi") + status.Used[r] = resource.MustParse("1Gi") + + newPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements("250m", "0")}}, + }} + _, err := IncrementUsage(admission.NewAttributesRecord(newPod, "Pod", namespace, "pods", "CREATE"), status, client) + if err == nil { + t.Errorf("Expected memory unbounded usage error") + } +} + func TestExceedUsageCPU(t *testing.T) { namespace := "default" client := testclient.NewSimpleFake(&api.PodList{ From 5036289b5e29685762fd156bc301ca72407902a1 Mon Sep 17 00:00:00 2001 From: derekwaynecarr Date: Mon, 20 Apr 2015 14:56:15 -0400 Subject: [PATCH 05/43] Improve container resource requirements validation --- pkg/api/validation/validation.go | 15 +++++++++++---- pkg/api/validation/validation_test.go | 10 ++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index b49e883796b..9a2d49e30bc 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -740,7 +740,7 @@ func validateContainers(containers []api.Container, volumes util.StringSet) errs cErrs = append(cErrs, validateEnv(ctr.Env).Prefix("env")...) cErrs = append(cErrs, validateVolumeMounts(ctr.VolumeMounts, volumes).Prefix("volumeMounts")...) cErrs = append(cErrs, validatePullPolicy(&ctr).Prefix("pullPolicy")...) - cErrs = append(cErrs, validateResourceRequirements(&ctr).Prefix("resources")...) + cErrs = append(cErrs, ValidateResourceRequirements(&ctr.Resources).Prefix("resources")...) allErrs = append(allErrs, cErrs.PrefixIndex(i)...) } // Check for colliding ports across all containers. @@ -1167,9 +1167,9 @@ func validateBasicResource(quantity resource.Quantity) errs.ValidationErrorList } // Validates resource requirement spec. -func validateResourceRequirements(container *api.Container) errs.ValidationErrorList { +func ValidateResourceRequirements(requirements *api.ResourceRequirements) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} - for resourceName, quantity := range container.Resources.Limits { + for resourceName, quantity := range requirements.Limits { // Validate resource name. errs := validateResourceName(resourceName.String(), fmt.Sprintf("resources.limits[%s]", resourceName)) if api.IsStandardResourceName(resourceName.String()) { @@ -1177,7 +1177,14 @@ func validateResourceRequirements(container *api.Container) errs.ValidationError } allErrs = append(allErrs, errs...) } - + for resourceName, quantity := range requirements.Requests { + // Validate resource name. + errs := validateResourceName(resourceName.String(), fmt.Sprintf("resources.requests[%s]", resourceName)) + if api.IsStandardResourceName(resourceName.String()) { + errs = append(errs, validateBasicResource(quantity).Prefix(fmt.Sprintf("Resource %s: ", resourceName))...) + } + allErrs = append(allErrs, errs...) + } return allErrs } diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index ddc27439946..3b740095670 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -954,6 +954,16 @@ func TestValidateContainers(t *testing.T) { ImagePullPolicy: "IfNotPresent", }, }, + "Resource Requests CPU invalid": { + { + Name: "abc-123", + Image: "image", + Resources: api.ResourceRequirements{ + Requests: getResourceLimits("-10", "0"), + }, + ImagePullPolicy: "IfNotPresent", + }, + }, "Resource Memory invalid": { { Name: "abc-123", From bf8f258471ca0e67e9f026ad7826df8d71828f60 Mon Sep 17 00:00:00 2001 From: Ravi Gadde Date: Mon, 20 Apr 2015 11:53:06 -0700 Subject: [PATCH 06/43] Added field selector for listing pods. --- cmd/integration/integration.go | 5 +++-- pkg/client/pods.go | 8 ++++---- pkg/client/pods_test.go | 7 ++++--- pkg/client/testclient/fake_pods.go | 2 +- pkg/cloudprovider/nodecontroller/nodecontroller.go | 2 +- pkg/controller/replication_controller.go | 2 +- pkg/kubectl/describe.go | 5 +++-- pkg/namespace/namespace_controller.go | 2 +- pkg/resourcequota/resource_quota_controller.go | 3 ++- pkg/service/endpoints_controller.go | 2 +- test/e2e/density.go | 12 ++++++------ test/e2e/es_cluster_logging.go | 2 +- test/e2e/events.go | 2 +- test/e2e/monitoring.go | 2 +- test/e2e/pods.go | 10 +++++----- test/e2e/rc.go | 7 ++++--- test/integration/client_test.go | 4 ++-- test/integration/metrics_test.go | 3 ++- 18 files changed, 43 insertions(+), 37 deletions(-) diff --git a/cmd/integration/integration.go b/cmd/integration/integration.go index 676deee188f..27c5b6c0c68 100644 --- a/cmd/integration/integration.go +++ b/cmd/integration/integration.go @@ -43,6 +43,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/nodecontroller" replicationControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/controller" + "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/cadvisor" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" @@ -260,7 +261,7 @@ func podsOnMinions(c *client.Client, podNamespace string, labelSelector labels.S podInfo := fakeKubeletClient{} // wait for minions to indicate they have info about the desired pods return func() (bool, error) { - pods, err := c.Pods(podNamespace).List(labelSelector) + pods, err := c.Pods(podNamespace).List(labelSelector, fields.Everything()) if err != nil { glog.Infof("Unable to get pods to list: %v", err) return false, nil @@ -384,7 +385,7 @@ containers: namespace := kubelet.NamespaceDefault if err := wait.Poll(time.Second, time.Minute*2, podRunning(c, namespace, podName)); err != nil { - if pods, err := c.Pods(namespace).List(labels.Everything()); err == nil { + if pods, err := c.Pods(namespace).List(labels.Everything(), fields.Everything()); err == nil { for _, pod := range pods.Items { glog.Infof("pod found: %s/%s", namespace, pod.Name) } diff --git a/pkg/client/pods.go b/pkg/client/pods.go index ced3c2679a0..b61c4ed4f0e 100644 --- a/pkg/client/pods.go +++ b/pkg/client/pods.go @@ -30,7 +30,7 @@ type PodsNamespacer interface { // PodInterface has methods to work with Pod resources. type PodInterface interface { - List(selector labels.Selector) (*api.PodList, error) + List(label labels.Selector, field fields.Selector) (*api.PodList, error) Get(name string) (*api.Pod, error) Delete(name string) error Create(pod *api.Pod) (*api.Pod, error) @@ -54,10 +54,10 @@ func newPods(c *Client, namespace string) *pods { } } -// List takes a selector, and returns the list of pods that match that selector. -func (c *pods) List(selector labels.Selector) (result *api.PodList, err error) { +// List takes label and field selectors, and returns the list of pods that match those selectors. +func (c *pods) List(label labels.Selector, field fields.Selector) (result *api.PodList, err error) { result = &api.PodList{} - err = c.r.Get().Namespace(c.ns).Resource("pods").LabelsSelectorParam(selector).Do().Into(result) + err = c.r.Get().Namespace(c.ns).Resource("pods").LabelsSelectorParam(label).FieldsSelectorParam(field).Do().Into(result) return } diff --git a/pkg/client/pods_test.go b/pkg/client/pods_test.go index 7316e2a573a..37a68e6c88b 100644 --- a/pkg/client/pods_test.go +++ b/pkg/client/pods_test.go @@ -22,6 +22,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" + "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) @@ -31,7 +32,7 @@ func TestListEmptyPods(t *testing.T) { Request: testRequest{Method: "GET", Path: testapi.ResourcePath("pods", ns, ""), Query: buildQueryValues(ns, nil)}, Response: Response{StatusCode: 200, Body: &api.PodList{}}, } - podList, err := c.Setup().Pods(ns).List(labels.Everything()) + podList, err := c.Setup().Pods(ns).List(labels.Everything(), fields.Everything()) c.Validate(t, podList, err) } @@ -57,7 +58,7 @@ func TestListPods(t *testing.T) { }, }, } - receivedPodList, err := c.Setup().Pods(ns).List(labels.Everything()) + receivedPodList, err := c.Setup().Pods(ns).List(labels.Everything(), fields.Everything()) c.Validate(t, receivedPodList, err) } @@ -91,7 +92,7 @@ func TestListPodsLabels(t *testing.T) { c.Setup() c.QueryValidator[labelSelectorQueryParamName] = validateLabels selector := labels.Set{"foo": "bar", "name": "baz"}.AsSelector() - receivedPodList, err := c.Pods(ns).List(selector) + receivedPodList, err := c.Pods(ns).List(selector, fields.Everything()) c.Validate(t, receivedPodList, err) } diff --git a/pkg/client/testclient/fake_pods.go b/pkg/client/testclient/fake_pods.go index 61bbfdf8123..9cba18ca77f 100644 --- a/pkg/client/testclient/fake_pods.go +++ b/pkg/client/testclient/fake_pods.go @@ -30,7 +30,7 @@ type FakePods struct { Namespace string } -func (c *FakePods) List(selector labels.Selector) (*api.PodList, error) { +func (c *FakePods) List(label labels.Selector, field fields.Selector) (*api.PodList, error) { obj, err := c.Fake.Invokes(FakeAction{Action: "list-pods"}, &api.PodList{}) return obj.(*api.PodList), err } diff --git a/pkg/cloudprovider/nodecontroller/nodecontroller.go b/pkg/cloudprovider/nodecontroller/nodecontroller.go index 1fb0bb70e96..664afaddcbe 100644 --- a/pkg/cloudprovider/nodecontroller/nodecontroller.go +++ b/pkg/cloudprovider/nodecontroller/nodecontroller.go @@ -652,7 +652,7 @@ func (nc *NodeController) getCloudNodesWithSpec() (*api.NodeList, error) { func (nc *NodeController) deletePods(nodeID string) error { glog.V(2).Infof("Delete all pods from %v", nodeID) // TODO: We don't yet have field selectors from client, see issue #1362. - pods, err := nc.kubeClient.Pods(api.NamespaceAll).List(labels.Everything()) + pods, err := nc.kubeClient.Pods(api.NamespaceAll).List(labels.Everything(), fields.Everything()) if err != nil { return err } diff --git a/pkg/controller/replication_controller.go b/pkg/controller/replication_controller.go index b74578261e9..4c81af800d1 100644 --- a/pkg/controller/replication_controller.go +++ b/pkg/controller/replication_controller.go @@ -201,7 +201,7 @@ func FilterActivePods(pods []api.Pod) []*api.Pod { func (rm *ReplicationManager) syncReplicationController(controller api.ReplicationController) error { s := labels.Set(controller.Spec.Selector).AsSelector() - podList, err := rm.kubeClient.Pods(controller.Namespace).List(s) + podList, err := rm.kubeClient.Pods(controller.Namespace).List(s, fields.Everything()) if err != nil { return err } diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index 14cb35bdc9b..41099bc9a60 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -26,6 +26,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/types" "github.com/golang/glog" @@ -479,7 +480,7 @@ func (d *NodeDescriber) Describe(namespace, name string) (string, error) { } var pods []*api.Pod - allPods, err := d.Pods(namespace).List(labels.Everything()) + allPods, err := d.Pods(namespace).List(labels.Everything(), fields.Everything()) if err != nil { return "", err } @@ -613,7 +614,7 @@ func printReplicationControllersByLabels(matchingRCs []api.ReplicationController } func getPodStatusForReplicationController(c client.PodInterface, controller *api.ReplicationController) (running, waiting, succeeded, failed int, err error) { - rcPods, err := c.List(labels.SelectorFromSet(controller.Spec.Selector)) + rcPods, err := c.List(labels.SelectorFromSet(controller.Spec.Selector), fields.Everything()) if err != nil { return } diff --git a/pkg/namespace/namespace_controller.go b/pkg/namespace/namespace_controller.go index cb3bff21b39..6b486811734 100644 --- a/pkg/namespace/namespace_controller.go +++ b/pkg/namespace/namespace_controller.go @@ -236,7 +236,7 @@ func deleteReplicationControllers(kubeClient client.Interface, ns string) error } func deletePods(kubeClient client.Interface, ns string) error { - items, err := kubeClient.Pods(ns).List(labels.Everything()) + items, err := kubeClient.Pods(ns).List(labels.Everything(), fields.Everything()) if err != nil { return err } diff --git a/pkg/resourcequota/resource_quota_controller.go b/pkg/resourcequota/resource_quota_controller.go index fb4189848e8..5df8a4e8bb1 100644 --- a/pkg/resourcequota/resource_quota_controller.go +++ b/pkg/resourcequota/resource_quota_controller.go @@ -23,6 +23,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/golang/glog" @@ -147,7 +148,7 @@ func (rm *ResourceQuotaManager) syncResourceQuota(quota api.ResourceQuota) (err pods := &api.PodList{} if set[api.ResourcePods] || set[api.ResourceMemory] || set[api.ResourceCPU] { - pods, err = rm.kubeClient.Pods(usage.Namespace).List(labels.Everything()) + pods, err = rm.kubeClient.Pods(usage.Namespace).List(labels.Everything(), fields.Everything()) if err != nil { return err } diff --git a/pkg/service/endpoints_controller.go b/pkg/service/endpoints_controller.go index 1be624dc97f..19a34cc8901 100644 --- a/pkg/service/endpoints_controller.go +++ b/pkg/service/endpoints_controller.go @@ -85,7 +85,7 @@ func NewEndpointController(client *client.Client) *EndpointController { e.podStore.Store, e.podController = framework.NewInformer( &cache.ListWatch{ ListFunc: func() (runtime.Object, error) { - return e.client.Pods(api.NamespaceAll).List(labels.Everything()) + return e.client.Pods(api.NamespaceAll).List(labels.Everything(), fields.Everything()) }, WatchFunc: func(rv string) (watch.Interface, error) { return e.client.Pods(api.NamespaceAll).Watch(labels.Everything(), fields.Everything(), rv) diff --git a/test/e2e/density.go b/test/e2e/density.go index 8620fd98503..d25c5fe2fdb 100644 --- a/test/e2e/density.go +++ b/test/e2e/density.go @@ -35,14 +35,14 @@ import ( ) // Convenient wrapper around listing pods supporting retries. -func listPods(c *client.Client, namespace string, label labels.Selector) (*api.PodList, error) { +func listPods(c *client.Client, namespace string, label labels.Selector, field fields.Selector) (*api.PodList, error) { maxRetries := 4 - pods, err := c.Pods(namespace).List(label) + pods, err := c.Pods(namespace).List(label, field) for i := 0; i < maxRetries; i++ { if err == nil { return pods, nil } - pods, err = c.Pods(namespace).List(label) + pods, err = c.Pods(namespace).List(label, field) } return pods, err } @@ -127,7 +127,7 @@ func RunRC(c *client.Client, name string, ns, image string, replicas int) { By(fmt.Sprintf("Making sure all %d replicas exist", replicas)) label := labels.SelectorFromSet(labels.Set(map[string]string{"name": name})) - pods, err := listPods(c, ns, label) + pods, err := listPods(c, ns, label, fields.Everything()) Expect(err).NotTo(HaveOccurred()) current = len(pods.Items) failCount := 5 @@ -147,7 +147,7 @@ func RunRC(c *client.Client, name string, ns, image string, replicas int) { last = current time.Sleep(5 * time.Second) - pods, err = listPods(c, ns, label) + pods, err = listPods(c, ns, label, fields.Everything()) Expect(err).NotTo(HaveOccurred()) current = len(pods.Items) } @@ -166,7 +166,7 @@ func RunRC(c *client.Client, name string, ns, image string, replicas int) { unknown := 0 time.Sleep(10 * time.Second) - currentPods, listErr := listPods(c, ns, label) + currentPods, listErr := listPods(c, ns, label, fields.Everything()) Expect(listErr).NotTo(HaveOccurred()) if len(currentPods.Items) != len(pods.Items) { Failf("Number of reported pods changed: %d vs %d", len(currentPods.Items), len(pods.Items)) diff --git a/test/e2e/es_cluster_logging.go b/test/e2e/es_cluster_logging.go index 2c93c2c33d0..11fff3d4785 100644 --- a/test/e2e/es_cluster_logging.go +++ b/test/e2e/es_cluster_logging.go @@ -83,7 +83,7 @@ func ClusterLevelLoggingWithElasticsearch(c *client.Client) { // Wait for the Elasticsearch pods to enter the running state. By("Checking to make sure the Elasticsearch pods are running") label := labels.SelectorFromSet(labels.Set(map[string]string{"name": "elasticsearch-logging"})) - pods, err := c.Pods(api.NamespaceDefault).List(label) + pods, err := c.Pods(api.NamespaceDefault).List(label, fields.Everything()) Expect(err).NotTo(HaveOccurred()) for _, pod := range pods.Items { err = waitForPodRunning(c, pod.Name) diff --git a/test/e2e/events.go b/test/e2e/events.go index cc98d903618..ff7e6f6bcbc 100644 --- a/test/e2e/events.go +++ b/test/e2e/events.go @@ -78,7 +78,7 @@ var _ = Describe("Events", func() { expectNoError(waitForPodRunning(c, pod.Name)) By("verifying the pod is in kubernetes") - pods, err := podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))) + pods, err := podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value})), fields.Everything()) Expect(len(pods.Items)).To(Equal(1)) By("retrieving the pod") diff --git a/test/e2e/monitoring.go b/test/e2e/monitoring.go index 5c5aec5243c..f426d0b4cfd 100644 --- a/test/e2e/monitoring.go +++ b/test/e2e/monitoring.go @@ -86,7 +86,7 @@ func verifyExpectedRcsExistAndGetExpectedPods(c *client.Client) ([]string, error return nil, fmt.Errorf("expected to find only one replica for rc %q, found %d", rc.Name, rc.Status.Replicas) } expectedRcs[rc.Name] = true - podList, err := c.Pods(api.NamespaceDefault).List(labels.Set(rc.Spec.Selector).AsSelector()) + podList, err := c.Pods(api.NamespaceDefault).List(labels.Set(rc.Spec.Selector).AsSelector(), fields.Everything()) if err != nil { return nil, err } diff --git a/test/e2e/pods.go b/test/e2e/pods.go index 6c041f3f8ba..1992ddc9a7e 100644 --- a/test/e2e/pods.go +++ b/test/e2e/pods.go @@ -174,7 +174,7 @@ var _ = Describe("Pods", func() { } By("setting up watch") - pods, err := podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))) + pods, err := podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value})), fields.Everything()) if err != nil { Fail(fmt.Sprintf("Failed to query for pods: %v", err)) } @@ -196,7 +196,7 @@ var _ = Describe("Pods", func() { } By("verifying the pod is in kubernetes") - pods, err = podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))) + pods, err = podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value})), fields.Everything()) if err != nil { Fail(fmt.Sprintf("Failed to query for pods: %v", err)) } @@ -214,7 +214,7 @@ var _ = Describe("Pods", func() { By("deleting the pod") podClient.Delete(pod.Name) - pods, err = podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))) + pods, err = podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value})), fields.Everything()) if err != nil { Fail(fmt.Sprintf("Failed to delete pod: %v", err)) } @@ -286,7 +286,7 @@ var _ = Describe("Pods", func() { expectNoError(waitForPodRunning(c, pod.Name)) By("verifying the pod is in kubernetes") - pods, err := podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))) + pods, err := podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value})), fields.Everything()) Expect(len(pods.Items)).To(Equal(1)) By("retrieving the pod") @@ -309,7 +309,7 @@ var _ = Describe("Pods", func() { expectNoError(waitForPodRunning(c, pod.Name)) By("verifying the updated pod is in kubernetes") - pods, err = podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))) + pods, err = podClient.List(labels.SelectorFromSet(labels.Set(map[string]string{"time": value})), fields.Everything()) Expect(len(pods.Items)).To(Equal(1)) fmt.Println("pod update OK") }) diff --git a/test/e2e/rc.go b/test/e2e/rc.go index 68fe8530a69..624342a26db 100644 --- a/test/e2e/rc.go +++ b/test/e2e/rc.go @@ -22,6 +22,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/types" @@ -110,7 +111,7 @@ func ServeImageOrFail(c *client.Client, test string, image string) { // List the pods, making sure we observe all the replicas. listTimeout := time.Minute label := labels.SelectorFromSet(labels.Set(map[string]string{"name": name})) - pods, err := c.Pods(ns).List(label) + pods, err := c.Pods(ns).List(label, fields.Everything()) Expect(err).NotTo(HaveOccurred()) t := time.Now() for { @@ -123,7 +124,7 @@ func ServeImageOrFail(c *client.Client, test string, image string) { name, replicas, len(pods.Items), time.Since(t).Seconds()) } time.Sleep(5 * time.Second) - pods, err = c.Pods(ns).List(label) + pods, err = c.Pods(ns).List(label, fields.Everything()) Expect(err).NotTo(HaveOccurred()) } @@ -165,7 +166,7 @@ type responseChecker struct { func (r responseChecker) checkAllResponses() (done bool, err error) { successes := 0 - currentPods, err := r.c.Pods(r.ns).List(r.label) + currentPods, err := r.c.Pods(r.ns).List(r.label, fields.Everything()) Expect(err).NotTo(HaveOccurred()) for i, pod := range r.pods.Items { // Check that the replica list remains unchanged, otherwise we have problems. diff --git a/test/integration/client_test.go b/test/integration/client_test.go index ad43765d027..65f32870e7a 100644 --- a/test/integration/client_test.go +++ b/test/integration/client_test.go @@ -56,7 +56,7 @@ func TestClient(t *testing.T) { t.Errorf("expected %#v, got %#v", e, a) } - pods, err := client.Pods(ns).List(labels.Everything()) + pods, err := client.Pods(ns).List(labels.Everything(), fields.Everything()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -94,7 +94,7 @@ func TestClient(t *testing.T) { } // pod is shown, but not scheduled - pods, err = client.Pods(ns).List(labels.Everything()) + pods, err = client.Pods(ns).List(labels.Everything(), fields.Everything()) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/test/integration/metrics_test.go b/test/integration/metrics_test.go index 0e331861a91..f859f03832e 100644 --- a/test/integration/metrics_test.go +++ b/test/integration/metrics_test.go @@ -28,6 +28,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/golang/glog" "github.com/golang/protobuf/proto" @@ -111,7 +112,7 @@ func TestApiserverMetrics(t *testing.T) { // Make a request to the apiserver to ensure there's at least one data point // for the metrics we're expecting -- otherwise, they won't be exported. client := client.NewOrDie(&client.Config{Host: s.URL, Version: testapi.Version()}) - if _, err := client.Pods(api.NamespaceDefault).List(labels.Everything()); err != nil { + if _, err := client.Pods(api.NamespaceDefault).List(labels.Everything(), fields.Everything()); err != nil { t.Fatalf("unexpected error getting pods: %v", err) } From 0a7f6c2999d53183d2c3a145e0f82a9fcb56a8d5 Mon Sep 17 00:00:00 2001 From: Sami Wagiaalla Date: Tue, 21 Apr 2015 10:30:47 -0400 Subject: [PATCH 07/43] iscsi Test: Add explicit check for attach and detach calls. Signed-off-by: Sami Wagiaalla --- pkg/volume/iscsi/iscsi_test.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/pkg/volume/iscsi/iscsi_test.go b/pkg/volume/iscsi/iscsi_test.go index 8b3b6cc646b..bc1396de945 100644 --- a/pkg/volume/iscsi/iscsi_test.go +++ b/pkg/volume/iscsi/iscsi_test.go @@ -39,7 +39,10 @@ func TestCanSupport(t *testing.T) { } } -type fakeDiskManager struct{} +type fakeDiskManager struct { + attachCalled bool + detachCalled bool +} func (fake *fakeDiskManager) MakeGlobalPDName(disk iscsiDisk) string { return "/tmp/fake_iscsi_path" @@ -50,6 +53,11 @@ func (fake *fakeDiskManager) AttachDisk(disk iscsiDisk) error { if err != nil { return err } + // Simulate the global mount so that the fakeMounter returns the + // expected number of mounts for the attached disk. + disk.mounter.Mount(globalPath, globalPath, disk.fsType, 0, "") + + fake.attachCalled = true return nil } @@ -59,6 +67,7 @@ func (fake *fakeDiskManager) DetachDisk(disk iscsiDisk, mntPath string) error { if err != nil { return err } + fake.detachCalled = true return nil } @@ -81,7 +90,9 @@ func TestPlugin(t *testing.T) { }, }, } - builder, err := plug.(*ISCSIPlugin).newBuilderInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), &fakeDiskManager{}, &mount.FakeMounter{}) + fakeManager := &fakeDiskManager{} + fakeMounter := &mount.FakeMounter{} + builder, err := plug.(*ISCSIPlugin).newBuilderInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), fakeManager, fakeMounter) if err != nil { t.Errorf("Failed to make a new Builder: %v", err) } @@ -111,8 +122,12 @@ func TestPlugin(t *testing.T) { t.Errorf("SetUp() failed: %v", err) } } + if !fakeManager.attachCalled { + t.Errorf("Attach was not called") + } - cleaner, err := plug.(*ISCSIPlugin).newCleanerInternal("vol1", types.UID("poduid"), &fakeDiskManager{}, &mount.FakeMounter{}) + fakeManager = &fakeDiskManager{} + cleaner, err := plug.(*ISCSIPlugin).newCleanerInternal("vol1", types.UID("poduid"), fakeManager, fakeMounter) if err != nil { t.Errorf("Failed to make a new Cleaner: %v", err) } @@ -128,4 +143,7 @@ func TestPlugin(t *testing.T) { } else if !os.IsNotExist(err) { t.Errorf("SetUp() failed: %v", err) } + if !fakeManager.detachCalled { + t.Errorf("Detach was not called") + } } From 7475efbcfb6a2effa00d70cd152240d96ec25f0c Mon Sep 17 00:00:00 2001 From: Eric Tune Date: Tue, 21 Apr 2015 08:22:31 -0700 Subject: [PATCH 08/43] Extend PR#5470 for AWS and Vagrant --- cluster/aws/templates/create-dynamic-salt-files.sh | 11 +++++++++++ cluster/vagrant/provision-master.sh | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/cluster/aws/templates/create-dynamic-salt-files.sh b/cluster/aws/templates/create-dynamic-salt-files.sh index 682d9fbbc3b..33050b3591c 100644 --- a/cluster/aws/templates/create-dynamic-salt-files.sh +++ b/cluster/aws/templates/create-dynamic-salt-files.sh @@ -52,3 +52,14 @@ known_tokens_file="/srv/salt-overlay/salt/kube-apiserver/known_tokens.csv" mkdir -p /srv/salt-overlay/salt/kubelet kubelet_auth_file="/srv/salt-overlay/salt/kubelet/kubernetes_auth" (umask u=rw,go= ; echo "{\"BearerToken\": \"$kubelet_token\", \"Insecure\": true }" > $kubelet_auth_file) + +# Generate tokens for other "service accounts". Append to known_tokens. +# +# NB: If this list ever changes, this script actually has to +# change to detect the existence of this file, kill any deleted +# old tokens and add any new tokens (to handle the upgrade case). +local -r service_accounts=("system:scheduler" "system:controller_manager" "system:logging" "system:monitoring" "system:dns") +for account in "${service_accounts[@]}"; do + token=$(dd if=/dev/urandom bs=128 count=1 2>/dev/null | base64 | tr -d "=+/" | dd bs=32 count=1 2>/dev/null) + echo "${token},${account},${account}" >> "${KNOWN_TOKENS_FILE}" +done diff --git a/cluster/vagrant/provision-master.sh b/cluster/vagrant/provision-master.sh index 774201ed8fd..0175cf70ed2 100755 --- a/cluster/vagrant/provision-master.sh +++ b/cluster/vagrant/provision-master.sh @@ -145,6 +145,17 @@ if [[ ! -f "${known_tokens_file}" ]]; then mkdir -p /srv/salt-overlay/salt/kubelet kubelet_auth_file="/srv/salt-overlay/salt/kubelet/kubernetes_auth" (umask u=rw,go= ; echo "{\"BearerToken\": \"$kubelet_token\", \"Insecure\": true }" > $kubelet_auth_file) + + # Generate tokens for other "service accounts". Append to known_tokens. + # + # NB: If this list ever changes, this script actually has to + # change to detect the existence of this file, kill any deleted + # old tokens and add any new tokens (to handle the upgrade case). + local -r service_accounts=("system:scheduler" "system:controller_manager" "system:logging" "system:monitoring" "system:dns") + for account in "${service_accounts[@]}"; do + token=$(dd if=/dev/urandom bs=128 count=1 2>/dev/null | base64 | tr -d "=+/" | dd bs=32 count=1 2>/dev/null) + echo "${token},${account},${account}" >> "${KNOWN_TOKENS_FILE}" + done fi # Configure nginx authorization From 2ca8a9d15d98f89e3e61e8e25efbb4ad4589f717 Mon Sep 17 00:00:00 2001 From: Eric Tune Date: Tue, 21 Apr 2015 09:09:45 -0700 Subject: [PATCH 09/43] Added kube-proxy token. Generates the new token on AWS, GCE, Vagrant. Renames instance metadata from "kube-token" to "kubelet-token". (Is this okay for GKE?) Having separate tokens for kubelet and kube-proxy permits using principle of least privilege, makes it easy to rate limit the clients separately, allows annotation of apiserver logs with the client identity at a finer grain than just source-ip. --- .../templates/create-dynamic-salt-files.sh | 9 +++++++-- cluster/gce/configure-vm.sh | 19 +++++++++++++------ cluster/gce/util.sh | 13 ++++++++----- cluster/vagrant/provision-master.sh | 5 ++++- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/cluster/aws/templates/create-dynamic-salt-files.sh b/cluster/aws/templates/create-dynamic-salt-files.sh index 682d9fbbc3b..125e8e727d7 100644 --- a/cluster/aws/templates/create-dynamic-salt-files.sh +++ b/cluster/aws/templates/create-dynamic-salt-files.sh @@ -40,14 +40,19 @@ mkdir -p /srv/salt-overlay/salt/nginx echo $MASTER_HTPASSWD > /srv/salt-overlay/salt/nginx/htpasswd # Generate and distribute a shared secret (bearer token) to -# apiserver and kubelet so that kubelet can authenticate to +# apiserver and nodes so that kubelet/kube-proxy can authenticate to # apiserver to send events. # This works on CoreOS, so it should work on a lot of distros. kubelet_token=$(cat /dev/urandom | base64 | tr -d "=+/" | dd bs=32 count=1 2> /dev/null) +# Same thing for kube-proxy. +kube_proxy_token=$(cat /dev/urandom | base64 | tr -d "=+/" | dd bs=32 count=1 2> /dev/null) +# Make a list of tokens and usernames to be pushed to the apiserver mkdir -p /srv/salt-overlay/salt/kube-apiserver known_tokens_file="/srv/salt-overlay/salt/kube-apiserver/known_tokens.csv" -(umask u=rw,go= ; echo "$kubelet_token,kubelet,kubelet" > $known_tokens_file) +(umask u=rw,go= ; echo "" > $known_tokens_file) +echo "$kubelet_token,kubelet,kubelet" >> $known_tokens_file ; +echo "$kube_proxy_token,kube_proxy,kube_proxy" >> $known_tokens_file mkdir -p /srv/salt-overlay/salt/kubelet kubelet_auth_file="/srv/salt-overlay/salt/kubelet/kubernetes_auth" diff --git a/cluster/gce/configure-vm.sh b/cluster/gce/configure-vm.sh index 7424c445b68..b894f842a81 100644 --- a/cluster/gce/configure-vm.sh +++ b/cluster/gce/configure-vm.sh @@ -73,17 +73,23 @@ for k,v in yaml.load(sys.stdin).iteritems(): fi } -function ensure-kube-token() { +function ensure-kube-tokens() { # We bake the KUBELET_TOKEN in separately to avoid auth information # having to be re-communicated on kube-push. (Otherwise the client # has to keep the bearer token around to handle generating a valid # kube-env.) if [[ -z "${KUBELET_TOKEN:-}" ]] && [[ ! -e "${KNOWN_TOKENS_FILE}" ]]; then - until KUBELET_TOKEN=$(curl-metadata kube-token); do + until KUBELET_TOKEN=$(curl-metadata kubelet-token); do echo 'Waiting for metadata KUBELET_TOKEN...' sleep 3 done fi + if [[ -z "${KUBE_PROXY_TOKEN:-}" ]] && [[ ! -e "${KNOWN_TOKENS_FILE}" ]]; then + until KUBE_PROXY_TOKEN=$(curl-metadata kube-proxy-token); do + echo 'Waiting for metadata KUBE_PROXY_TOKEN...' + sleep 3 + done + fi } function remove-docker-artifacts() { @@ -252,7 +258,7 @@ EOF # This should only happen on cluster initialization. Uses # MASTER_HTPASSWORD to generate the nginx/htpasswd file, and the -# KUBELET_TOKEN, plus /dev/urandom, to generate known_tokens.csv +# KUBELET_TOKEN and KUBE_PROXY_TOKEN, to generate known_tokens.csv # (KNOWN_TOKENS_FILE). After the first boot and on upgrade, these # files exist on the master-pd and should never be touched again # (except perhaps an additional service account, see NB below.) @@ -266,8 +272,9 @@ function create-salt-auth() { if [ ! -e "${KNOWN_TOKENS_FILE}" ]; then mkdir -p /srv/salt-overlay/salt/kube-apiserver - (umask 077; - echo "${KUBELET_TOKEN},kubelet,kubelet" > "${KNOWN_TOKENS_FILE}") + (umask 077; echo "" > "${KNOWN_TOKENS_FILE}") + echo "${KUBELET_TOKEN},kubelet,kubelet" >> "${KNOWN_TOKENS_FILE}" + echo "${KUBE_PROXY_TOKEN},kube_proxy,kube_proxy" >> "${KNOWN_TOKENS_FILE}" mkdir -p /srv/salt-overlay/salt/kubelet kubelet_auth_file="/srv/salt-overlay/salt/kubelet/kubernetes_auth" @@ -422,7 +429,7 @@ if [[ -z "${is_push}" ]]; then ensure-install-dir set-kube-env [[ "${KUBERNETES_MASTER}" == "true" ]] && mount-master-pd - ensure-kube-token + ensure-kube-tokens create-salt-pillar create-salt-auth download-release diff --git a/cluster/gce/util.sh b/cluster/gce/util.sh index cddb03f26ea..02e95223869 100755 --- a/cluster/gce/util.sh +++ b/cluster/gce/util.sh @@ -585,11 +585,12 @@ function kube-up { --zone "${ZONE}" \ --size "10GB" - # Generate a bearer token for this cluster. We push this separately - # from the other cluster variables so that the client (this + # Generate a bearer token for kubelets in this cluster. We push this + # separately from the other cluster variables so that the client (this # computer) can forget it later. This should disappear with # https://github.com/GoogleCloudPlatform/kubernetes/issues/3168 KUBELET_TOKEN=$(dd if=/dev/urandom bs=128 count=1 2>/dev/null | base64 | tr -d "=+/" | dd bs=32 count=1 2>/dev/null) + KUBE_PROXY_TOKEN=$(dd if=/dev/urandom bs=128 count=1 2>/dev/null | base64 | tr -d "=+/" | dd bs=32 count=1 2>/dev/null) # Reserve the master's IP so that it can later be transferred to another VM # without disrupting the kubelets. IPs are associated with regions, not zones, @@ -616,7 +617,8 @@ function kube-up { # Wait for last batch of jobs wait-for-jobs - add-instance-metadata "${MASTER_NAME}" "kube-token=${KUBELET_TOKEN}" + add-instance-metadata "${MASTER_NAME}" "kubelet-token=${KUBELET_TOKEN}" + add-instance-metadata "${MASTER_NAME}" "kube-proxy-token=${KUBE_PROXY_TOKEN}" echo "Creating minions." @@ -631,7 +633,8 @@ function kube-up { create-node-template "${NODE_INSTANCE_PREFIX}-template" "${scope_flags[*]}" \ "startup-script=${KUBE_ROOT}/cluster/gce/configure-vm.sh" \ "kube-env=${KUBE_TEMP}/node-kube-env.yaml" \ - "kube-token=${KUBELET_TOKEN}" + "kubelet-token=${KUBELET_TOKEN}" \ + "kube-proxy-token=${KUBE_PROXY_TOKEN}" gcloud preview managed-instance-groups --zone "${ZONE}" \ create "${NODE_INSTANCE_PREFIX}-group" \ @@ -869,7 +872,7 @@ function kube-push { # TODO(zmerlynn): Re-create instance-template with the new # node-kube-env. This isn't important until the node-ip-range issue # is solved (because that's blocking automatic dynamic nodes from - # working). The node-kube-env has to be composed with the kube-token + # working). The node-kube-env has to be composed with the kube*-token # metadata. Ideally we would have # https://github.com/GoogleCloudPlatform/kubernetes/issues/3168 # implemented before then, though, so avoiding this mess until then. diff --git a/cluster/vagrant/provision-master.sh b/cluster/vagrant/provision-master.sh index 774201ed8fd..bd6e9f72e71 100755 --- a/cluster/vagrant/provision-master.sh +++ b/cluster/vagrant/provision-master.sh @@ -137,10 +137,13 @@ EOF known_tokens_file="/srv/salt-overlay/salt/kube-apiserver/known_tokens.csv" if [[ ! -f "${known_tokens_file}" ]]; then kubelet_token=$(cat /dev/urandom | base64 | tr -d "=+/" | dd bs=32 count=1 2> /dev/null) + kube_proxy_token=$(cat /dev/urandom | base64 | tr -d "=+/" | dd bs=32 count=1 2> /dev/null) mkdir -p /srv/salt-overlay/salt/kube-apiserver known_tokens_file="/srv/salt-overlay/salt/kube-apiserver/known_tokens.csv" - (umask u=rw,go= ; echo "$kubelet_token,kubelet,kubelet" > $known_tokens_file) + (umask u=rw,go= ; echo "" > $known_tokens_file) + echo "$kubelet_token,kubelet,kubelet" >> $known_tokens_file + echo "$kube_proxy_token,kube-proxy,kube-proxy" >> $known_tokens_file mkdir -p /srv/salt-overlay/salt/kubelet kubelet_auth_file="/srv/salt-overlay/salt/kubelet/kubernetes_auth" From b7fc22be8a52b63bb74fa50ff80e6177c2ae42f2 Mon Sep 17 00:00:00 2001 From: Akram Ben Aissi Date: Tue, 21 Apr 2015 20:15:54 +0200 Subject: [PATCH 10/43] Fixes an issue with hosts having an IPv6 address on localhost - When 'getent hosts localhost' returns '::1' the creation of the listener fails because of the port parsing which uses ":" as a separator - Use of net.SplitHostPort() to do the job - Adding unit tests to ensure that the creation succeeds - On docker.go: adds a test on the presence the socat command which was failing silenty if not installed - Code Review 1 - Fixed typo on Expected - The UT now fails if the PortForwarder could not be created - Code Review 2 - Simplify socat error message - Changing t.Fatal to to.Error on unit tests - Code Review 3 - Removing useless uses cases in unit tests - Code Review 4 - Removing useless initiliasiation of PortForwarder - Changing error message - Code Review 5 - Simplifying TestCast struct - Adding addition test in one test case - Closing the listener - Code Review 6 - Improving unit test --- pkg/client/portforward/portforward.go | 33 +++++++--- pkg/client/portforward/portforward_test.go | 77 ++++++++++++++++++++++ pkg/kubelet/dockertools/docker.go | 6 +- 3 files changed, 105 insertions(+), 11 deletions(-) diff --git a/pkg/client/portforward/portforward.go b/pkg/client/portforward/portforward.go index 6cf92c54173..73d39dfcfaa 100644 --- a/pkg/client/portforward/portforward.go +++ b/pkg/client/portforward/portforward.go @@ -183,21 +183,13 @@ func (pf *PortForwarder) forward() error { return nil } -// listenOnPort creates a new listener on port and waits for new connections +// listenOnPort delegates listener creation and waits for new connections // in the background. func (pf *PortForwarder) listenOnPort(port *ForwardedPort) error { - listener, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port.Local)) + listener, err := pf.getListener("tcp", "localhost", port) if err != nil { return err } - parts := strings.Split(listener.Addr().String(), ":") - localPort, err := strconv.ParseUint(parts[1], 10, 16) - if err != nil { - return fmt.Errorf("Error parsing local part: %s", err) - } - port.Local = uint16(localPort) - glog.Infof("Forwarding from %d -> %d", localPort, port.Remote) - pf.listeners = append(pf.listeners, listener) go pf.waitForConnection(listener, *port) @@ -205,6 +197,27 @@ func (pf *PortForwarder) listenOnPort(port *ForwardedPort) error { return nil } +// getListener creates a listener on the interface targeted by the given hostname on the given port with +// the given protocol. protocol is in net.Listen style which basically admits values like tcp, tcp4, tcp6 +func (pf *PortForwarder) getListener(protocol string, hostname string, port *ForwardedPort) (net.Listener, error) { + listener, err := net.Listen(protocol, fmt.Sprintf("%s:%d", hostname, port.Local)) + if err != nil { + glog.Errorf("Unable to create listener: Error %s", err) + return nil, err + } + listenerAddress := listener.Addr().String() + host, localPort, _ := net.SplitHostPort(listenerAddress) + localPortUInt, err := strconv.ParseUint(localPort, 10, 16) + + if err != nil { + return nil, fmt.Errorf("Error parsing local port: %s from %s (%s)", err, listenerAddress, host) + } + port.Local = uint16(localPortUInt) + glog.Infof("Forwarding from %d -> %d", localPortUInt, port.Remote) + + return listener, nil +} + // waitForConnection waits for new connections to listener and handles them in // the background. func (pf *PortForwarder) waitForConnection(listener net.Listener, port ForwardedPort) { diff --git a/pkg/client/portforward/portforward_test.go b/pkg/client/portforward/portforward_test.go index 99d4ee43078..00132db46d8 100644 --- a/pkg/client/portforward/portforward_test.go +++ b/pkg/client/portforward/portforward_test.go @@ -209,6 +209,82 @@ func (s *fakeUpgradeStream) Headers() http.Header { return http.Header{} } +func TestGetListener(t *testing.T) { + var pf PortForwarder + testCases := []struct { + Hostname string + Protocol string + ShouldRaiseError bool + ExpectedListenerAddress string + }{ + { + Hostname: "localhost", + Protocol: "tcp4", + ShouldRaiseError: false, + ExpectedListenerAddress: "127.0.0.1", + }, + { + Hostname: "127.0.0.1", + Protocol: "tcp4", + ShouldRaiseError: false, + ExpectedListenerAddress: "127.0.0.1", + }, + { + Hostname: "[::1]", + Protocol: "tcp6", + ShouldRaiseError: false, + ExpectedListenerAddress: "::1", + }, + { + Hostname: "localhost", + Protocol: "tcp6", + ShouldRaiseError: false, + ExpectedListenerAddress: "::1", + }, + { + Hostname: "[::1]", + Protocol: "tcp4", + ShouldRaiseError: true, + }, + { + Hostname: "127.0.0.1", + Protocol: "tcp6", + ShouldRaiseError: true, + }, + } + + for i, testCase := range testCases { + expectedListenerPort := "12345" + listener, err := pf.getListener(testCase.Protocol, testCase.Hostname, &ForwardedPort{12345, 12345}) + errorRaised := err != nil + + if testCase.ShouldRaiseError != errorRaised { + t.Errorf("Test case #%d failed: Data %v an error has been raised(%t) where it should not (or reciprocally): %v", i, testCase, testCase.ShouldRaiseError, err) + continue + } + if errorRaised { + continue + } + + if listener == nil { + t.Errorf("Test case #%d did not raised an error (%t) but failed in initializing listener", i, err) + continue + } + + host, port, _ := net.SplitHostPort(listener.Addr().String()) + t.Logf("Asked a %s forward for: %s:%v, got listener %s:%s, expected: %s", testCase.Protocol, testCase.Hostname, 12345, host, port, expectedListenerPort) + if host != testCase.ExpectedListenerAddress { + t.Errorf("Test case #%d failed: Listener does not listen on exepected address: asked %v got %v", i, testCase.ExpectedListenerAddress, host) + } + if port != expectedListenerPort { + t.Errorf("Test case #%d failed: Listener does not listen on exepected port: asked %v got %v", i, expectedListenerPort, port) + + } + listener.Close() + + } +} + func TestForwardPorts(t *testing.T) { testCases := []struct { Upgrader *fakeUpgrader @@ -313,4 +389,5 @@ func TestForwardPorts(t *testing.T) { t.Fatalf("%d: expected conn closure", i) } } + } diff --git a/pkg/kubelet/dockertools/docker.go b/pkg/kubelet/dockertools/docker.go index 2f0a7d1c655..384ed0739c6 100644 --- a/pkg/kubelet/dockertools/docker.go +++ b/pkg/kubelet/dockertools/docker.go @@ -295,7 +295,11 @@ func (d *dockerContainerCommandRunner) PortForward(pod *kubecontainer.Pod, port } containerPid := container.State.Pid - // TODO use exec.LookPath for socat / what if the host doesn't have it??? + // TODO what if the host doesn't have it??? + _, lookupErr := exec.LookPath("socat") + if lookupErr != nil { + return fmt.Errorf("Unable to do port forwarding: socat not found.") + } args := []string{"-t", fmt.Sprintf("%d", containerPid), "-n", "socat", "-", fmt.Sprintf("TCP4:localhost:%d", port)} // TODO use exec.LookPath command := exec.Command("nsenter", args...) From 29b64bf64c02b9a9a959b4139c2637868a1dcd80 Mon Sep 17 00:00:00 2001 From: Ido Shamun Date: Tue, 21 Apr 2015 22:05:39 +0300 Subject: [PATCH 11/43] update etcd2 configuration --- .../kubernetes-cluster-etcd-node-template.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/getting-started-guides/coreos/azure/cloud_config_templates/kubernetes-cluster-etcd-node-template.yml b/docs/getting-started-guides/coreos/azure/cloud_config_templates/kubernetes-cluster-etcd-node-template.yml index f6e9fcda92a..bf138e36794 100644 --- a/docs/getting-started-guides/coreos/azure/cloud_config_templates/kubernetes-cluster-etcd-node-template.yml +++ b/docs/getting-started-guides/coreos/azure/cloud_config_templates/kubernetes-cluster-etcd-node-template.yml @@ -37,6 +37,7 @@ coreos: Environment=ETCD_INITIAL_ADVERTISE_PEER_URLS=http://%host%:2380 Environment=ETCD_LISTEN_PEER_URLS=http://%host%:2380 Environment=ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379,http://0.0.0.0:4001 + Environment=ETCD_ADVERTISE_CLIENT_URLS=http://%host%:2379,http://%host%:4001 Environment=ETCD_INITIAL_CLUSTER=%cluster% Environment=ETCD_INITIAL_CLUSTER_STATE=new ExecStart=/opt/bin/etcd2 From e9c5e4476714ae3c402ce62d144b24f4714c8b82 Mon Sep 17 00:00:00 2001 From: caesarxuchao Date: Tue, 21 Apr 2015 12:20:42 -0700 Subject: [PATCH 12/43] Use service UID as the ELB name --- pkg/cloudprovider/cloud.go | 12 ++++++++++-- .../nodecontroller/nodecontroller.go | 2 +- .../nodecontroller/nodecontroller_test.go | 15 ++++++++------- .../servicecontroller/servicecontroller.go | 2 +- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/pkg/cloudprovider/cloud.go b/pkg/cloudprovider/cloud.go index f3c3b18491d..57f9d110cf5 100644 --- a/pkg/cloudprovider/cloud.go +++ b/pkg/cloudprovider/cloud.go @@ -18,6 +18,7 @@ package cloudprovider import ( "net" + "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" ) @@ -44,8 +45,15 @@ type Clusters interface { // TODO(#6812): Use a shorter name that's less likely to be longer than cloud // providers' name length limits. -func GetLoadBalancerName(clusterName, serviceNamespace, serviceName string) string { - return clusterName + "-" + serviceNamespace + "-" + serviceName +func GetLoadBalancerName(service *api.Service) string { + //GCE requires that the name of a load balancer starts with a lower case letter. + ret := "a" + string(service.UID) + ret = strings.Replace(ret, "-", "", -1) + //AWS requires that the name of a load balancer is shorter than 32 bytes. + if len(ret) > 32 { + ret = ret[:32] + } + return ret } // TCPLoadBalancer is an abstract, pluggable interface for TCP load balancers. diff --git a/pkg/cloudprovider/nodecontroller/nodecontroller.go b/pkg/cloudprovider/nodecontroller/nodecontroller.go index 1fb0bb70e96..3eed36eaa32 100644 --- a/pkg/cloudprovider/nodecontroller/nodecontroller.go +++ b/pkg/cloudprovider/nodecontroller/nodecontroller.go @@ -266,7 +266,7 @@ func (nc *NodeController) reconcileExternalServices(nodes *api.NodeList) (should glog.Errorf("External load balancers for non TCP services are not currently supported: %v.", service) continue } - name := cloudprovider.GetLoadBalancerName(nc.clusterName, service.Namespace, service.Name) + name := cloudprovider.GetLoadBalancerName(&service) err := balancer.UpdateTCPLoadBalancer(name, zone.Region, hosts) if err != nil { glog.Errorf("External error while updating TCP load balancer: %v.", err) diff --git a/pkg/cloudprovider/nodecontroller/nodecontroller_test.go b/pkg/cloudprovider/nodecontroller/nodecontroller_test.go index b174a3c8875..8155ed6c2b1 100644 --- a/pkg/cloudprovider/nodecontroller/nodecontroller_test.go +++ b/pkg/cloudprovider/nodecontroller/nodecontroller_test.go @@ -32,6 +32,7 @@ import ( fake_cloud "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/fake" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/types" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" ) @@ -609,7 +610,7 @@ func TestSyncCloudNodesReconcilesExternalService(t *testing.T) { // Set of nodes does not change: do nothing. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{newNode("node0"), newNode("node1")}, - Fake: testclient.NewSimpleFake(&api.ServiceList{Items: []api.Service{*newService("service0", true), *newService("service1", false)}})}, + Fake: testclient.NewSimpleFake(&api.ServiceList{Items: []api.Service{*newService("service0", types.UID(""), true), *newService("service1", types.UID(""), false)}})}, fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0", "node1"}, }, @@ -621,28 +622,28 @@ func TestSyncCloudNodesReconcilesExternalService(t *testing.T) { // Delete "node1", target pool for "service0" should shrink. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{newNode("node0"), newNode("node1")}, - Fake: testclient.NewSimpleFake(&api.ServiceList{Items: []api.Service{*newService("service0", true), *newService("service1", false)}})}, + Fake: testclient.NewSimpleFake(&api.ServiceList{Items: []api.Service{*newService("service0", types.UID("2c104a7c-e79e-11e4-8187-42010af0068a"), true), *newService("service1", types.UID(""), false)}})}, fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0"}, }, matchRE: ".*", expectedClientActions: []testclient.FakeAction{{Action: "list-pods"}, {Action: "list-services"}}, expectedUpdateCalls: []fake_cloud.FakeUpdateBalancerCall{ - {Name: "kubernetes-namespace-service0", Hosts: []string{"node0"}}, + {Name: "a2c104a7ce79e11e4818742010af0068", Hosts: []string{"node0"}}, }, }, { // Add "node1", target pool for "service0" should grow. fakeNodeHandler: &FakeNodeHandler{ Existing: []*api.Node{newNode("node0")}, - Fake: testclient.NewSimpleFake(&api.ServiceList{Items: []api.Service{*newService("service0", true), *newService("service1", false)}})}, + Fake: testclient.NewSimpleFake(&api.ServiceList{Items: []api.Service{*newService("service0", types.UID("2c104a7c-e79e-11e4-8187-42010af0068a"), true), *newService("service1", types.UID(""), false)}})}, fakeCloud: &fake_cloud.FakeCloud{ Machines: []string{"node0", "node1"}, }, matchRE: ".*", expectedClientActions: []testclient.FakeAction{{Action: "list-services"}}, expectedUpdateCalls: []fake_cloud.FakeUpdateBalancerCall{ - {Name: "kubernetes-namespace-service0", Hosts: []string{"node0", "node1"}}, + {Name: "a2c104a7ce79e11e4818742010af0068", Hosts: []string{"node0", "node1"}}, }, }, } @@ -1128,8 +1129,8 @@ func newPod(name, host string) *api.Pod { return &api.Pod{ObjectMeta: api.ObjectMeta{Name: name}, Spec: api.PodSpec{Host: host}} } -func newService(name string, external bool) *api.Service { - return &api.Service{ObjectMeta: api.ObjectMeta{Name: name, Namespace: "namespace"}, Spec: api.ServiceSpec{CreateExternalLoadBalancer: external}} +func newService(name string, uid types.UID, external bool) *api.Service { + return &api.Service{ObjectMeta: api.ObjectMeta{Name: name, Namespace: "namespace", UID: uid}, Spec: api.ServiceSpec{CreateExternalLoadBalancer: external}} } func sortedNodeNames(nodes []*api.Node) []string { diff --git a/pkg/cloudprovider/servicecontroller/servicecontroller.go b/pkg/cloudprovider/servicecontroller/servicecontroller.go index 1d17323f10d..055eb84b1f1 100644 --- a/pkg/cloudprovider/servicecontroller/servicecontroller.go +++ b/pkg/cloudprovider/servicecontroller/servicecontroller.go @@ -419,7 +419,7 @@ func needsUpdate(oldService *api.Service, newService *api.Service) bool { } func (s *ServiceController) loadBalancerName(service *api.Service) string { - return cloudprovider.GetLoadBalancerName(s.clusterName, service.Namespace, service.Name) + return cloudprovider.GetLoadBalancerName(service) } func getTCPPorts(service *api.Service) ([]int, error) { From f9d5629c5c2a1bdde3d81ddb1ba34b5cf9644c41 Mon Sep 17 00:00:00 2001 From: Patrick Reilly Date: Tue, 21 Apr 2015 12:49:15 -0700 Subject: [PATCH 13/43] Update README to address issues Update README to address issues in https://github.com/GoogleCloudPlatform/kubernetes/issues/7122 Providing a bit more clarity to how to test and develop the kubernetes Web UI. --- www/README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/www/README.md b/www/README.md index e5e8c844953..dc4db788872 100644 --- a/www/README.md +++ b/www/README.md @@ -30,7 +30,7 @@ Bower components should be refernced in one of the `vendor.json` files below: ### Serving the app during development -The app can be served through `kubectl`, but for some types of review a local web server is convenient. One can be installed as follows: +For development you can serve the files locally by installing a webserver as follows: ``` sudo npm install -g http-server @@ -43,6 +43,9 @@ cd app http-server -a localhost -p 8000 ``` +### Serving the app in production +https:///static/app/ + ### Configuration #### Configuration settings A json file can be used by `gulp` to automatically create angular constants. This is useful for setting per environment variables such as api endpoints. @@ -57,7 +60,6 @@ www/master ├── shared/config/development.json └── components ├── dashboard/config/development.json - ├── graph/config/development.json └── my_component/config/development.json ``` produces ```www/master/shared/config/generated-config.js```: @@ -66,14 +68,16 @@ angular.module('kubernetesApp.config', []) .constant('ENV', { '/': , 'dashboard': , - 'graph': , 'my_component': }); ``` #### Kubernetes server configuration -**RECOMMENDED**: By default the Kubernetes api server does not support CORS, +You'll need to run ```hack/build-ui.sh``` to create a new ```pkg/ui/datafile.go``` file. +This is the file that is built-in to the kube-apiserver. + +**RECOMMENDED**: When working in development mode the Kubernetes api server does not support CORS, so the `kube-apiserver.service` must be started with `--cors_allowed_origins=.*` or `--cors_allowed_origins=http://` @@ -87,7 +91,7 @@ angular.module('kubernetesApp.config', []) See [master/components/README.md](master/components/README.md). ### Testing -Currently kuberntes-ui includes both unit-testing (run via [Karma](http://karma-runner.github.io/0.12/index.html)) and +Currently kuberntes/www includes both unit-testing (run via [Karma](http://karma-runner.github.io/0.12/index.html)) and end-to-end testing (run via [Protractor](http://angular.github.io/protractor/#/)). From 55949813408b866a2d654cd7b050ea72ef01a4ff Mon Sep 17 00:00:00 2001 From: Yifan Gu Date: Fri, 17 Apr 2015 16:12:08 -0700 Subject: [PATCH 14/43] kubelet: Refactor computePodContainerChanges(). Pull generatePodStatus() and makePodDataDirs() out as they are the common part for container runtimes. --- pkg/kubelet/kubelet.go | 33 +++++++++++++----------------- pkg/kubelet/kubelet_test.go | 40 ++++++++++++++++++------------------- 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index c58189628ab..ece4508dc0b 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -1060,20 +1060,16 @@ type podContainerChangesSpec struct { containersToKeep map[dockertools.DockerID]int } -func (kl *Kubelet) computePodContainerChanges(pod *api.Pod, runningPod kubecontainer.Pod) (podContainerChangesSpec, error) { +func (kl *Kubelet) computePodContainerChanges(pod *api.Pod, runningPod kubecontainer.Pod, podStatus api.PodStatus) (podContainerChangesSpec, error) { podFullName := kubecontainer.GetPodFullName(pod) uid := pod.UID glog.V(4).Infof("Syncing Pod %+v, podFullName: %q, uid: %q", pod, podFullName, uid) - err := kl.makePodDataDirs(pod) - if err != nil { - return podContainerChangesSpec{}, err - } - containersToStart := make(map[int]empty) containersToKeep := make(map[dockertools.DockerID]int) createPodInfraContainer := false + var err error var podInfraContainerID dockertools.DockerID var changed bool podInfraContainer := runningPod.FindContainerByName(dockertools.PodInfraContainerName) @@ -1097,18 +1093,6 @@ func (kl *Kubelet) computePodContainerChanges(pod *api.Pod, runningPod kubeconta containersToKeep[podInfraContainerID] = -1 } - // Do not use the cache here since we need the newest status to check - // if we need to restart the container below. - pod, found := kl.GetPodByFullName(podFullName) - if !found { - return podContainerChangesSpec{}, fmt.Errorf("couldn't find pod %q", podFullName) - } - podStatus, err := kl.generatePodStatus(pod) - if err != nil { - glog.Errorf("Unable to get pod with name %q and uid %q info with error(%v)", podFullName, uid, err) - return podContainerChangesSpec{}, err - } - for index, container := range pod.Spec.Containers { expectedHash := dockertools.HashContainer(&container) @@ -1213,7 +1197,18 @@ func (kl *Kubelet) syncPod(pod *api.Pod, mirrorPod *api.Pod, runningPod kubecont return err } - containerChanges, err := kl.computePodContainerChanges(pod, runningPod) + if err := kl.makePodDataDirs(pod); err != nil { + glog.Errorf("Unable to make pod data directories for pod %q (uid %q): %v", podFullName, uid, err) + return err + } + + podStatus, err := kl.generatePodStatus(pod) + if err != nil { + glog.Errorf("Unable to get status for pod %q (uid %q): %v", podFullName, uid, err) + return err + } + + containerChanges, err := kl.computePodContainerChanges(pod, runningPod, podStatus) glog.V(3).Infof("Got container changes for pod %q: %+v", podFullName, containerChanges) if err != nil { return err diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index 5fc8e740410..2b07b766302 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -511,10 +511,10 @@ func TestSyncPodsDoesNothing(t *testing.T) { waitGroup.Wait() verifyCalls(t, fakeDocker, []string{ "list", "list", - // Check the pod infra contianer. - "inspect_container", // Get pod status. "list", "inspect_container", "inspect_container", + // Check the pod infra contianer. + "inspect_container", // Get pod status. "list", "inspect_container", "inspect_container"}) } @@ -743,10 +743,10 @@ func TestSyncPodsWithPodInfraCreatesContainer(t *testing.T) { verifyCalls(t, fakeDocker, []string{ "list", "list", - // Check the pod infra container. - "inspect_container", // Get pod status. "list", "inspect_container", "inspect_image", + // Check the pod infra container. + "inspect_container", // Create container. "create", "start", // Get pod status. @@ -818,10 +818,10 @@ func TestSyncPodsWithPodInfraCreatesContainerCallsHandler(t *testing.T) { verifyCalls(t, fakeDocker, []string{ "list", "list", - // Check the pod infra container. - "inspect_container", // Get pod status. "list", "inspect_container", "inspect_image", + // Check the pod infra container. + "inspect_container", // Create container. "create", "start", // Get pod status. @@ -1104,10 +1104,10 @@ func TestSyncPodsDeletesDuplicate(t *testing.T) { verifyCalls(t, fakeDocker, []string{ "list", "list", - // Check the pod infra container. - "inspect_container", // Get pod status. "list", "inspect_container", "inspect_container", "inspect_container", + // Check the pod infra container. + "inspect_container", // Kill the duplicated container. "stop", // Get pod status. @@ -1175,10 +1175,10 @@ func TestSyncPodsBadHash(t *testing.T) { verifyCalls(t, fakeDocker, []string{ "list", "list", - // Check the pod infra container. - "inspect_container", // Get pod status. "list", "inspect_container", "inspect_container", + // Check the pod infra container. + "inspect_container", // Kill and restart the bad hash container. "stop", "create", "start", // Get pod status. @@ -1249,10 +1249,10 @@ func TestSyncPodsUnhealthy(t *testing.T) { verifyCalls(t, fakeDocker, []string{ "list", "list", - // Check the pod infra container. - "inspect_container", // Get pod status. "list", "inspect_container", "inspect_container", + // Check the pod infra container. + "inspect_container", // Kill the unhealthy container. "stop", // Restart the unhealthy container. @@ -1868,10 +1868,10 @@ func TestSyncPodEventHandlerFails(t *testing.T) { verifyCalls(t, fakeDocker, []string{ "list", "list", - // Check the pod infra container. - "inspect_container", // Get pod status. "list", "inspect_container", "inspect_image", + // Check the pod infra container. + "inspect_container", // Create the container. "create", "start", // Kill the container since event handler fails. @@ -3871,10 +3871,10 @@ func TestSyncPodsWithRestartPolicy(t *testing.T) { { api.RestartPolicyAlways, []string{"list", "list", - // Check the pod infra container. - "inspect_container", // Get pod status. "list", "inspect_container", "inspect_container", "inspect_container", + // Check the pod infra container. + "inspect_container", // Restart both containers. "create", "start", "create", "start", // Get pod status. @@ -3885,10 +3885,10 @@ func TestSyncPodsWithRestartPolicy(t *testing.T) { { api.RestartPolicyOnFailure, []string{"list", "list", - // Check the pod infra container. - "inspect_container", // Get pod status. "list", "inspect_container", "inspect_container", "inspect_container", + // Check the pod infra container. + "inspect_container", // Restart the failed container. "create", "start", // Get pod status. @@ -3899,10 +3899,10 @@ func TestSyncPodsWithRestartPolicy(t *testing.T) { { api.RestartPolicyNever, []string{"list", "list", - // Check the pod infra container. - "inspect_container", // Get pod status. "list", "inspect_container", "inspect_container", "inspect_container", + // Check the pod infra container. + "inspect_container", // Stop the last pod infra container. "stop", // Get pod status. From f1d88f6d2f627eee58af9738c53f4c96c2f2b9a4 Mon Sep 17 00:00:00 2001 From: Ravi Sankar Penta Date: Wed, 8 Apr 2015 18:22:44 -0700 Subject: [PATCH 15/43] Record an event on node schedulable changes --- pkg/kubelet/kubelet.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index c58189628ab..27a7f0d41b4 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -1770,6 +1770,21 @@ func (kl *Kubelet) recordNodeOnlineEvent() { kl.recorder.Eventf(kl.nodeRef, "online", "Node %s is now online", kl.hostname) } +func (kl *Kubelet) recordNodeSchedulableEvent() { + // TODO: This requires a transaction, either both node status is updated + // and event is recorded or neither should happen, see issue #6055. + kl.recorder.Eventf(kl.nodeRef, "schedulable", "Node %s is now schedulable", kl.hostname) +} + +func (kl *Kubelet) recordNodeUnschedulableEvent() { + // TODO: This requires a transaction, either both node status is updated + // and event is recorded or neither should happen, see issue #6055. + kl.recorder.Eventf(kl.nodeRef, "unschedulable", "Node %s is now unschedulable", kl.hostname) +} + +// Maintains Node.Spec.Unschedulable value from previous run of tryUpdateNodeStatus() +var oldNodeUnschedulable bool + // tryUpdateNodeStatus tries to update node status to master. func (kl *Kubelet) tryUpdateNodeStatus() error { node, err := kl.kubeClient.Nodes().Get(kl.hostname) @@ -1836,6 +1851,14 @@ func (kl *Kubelet) tryUpdateNodeStatus() error { kl.recordNodeOnlineEvent() } + if oldNodeUnschedulable != node.Spec.Unschedulable { + if node.Spec.Unschedulable { + kl.recordNodeUnschedulableEvent() + } else { + kl.recordNodeSchedulableEvent() + } + oldNodeUnschedulable = node.Spec.Unschedulable + } _, err = kl.kubeClient.Nodes().UpdateStatus(node) return err } From c203117e42bed057782dc80992b811e584b2f741 Mon Sep 17 00:00:00 2001 From: Abhishek Gupta Date: Fri, 17 Apr 2015 12:01:18 -0700 Subject: [PATCH 16/43] Including balanced resource allocation priority in the default set --- plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go b/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go index 2dffdeb0e0f..98f4d173264 100644 --- a/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go +++ b/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go @@ -60,6 +60,8 @@ func defaultPriorities() util.StringSet { return util.NewStringSet( // Prioritize nodes by least requested utilization. factory.RegisterPriorityFunction("LeastRequestedPriority", algorithm.LeastRequestedPriority, 1), + // Prioritizes nodes to help achieve balanced resource usage + factory.RegisterPriorityFunction("BalancedResourceAllocation", algorithm.BalancedResourceAllocation, 1), // spreads pods by minimizing the number of pods (belonging to the same service) on the same minion. factory.RegisterPriorityConfigFactory( "ServiceSpreadingPriority", From 611fb25926153e6ac9b9992eee2ffb8ec96b369a Mon Sep 17 00:00:00 2001 From: Yifan Gu Date: Tue, 21 Apr 2015 13:02:50 -0700 Subject: [PATCH 17/43] kubelet: Refactor GetDockerVersion(). Remove GetDockerServerVersion() from DockerContainerCommandRunner interface, replaced with runtime.Version(). Also added Version type in runtime for version comparision. --- pkg/kubelet/container/runtime.go | 13 +++++++-- pkg/kubelet/dockertools/docker.go | 6 ++-- pkg/kubelet/dockertools/docker_test.go | 6 ++-- pkg/kubelet/dockertools/manager.go | 40 ++++++++++++++++++++++++++ pkg/kubelet/kubelet.go | 11 ++++--- pkg/kubelet/kubelet_test.go | 4 --- pkg/kubelet/server.go | 19 ++++++------ pkg/kubelet/server_test.go | 19 ++++++------ 8 files changed, 81 insertions(+), 37 deletions(-) diff --git a/pkg/kubelet/container/runtime.go b/pkg/kubelet/container/runtime.go index 65c60f95844..c9d736d837c 100644 --- a/pkg/kubelet/container/runtime.go +++ b/pkg/kubelet/container/runtime.go @@ -25,11 +25,20 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/volume" ) +type Version interface { + // Compare compares two versions of the runtime. On success it returns -1 + // if the version is less than the other, 1 if it is greater than the other, + // or 0 if they are equal. + Compare(other string) (int, error) + // String returns a string that represents the version. + String() string +} + // Runtime interface defines the interfaces that should be implemented // by a container runtime. type Runtime interface { - // Version returns a map of version information of the container runtime. - Version() (map[string]string, error) + // Version returns the version information of the container runtime. + Version() (Version, error) // GetPods returns a list containers group by pods. The boolean parameter // specifies whether the runtime returns all containers including those already // exited and dead containers (used for garbage collection). diff --git a/pkg/kubelet/dockertools/docker.go b/pkg/kubelet/dockertools/docker.go index 2f0a7d1c655..1156dbd1426 100644 --- a/pkg/kubelet/dockertools/docker.go +++ b/pkg/kubelet/dockertools/docker.go @@ -120,7 +120,8 @@ type dockerContainerCommandRunner struct { var dockerAPIVersionWithExec, _ = docker.NewAPIVersion("1.15") // Returns the major and minor version numbers of docker server. -func (d *dockerContainerCommandRunner) GetDockerServerVersion() (docker.APIVersion, error) { +// TODO(yifan): Remove this once the ContainerCommandRunner is implemented by dockerManager. +func (d *dockerContainerCommandRunner) getDockerServerVersion() (docker.APIVersion, error) { env, err := d.client.Version() if err != nil { return nil, fmt.Errorf("failed to get docker server version - %v", err) @@ -136,7 +137,7 @@ func (d *dockerContainerCommandRunner) GetDockerServerVersion() (docker.APIVersi } func (d *dockerContainerCommandRunner) nativeExecSupportExists() (bool, error) { - version, err := d.GetDockerServerVersion() + version, err := d.getDockerServerVersion() if err != nil { return false, err } @@ -479,7 +480,6 @@ func ConnectToDockerOrDie(dockerEndpoint string) DockerInterface { // TODO(yifan): Move this to container.Runtime. type ContainerCommandRunner interface { RunInContainer(containerID string, cmd []string) ([]byte, error) - GetDockerServerVersion() (docker.APIVersion, error) ExecInContainer(containerID string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error PortForward(pod *kubecontainer.Pod, port uint16, stream io.ReadWriteCloser) error } diff --git a/pkg/kubelet/dockertools/docker_test.go b/pkg/kubelet/dockertools/docker_test.go index 2b57a5820a5..514c2ceafa1 100644 --- a/pkg/kubelet/dockertools/docker_test.go +++ b/pkg/kubelet/dockertools/docker_test.go @@ -129,10 +129,10 @@ func TestContainerManifestNaming(t *testing.T) { } } -func TestGetDockerServerVersion(t *testing.T) { +func TestVersion(t *testing.T) { fakeDocker := &FakeDockerClient{VersionInfo: docker.Env{"Version=1.1.3", "ApiVersion=1.15"}} - runner := dockerContainerCommandRunner{fakeDocker} - version, err := runner.GetDockerServerVersion() + manager := &DockerManager{client: fakeDocker} + version, err := manager.Version() if err != nil { t.Errorf("got error while getting docker server version - %s", err) } diff --git a/pkg/kubelet/dockertools/manager.go b/pkg/kubelet/dockertools/manager.go index 4e8b15e1a28..32d41aca438 100644 --- a/pkg/kubelet/dockertools/manager.go +++ b/pkg/kubelet/dockertools/manager.go @@ -655,3 +655,43 @@ func (dm *DockerManager) PodInfraContainerChanged(pod *api.Pod, podInfraContaine } return podInfraContainer.Hash != HashContainer(expectedPodInfraContainer), nil } + +type dockerVersion docker.APIVersion + +func NewVersion(input string) (dockerVersion, error) { + version, err := docker.NewAPIVersion(input) + return dockerVersion(version), err +} + +func (dv dockerVersion) String() string { + return docker.APIVersion(dv).String() +} + +func (dv dockerVersion) Compare(other string) (int, error) { + a := docker.APIVersion(dv) + b, err := docker.NewAPIVersion(other) + if err != nil { + return 0, err + } + if a.LessThan(b) { + return -1, nil + } + if a.GreaterThan(b) { + return 1, nil + } + return 0, nil +} + +func (dm *DockerManager) Version() (kubecontainer.Version, error) { + env, err := dm.client.Version() + if err != nil { + return nil, fmt.Errorf("docker: failed to get docker version: %v", err) + } + + apiVersion := env.Get("ApiVersion") + version, err := docker.NewAPIVersion(apiVersion) + if err != nil { + return nil, fmt.Errorf("docker: failed to parse docker server version %q: %v", apiVersion, err) + } + return dockerVersion(version), nil +} diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index c58189628ab..86c719d7839 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -1658,13 +1658,12 @@ func (kl *Kubelet) syncLoop(updates <-chan PodUpdate, handler SyncHandler) { } } -// Returns Docker version for this Kubelet. -func (kl *Kubelet) GetDockerVersion() (docker.APIVersion, error) { - if kl.dockerClient == nil { - return nil, fmt.Errorf("no Docker client") +// Returns the container runtime version for this Kubelet. +func (kl *Kubelet) GetContainerRuntimeVersion() (kubecontainer.Version, error) { + if kl.containerManager == nil { + return nil, fmt.Errorf("no container runtime") } - dockerRunner := dockertools.NewDockerContainerCommandRunner(kl.dockerClient) - return dockerRunner.GetDockerServerVersion() + return kl.containerManager.Version() } func (kl *Kubelet) validatePodPhase(podStatus *api.PodStatus) error { diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index 5fc8e740410..409aebd7480 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -1597,10 +1597,6 @@ func (f *fakeContainerCommandRunner) RunInContainer(id string, cmd []string) ([] return []byte{}, f.E } -func (f *fakeContainerCommandRunner) GetDockerServerVersion() (docker.APIVersion, error) { - return nil, nil -} - func (f *fakeContainerCommandRunner) ExecInContainer(id string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error { f.Cmd = cmd f.ID = id diff --git a/pkg/kubelet/server.go b/pkg/kubelet/server.go index 4b98f0232cb..e261e0ff8c3 100644 --- a/pkg/kubelet/server.go +++ b/pkg/kubelet/server.go @@ -41,7 +41,6 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/util/flushwriter" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/httpstream" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/httpstream/spdy" - "github.com/fsouza/go-dockerclient" "github.com/golang/glog" cadvisorApi "github.com/google/cadvisor/info/v1" "github.com/prometheus/client_golang/prometheus" @@ -101,7 +100,7 @@ func ListenAndServeKubeletReadOnlyServer(host HostInterface, address net.IP, por type HostInterface interface { GetContainerInfo(podFullName string, uid types.UID, containerName string, req *cadvisorApi.ContainerInfoRequest) (*cadvisorApi.ContainerInfo, error) GetRootInfo(req *cadvisorApi.ContainerInfoRequest) (*cadvisorApi.ContainerInfo, error) - GetDockerVersion() (docker.APIVersion, error) + GetContainerRuntimeVersion() (kubecontainer.Version, error) GetCachedMachineInfo() (*cadvisorApi.MachineInfo, error) GetPods() []*api.Pod GetPodByName(namespace, name string) (*api.Pod, bool) @@ -160,18 +159,18 @@ func (s *Server) error(w http.ResponseWriter, err error) { http.Error(w, msg, http.StatusInternalServerError) } -func isValidDockerVersion(ver docker.APIVersion) bool { - minAllowedVersion, _ := docker.NewAPIVersion("1.15") - return ver.GreaterThanOrEqualTo(minAllowedVersion) -} - func (s *Server) dockerHealthCheck(req *http.Request) error { - version, err := s.host.GetDockerVersion() + version, err := s.host.GetContainerRuntimeVersion() if err != nil { return errors.New("unknown Docker version") } - if !isValidDockerVersion(version) { - return fmt.Errorf("Docker version is too old (%v)", version.String()) + // Verify the docker version. + result, err := version.Compare("1.15") + if err != nil { + return err + } + if result < 0 { + return fmt.Errorf("Docker version is too old: %q", version.String()) } return nil } diff --git a/pkg/kubelet/server_test.go b/pkg/kubelet/server_test.go index 712ffdd9b2b..778cf720346 100644 --- a/pkg/kubelet/server_test.go +++ b/pkg/kubelet/server_test.go @@ -32,10 +32,11 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + kubecontainer "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/container" + "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" "github.com/GoogleCloudPlatform/kubernetes/pkg/types" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/httpstream" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/httpstream/spdy" - "github.com/fsouza/go-dockerclient" cadvisorApi "github.com/google/cadvisor/info/v1" ) @@ -48,7 +49,7 @@ type fakeKubelet struct { podsFunc func() []*api.Pod logFunc func(w http.ResponseWriter, req *http.Request) runFunc func(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) - dockerVersionFunc func() (docker.APIVersion, error) + containerVersionFunc func() (kubecontainer.Version, error) execFunc func(pod string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error portForwardFunc func(name string, uid types.UID, port uint16, stream io.ReadWriteCloser) error containerLogsFunc func(podFullName, containerName, tail string, follow bool, stdout, stderr io.Writer) error @@ -72,8 +73,8 @@ func (fk *fakeKubelet) GetRootInfo(req *cadvisorApi.ContainerInfoRequest) (*cadv return fk.rootInfoFunc(req) } -func (fk *fakeKubelet) GetDockerVersion() (docker.APIVersion, error) { - return fk.dockerVersionFunc() +func (fk *fakeKubelet) GetContainerRuntimeVersion() (kubecontainer.Version, error) { + return fk.containerVersionFunc() } func (fk *fakeKubelet) GetCachedMachineInfo() (*cadvisorApi.MachineInfo, error) { @@ -450,8 +451,8 @@ func TestPodsInfo(t *testing.T) { func TestHealthCheck(t *testing.T) { fw := newServerTest() - fw.fakeKubelet.dockerVersionFunc = func() (docker.APIVersion, error) { - return docker.NewAPIVersion("1.15") + fw.fakeKubelet.containerVersionFunc = func() (kubecontainer.Version, error) { + return dockertools.NewVersion("1.15") } fw.fakeKubelet.hostnameFunc = func() string { return "127.0.0.1" @@ -489,9 +490,9 @@ func TestHealthCheck(t *testing.T) { t.Errorf("expected status code %d, got %d", http.StatusOK, resp.StatusCode) } - //Test with old docker version - fw.fakeKubelet.dockerVersionFunc = func() (docker.APIVersion, error) { - return docker.NewAPIVersion("1.1") + //Test with old container runtime version + fw.fakeKubelet.containerVersionFunc = func() (kubecontainer.Version, error) { + return dockertools.NewVersion("1.1") } resp, err = http.Get(fw.testHTTPServer.URL + "/healthz") From 71e6b05825c846bd97e7bb74fb2e90904fb20f3d Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Tue, 21 Apr 2015 13:59:26 -0700 Subject: [PATCH 18/43] Fix kube-apiserver restart. --- cluster/gce/util.sh | 2 +- cluster/gke/util.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster/gce/util.sh b/cluster/gce/util.sh index 6a3811dbd0a..c5f3fce8f27 100755 --- a/cluster/gce/util.sh +++ b/cluster/gce/util.sh @@ -981,7 +981,7 @@ function restart-kube-proxy { # Restart the kube-apiserver on a node ($1) function restart-apiserver { - ssh-to-node "$1" "sudo docker kill `sudo docker ps | grep /kube-apiserver | awk '{print $1}'`" + ssh-to-node "$1" "sudo docker kill \`sudo docker ps | grep /kube-apiserver | awk '{print $1}'\`" } # Perform preparations required to run e2e tests diff --git a/cluster/gke/util.sh b/cluster/gke/util.sh index f8422386ca8..c4451adad26 100755 --- a/cluster/gke/util.sh +++ b/cluster/gke/util.sh @@ -251,7 +251,7 @@ function restart-kube-proxy() { # Restart the kube-proxy on master ($1) function restart-apiserver() { echo "... in restart-kube-apiserver()" >&2 - ssh-to-node "$1" "sudo docker kill `sudo docker ps | grep /kube-apiserver | awk '{print $1}'`" + ssh-to-node "$1" "sudo docker kill \`sudo docker ps | grep /kube-apiserver | awk '{print $1}'\`" } # Execute after running tests to perform any required clean-up. This is called From a9902fe932b333f96c370b44c2cf26a60bd32fc0 Mon Sep 17 00:00:00 2001 From: Alex Robinson Date: Tue, 21 Apr 2015 22:19:22 +0000 Subject: [PATCH 19/43] Update the external load balancer test to use a different namespace in each run, making stuck resources less painful. --- test/e2e/service.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/e2e/service.go b/test/e2e/service.go index 8c3d5a11264..8c74c87027e 100644 --- a/test/e2e/service.go +++ b/test/e2e/service.go @@ -257,7 +257,7 @@ var _ = Describe("Services", func() { It("should be able to create a functioning external load balancer", func() { serviceName := "external-lb-test" - ns := api.NamespaceDefault + ns := namespace0 labels := map[string]string{ "key0": "value0", } @@ -275,9 +275,6 @@ var _ = Describe("Services", func() { }, } - By("cleaning up previous service " + serviceName + " from namespace " + ns) - c.Services(ns).Delete(serviceName) - By("creating service " + serviceName + " with external load balancer in namespace " + ns) result, err := c.Services(ns).Create(service) Expect(err).NotTo(HaveOccurred()) @@ -317,7 +314,7 @@ var _ = Describe("Services", func() { } By("creating pod to be part of service " + serviceName) - podClient := c.Pods(api.NamespaceDefault) + podClient := c.Pods(ns) defer func() { By("deleting pod " + pod.Name) defer GinkgoRecover() @@ -326,7 +323,7 @@ var _ = Describe("Services", func() { if _, err := podClient.Create(pod); err != nil { Failf("Failed to create pod %s: %v", pod.Name, err) } - expectNoError(waitForPodRunning(c, pod.Name)) + expectNoError(waitForPodRunningInNamespace(c, pod.Name, ns)) By("hitting the pod through the service's external load balancer") var resp *http.Response From 78dabbdb7f5813d257d054fd8e3d57903e5034ff Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Tue, 21 Apr 2015 15:27:38 -0700 Subject: [PATCH 20/43] Fix the ssh-to-node to actually fail on failures. --- cluster/gce/util.sh | 6 +++++- cluster/gke/util.sh | 11 +++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/cluster/gce/util.sh b/cluster/gce/util.sh index c5f3fce8f27..c2267902cce 100755 --- a/cluster/gce/util.sh +++ b/cluster/gce/util.sh @@ -967,11 +967,15 @@ function test-teardown { function ssh-to-node { local node="$1" local cmd="$2" + # Loop until we can successfully ssh into the box for try in $(seq 1 5); do - if gcloud compute ssh --ssh-flag="-o LogLevel=quiet" --project "${PROJECT}" --zone="${ZONE}" "${node}" --command "${cmd}"; then + if gcloud compute ssh --ssh-flag="-o LogLevel=quiet" --project "${PROJECT}" --zone="${ZONE}" "${node}" --command "echo test"; then break fi + sleep 5 done + # Then actually try the command. + gcloud compute ssh --ssh-flag="-o LogLevel=quiet" --project "${PROJECT}" --zone="${ZONE}" "${node}" --command "${cmd}" } # Restart the kube-proxy on a node ($1) diff --git a/cluster/gke/util.sh b/cluster/gke/util.sh index c4451adad26..ea66997dac8 100755 --- a/cluster/gke/util.sh +++ b/cluster/gke/util.sh @@ -238,8 +238,15 @@ function ssh-to-node() { local node="$1" local cmd="$2" - "${GCLOUD}" compute ssh --ssh-flag="-o LogLevel=quiet" --project "${PROJECT}" \ - --zone="${ZONE}" "${node}" --command "${cmd}" + # Loop until we can successfully ssh into the box + for try in $(seq 1 5); do + if gcloud compute ssh --ssh-flag="-o LogLevel=quiet" --project "${PROJECT}" --zone="${ZONE}" "${node}" --command "echo test"; then + break + fi + sleep 5 + done + # Then actually try the command. + gcloud compute ssh --ssh-flag="-o LogLevel=quiet" --project "${PROJECT}" --zone="${ZONE}" "${node}" --command "${cmd}" } # Restart the kube-proxy on a node ($1) From 06125f37d345035c35ddedd7c1eb25f7befe0ae7 Mon Sep 17 00:00:00 2001 From: Yu-Ju Hong Date: Mon, 20 Apr 2015 16:38:21 -0700 Subject: [PATCH 21/43] Print container statuses in `kubectl get pods` `kubectl get pod` already prints one container per line. This change fills in the status for each container listed. This aims to help users quickly identify unhealthy pods (e.g. in a crash loop) at a glance. - The first row of every pod would display the pod information and status - Each row of the subsequent rows corresponds to a container in that pod: * STATUS refers to the container status (Running, Waiting, Terminated). * CREATED refers to the elapsed time since the last start time of the container. * MESSAGE is a string which explains the last termination reason, and/or the reason behind the waiting status. --- pkg/kubectl/resource_printer.go | 92 +++++++++++++++++++++++++----- pkg/kubelet/dockertools/manager.go | 2 + 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 958491f6e9f..31aa674efd3 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -32,6 +32,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/volume" "github.com/docker/docker/pkg/units" "github.com/ghodss/yaml" @@ -243,7 +244,7 @@ func (h *HumanReadablePrinter) HandledResources() []string { return keys } -var podColumns = []string{"POD", "IP", "CONTAINER(S)", "IMAGE(S)", "HOST", "LABELS", "STATUS", "CREATED"} +var podColumns = []string{"POD", "IP", "CONTAINER(S)", "IMAGE(S)", "HOST", "LABELS", "STATUS", "CREATED", "MESSAGE"} var replicationControllerColumns = []string{"CONTROLLER", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "REPLICAS"} var serviceColumns = []string{"NAME", "LABELS", "SELECTOR", "IP", "PORT(S)"} var endpointColumns = []string{"NAME", "ENDPOINTS"} @@ -339,32 +340,97 @@ func podHostString(host, ip string) string { return host + "/" + ip } +// translateTimestamp returns the elapsed time since timestamp in +// human-readable approximation. +func translateTimestamp(timestamp util.Time) string { + return units.HumanDuration(time.Now().Sub(timestamp.Time)) +} + +// interpretContainerStatus interprets the container status and returns strings +// associated with columns "STATUS", "CREATED", and "MESSAGE". +// The meaning of MESSAGE varies based on the context of STATUS: +// STATUS: Waiting; MESSAGE: reason for waiting +// STATUS: Running; MESSAGE: reason for the last termination +// STATUS: Terminated; MESSAGE: reason for this termination +func interpretContainerStatus(status *api.ContainerStatus) (string, string, string, error) { + // Helper function to compose a meaning message from terminate state. + getTermMsg := func(state *api.ContainerStateTerminated) string { + var message string + if state != nil { + message = fmt.Sprintf("exit code %d", state.ExitCode) + if state.Reason != "" { + message = fmt.Sprintf("%s, reason: %s", state.Reason) + } + } + return message + } + + state := &status.State + if state.Waiting != nil { + return "Waiting", "", state.Waiting.Reason, nil + } else if state.Running != nil { + // Get the information of the last termination state. This is useful if + // a container is stuck in a crash loop. + message := getTermMsg(status.LastTerminationState.Termination) + if message != "" { + message = "last termination: " + message + } + return "Running", translateTimestamp(state.Running.StartedAt), message, nil + } else if state.Termination != nil { + return "Terminated", translateTimestamp(state.Termination.StartedAt), getTermMsg(state.Termination), nil + } + return "", "", "", fmt.Errorf("unknown container state %#v", *state) +} + func printPod(pod *api.Pod, w io.Writer) error { // TODO: remove me when pods are converted spec := &api.PodSpec{} if err := api.Scheme.Convert(&pod.Spec, spec); err != nil { glog.Errorf("Unable to convert pod manifest: %v", err) } - containers := spec.Containers - var firstContainer api.Container - if len(containers) > 0 { - firstContainer, containers = containers[0], containers[1:] - } - _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", + _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", pod.Name, pod.Status.PodIP, - firstContainer.Name, - firstContainer.Image, + "", "", podHostString(pod.Spec.Host, pod.Status.HostIP), formatLabels(pod.Labels), pod.Status.Phase, - units.HumanDuration(time.Now().Sub(pod.CreationTimestamp.Time))) + translateTimestamp(pod.CreationTimestamp), + pod.Status.Message, + ) if err != nil { return err } - // Lay out all the other containers on separate lines. - for _, container := range containers { - _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", "", "", container.Name, container.Image, "", "", "", "") + // Lay out all containers on separate lines. + statuses := pod.Status.ContainerStatuses + if len(statuses) == 0 { + // Container status has not been reported yet. Print basic information + // of the containers and exit the function. + for _, container := range pod.Spec.Containers { + _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", + "", "", container.Name, container.Image, "", "", "", "") + if err != nil { + return err + } + } + return nil + } + + // Print the actual container statuses. + for _, status := range statuses { + state, created, message, err := interpretContainerStatus(&status) + if err != nil { + return err + } + _, err = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", + "", "", + status.Name, + status.Image, + "", "", + state, + created, + message, + ) if err != nil { return err } diff --git a/pkg/kubelet/dockertools/manager.go b/pkg/kubelet/dockertools/manager.go index 4e8b15e1a28..0bc9957c254 100644 --- a/pkg/kubelet/dockertools/manager.go +++ b/pkg/kubelet/dockertools/manager.go @@ -336,6 +336,8 @@ func (dm *DockerManager) GetPodStatus(pod *api.Pod) (*api.PodStatus, error) { continue } var containerStatus api.ContainerStatus + containerStatus.Name = container.Name + containerStatus.Image = container.Image if oldStatus, found := oldStatuses[container.Name]; found { // Some states may be lost due to GC; apply the last observed // values if possible. From fe388a6b792a88c07d907393d48af92f9c177294 Mon Sep 17 00:00:00 2001 From: Patrick Reilly Date: Tue, 21 Apr 2015 15:46:52 -0700 Subject: [PATCH 22/43] fix typo --- www/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/README.md b/www/README.md index dc4db788872..41ac8b6a0e0 100644 --- a/www/README.md +++ b/www/README.md @@ -91,7 +91,7 @@ This is the file that is built-in to the kube-apiserver. See [master/components/README.md](master/components/README.md). ### Testing -Currently kuberntes/www includes both unit-testing (run via [Karma](http://karma-runner.github.io/0.12/index.html)) and +Currently kubernetes/www includes both unit-testing (run via [Karma](http://karma-runner.github.io/0.12/index.html)) and end-to-end testing (run via [Protractor](http://angular.github.io/protractor/#/)). From 71b98840c8ccd5c8ef3bd0b222ad354ebdebfb26 Mon Sep 17 00:00:00 2001 From: dingh Date: Fri, 17 Apr 2015 14:22:07 +0800 Subject: [PATCH 23/43] Listing available algorithm providers in scheduler List the available algorithm providers with 'kube-scheduler --help' under field `algorithm_provider` --- plugin/cmd/kube-scheduler/app/server.go | 2 +- plugin/pkg/scheduler/factory/plugins.go | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/plugin/cmd/kube-scheduler/app/server.go b/plugin/cmd/kube-scheduler/app/server.go index c8696ee1173..9366e5a12ad 100644 --- a/plugin/cmd/kube-scheduler/app/server.go +++ b/plugin/cmd/kube-scheduler/app/server.go @@ -70,7 +70,7 @@ func NewSchedulerServer() *SchedulerServer { func (s *SchedulerServer) AddFlags(fs *pflag.FlagSet) { fs.IntVar(&s.Port, "port", s.Port, "The port that the scheduler's http service runs on") fs.Var(&s.Address, "address", "The IP address to serve on (set to 0.0.0.0 for all interfaces)") - fs.StringVar(&s.AlgorithmProvider, "algorithm_provider", s.AlgorithmProvider, "The scheduling algorithm provider to use") + fs.StringVar(&s.AlgorithmProvider, "algorithm_provider", s.AlgorithmProvider, "The scheduling algorithm provider to use, one of: "+factory.ListAlgorithmProviders()) fs.StringVar(&s.PolicyConfigFile, "policy_config_file", s.PolicyConfigFile, "File with scheduler policy configuration") fs.BoolVar(&s.EnableProfiling, "profiling", true, "Enable profiling via web interface host:port/debug/pprof/") fs.StringVar(&s.Master, "master", s.Master, "The address of the Kubernetes API server (overrides any value in kubeconfig)") diff --git a/plugin/pkg/scheduler/factory/plugins.go b/plugin/pkg/scheduler/factory/plugins.go index a8737f5723f..0e44f2b81ce 100644 --- a/plugin/pkg/scheduler/factory/plugins.go +++ b/plugin/pkg/scheduler/factory/plugins.go @@ -19,6 +19,7 @@ package factory import ( "fmt" "regexp" + "strings" "sync" algorithm "github.com/GoogleCloudPlatform/kubernetes/pkg/scheduler" @@ -300,3 +301,12 @@ func validatePriorityOrDie(priority schedulerapi.PriorityPolicy) { } } } + +// ListAlgorithmProviders is called when listing all available algortihm providers in `kube-scheduler --help` +func ListAlgorithmProviders() string { + var availableAlgorithmProviders []string + for name := range algorithmProviderMap { + availableAlgorithmProviders = append(availableAlgorithmProviders, name) + } + return strings.Join(availableAlgorithmProviders, " | ") +} From 588016684586cc558d7d5a473f9516e620b358cb Mon Sep 17 00:00:00 2001 From: Max Forbes Date: Tue, 21 Apr 2015 15:22:36 -0700 Subject: [PATCH 24/43] Add support for using released tars (instead of just ci) as well as using a stable version (instead of just latest). --- hack/jenkins/e2e.sh | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/hack/jenkins/e2e.sh b/hack/jenkins/e2e.sh index 2e07193a4e6..32970ddab04 100755 --- a/hack/jenkins/e2e.sh +++ b/hack/jenkins/e2e.sh @@ -104,8 +104,21 @@ else sudo flock -x -n /var/run/lock/gcloud-components.lock -c "gcloud components update -q" || true - GITHASH=$(gsutil cat gs://kubernetes-release/ci/latest.txt) - gsutil -m cp gs://kubernetes-release/ci/${GITHASH}/kubernetes.tar.gz gs://kubernetes-release/ci/${GITHASH}/kubernetes-test.tar.gz . + # The "ci" bucket is for builds like "v0.15.0-468-gfa648c1" + bucket="ci" + # The "latest" version picks the most recent "ci" or "release" build. + version_file="latest" + if [[ ${JENKINS_USE_RELEASE_TARS:-} =~ ^[yY]$ ]]; then + # The "release" bucket is for builds like "v0.15.0" + bucket="release" + if [[ ${JENKINS_USE_STABLE:-} =~ ^[yY]$ ]]; then + # The "stable" version picks the most recent "release" build. + version_file="stable" + fi + fi + + githash=$(gsutil cat gs://kubernetes-release/${bucket}/${version_file}.txt) + gsutil -m cp gs://kubernetes-release/${bucket}/${githash}/kubernetes.tar.gz gs://kubernetes-release/${bucket}/${githash}/kubernetes-test.tar.gz . fi md5sum kubernetes*.tar.gz @@ -115,9 +128,9 @@ cd kubernetes # Set by GKE-CI to change the CLUSTER_API_VERSION to the git version if [[ ! -z ${E2E_SET_CLUSTER_API_VERSION:-} ]]; then - export CLUSTER_API_VERSION=$(echo ${GITHASH} | cut -c 2-) -elif [[ ! -z ${E2E_USE_LATEST_RELEASE_VERSION:-} ]]; then - release=$(gsutil cat gs://kubernetes-release/release/latest.txt | cut -c 2-) + export CLUSTER_API_VERSION=$(echo ${githash} | cut -c 2-) +elif [[ ${JENKINS_USE_RELEASE_TARS:-} =~ ^[yY]$ ]]; then + release=$(gsutil cat gs://kubernetes-release/release/${version_file}.txt | cut -c 2-) export CLUSTER_API_VERSION=${release} fi From acb64e66e8ea036ecd0c7f4cd9d5d13526c8f2b7 Mon Sep 17 00:00:00 2001 From: fabioy Date: Tue, 21 Apr 2015 15:57:05 -0700 Subject: [PATCH 25/43] Fix validate-cluster.sh to work on Mac. --- cluster/validate-cluster.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cluster/validate-cluster.sh b/cluster/validate-cluster.sh index c4b37da848c..ac0e638af0a 100755 --- a/cluster/validate-cluster.sh +++ b/cluster/validate-cluster.sh @@ -59,10 +59,9 @@ while true; do # Parse the output to capture the value of the second column("HEALTH"), then use grep to # count the number of times it doesn't match "success". # Because of the header, the actual unsuccessful count is 1 minus the count. - non_success_count=$(echo "${kubectl_output}" | \ - sed -n 's/^\([[:alnum:][:punct:]]\+\)\s\+\([[:alnum:][:punct:]]\+\)\s\+.*/\2/p' | \ - grep 'Healthy' --invert-match -c) + sed -n 's/^[[:alnum:][:punct:]]/&/p' | \ + grep --invert-match -c '^[[:alnum:][:punct:]]\{1,\}[[:space:]]\{1,\}Healthy') if ((non_success_count > 1)); then if ((attempt < 5)); then From 61818cfcbea76379cf557dffadef81904c4f3f50 Mon Sep 17 00:00:00 2001 From: caesarxuchao Date: Tue, 21 Apr 2015 21:22:28 -0700 Subject: [PATCH 26/43] fix the link to services.md --- docs/design/architecture.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/architecture.md b/docs/design/architecture.md index 06a0a0ef0ae..3f021aaf35e 100644 --- a/docs/design/architecture.md +++ b/docs/design/architecture.md @@ -17,7 +17,7 @@ The **Kubelet** manages [pods](../pods.md) and their containers, their images, t ### Kube-Proxy -Each node also runs a simple network proxy and load balancer (see the [services FAQ](https://github.com/GoogleCloudPlatform/kubernetes/wiki/Services-FAQ) for more details). This reflects `services` (see [the services doc](../docs/services.md) for more details) as defined in the Kubernetes API on each node and can do simple TCP and UDP stream forwarding (round robin) across a set of backends. +Each node also runs a simple network proxy and load balancer (see the [services FAQ](https://github.com/GoogleCloudPlatform/kubernetes/wiki/Services-FAQ) for more details). This reflects `services` (see [the services doc](../services.md) for more details) as defined in the Kubernetes API on each node and can do simple TCP and UDP stream forwarding (round robin) across a set of backends. Service endpoints are currently found via [DNS](../dns.md) or through environment variables (both [Docker-links-compatible](https://docs.docker.com/userguide/dockerlinks/) and Kubernetes {FOO}_SERVICE_HOST and {FOO}_SERVICE_PORT variables are supported). These variables resolve to ports managed by the service proxy. From 405ebf4b1e65fff81b45ed21b515a3e59e65eea0 Mon Sep 17 00:00:00 2001 From: Xiang Li Date: Tue, 21 Apr 2015 23:04:32 -0700 Subject: [PATCH 27/43] pkg/apiserver: use httpError in handlers.go --- pkg/apiserver/handlers.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/apiserver/handlers.go b/pkg/apiserver/handlers.go index ab0d83e6d8d..cd20d35af8f 100644 --- a/pkg/apiserver/handlers.go +++ b/pkg/apiserver/handlers.go @@ -104,8 +104,7 @@ func RateLimit(rl util.RateLimiter, handler http.Handler) http.Handler { func tooManyRequests(w http.ResponseWriter) { // Return a 429 status indicating "Too Many Requests" w.Header().Set("Retry-After", RetryAfter) - w.WriteHeader(errors.StatusTooManyRequests) - fmt.Fprintf(w, "Too many requests, please try again later.") + http.Error(w, "Too many requests, please try again later.", errors.StatusTooManyRequests) } // RecoverPanics wraps an http Handler to recover and log panics. @@ -113,8 +112,7 @@ func RecoverPanics(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { defer func() { if x := recover(); x != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprint(w, "apis panic. Look in log for details.") + http.Error(w, "apis panic. Look in log for details.", http.StatusInternalServerError) glog.Infof("APIServer panic'd on %v %v: %v\n%s\n", req.Method, req.RequestURI, x, debug.Stack()) } }() From e982ac5b550dbbb8613d937f15bb9ece4482689b Mon Sep 17 00:00:00 2001 From: CJ Cullen Date: Mon, 20 Apr 2015 14:50:18 -0700 Subject: [PATCH 28/43] Change kube2sky to use token-system-dns secret, point at https endpoint (instead of kubernetes-ro service). --- cluster/addons/dns/kube2sky/kube2sky.go | 37 ++++++++++++++++--------- cluster/addons/dns/skydns-rc.yaml.in | 15 +++++++++- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/cluster/addons/dns/kube2sky/kube2sky.go b/cluster/addons/dns/kube2sky/kube2sky.go index e8d9aeac824..fb940136c56 100644 --- a/cluster/addons/dns/kube2sky/kube2sky.go +++ b/cluster/addons/dns/kube2sky/kube2sky.go @@ -29,6 +29,7 @@ import ( kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + kclientcmd "github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd" kfields "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" klabels "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" tools "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" @@ -42,6 +43,7 @@ var ( etcd_mutation_timeout = flag.Duration("etcd_mutation_timeout", 10*time.Second, "crash after retrying etcd mutation for a specified duration") etcd_server = flag.String("etcd-server", "http://127.0.0.1:4001", "URL to etcd server") verbose = flag.Bool("verbose", false, "log extra information") + kubecfg_file = flag.String("kubecfg_file", "", "Location of kubecfg file for access to kubernetes service") ) func removeDNS(record string, etcdClient *etcd.Client) error { @@ -128,22 +130,31 @@ func newEtcdClient() (client *etcd.Client) { // TODO: evaluate using pkg/client/clientcmd func newKubeClient() (*kclient.Client, error) { - config := &kclient.Config{} - - masterHost := os.Getenv("KUBERNETES_RO_SERVICE_HOST") - if masterHost == "" { - log.Fatalf("KUBERNETES_RO_SERVICE_HOST is not defined") + var config *kclient.Config + if *kubecfg_file == "" { + // No kubecfg file provided. Use kubernetes_ro service. + masterHost := os.Getenv("KUBERNETES_RO_SERVICE_HOST") + if masterHost == "" { + log.Fatalf("KUBERNETES_RO_SERVICE_HOST is not defined") + } + masterPort := os.Getenv("KUBERNETES_RO_SERVICE_PORT") + if masterPort == "" { + log.Fatalf("KUBERNETES_RO_SERVICE_PORT is not defined") + } + config = &kclient.Config{ + Host: fmt.Sprintf("http://%s:%s", masterHost, masterPort), + Version: "v1beta1", + } + } else { + var err error + if config, err = kclientcmd.NewNonInteractiveDeferredLoadingClientConfig( + &kclientcmd.ClientConfigLoadingRules{ExplicitPath: *kubecfg_file}, + &kclientcmd.ConfigOverrides{}).ClientConfig(); err != nil { + return nil, err + } } - masterPort := os.Getenv("KUBERNETES_RO_SERVICE_PORT") - if masterPort == "" { - log.Fatalf("KUBERNETES_RO_SERVICE_PORT is not defined") - } - config.Host = fmt.Sprintf("http://%s:%s", masterHost, masterPort) log.Printf("Using %s for kubernetes master", config.Host) - - config.Version = "v1beta1" log.Printf("Using kubernetes API %s", config.Version) - return kclient.New(config) } diff --git a/cluster/addons/dns/skydns-rc.yaml.in b/cluster/addons/dns/skydns-rc.yaml.in index 048785f9738..7329ef3a3a2 100644 --- a/cluster/addons/dns/skydns-rc.yaml.in +++ b/cluster/addons/dns/skydns-rc.yaml.in @@ -29,10 +29,15 @@ desiredState: "-advertise-client-urls=http://127.0.0.1:4001", ] - name: kube2sky - image: gcr.io/google_containers/kube2sky:1.1 + image: gcr.io/google_containers/kube2sky:1.2 + volumeMounts: + - name: dns-token + mountPath: /etc/dns_token + readOnly: true command: [ # entrypoint = "/kube2sky", "-domain={{ pillar['dns_domain'] }}", + "-kubecfg_file=/etc/dns_token/kubeconfig", ] - name: skydns image: gcr.io/google_containers/skydns:2015-03-11-001 @@ -46,3 +51,11 @@ desiredState: - name: dns containerPort: 53 protocol: UDP + volumes: + - name: dns-token + source: + secret: + target: + kind: Secret + namespace: default + name: token-system-dns \ No newline at end of file From eb2cf2f904b6e4d1249ab8a8d4d7d8af98dcf41c Mon Sep 17 00:00:00 2001 From: Ido Shamun Date: Wed, 22 Apr 2015 14:05:09 +0300 Subject: [PATCH 29/43] set weave version to 0.9.0 instead of latest --- .../kubernetes-cluster-main-nodes-template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started-guides/coreos/azure/cloud_config_templates/kubernetes-cluster-main-nodes-template.yml b/docs/getting-started-guides/coreos/azure/cloud_config_templates/kubernetes-cluster-main-nodes-template.yml index 7c9a4d3cba7..d5396783035 100644 --- a/docs/getting-started-guides/coreos/azure/cloud_config_templates/kubernetes-cluster-main-nodes-template.yml +++ b/docs/getting-started-guides/coreos/azure/cloud_config_templates/kubernetes-cluster-main-nodes-template.yml @@ -129,7 +129,7 @@ coreos: ExecStartPre=/usr/bin/curl \ --silent \ --location \ - https://github.com/zettio/weave/releases/download/latest_release/weave \ + https://github.com/weaveworks/weave/releases/download/v0.9.0/weave \ --output /opt/bin/weave ExecStartPre=/usr/bin/curl \ --silent \ From ea314d55f709b9a4b8289731b777d951717bd3fd Mon Sep 17 00:00:00 2001 From: Wojciech Tyczynski Date: Wed, 22 Apr 2015 14:25:28 +0200 Subject: [PATCH 30/43] Create benchmarks for conversions. --- pkg/conversion/conversion_test.go | 97 +++++++++++++++++ pkg/conversion/node_example.json | 49 +++++++++ pkg/conversion/pod_example.json | 102 ++++++++++++++++++ .../replication_controller_example.json | 82 ++++++++++++++ pkg/runtime/scheme.go | 4 + 5 files changed, 334 insertions(+) create mode 100644 pkg/conversion/conversion_test.go create mode 100644 pkg/conversion/node_example.json create mode 100644 pkg/conversion/pod_example.json create mode 100644 pkg/conversion/replication_controller_example.json diff --git a/pkg/conversion/conversion_test.go b/pkg/conversion/conversion_test.go new file mode 100644 index 00000000000..24897ecf89b --- /dev/null +++ b/pkg/conversion/conversion_test.go @@ -0,0 +1,97 @@ +/* +Copyright 2015 Google Inc. All rights reserved. + +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 conversion_test + +import ( + "io/ioutil" + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" + _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" + _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" + _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3" +) + +func BenchmarkPodConversion(b *testing.B) { + data, err := ioutil.ReadFile("pod_example.json") + if err != nil { + b.Fatalf("unexpected error while reading file: %v", err) + } + var pod api.Pod + if err := api.Scheme.DecodeInto(data, &pod); err != nil { + b.Fatalf("unexpected error decoding pod: %v", err) + } + + scheme := api.Scheme.Raw() + for i := 0; i < b.N; i++ { + versionedObj, err := scheme.ConvertToVersion(&pod, testapi.Version()) + if err != nil { + b.Fatalf("Conversion error: %v", err) + } + _, err = scheme.ConvertToVersion(versionedObj, scheme.InternalVersion) + if err != nil { + b.Fatalf("Conversion error: %v", err) + } + } +} + +func BenchmarkNodeConversion(b *testing.B) { + data, err := ioutil.ReadFile("node_example.json") + if err != nil { + b.Fatalf("unexpected error while reading file: %v", err) + } + var node api.Node + if err := api.Scheme.DecodeInto(data, &node); err != nil { + b.Fatalf("unexpected error decoding node: %v", err) + } + + scheme := api.Scheme.Raw() + for i := 0; i < b.N; i++ { + versionedObj, err := scheme.ConvertToVersion(&node, testapi.Version()) + if err != nil { + b.Fatalf("Conversion error: %v", err) + } + _, err = scheme.ConvertToVersion(versionedObj, scheme.InternalVersion) + if err != nil { + b.Fatalf("Conversion error: %v", err) + } + } +} + +func BenchmarkReplicationControllerConversion(b *testing.B) { + data, err := ioutil.ReadFile("replication_controller_example.json") + if err != nil { + b.Fatalf("unexpected error while reading file: %v", err) + } + var replicationController api.ReplicationController + if err := api.Scheme.DecodeInto(data, &replicationController); err != nil { + b.Fatalf("unexpected error decoding node: %v", err) + } + + scheme := api.Scheme.Raw() + for i := 0; i < b.N; i++ { + versionedObj, err := scheme.ConvertToVersion(&replicationController, testapi.Version()) + if err != nil { + b.Fatalf("Conversion error: %v", err) + } + _, err = scheme.ConvertToVersion(versionedObj, scheme.InternalVersion) + if err != nil { + b.Fatalf("Conversion error: %v", err) + } + } +} diff --git a/pkg/conversion/node_example.json b/pkg/conversion/node_example.json new file mode 100644 index 00000000000..792995b84d7 --- /dev/null +++ b/pkg/conversion/node_example.json @@ -0,0 +1,49 @@ +{ + "kind": "Node", + "apiVersion": "v1beta3", + "metadata": { + "name": "e2e-test-wojtekt-minion-etd6", + "selfLink": "/api/v1beta1/nodes/e2e-test-wojtekt-minion-etd6", + "uid": "a7e89222-e8e5-11e4-8fde-42010af09327", + "resourceVersion": "379", + "creationTimestamp": "2015-04-22T11:49:39Z" + }, + "spec": { + "externalID": "15488322946290398375" + }, + "status": { + "capacity": { + "cpu": "1", + "memory": "1745152Ki" + }, + "conditions": [ + { + "type": "Ready", + "status": "True", + "lastHeartbeatTime": "2015-04-22T11:58:17Z", + "lastTransitionTime": "2015-04-22T11:49:52Z", + "reason": "kubelet is posting ready status" + } + ], + "addresses": [ + { + "type": "ExternalIP", + "address": "104.197.49.213" + }, + { + "type": "LegacyHostIP", + "address": "104.197.20.11" + } + ], + "nodeInfo": { + "machineID": "", + "systemUUID": "D59FA3FA-7B5B-7287-5E1A-1D79F13CB577", + "bootID": "44a832f3-8cfb-4de5-b7d2-d66030b6cd95", + "kernelVersion": "3.16.0-0.bpo.4-amd64", + "osImage": "Debian GNU/Linux 7 (wheezy)", + "containerRuntimeVersion": "docker://1.5.0", + "kubeletVersion": "v0.15.0-484-g0c8ee980d705a3-dirty", + "KubeProxyVersion": "v0.15.0-484-g0c8ee980d705a3-dirty" + } + } +} diff --git a/pkg/conversion/pod_example.json b/pkg/conversion/pod_example.json new file mode 100644 index 00000000000..0bfa6adab9e --- /dev/null +++ b/pkg/conversion/pod_example.json @@ -0,0 +1,102 @@ +{ + "kind": "Pod", + "apiVersion": "v1beta3", + "metadata": { + "name": "etcd-server-e2e-test-wojtekt-master", + "namespace": "default", + "selfLink": "/api/v1beta1/pods/etcd-server-e2e-test-wojtekt-master?namespace=default", + "uid": "a671734a-e8e5-11e4-8fde-42010af09327", + "resourceVersion": "22", + "creationTimestamp": "2015-04-22T11:49:36Z", + "annotations": { + "kubernetes.io/config.mirror": "mirror", + "kubernetes.io/config.source": "file" + } + }, + "spec": { + "volumes": [ + { + "name": "varetcd", + "hostPath": { + "path": "/mnt/master-pd/var/etcd" + }, + "emptyDir": null, + "gcePersistentDisk": null, + "awsElasticBlockStore": null, + "gitRepo": null, + "secret": null, + "nfs": null, + "iscsi": null, + "glusterfs": null + } + ], + "containers": [ + { + "name": "etcd-container", + "image": "gcr.io/google_containers/etcd:2.0.9", + "command": [ + "/usr/local/bin/etcd", + "--addr", + "127.0.0.1:4001", + "--bind-addr", + "127.0.0.1:4001", + "--data-dir", + "/var/etcd/data" + ], + "ports": [ + { + "name": "serverport", + "hostPort": 2380, + "containerPort": 2380, + "protocol": "TCP" + }, + { + "name": "clientport", + "hostPort": 4001, + "containerPort": 4001, + "protocol": "TCP" + } + ], + "resources": {}, + "volumeMounts": [ + { + "name": "varetcd", + "mountPath": "/var/etcd" + } + ], + "terminationMessagePath": "/dev/termination-log", + "imagePullPolicy": "IfNotPresent", + "capabilities": {} + } + ], + "restartPolicy": "Always", + "dnsPolicy": "ClusterFirst", + "host": "e2e-test-wojtekt-master", + "hostNetwork": true + }, + "status": { + "phase": "Running", + "Condition": [ + { + "type": "Ready", + "status": "True" + } + ], + "containerStatuses": [ + { + "name": "etcd-container", + "state": { + "running": { + "startedAt": "2015-04-22T11:49:32Z" + } + }, + "lastState": {}, + "ready": true, + "restartCount": 0, + "image": "gcr.io/google_containers/etcd:2.0.9", + "imageID": "docker://b6b9a86dc06aa1361357ca1b105feba961f6a4145adca6c54e142c0be0fe87b0", + "containerID": "docker://3cbbf818f1addfc252957b4504f56ef2907a313fe6afc47fc75373674255d46d" + } + ] + } +} diff --git a/pkg/conversion/replication_controller_example.json b/pkg/conversion/replication_controller_example.json new file mode 100644 index 00000000000..8d2b3594ba9 --- /dev/null +++ b/pkg/conversion/replication_controller_example.json @@ -0,0 +1,82 @@ +{ + "kind": "ReplicationController", + "apiVersion": "v1beta3", + "metadata": { + "name": "elasticsearch-logging-controller", + "namespace": "default", + "selfLink": "/api/v1beta1/replicationControllers/elasticsearch-logging-controller?namespace=default", + "uid": "aa76f162-e8e5-11e4-8fde-42010af09327", + "resourceVersion": "98", + "creationTimestamp": "2015-04-22T11:49:43Z", + "labels": { + "kubernetes.io/cluster-service": "true", + "name": "elasticsearch-logging" + } + }, + "spec": { + "replicas": 1, + "selector": { + "name": "elasticsearch-logging" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "kubernetes.io/cluster-service": "true", + "name": "elasticsearch-logging" + } + }, + "spec": { + "volumes": [ + { + "name": "es-persistent-storage", + "hostPath": null, + "emptyDir": { + "medium": "" + }, + "gcePersistentDisk": null, + "awsElasticBlockStore": null, + "gitRepo": null, + "secret": null, + "nfs": null, + "iscsi": null, + "glusterfs": null + } + ], + "containers": [ + { + "name": "elasticsearch-logging", + "image": "gcr.io/google_containers/elasticsearch:1.0", + "ports": [ + { + "name": "es-port", + "containerPort": 9200, + "protocol": "TCP" + }, + { + "name": "es-transport-port", + "containerPort": 9300, + "protocol": "TCP" + } + ], + "resources": {}, + "volumeMounts": [ + { + "name": "es-persistent-storage", + "mountPath": "/data" + } + ], + "terminationMessagePath": "/dev/termination-log", + "imagePullPolicy": "IfNotPresent", + "capabilities": {} + } + ], + "restartPolicy": "Always", + "dnsPolicy": "ClusterFirst" + } + } + }, + "status": { + "replicas": 1 + } +} diff --git a/pkg/runtime/scheme.go b/pkg/runtime/scheme.go index b8047c7dc3c..2670c0946e0 100644 --- a/pkg/runtime/scheme.go +++ b/pkg/runtime/scheme.go @@ -36,6 +36,10 @@ type Scheme struct { // Function to convert a field selector to internal representation. type FieldLabelConversionFunc func(label, value string) (internalLabel, internalValue string, err error) +func (self *Scheme) Raw() *conversion.Scheme { + return self.raw +} + // fromScope gets the input version, desired output version, and desired Scheme // from a conversion.Scope. func (self *Scheme) fromScope(s conversion.Scope) (inVersion, outVersion string, scheme *Scheme) { From 93573419954865f8727670d9019a2373c590624d Mon Sep 17 00:00:00 2001 From: Filip Grzadkowski Date: Wed, 22 Apr 2015 16:46:03 +0200 Subject: [PATCH 31/43] Add metrics handler in controller manager. --- .../app/controllermanager.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index 92e2ba8b281..d6aad693fae 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -35,6 +35,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/nodecontroller" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/servicecontroller" replicationControllerPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/controller" + "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz" "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/namespace" "github.com/GoogleCloudPlatform/kubernetes/pkg/resourcequota" @@ -42,6 +43,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/golang/glog" + "github.com/prometheus/client_golang/prometheus" "github.com/spf13/pflag" ) @@ -179,13 +181,20 @@ func (s *CMServer) Run(_ []string) error { } go func() { + mux := http.NewServeMux() + healthz.InstallHandler(mux) if s.EnableProfiling { - mux := http.NewServeMux() mux.HandleFunc("/debug/pprof/", pprof.Index) mux.HandleFunc("/debug/pprof/profile", pprof.Profile) mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) } - http.ListenAndServe(net.JoinHostPort(s.Address.String(), strconv.Itoa(s.Port)), nil) + mux.Handle("/metrics", prometheus.Handler()) + + server := &http.Server{ + Addr: net.JoinHostPort(s.Address.String(), strconv.Itoa(s.Port)), + Handler: mux, + } + glog.Fatal(server.ListenAndServe()) }() endpoints := service.NewEndpointController(kubeClient) From d381db1778117794eeabb08d39aab4d6a3c652dd Mon Sep 17 00:00:00 2001 From: Paul Morie Date: Wed, 22 Apr 2015 10:54:54 -0400 Subject: [PATCH 32/43] Fix typo in secrets integration test --- test/integration/secret_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/secret_test.go b/test/integration/secret_test.go index a031df8d64f..a936f3074b1 100644 --- a/test/integration/secret_test.go +++ b/test/integration/secret_test.go @@ -39,7 +39,7 @@ func init() { func deletePodOrErrorf(t *testing.T, c *client.Client, ns, name string) { if err := c.Pods(ns).Delete(name); err != nil { - t.Errorf("unable to delete pods %v: %v", name, err) + t.Errorf("unable to delete pod %v: %v", name, err) } } func deleteSecretOrErrorf(t *testing.T, c *client.Client, ns, name string) { @@ -136,7 +136,7 @@ func DoTestSecrets(t *testing.T, client *client.Client, apiVersion string) { defer deletePodOrErrorf(t, client, ns, pod.Name) // Create a pod that consumes non-existent secret. - pod.ObjectMeta.Name = "uses-non-existant-secret" + pod.ObjectMeta.Name = "uses-non-existent-secret" if _, err := client.Pods(ns).Create(pod); err != nil { t.Errorf("Failed to create pod: %v", err) } From 87ddd7c2e7e9f808d1a6001547259a710eb55498 Mon Sep 17 00:00:00 2001 From: Paul Morie Date: Wed, 22 Apr 2015 10:57:26 -0400 Subject: [PATCH 33/43] Fix typo in networking e2e --- test/e2e/networking.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/networking.go b/test/e2e/networking.go index e16cf8d9c75..c6933b2c3f8 100644 --- a/test/e2e/networking.go +++ b/test/e2e/networking.go @@ -85,7 +85,7 @@ var _ = Describe("Networking", func() { //Assert basic external connectivity. //Since this is not really a test of kubernetes in any way, we //leave it as a pre-test assertion, rather than a Ginko test. - By("Executing a successfull http request from the external internet") + By("Executing a successful http request from the external internet") resp, err := http.Get("http://google.com") if err != nil { Failf("Unable to connect/talk to the internet: %v", err) From 42121d1809cc5963160dc52e7bc398f61665bf44 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Tue, 21 Apr 2015 16:11:15 -0700 Subject: [PATCH 34/43] Extend the get-cluster.sh script to use sudo if necessary. --- cluster/gce/util.sh | 12 ++++++++++-- cluster/gke/util.sh | 13 ++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/cluster/gce/util.sh b/cluster/gce/util.sh index 6a3811dbd0a..7b3695bed90 100755 --- a/cluster/gce/util.sh +++ b/cluster/gce/util.sh @@ -25,6 +25,7 @@ source "${KUBE_ROOT}/cluster/common.sh" NODE_INSTANCE_PREFIX="${INSTANCE_PREFIX}-minion" KUBE_PROMPT_FOR_UPDATE=y +KUBE_SKIP_UPDATE=${KUBE_SKIP_UPDATE-"n"} # Verify prereqs function verify-prereqs { @@ -48,12 +49,19 @@ function verify-prereqs { fi fi done + if [[ "${KUBE_SKIP_UPDATE} == "y" ]]; then + return + fi # update and install components as needed if [[ "${KUBE_PROMPT_FOR_UPDATE}" != "y" ]]; then gcloud_prompt="-q" fi - gcloud ${gcloud_prompt:-} components update preview || true - gcloud ${gcloud_prompt:-} components update || true + if [ ! -w $(dirname `which gcloud`) ]; then + sudo_prefix="sudo" + fi + ${sudo_prefix} gcloud ${gcloud_prompt:-} components update preview || true + ${sudo_prefix} gcloud ${gcloud_prompt:-} components update alpha || true + ${sudo_prefix} gcloud ${gcloud_prompt:-} components update || true } # Create a temp dir that'll be deleted at the end of this bash session. diff --git a/cluster/gke/util.sh b/cluster/gke/util.sh index f8422386ca8..a57bfbaada2 100755 --- a/cluster/gke/util.sh +++ b/cluster/gke/util.sh @@ -20,6 +20,7 @@ # config-default.sh. KUBE_PROMPT_FOR_UPDATE=y +KUBE_SKIP_UPDATE=${KUBE_SKIP_UPDATE-"n"} KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../.. source "${KUBE_ROOT}/cluster/gke/${KUBE_CONFIG_FILE:-config-default.sh}" @@ -86,13 +87,19 @@ function verify-prereqs() { exit 1 fi fi + if [[ "${KUBE_SKIP_UPDATE} == "y" ]]; then + return + fi # update and install components as needed if [[ "${KUBE_PROMPT_FOR_UPDATE}" != "y" ]]; then gcloud_prompt="-q" fi - gcloud ${gcloud_prompt:-} components update preview || true - gcloud ${gcloud_prompt:-} components update alpha || true - gcloud ${gcloud_prompt:-} components update || true + if [ ! -w $(dirname `which gcloud`) ]; then + sudo_prefix="sudo" + fi + ${sudo_prefix} gcloud ${gcloud_prompt:-} components update preview || true + ${sudo_prefix} gcloud ${gcloud_prompt:-} components update alpha|| true + ${sudo_prefix} gcloud ${gcloud_prompt:-} components update || true } # Instantiate a kubernetes cluster From 10c2ace6bfaa2462829c90d750104b823e487ac6 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Tue, 3 Mar 2015 19:54:17 -0500 Subject: [PATCH 35/43] Pod Templates --- examples/examples_test.go | 13 ++- examples/walkthrough/podtemplate.json | 22 +++++ hack/test-cmd.sh | 28 ++++++ pkg/api/latest/latest_test.go | 4 + pkg/api/register.go | 4 + pkg/api/rest/resttest/resttest.go | 27 +++++- pkg/api/serialization_test.go | 23 +++-- pkg/api/validation/validation.go | 18 ++++ pkg/kubectl/resource_printer.go | 42 +++++++-- pkg/master/master.go | 18 ++++ pkg/registry/pod/rest.go | 1 - pkg/registry/podtemplate/doc.go | 18 ++++ pkg/registry/podtemplate/etcd/etcd.go | 63 ++++++++++++++ pkg/registry/podtemplate/etcd/etcd_test.go | 99 ++++++++++++++++++++++ pkg/registry/podtemplate/rest.go | 81 ++++++++++++++++++ 15 files changed, 443 insertions(+), 18 deletions(-) create mode 100644 examples/walkthrough/podtemplate.json create mode 100644 pkg/registry/podtemplate/doc.go create mode 100644 pkg/registry/podtemplate/etcd/etcd.go create mode 100644 pkg/registry/podtemplate/etcd/etcd_test.go create mode 100644 pkg/registry/podtemplate/rest.go diff --git a/examples/examples_test.go b/examples/examples_test.go index d21c1eea150..dbf19b025a8 100644 --- a/examples/examples_test.go +++ b/examples/examples_test.go @@ -34,7 +34,6 @@ import ( ) func validateObject(obj runtime.Object) (errors []error) { - ctx := api.NewDefaultContext() switch t := obj.(type) { case *api.ReplicationController: if t.Namespace == "" { @@ -49,7 +48,6 @@ func validateObject(obj runtime.Object) (errors []error) { if t.Namespace == "" { t.Namespace = api.NamespaceDefault } - api.ValidNamespace(ctx, &t.ObjectMeta) errors = validation.ValidateService(t) case *api.ServiceList: for i := range t.Items { @@ -59,7 +57,6 @@ func validateObject(obj runtime.Object) (errors []error) { if t.Namespace == "" { t.Namespace = api.NamespaceDefault } - api.ValidNamespace(ctx, &t.ObjectMeta) errors = validation.ValidatePod(t) case *api.PodList: for i := range t.Items { @@ -68,8 +65,15 @@ func validateObject(obj runtime.Object) (errors []error) { case *api.PersistentVolume: errors = validation.ValidatePersistentVolume(t) case *api.PersistentVolumeClaim: - api.ValidNamespace(ctx, &t.ObjectMeta) + if t.Namespace == "" { + t.Namespace = api.NamespaceDefault + } errors = validation.ValidatePersistentVolumeClaim(t) + case *api.PodTemplate: + if t.Namespace == "" { + t.Namespace = api.NamespaceDefault + } + errors = validation.ValidatePodTemplate(t) default: return []error{fmt.Errorf("no validation defined for %#v", obj)} } @@ -156,6 +160,7 @@ func TestExampleObjectSchemas(t *testing.T) { "pod-with-http-healthcheck": &api.Pod{}, "service": &api.Service{}, "replication-controller": &api.ReplicationController{}, + "podtemplate": &api.PodTemplate{}, }, "../examples/update-demo": { "kitten-rc": &api.ReplicationController{}, diff --git a/examples/walkthrough/podtemplate.json b/examples/walkthrough/podtemplate.json new file mode 100644 index 00000000000..0fbdeb37de2 --- /dev/null +++ b/examples/walkthrough/podtemplate.json @@ -0,0 +1,22 @@ + { + "apiVersion": "v1beta3", + "kind": "PodTemplate", + "metadata": { + "name": "nginx" + }, + "spec": { + "metadata": { + "labels": { + "name": "nginx" + }, + "generateName": "nginx-" + }, + "spec": { + "containers": [{ + "name": "nginx", + "image": "dockerfile/nginx", + "ports": [{"containerPort": 80}] + }] + } + } + } diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index b86756a9a02..3e534329de2 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -358,6 +358,34 @@ for version in "${kube_api_versions[@]}"; do kube::test::get_object_assert 'pods --namespace=other' "{{range.items}}{{$id_field}}:{{end}}" '' + ################# + # Pod templates # + ################# + + # Note: pod templates exist only in v1beta3 and above, so output will always be in that form + + ### Create PODTEMPLATE + # Pre-condition: no PODTEMPLATE + kube::test::get_object_assert podtemplates "{{range.items}}{{.metadata.name}}:{{end}}" '' + # Command + kubectl create -f examples/walkthrough/podtemplate.json "${kube_flags[@]}" + # Post-condition: nginx PODTEMPLATE is available + kube::test::get_object_assert podtemplates "{{range.items}}{{.metadata.name}}:{{end}}" 'nginx:' + + ### Printing pod templates works + kubectl get podtemplates "${kube_flags[@]}" + ### Display of an object which doesn't existing in v1beta1 and v1beta2 works + [[ "$(kubectl get podtemplates -o yaml "${kube_flags[@]}" | grep nginx)" ]] + + ### Delete nginx pod template by name + # Pre-condition: nginx pod template is available + kube::test::get_object_assert podtemplates "{{range.items}}{{.metadata.name}}:{{end}}" 'nginx:' + # Command + kubectl delete podtemplate nginx "${kube_flags[@]}" + # Post-condition: No templates exist + kube::test::get_object_assert podtemplate "{{range.items}}{{.metadata.name}}:{{end}}" '' + + ############ # Services # ############ diff --git a/pkg/api/latest/latest_test.go b/pkg/api/latest/latest_test.go index 724f5ee59d9..6b1591b5117 100644 --- a/pkg/api/latest/latest_test.go +++ b/pkg/api/latest/latest_test.go @@ -80,6 +80,10 @@ func TestRESTMapper(t *testing.T) { t.Errorf("unexpected version mapping: %s %s %v", v, k, err) } + if m, err := RESTMapper.RESTMapping("PodTemplate", ""); err != nil || m.APIVersion != "v1beta3" || m.Resource != "podtemplates" { + t.Errorf("unexpected version mapping: %#v %v", m, err) + } + for _, version := range Versions { mapping, err := RESTMapper.RESTMapping("ReplicationController", version) if err != nil { diff --git a/pkg/api/register.go b/pkg/api/register.go index 83fc40e23af..823f8bb7685 100644 --- a/pkg/api/register.go +++ b/pkg/api/register.go @@ -28,6 +28,8 @@ func init() { &Pod{}, &PodList{}, &PodStatusResult{}, + &PodTemplate{}, + &PodTemplateList{}, &ReplicationControllerList{}, &ReplicationController{}, &ServiceList{}, @@ -71,6 +73,8 @@ func init() { func (*Pod) IsAnAPIObject() {} func (*PodList) IsAnAPIObject() {} func (*PodStatusResult) IsAnAPIObject() {} +func (*PodTemplate) IsAnAPIObject() {} +func (*PodTemplateList) IsAnAPIObject() {} func (*ReplicationController) IsAnAPIObject() {} func (*ReplicationControllerList) IsAnAPIObject() {} func (*Service) IsAnAPIObject() {} diff --git a/pkg/api/rest/resttest/resttest.go b/pkg/api/rest/resttest/resttest.go index 29585d7dc7e..2732a0e563f 100644 --- a/pkg/api/rest/resttest/resttest.go +++ b/pkg/api/rest/resttest/resttest.go @@ -179,7 +179,7 @@ func (t *Tester) TestCreateRejectsMismatchedNamespace(valid runtime.Object) { if err == nil { t.Errorf("Expected an error, but we didn't get one") } else if strings.Contains(err.Error(), "Controller.Namespace does not match the provided context") { - t.Errorf("Expected 'Controller.Namespace does not match the provided context' error, got '%v'", err.Error()) + t.Errorf("Expected 'Controller.Namespace does not match the provided context' error, got '%v'", err) } } @@ -195,7 +195,30 @@ func (t *Tester) TestCreateRejectsNamespace(valid runtime.Object) { if err == nil { t.Errorf("Expected an error, but we didn't get one") } else if strings.Contains(err.Error(), "Controller.Namespace does not match the provided context") { - t.Errorf("Expected 'Controller.Namespace does not match the provided context' error, got '%v'", err.Error()) + t.Errorf("Expected 'Controller.Namespace does not match the provided context' error, got '%v'", err) + } +} + +func (t *Tester) TestUpdate(valid runtime.Object, existing, older runtime.Object) { + t.TestUpdateFailsOnNotFound(copyOrDie(valid)) + t.TestUpdateFailsOnVersion(copyOrDie(older)) +} + +func (t *Tester) TestUpdateFailsOnNotFound(valid runtime.Object) { + _, _, err := t.storage.(rest.Updater).Update(api.NewDefaultContext(), valid) + if err == nil { + t.Errorf("Expected an error, but we didn't get one") + } else if !errors.IsNotFound(err) { + t.Errorf("Expected NotFound error, got '%v'", err) + } +} + +func (t *Tester) TestUpdateFailsOnVersion(older runtime.Object) { + _, _, err := t.storage.(rest.Updater).Update(api.NewDefaultContext(), older) + if err == nil { + t.Errorf("Expected an error, but we didn't get one") + } else if !errors.IsConflict(err) { + t.Errorf("Expected Conflict error, got '%v'", err) } } diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index ca79e6f9408..e9f65233307 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -85,13 +85,20 @@ func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) { } // roundTripSame verifies the same source object is tested in all API versions. -func roundTripSame(t *testing.T, item runtime.Object) { +func roundTripSame(t *testing.T, item runtime.Object, except ...string) { + set := util.NewStringSet(except...) seed := rand.Int63() fuzzInternalObject(t, "", item, seed) - roundTrip(t, v1beta1.Codec, item) - roundTrip(t, v1beta2.Codec, item) - fuzzInternalObject(t, "v1beta3", item, seed) - roundTrip(t, v1beta3.Codec, item) + if !set.Has("v1beta1") { + roundTrip(t, v1beta1.Codec, item) + } + if !set.Has("v1beta2") { + roundTrip(t, v1beta2.Codec, item) + } + if !set.Has("v1beta3") { + fuzzInternalObject(t, "v1beta3", item, seed) + roundTrip(t, v1beta3.Codec, item) + } } func roundTripAll(t *testing.T, item runtime.Object) { @@ -130,6 +137,10 @@ func TestList(t *testing.T) { var nonRoundTrippableTypes = util.NewStringSet("ContainerManifest", "ContainerManifestList") var nonInternalRoundTrippableTypes = util.NewStringSet("List", "ListOptions", "PodExecOptions") +var nonRoundTrippableTypesByVersion = map[string][]string{ + "PodTemplate": {"v1beta1", "v1beta2"}, + "PodTemplateList": {"v1beta1", "v1beta2"}, +} func TestRoundTripTypes(t *testing.T) { // api.Scheme.Log(t) @@ -148,7 +159,7 @@ func TestRoundTripTypes(t *testing.T) { if _, err := meta.TypeAccessor(item); err != nil { t.Fatalf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableTypes: %v", kind, err) } - roundTripSame(t, item) + roundTripSame(t, item, nonRoundTrippableTypesByVersion[kind]...) if !nonInternalRoundTrippableTypes.Has(kind) { roundTrip(t, api.Codec, fuzzInternalObject(t, "", item, rand.Int63())) } diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index a1445e71292..1758375ddc8 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -888,6 +888,24 @@ func ValidatePodStatusUpdate(newPod, oldPod *api.Pod) errs.ValidationErrorList { return allErrs } +// ValidatePodTemplate tests if required fields in the pod template are set. +func ValidatePodTemplate(pod *api.PodTemplate) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + allErrs = append(allErrs, ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName).Prefix("metadata")...) + allErrs = append(allErrs, ValidatePodTemplateSpec(&pod.Spec, 0).Prefix("spec")...) + return allErrs +} + +// ValidatePodTemplateUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields +// that cannot be changed. +func ValidatePodTemplateUpdate(newPod, oldPod *api.PodTemplate) errs.ValidationErrorList { + allErrs := errs.ValidationErrorList{} + + allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldPod.ObjectMeta, &newPod.ObjectMeta).Prefix("metadata")...) + allErrs = append(allErrs, ValidatePodTemplateSpec(&newPod.Spec, 0).Prefix("spec")...) + return allErrs +} + var supportedSessionAffinityType = util.NewStringSet(string(api.AffinityTypeClientIP), string(api.AffinityTypeNone)) // ValidateService tests if required fields in the service are set. diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 31aa674efd3..2b10dd93aaf 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -245,6 +245,7 @@ func (h *HumanReadablePrinter) HandledResources() []string { } var podColumns = []string{"POD", "IP", "CONTAINER(S)", "IMAGE(S)", "HOST", "LABELS", "STATUS", "CREATED", "MESSAGE"} +var podTemplateColumns = []string{"TEMPLATE", "CONTAINER(S)", "IMAGE(S)", "PODLABELS"} var replicationControllerColumns = []string{"CONTROLLER", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "REPLICAS"} var serviceColumns = []string{"NAME", "LABELS", "SELECTOR", "IP", "PORT(S)"} var endpointColumns = []string{"NAME", "ENDPOINTS"} @@ -263,6 +264,8 @@ var componentStatusColumns = []string{"NAME", "STATUS", "MESSAGE", "ERROR"} func (h *HumanReadablePrinter) addDefaultHandlers() { h.Handler(podColumns, printPod) h.Handler(podColumns, printPodList) + h.Handler(podTemplateColumns, printPodTemplate) + h.Handler(podTemplateColumns, printPodTemplateList) h.Handler(replicationControllerColumns, printReplicationController) h.Handler(replicationControllerColumns, printReplicationControllerList) h.Handler(serviceColumns, printService) @@ -383,11 +386,6 @@ func interpretContainerStatus(status *api.ContainerStatus) (string, string, stri } func printPod(pod *api.Pod, w io.Writer) error { - // TODO: remove me when pods are converted - spec := &api.PodSpec{} - if err := api.Scheme.Convert(&pod.Spec, spec); err != nil { - glog.Errorf("Unable to convert pod manifest: %v", err) - } _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", pod.Name, pod.Status.PodIP, @@ -447,6 +445,40 @@ func printPodList(podList *api.PodList, w io.Writer) error { return nil } +func printPodTemplate(pod *api.PodTemplate, w io.Writer) error { + containers := pod.Spec.Spec.Containers + var firstContainer api.Container + if len(containers) > 0 { + firstContainer, containers = containers[0], containers[1:] + } + _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", + pod.Name, + firstContainer.Name, + firstContainer.Image, + formatLabels(pod.Spec.Labels), + ) + if err != nil { + return err + } + // Lay out all the other containers on separate lines. + for _, container := range containers { + _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", "", container.Name, container.Image, "") + if err != nil { + return err + } + } + return nil +} + +func printPodTemplateList(podList *api.PodTemplateList, w io.Writer) error { + for _, pod := range podList.Items { + if err := printPodTemplate(&pod, w); err != nil { + return err + } + } + return nil +} + func printReplicationController(controller *api.ReplicationController, w io.Writer) error { containers := controller.Spec.Template.Spec.Containers var firstContainer api.Container diff --git a/pkg/master/master.go b/pkg/master/master.go index 0b4c582f978..b8440e52d03 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -58,6 +58,7 @@ import ( pvcetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/persistentvolumeclaim/etcd" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod" podetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod/etcd" + podtemplateetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/podtemplate/etcd" resourcequotaetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota/etcd" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service" @@ -360,9 +361,18 @@ func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) // init initializes master. func (m *Master) init(c *Config) { + // TODO: make initialization of the helper part of the Master, and allow some storage + // objects to have a newer storage version than the user's default. + newerHelper, err := NewEtcdHelper(c.EtcdHelper.Client, "v1beta3") + if err != nil { + glog.Fatalf("Unable to setup storage for v1beta3: %v", err) + } + podStorage := podetcd.NewStorage(c.EtcdHelper, c.KubeletClient) podRegistry := pod.NewRegistry(podStorage.Pod) + podTemplateStorage := podtemplateetcd.NewREST(newerHelper) + eventRegistry := event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds())) limitRangeRegistry := limitrange.NewEtcdRegistry(c.EtcdHelper) @@ -397,6 +407,8 @@ func (m *Master) init(c *Config) { "pods/binding": podStorage.Binding, "bindings": podStorage.Binding, + "podTemplates": podTemplateStorage, + "replicationControllers": controllerStorage, "services": service.NewStorage(m.serviceRegistry, m.nodeRegistry, m.endpointRegistry, m.portalNet, c.ClusterName), "endpoints": endpointsStorage, @@ -606,6 +618,9 @@ func (m *Master) defaultAPIGroupVersion() *apiserver.APIGroupVersion { func (m *Master) api_v1beta1() *apiserver.APIGroupVersion { storage := make(map[string]rest.Storage) for k, v := range m.storage { + if k == "podTemplates" { + continue + } storage[k] = v } version := m.defaultAPIGroupVersion() @@ -619,6 +634,9 @@ func (m *Master) api_v1beta1() *apiserver.APIGroupVersion { func (m *Master) api_v1beta2() *apiserver.APIGroupVersion { storage := make(map[string]rest.Storage) for k, v := range m.storage { + if k == "podTemplates" { + continue + } storage[k] = v } version := m.defaultAPIGroupVersion() diff --git a/pkg/registry/pod/rest.go b/pkg/registry/pod/rest.go index 33738073cd8..1cab2036331 100644 --- a/pkg/registry/pod/rest.go +++ b/pkg/registry/pod/rest.go @@ -35,7 +35,6 @@ import ( ) // podStrategy implements behavior for Pods -// TODO: move to a pod specific package. type podStrategy struct { runtime.ObjectTyper api.NameGenerator diff --git a/pkg/registry/podtemplate/doc.go b/pkg/registry/podtemplate/doc.go new file mode 100644 index 00000000000..3be9642c4d0 --- /dev/null +++ b/pkg/registry/podtemplate/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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 podtemplate provides RESTStorage implementations for storing PodTemplate API objects. +package podtemplate diff --git a/pkg/registry/podtemplate/etcd/etcd.go b/pkg/registry/podtemplate/etcd/etcd.go new file mode 100644 index 00000000000..421ddb8cbb9 --- /dev/null +++ b/pkg/registry/podtemplate/etcd/etcd.go @@ -0,0 +1,63 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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 etcd + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic" + etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd" + "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/podtemplate" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" +) + +// rest implements a RESTStorage for pod templates against etcd +type REST struct { + etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against pod templates. +func NewREST(h tools.EtcdHelper) *REST { + prefix := "/registry/podtemplates" + store := etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.PodTemplate{} }, + NewListFunc: func() runtime.Object { return &api.PodTemplateList{} }, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.PodTemplate).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return podtemplate.MatchPodTemplate(label, field) + }, + EndpointName: "podtemplates", + + CreateStrategy: podtemplate.Strategy, + UpdateStrategy: podtemplate.Strategy, + ReturnDeletedObject: true, + + Helper: h, + } + + return &REST{store} +} diff --git a/pkg/registry/podtemplate/etcd/etcd_test.go b/pkg/registry/podtemplate/etcd/etcd_test.go new file mode 100644 index 00000000000..b3b01c8b711 --- /dev/null +++ b/pkg/registry/podtemplate/etcd/etcd_test.go @@ -0,0 +1,99 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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 etcd + +import ( + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest/resttest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3" + "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" +) + +func newHelper(t *testing.T) (*tools.FakeEtcdClient, tools.EtcdHelper) { + fakeEtcdClient := tools.NewFakeEtcdClient(t) + fakeEtcdClient.TestIndex = true + helper := tools.NewEtcdHelper(fakeEtcdClient, v1beta3.Codec) + return fakeEtcdClient, helper +} + +func validNewPodTemplate(name string) *api.PodTemplate { + return &api.PodTemplate{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: api.NamespaceDefault, + }, + Spec: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"test": "foo"}, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{ + { + Name: "foo", + Image: "test", + ImagePullPolicy: api.PullAlways, + + TerminationMessagePath: api.TerminationMessagePathDefault, + }, + }, + }, + }, + } +} + +func TestCreate(t *testing.T) { + fakeEtcdClient, helper := newHelper(t) + storage := NewREST(helper) + test := resttest.New(t, storage, fakeEtcdClient.SetError) + pod := validNewPodTemplate("foo") + pod.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + pod, + // invalid + &api.PodTemplate{ + Spec: api.PodTemplateSpec{}, + }, + ) +} + +func TestUpdate(t *testing.T) { + fakeEtcdClient, helper := newHelper(t) + storage := NewREST(helper) + test := resttest.New(t, storage, fakeEtcdClient.SetError) + + fakeEtcdClient.ExpectNotFoundGet("/registry/podtemplates/default/foo") + fakeEtcdClient.ChangeIndex = 2 + pod := validNewPodTemplate("foo") + existing := validNewPodTemplate("exists") + obj, err := storage.Create(api.NewDefaultContext(), existing) + if err != nil { + t.Fatalf("unable to create object: %v", err) + } + older := obj.(*api.PodTemplate) + older.ResourceVersion = "1" + + test.TestUpdate( + pod, + existing, + older, + ) +} diff --git a/pkg/registry/podtemplate/rest.go b/pkg/registry/podtemplate/rest.go new file mode 100644 index 00000000000..9204c098e75 --- /dev/null +++ b/pkg/registry/podtemplate/rest.go @@ -0,0 +1,81 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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 podtemplate + +import ( + "fmt" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" + "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + errs "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors" +) + +// podTemplateStrategy implements behavior for PodTemplates +type podTemplateStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating PodTemplate +// objects via the REST API. +var Strategy = podTemplateStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped is true for pod templates. +func (podTemplateStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears fields that are not allowed to be set by end users on creation. +func (podTemplateStrategy) PrepareForCreate(obj runtime.Object) { + _ = obj.(*api.PodTemplate) +} + +// Validate validates a new pod template. +func (podTemplateStrategy) Validate(ctx api.Context, obj runtime.Object) errs.ValidationErrorList { + pod := obj.(*api.PodTemplate) + return validation.ValidatePodTemplate(pod) +} + +// AllowCreateOnUpdate is false for pod templates. +func (podTemplateStrategy) AllowCreateOnUpdate() bool { + return false +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (podTemplateStrategy) PrepareForUpdate(obj, old runtime.Object) { + _ = obj.(*api.PodTemplate) +} + +// ValidateUpdate is the default update validation for an end user. +func (podTemplateStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) errs.ValidationErrorList { + return validation.ValidatePodTemplateUpdate(obj.(*api.PodTemplate), old.(*api.PodTemplate)) +} + +// MatchPodTemplate returns a generic matcher for a given label and field selector. +func MatchPodTemplate(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + podObj, ok := obj.(*api.PodTemplate) + if !ok { + return false, fmt.Errorf("not a pod template") + } + return label.Matches(labels.Set(podObj.Labels)), nil + }) +} From e6e034af4f12046d4a264646238233d82fd7b66c Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Wed, 4 Mar 2015 10:46:27 -0500 Subject: [PATCH 36/43] Rename PodTemplate.Spec to PodTemplate.Template --- examples/walkthrough/podtemplate.json | 2 +- pkg/api/types.go | 4 +- pkg/api/v1beta3/types.go | 4 +- pkg/api/validation/validation.go | 4 +- pkg/api/validation/validation_test.go | 69 +++++++++++++--------- pkg/kubectl/resource_printer.go | 4 +- pkg/registry/controller/etcd/etcd_test.go | 12 ++-- pkg/registry/podtemplate/etcd/etcd_test.go | 4 +- 8 files changed, 58 insertions(+), 45 deletions(-) diff --git a/examples/walkthrough/podtemplate.json b/examples/walkthrough/podtemplate.json index 0fbdeb37de2..5732a113584 100644 --- a/examples/walkthrough/podtemplate.json +++ b/examples/walkthrough/podtemplate.json @@ -4,7 +4,7 @@ "metadata": { "name": "nginx" }, - "spec": { + "template": { "metadata": { "labels": { "name": "nginx" diff --git a/pkg/api/types.go b/pkg/api/types.go index 6dc455f7bb3..1aeb63f9f43 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -848,8 +848,8 @@ type PodTemplate struct { TypeMeta `json:",inline"` ObjectMeta `json:"metadata,omitempty"` - // Spec defines the pods that will be created from this template - Spec PodTemplateSpec `json:"spec,omitempty"` + // Template defines the pods that will be created from this pod template + Template PodTemplateSpec `json:"template,omitempty"` } // PodTemplateList is a list of PodTemplates. diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index f9ecda57586..065fe3bb37d 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -854,8 +854,8 @@ type PodTemplate struct { TypeMeta `json:",inline"` ObjectMeta `json:"metadata,omitempty" description:"standard object metadata; see https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#metadata"` - // Spec defines the behavior of a pod. - Spec PodTemplateSpec `json:"spec,omitempty" description:"specification of the desired behavior of the pod; https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#spec-and-status"` + // Template defines the pods that will be created from this pod template + Template PodTemplateSpec `json:"template,omitempty" description:"the template of the desired behavior of the pod; https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#spec-and-status"` } // PodTemplateList is a list of PodTemplates. diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 1758375ddc8..d2535dc1676 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -892,7 +892,7 @@ func ValidatePodStatusUpdate(newPod, oldPod *api.Pod) errs.ValidationErrorList { func ValidatePodTemplate(pod *api.PodTemplate) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} allErrs = append(allErrs, ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName).Prefix("metadata")...) - allErrs = append(allErrs, ValidatePodTemplateSpec(&pod.Spec, 0).Prefix("spec")...) + allErrs = append(allErrs, ValidatePodTemplateSpec(&pod.Template, 0).Prefix("template")...) return allErrs } @@ -902,7 +902,7 @@ func ValidatePodTemplateUpdate(newPod, oldPod *api.PodTemplate) errs.ValidationE allErrs := errs.ValidationErrorList{} allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldPod.ObjectMeta, &newPod.ObjectMeta).Prefix("metadata")...) - allErrs = append(allErrs, ValidatePodTemplateSpec(&newPod.Spec, 0).Prefix("spec")...) + allErrs = append(allErrs, ValidatePodTemplateSpec(&newPod.Template, 0).Prefix("template")...) return allErrs } diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index 3b740095670..ca10ea7b83a 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -1676,7 +1676,7 @@ func TestValidateService(t *testing.T) { func TestValidateReplicationControllerUpdate(t *testing.T) { validSelector := map[string]string{"a": "b"} validPodTemplate := api.PodTemplate{ - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: validSelector, }, @@ -1688,7 +1688,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { }, } readWriteVolumePodTemplate := api.PodTemplate{ - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: validSelector, }, @@ -1702,7 +1702,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { } invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} invalidPodTemplate := api.PodTemplate{ - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, @@ -1722,7 +1722,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, update: api.ReplicationController{ @@ -1730,7 +1730,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 3, Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, }, @@ -1739,7 +1739,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, update: api.ReplicationController{ @@ -1747,7 +1747,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 1, Selector: validSelector, - Template: &readWriteVolumePodTemplate.Spec, + Template: &readWriteVolumePodTemplate.Template, }, }, }, @@ -1765,7 +1765,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, update: api.ReplicationController{ @@ -1773,7 +1773,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 2, Selector: validSelector, - Template: &readWriteVolumePodTemplate.Spec, + Template: &readWriteVolumePodTemplate.Template, }, }, }, @@ -1782,7 +1782,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, update: api.ReplicationController{ @@ -1790,7 +1790,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 2, Selector: invalidSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, }, @@ -1799,7 +1799,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, update: api.ReplicationController{ @@ -1807,7 +1807,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 2, Selector: validSelector, - Template: &invalidPodTemplate.Spec, + Template: &invalidPodTemplate.Template, }, }, }, @@ -1816,7 +1816,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, update: api.ReplicationController{ @@ -1824,7 +1824,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: -1, Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, }, @@ -1840,7 +1840,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) { func TestValidateReplicationController(t *testing.T) { validSelector := map[string]string{"a": "b"} validPodTemplate := api.PodTemplate{ - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: validSelector, }, @@ -1852,7 +1852,7 @@ func TestValidateReplicationController(t *testing.T) { }, } readWriteVolumePodTemplate := api.PodTemplate{ - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: validSelector, }, @@ -1866,7 +1866,7 @@ func TestValidateReplicationController(t *testing.T) { } invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} invalidPodTemplate := api.PodTemplate{ - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, @@ -1881,14 +1881,14 @@ func TestValidateReplicationController(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, { ObjectMeta: api.ObjectMeta{Name: "abc-123", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, { @@ -1896,7 +1896,7 @@ func TestValidateReplicationController(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 1, Selector: validSelector, - Template: &readWriteVolumePodTemplate.Spec, + Template: &readWriteVolumePodTemplate.Template, }, }, } @@ -1911,27 +1911,27 @@ func TestValidateReplicationController(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, "missing-namespace": { ObjectMeta: api.ObjectMeta{Name: "abc-123"}, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, "empty selector": { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, "selector_doesnt_match": { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: api.ReplicationControllerSpec{ Selector: map[string]string{"foo": "bar"}, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, "invalid manifest": { @@ -1945,7 +1945,7 @@ func TestValidateReplicationController(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 2, Selector: validSelector, - Template: &readWriteVolumePodTemplate.Spec, + Template: &readWriteVolumePodTemplate.Template, }, }, "negative_replicas": { @@ -1965,7 +1965,7 @@ func TestValidateReplicationController(t *testing.T) { }, Spec: api.ReplicationControllerSpec{ Selector: validSelector, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, "invalid_label 2": { @@ -1977,7 +1977,20 @@ func TestValidateReplicationController(t *testing.T) { }, }, Spec: api.ReplicationControllerSpec{ - Template: &invalidPodTemplate.Spec, + Template: &invalidPodTemplate.Template, + }, + }, + "invalid_annotation": { + ObjectMeta: api.ObjectMeta{ + Name: "abc-123", + Namespace: api.NamespaceDefault, + Annotations: map[string]string{ + "NoUppercaseOrSpecialCharsLike=Equals": "bar", + }, + }, + Spec: api.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, }, }, "invalid restart policy 1": { diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 2b10dd93aaf..d760ac86250 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -446,7 +446,7 @@ func printPodList(podList *api.PodList, w io.Writer) error { } func printPodTemplate(pod *api.PodTemplate, w io.Writer) error { - containers := pod.Spec.Spec.Containers + containers := pod.Template.Spec.Containers var firstContainer api.Container if len(containers) > 0 { firstContainer, containers = containers[0], containers[1:] @@ -455,7 +455,7 @@ func printPodTemplate(pod *api.PodTemplate, w io.Writer) error { pod.Name, firstContainer.Name, firstContainer.Image, - formatLabels(pod.Spec.Labels), + formatLabels(pod.Template.Labels), ) if err != nil { return err diff --git a/pkg/registry/controller/etcd/etcd_test.go b/pkg/registry/controller/etcd/etcd_test.go index f1f3b48bd30..c6c0d630294 100644 --- a/pkg/registry/controller/etcd/etcd_test.go +++ b/pkg/registry/controller/etcd/etcd_test.go @@ -60,7 +60,7 @@ func createController(storage *REST, rc api.ReplicationController, t *testing.T) } var validPodTemplate = api.PodTemplate{ - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: map[string]string{"a": "b"}, }, @@ -79,8 +79,8 @@ var validPodTemplate = api.PodTemplate{ } var validControllerSpec = api.ReplicationControllerSpec{ - Selector: validPodTemplate.Spec.Labels, - Template: &validPodTemplate.Spec, + Selector: validPodTemplate.Template.Labels, + Template: &validPodTemplate.Template, } var validController = api.ReplicationController{ @@ -161,7 +161,7 @@ func TestCreateControllerWithGeneratedName(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 2, Selector: map[string]string{"a": "b"}, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, } @@ -663,7 +663,7 @@ func TestCreate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 2, Selector: map[string]string{"a": "b"}, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, // invalid @@ -671,7 +671,7 @@ func TestCreate(t *testing.T) { Spec: api.ReplicationControllerSpec{ Replicas: 2, Selector: map[string]string{}, - Template: &validPodTemplate.Spec, + Template: &validPodTemplate.Template, }, }, ) diff --git a/pkg/registry/podtemplate/etcd/etcd_test.go b/pkg/registry/podtemplate/etcd/etcd_test.go index b3b01c8b711..9400032331c 100644 --- a/pkg/registry/podtemplate/etcd/etcd_test.go +++ b/pkg/registry/podtemplate/etcd/etcd_test.go @@ -38,7 +38,7 @@ func validNewPodTemplate(name string) *api.PodTemplate { Name: name, Namespace: api.NamespaceDefault, }, - Spec: api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: map[string]string{"test": "foo"}, }, @@ -70,7 +70,7 @@ func TestCreate(t *testing.T) { pod, // invalid &api.PodTemplate{ - Spec: api.PodTemplateSpec{}, + Template: api.PodTemplateSpec{}, }, ) } From 7f1f0cd2339f2a9287b7d88fd58d1355c0d3141e Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Tue, 21 Apr 2015 17:25:56 -0400 Subject: [PATCH 37/43] Add swagger pod template output --- api/swagger-spec/v1beta1.json | 331 +++++++++--- api/swagger-spec/v1beta2.json | 327 +++++++++--- api/swagger-spec/v1beta3.json | 933 ++++++++++++++++++++++++++++++++-- 3 files changed, 1431 insertions(+), 160 deletions(-) diff --git a/api/swagger-spec/v1beta1.json b/api/swagger-spec/v1beta1.json index cef4299e613..08c608ec158 100644 --- a/api/swagger-spec/v1beta1.json +++ b/api/swagger-spec/v1beta1.json @@ -4742,21 +4742,40 @@ } ] }, + { + "path": "/api/v1beta1/pods/{name}/exec", + "description": "API at /api/v1beta1 version v1beta1", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/api/v1beta1/pods/{name}/log", "description": "API at /api/v1beta1 version v1beta1", "operations": [ { - "type": "v1beta1.PodLogOptions", + "type": "v1beta1.Pod", "method": "GET", - "summary": "read the specified PodLogOptions", - "nickname": "readPodLogOptions", + "summary": "read the specified Pod", + "nickname": "readPod", "parameters": [ { "type": "string", "paramType": "path", "name": "name", - "description": "name of the PodLogOptions", + "description": "name of the Pod", "required": true, "allowMultiple": false }, @@ -4773,7 +4792,7 @@ { "code": 200, "message": "OK", - "responseModel": "v1beta1.PodLogOptions" + "responseModel": "v1beta1.Pod" } ], "produces": [ @@ -4785,6 +4804,193 @@ } ] }, + { + "path": "/api/v1beta1/pods/{name}/portforward", + "description": "API at /api/v1beta1 version v1beta1", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta1/pods/{name}/proxy", + "description": "API at /api/v1beta1 version v1beta1", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "POST", + "summary": "connect POST requests to Pod", + "nickname": "connectPOSTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "PUT", + "summary": "connect PUT requests to Pod", + "nickname": "connectPUTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "DELETE", + "summary": "connect DELETE requests to Pod", + "nickname": "connectDELETEPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "HEAD", + "summary": "connect HEAD requests to Pod", + "nickname": "connectHEADPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "OPTIONS", + "summary": "connect OPTIONS requests to Pod", + "nickname": "connectOPTIONSPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta1/pods/{name}/proxy/{path:*}", + "description": "API at /api/v1beta1 version v1beta1", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "POST", + "summary": "connect POST requests to Pod", + "nickname": "connectPOSTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "PUT", + "summary": "connect PUT requests to Pod", + "nickname": "connectPUTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "DELETE", + "summary": "connect DELETE requests to Pod", + "nickname": "connectDELETEPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "HEAD", + "summary": "connect HEAD requests to Pod", + "nickname": "connectHEADPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "OPTIONS", + "summary": "connect OPTIONS requests to Pod", + "nickname": "connectOPTIONSPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/api/v1beta1/pods/{name}/status", "description": "API at /api/v1beta1 version v1beta1", @@ -6914,6 +7120,31 @@ "id": "", "properties": null }, + "v1beta1.AWSElasticBlockStoreVolumeSource": { + "id": "v1beta1.AWSElasticBlockStoreVolumeSource", + "required": [ + "volumeID" + ], + "properties": { + "fsType": { + "type": "string", + "description": "file system type to mount, such as ext4, xfs, ntfs" + }, + "partition": { + "type": "integer", + "format": "int32", + "description": "partition on the disk to mount (e.g., '1' for /dev/sda1); if omitted the plain device name (e.g., /dev/sda) will be mounted" + }, + "readOnly": { + "type": "boolean", + "description": "read-only if true, read-write otherwise (false or unspecified)" + }, + "volumeID": { + "type": "string", + "description": "unique id of the PD resource in AWS" + } + } + }, "v1beta1.AccessModeType": { "id": "v1beta1.AccessModeType", "properties": {} @@ -8668,6 +8899,19 @@ } } }, + "v1beta1.PersistentVolumeClaimVolumeSource": { + "id": "v1beta1.PersistentVolumeClaimVolumeSource", + "properties": { + "claimName": { + "type": "string", + "description": "the name of the claim in the same namespace to be mounted as a volume" + }, + "readOnly": { + "type": "boolean", + "description": "mount volume as read-only when true; default false" + } + } + }, "v1beta1.PersistentVolumeList": { "id": "v1beta1.PersistentVolumeList", "properties": { @@ -8727,9 +8971,10 @@ "v1beta1.PersistentVolumeSpec": { "id": "v1beta1.PersistentVolumeSpec", "required": [ - "persistentDisk", "hostPath", - "glusterfs" + "glusterfs", + "persistentDisk", + "awsElasticBlockStore" ], "properties": { "accessModes": { @@ -8739,6 +8984,10 @@ }, "description": "all ways the volume can be mounted" }, + "awsElasticBlockStore": { + "$ref": "v1beta1.AWSElasticBlockStoreVolumeSource", + "description": "AWS disk resource provisioned by an admin" + }, "capacity": { "type": "any", "description": "a description of the persistent volume's resources and capacity" @@ -8911,63 +9160,6 @@ } } }, - "v1beta1.PodLogOptions": { - "id": "v1beta1.PodLogOptions", - "properties": { - "annotations": { - "type": "any", - "description": "map of string keys and values that can be used by external tooling to store and retrieve arbitrary metadata about the object" - }, - "apiVersion": { - "type": "string", - "description": "version of the schema the object should have" - }, - "container": { - "type": "string", - "description": "the container for which to stream logs; defaults to only container if there is one container in the pod" - }, - "creationTimestamp": { - "type": "string", - "description": "RFC 3339 date and time at which the object was created; populated by the system, read-only; null for lists" - }, - "deletionTimestamp": { - "type": "string", - "description": "RFC 3339 date and time at which the object will be deleted; populated by the system when a graceful deletion is requested, read-only; if not set, graceful deletion of the object has not been requested" - }, - "follow": { - "type": "boolean", - "description": "follow the log stream of the pod; defaults to false" - }, - "generateName": { - "type": "string", - "description": "an optional prefix to use to generate a unique name; has the same validation rules as name; optional, and is applied only name if is not specified" - }, - "id": { - "type": "string", - "description": "name of the object; must be a DNS_SUBDOMAIN and unique among all objects of the same kind within the same namespace; used in resource URLs; cannot be updated" - }, - "kind": { - "type": "string", - "description": "kind of object, in CamelCase; cannot be updated" - }, - "namespace": { - "type": "string", - "description": "namespace to which the object belongs; must be a DNS_SUBDOMAIN; 'default' by default; cannot be updated" - }, - "resourceVersion": { - "$ref": "uint64", - "description": "string that identifies the internal version of this object that can be used by clients to determine when objects have changed; populated by the system, read-only; value must be treated as opaque by clients and passed unmodified back to the server: https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#concurrency-control-and-consistency" - }, - "selfLink": { - "type": "string", - "description": "URL for the object; populated by the system, read-only" - }, - "uid": { - "type": "string", - "description": "unique UUID across space and time; populated by the system, read-only; cannot be updated" - } - } - }, "v1beta1.PodState": { "id": "v1beta1.PodState", "properties": { @@ -9812,7 +10004,7 @@ }, "source": { "$ref": "v1beta1.VolumeSource", - "description": "location and type of volume to mount; at most one of HostDir, EmptyDir, GCEPersistentDisk, or GitRepo; default is EmptyDir" + "description": "location and type of volume to mount; at most one of HostDir, EmptyDir, GCEPersistentDisk, AWSElasticBlockStore, or GitRepo; default is EmptyDir" } } }, @@ -9851,6 +10043,7 @@ "hostDir", "emptyDir", "persistentDisk", + "awsElasticBlockStore", "gitRepo", "secret", "nfs", @@ -9858,6 +10051,10 @@ "glusterfs" ], "properties": { + "awsElasticBlockStore": { + "$ref": "v1beta1.AWSElasticBlockStoreVolumeSource", + "description": "AWS disk resource attached to the host machine on demand" + }, "emptyDir": { "$ref": "v1beta1.EmptyDirVolumeSource", "description": "temporary directory that shares a pod's lifetime" @@ -9886,6 +10083,10 @@ "$ref": "v1beta1.GCEPersistentDiskVolumeSource", "description": "GCE disk resource attached to the host machine on demand" }, + "persistentVolumeClaim": { + "$ref": "v1beta1.PersistentVolumeClaimVolumeSource", + "description": "a reference to a PersistentVolumeClaim in the same namespace" + }, "secret": { "$ref": "v1beta1.SecretVolumeSource", "description": "secret to populate volume with" diff --git a/api/swagger-spec/v1beta2.json b/api/swagger-spec/v1beta2.json index 56625e84621..58a626423da 100644 --- a/api/swagger-spec/v1beta2.json +++ b/api/swagger-spec/v1beta2.json @@ -4742,21 +4742,40 @@ } ] }, + { + "path": "/api/v1beta2/pods/{name}/exec", + "description": "API at /api/v1beta2 version v1beta2", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/api/v1beta2/pods/{name}/log", "description": "API at /api/v1beta2 version v1beta2", "operations": [ { - "type": "v1beta2.PodLogOptions", + "type": "v1beta2.Pod", "method": "GET", - "summary": "read the specified PodLogOptions", - "nickname": "readPodLogOptions", + "summary": "read the specified Pod", + "nickname": "readPod", "parameters": [ { "type": "string", "paramType": "path", "name": "name", - "description": "name of the PodLogOptions", + "description": "name of the Pod", "required": true, "allowMultiple": false }, @@ -4773,7 +4792,7 @@ { "code": 200, "message": "OK", - "responseModel": "v1beta2.PodLogOptions" + "responseModel": "v1beta2.Pod" } ], "produces": [ @@ -4785,6 +4804,193 @@ } ] }, + { + "path": "/api/v1beta2/pods/{name}/portforward", + "description": "API at /api/v1beta2 version v1beta2", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta2/pods/{name}/proxy", + "description": "API at /api/v1beta2 version v1beta2", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "POST", + "summary": "connect POST requests to Pod", + "nickname": "connectPOSTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "PUT", + "summary": "connect PUT requests to Pod", + "nickname": "connectPUTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "DELETE", + "summary": "connect DELETE requests to Pod", + "nickname": "connectDELETEPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "HEAD", + "summary": "connect HEAD requests to Pod", + "nickname": "connectHEADPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "OPTIONS", + "summary": "connect OPTIONS requests to Pod", + "nickname": "connectOPTIONSPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta2/pods/{name}/proxy/{path:*}", + "description": "API at /api/v1beta2 version v1beta2", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "POST", + "summary": "connect POST requests to Pod", + "nickname": "connectPOSTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "PUT", + "summary": "connect PUT requests to Pod", + "nickname": "connectPUTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "DELETE", + "summary": "connect DELETE requests to Pod", + "nickname": "connectDELETEPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "HEAD", + "summary": "connect HEAD requests to Pod", + "nickname": "connectHEADPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "OPTIONS", + "summary": "connect OPTIONS requests to Pod", + "nickname": "connectOPTIONSPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/api/v1beta2/pods/{name}/status", "description": "API at /api/v1beta2 version v1beta2", @@ -6914,6 +7120,31 @@ "id": "", "properties": null }, + "v1beta2.AWSElasticBlockStoreVolumeSource": { + "id": "v1beta2.AWSElasticBlockStoreVolumeSource", + "required": [ + "volumeID" + ], + "properties": { + "fsType": { + "type": "string", + "description": "file system type to mount, such as ext4, xfs, ntfs" + }, + "partition": { + "type": "integer", + "format": "int32", + "description": "partition on the disk to mount (e.g., '1' for /dev/sda1); if omitted the plain device name (e.g., /dev/sda) will be mounted" + }, + "readOnly": { + "type": "boolean", + "description": "read-only if true, read-write otherwise (false or unspecified)" + }, + "volumeID": { + "type": "string", + "description": "unique id of the PD resource in AWS" + } + } + }, "v1beta2.AccessModeType": { "id": "v1beta2.AccessModeType", "properties": {} @@ -8657,6 +8888,19 @@ } } }, + "v1beta2.PersistentVolumeClaimVolumeSource": { + "id": "v1beta2.PersistentVolumeClaimVolumeSource", + "properties": { + "claimName": { + "type": "string", + "description": "the name of the claim in the same namespace to be mounted as a volume" + }, + "readOnly": { + "type": "boolean", + "description": "mount volume as read-only when true; default false" + } + } + }, "v1beta2.PersistentVolumeList": { "id": "v1beta2.PersistentVolumeList", "properties": { @@ -8717,6 +8961,7 @@ "id": "v1beta2.PersistentVolumeSpec", "required": [ "persistentDisk", + "awsElasticBlockStore", "hostPath", "glusterfs" ], @@ -8728,6 +8973,10 @@ }, "description": "all ways the volume can be mounted" }, + "awsElasticBlockStore": { + "$ref": "v1beta2.AWSElasticBlockStoreVolumeSource", + "description": "AWS disk resource provisioned by an admin" + }, "capacity": { "type": "any", "description": "a description of the persistent volume's resources and capacity" @@ -8900,63 +9149,6 @@ } } }, - "v1beta2.PodLogOptions": { - "id": "v1beta2.PodLogOptions", - "properties": { - "annotations": { - "type": "any", - "description": "map of string keys and values that can be used by external tooling to store and retrieve arbitrary metadata about the object" - }, - "apiVersion": { - "type": "string", - "description": "version of the schema the object should have" - }, - "container": { - "type": "string", - "description": "the container for which to stream logs; defaults to only container if there is one container in the pod" - }, - "creationTimestamp": { - "type": "string", - "description": "RFC 3339 date and time at which the object was created; populated by the system, read-only; null for lists" - }, - "deletionTimestamp": { - "type": "string", - "description": "RFC 3339 date and time at which the object will be deleted; populated by the system when a graceful deletion is requested, read-only; if not set, graceful deletion of the object has not been requested" - }, - "follow": { - "type": "boolean", - "description": "follow the log stream of the pod; defaults to false" - }, - "generateName": { - "type": "string", - "description": "an optional prefix to use to generate a unique name; has the same validation rules as name; optional, and is applied only name if is not specified" - }, - "id": { - "type": "string", - "description": "name of the object; must be a DNS_SUBDOMAIN and unique among all objects of the same kind within the same namespace; used in resource URLs; cannot be updated" - }, - "kind": { - "type": "string", - "description": "kind of object, in CamelCase; cannot be updated" - }, - "namespace": { - "type": "string", - "description": "namespace to which the object belongs; must be a DNS_SUBDOMAIN; 'default' by default; cannot be updated" - }, - "resourceVersion": { - "$ref": "uint64", - "description": "string that identifies the internal version of this object that can be used by clients to determine when objects have changed; populated by the system, read-only; value must be treated as opaque by clients and passed unmodified back to the server: https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#concurrency-control-and-consistency" - }, - "selfLink": { - "type": "string", - "description": "URL for the object; populated by the system, read-only" - }, - "uid": { - "type": "string", - "description": "unique UUID across space and time; populated by the system, read-only" - } - } - }, "v1beta2.PodState": { "id": "v1beta2.PodState", "properties": { @@ -9801,7 +9993,7 @@ }, "source": { "$ref": "v1beta2.VolumeSource", - "description": "location and type of volume to mount; at most one of HostDir, EmptyDir, GCEPersistentDisk, or GitRepo; default is EmptyDir" + "description": "location and type of volume to mount; at most one of HostDir, EmptyDir, GCEPersistentDisk, AWSElasticBlockStore, or GitRepo; default is EmptyDir" } } }, @@ -9832,6 +10024,7 @@ "hostDir", "emptyDir", "persistentDisk", + "awsElasticBlockStore", "gitRepo", "secret", "nfs", @@ -9839,6 +10032,10 @@ "glusterfs" ], "properties": { + "awsElasticBlockStore": { + "$ref": "v1beta2.AWSElasticBlockStoreVolumeSource", + "description": "AWS disk resource attached to the host machine on demand" + }, "emptyDir": { "$ref": "v1beta2.EmptyDirVolumeSource", "description": "temporary directory that shares a pod's lifetime" @@ -9867,6 +10064,10 @@ "$ref": "v1beta2.GCEPersistentDiskVolumeSource", "description": "GCE disk resource attached to the host machine on demand" }, + "persistentVolumeClaim": { + "$ref": "v1beta2.PersistentVolumeClaimVolumeSource", + "description": "a reference to a PersistentVolumeClaim in the same namespace" + }, "secret": { "$ref": "v1beta2.SecretVolumeSource", "description": "secret to populate volume" diff --git a/api/swagger-spec/v1beta3.json b/api/swagger-spec/v1beta3.json index b989fcf0a0c..332703f42cf 100644 --- a/api/swagger-spec/v1beta3.json +++ b/api/swagger-spec/v1beta3.json @@ -4722,21 +4722,40 @@ } ] }, + { + "path": "/api/v1beta3/namespaces/{namespaces}/pods/{name}/exec", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/api/v1beta3/namespaces/{namespaces}/pods/{name}/log", "description": "API at /api/v1beta3 version v1beta3", "operations": [ { - "type": "v1beta3.PodLogOptions", + "type": "v1beta3.Pod", "method": "GET", - "summary": "read the specified PodLogOptions", - "nickname": "readPodLogOptions", + "summary": "read the specified Pod", + "nickname": "readPod", "parameters": [ { "type": "string", "paramType": "path", "name": "name", - "description": "name of the PodLogOptions", + "description": "name of the Pod", "required": true, "allowMultiple": false }, @@ -4753,7 +4772,7 @@ { "code": 200, "message": "OK", - "responseModel": "v1beta3.PodLogOptions" + "responseModel": "v1beta3.Pod" } ], "produces": [ @@ -4765,6 +4784,193 @@ } ] }, + { + "path": "/api/v1beta3/namespaces/{namespaces}/pods/{name}/portforward", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta3/namespaces/{namespaces}/pods/{name}/proxy", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "POST", + "summary": "connect POST requests to Pod", + "nickname": "connectPOSTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "PUT", + "summary": "connect PUT requests to Pod", + "nickname": "connectPUTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "DELETE", + "summary": "connect DELETE requests to Pod", + "nickname": "connectDELETEPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "HEAD", + "summary": "connect HEAD requests to Pod", + "nickname": "connectHEADPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "OPTIONS", + "summary": "connect OPTIONS requests to Pod", + "nickname": "connectOPTIONSPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta3/namespaces/{namespaces}/pods/{name}/proxy/{path:*}", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "string", + "method": "GET", + "summary": "connect GET requests to Pod", + "nickname": "connectGETPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "POST", + "summary": "connect POST requests to Pod", + "nickname": "connectPOSTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "PUT", + "summary": "connect PUT requests to Pod", + "nickname": "connectPUTPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "DELETE", + "summary": "connect DELETE requests to Pod", + "nickname": "connectDELETEPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "HEAD", + "summary": "connect HEAD requests to Pod", + "nickname": "connectHEADPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "string", + "method": "OPTIONS", + "summary": "connect OPTIONS requests to Pod", + "nickname": "connectOPTIONSPod", + "parameters": [], + "produces": [ + "*/*" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/api/v1beta3/namespaces/{namespaces}/pods/{name}/status", "description": "API at /api/v1beta3 version v1beta3", @@ -4816,6 +5022,550 @@ } ] }, + { + "path": "/api/v1beta3/namespaces/{namespaces}/podtemplates", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "v1beta3.PodTemplateList", + "method": "GET", + "summary": "list or watch objects of kind PodTemplate", + "nickname": "listPodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "a selector to restrict the list of returned objects by their fields; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "a selector to restrict the list of returned objects by their labels; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "when specified with a watch call, shows changes that occur after that particular version of a resource; defaults to changes from the beginning of history", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "watch for changes to the described resources and return them as a stream of add, update, and remove notifications; specify resourceVersion", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta3.PodTemplateList" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta3.PodTemplate", + "method": "POST", + "summary": "create a PodTemplate", + "nickname": "createPodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "v1beta3.PodTemplate", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta3.PodTemplate" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta3/watch/namespaces/{namespaces}/podtemplates", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "json.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of PodTemplate", + "nickname": "watchPodTemplatelist", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "a selector to restrict the list of returned objects by their fields; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "a selector to restrict the list of returned objects by their labels; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "when specified with a watch call, shows changes that occur after that particular version of a resource; defaults to changes from the beginning of history", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "watch for changes to the described resources and return them as a stream of add, update, and remove notifications; specify resourceVersion", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "json.WatchEvent" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta3/namespaces/{namespaces}/podtemplates/{name}", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "v1beta3.PodTemplate", + "method": "GET", + "summary": "read the specified PodTemplate", + "nickname": "readPodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the PodTemplate", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta3.PodTemplate" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta3.PodTemplate", + "method": "PUT", + "summary": "replace the specified PodTemplate", + "nickname": "replacePodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the PodTemplate", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "v1beta3.PodTemplate", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta3.PodTemplate" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + }, + { + "type": "v1beta3.PodTemplate", + "method": "PATCH", + "summary": "partially update the specified PodTemplate", + "nickname": "patchPodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the PodTemplate", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "string" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "application/json-patch+json", + "application/merge-patch+json", + "application/strategic-merge-patch+json" + ] + }, + { + "type": "v1beta3.Status", + "method": "DELETE", + "summary": "delete a PodTemplate", + "nickname": "deletePodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the PodTemplate", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "v1beta3.DeleteOptions", + "paramType": "body", + "name": "body", + "description": "", + "required": true, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta3.Status" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta3/watch/namespaces/{namespaces}/podtemplates/{name}", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "json.WatchEvent", + "method": "GET", + "summary": "watch changes to an object of kind PodTemplate", + "nickname": "watchPodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "path", + "name": "name", + "description": "name of the PodTemplate", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "path", + "name": "namespaces", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "a selector to restrict the list of returned objects by their fields; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "a selector to restrict the list of returned objects by their labels; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "when specified with a watch call, shows changes that occur after that particular version of a resource; defaults to changes from the beginning of history", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "watch for changes to the described resources and return them as a stream of add, update, and remove notifications; specify resourceVersion", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "json.WatchEvent" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta3/podtemplates", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "v1beta3.PodTemplateList", + "method": "GET", + "summary": "list or watch objects of kind PodTemplate", + "nickname": "listPodTemplate", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "a selector to restrict the list of returned objects by their fields; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "a selector to restrict the list of returned objects by their labels; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "when specified with a watch call, shows changes that occur after that particular version of a resource; defaults to changes from the beginning of history", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "watch for changes to the described resources and return them as a stream of add, update, and remove notifications; specify resourceVersion", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "v1beta3.PodTemplateList" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + } + ] + }, + { + "path": "/api/v1beta3/watch/podtemplates", + "description": "API at /api/v1beta3 version v1beta3", + "operations": [ + { + "type": "json.WatchEvent", + "method": "GET", + "summary": "watch individual changes to a list of PodTemplate", + "nickname": "watchPodTemplatelist", + "parameters": [ + { + "type": "string", + "paramType": "query", + "name": "fieldSelector", + "description": "a selector to restrict the list of returned objects by their fields; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "labelSelector", + "description": "a selector to restrict the list of returned objects by their labels; defaults to everything", + "required": false, + "allowMultiple": false + }, + { + "type": "string", + "paramType": "query", + "name": "resourceVersion", + "description": "when specified with a watch call, shows changes that occur after that particular version of a resource; defaults to changes from the beginning of history", + "required": false, + "allowMultiple": false + }, + { + "type": "boolean", + "paramType": "query", + "name": "watch", + "description": "watch for changes to the described resources and return them as a stream of add, update, and remove notifications; specify resourceVersion", + "required": false, + "allowMultiple": false + } + ], + "responseMessages": [ + { + "code": 200, + "message": "OK", + "responseModel": "json.WatchEvent" + } + ], + "produces": [ + "application/json" + ], + "consumes": [ + "*/*" + ] + } + ] + }, { "path": "/api/v1beta3/namespaces/{namespaces}/replicationcontrollers", "description": "API at /api/v1beta3 version v1beta3", @@ -7362,6 +8112,31 @@ } } }, + "v1beta3.AWSElasticBlockStoreVolumeSource": { + "id": "v1beta3.AWSElasticBlockStoreVolumeSource", + "required": [ + "volumeID" + ], + "properties": { + "fsType": { + "type": "string", + "description": "file system type to mount, such as ext4, xfs, ntfs" + }, + "partition": { + "type": "integer", + "format": "int32", + "description": "partition on the disk to mount (e.g., '1' for /dev/sda1); if omitted the plain device name (e.g., /dev/sda) will be mounted" + }, + "readOnly": { + "type": "boolean", + "description": "read-only if true, read-write otherwise (false or unspecified)" + }, + "volumeID": { + "type": "string", + "description": "unique id of the PD resource in AWS" + } + } + }, "v1beta3.AccessModeType": { "id": "v1beta3.AccessModeType", "properties": {} @@ -8885,6 +9660,19 @@ } } }, + "v1beta3.PersistentVolumeClaimVolumeSource": { + "id": "v1beta3.PersistentVolumeClaimVolumeSource", + "properties": { + "claimName": { + "type": "string", + "description": "the name of the claim in the same namespace to be mounted as a volume" + }, + "readOnly": { + "type": "boolean", + "description": "mount volume as read-only when true; default false" + } + } + }, "v1beta3.PersistentVolumeList": { "id": "v1beta3.PersistentVolumeList", "properties": { @@ -8917,6 +9705,7 @@ "id": "v1beta3.PersistentVolumeSpec", "required": [ "gcePersistentDisk", + "awsElasticBlockStore", "hostPath", "glusterfs" ], @@ -8928,6 +9717,10 @@ }, "description": "all ways the volume can be mounted" }, + "awsElasticBlockStore": { + "$ref": "v1beta3.AWSElasticBlockStoreVolumeSource", + "description": "AWS disk resource provisioned by an admin" + }, "capacity": { "type": "any", "description": "a description of the persistent volume's resources and capacity" @@ -9068,27 +9861,6 @@ } } }, - "v1beta3.PodLogOptions": { - "id": "v1beta3.PodLogOptions", - "properties": { - "apiVersion": { - "type": "string", - "description": "version of the schema the object should have" - }, - "container": { - "type": "string", - "description": "the container for which to stream logs; defaults to only container if there is one container in the pod" - }, - "follow": { - "type": "boolean", - "description": "follow the log stream of the pod; defaults to false" - }, - "kind": { - "type": "string", - "description": "kind of object, in CamelCase; cannot be updated" - } - } - }, "v1beta3.PodSpec": { "id": "v1beta3.PodSpec", "required": [ @@ -9167,6 +9939,94 @@ } } }, + "v1beta3.PodTemplate": { + "id": "v1beta3.PodTemplate", + "properties": { + "annotations": { + "type": "any", + "description": "map of string keys and values that can be used by external tooling to store and retrieve arbitrary metadata about objects" + }, + "apiVersion": { + "type": "string", + "description": "version of the schema the object should have" + }, + "creationTimestamp": { + "type": "string", + "description": "RFC 3339 date and time at which the object was created; populated by the system, read-only; null for lists" + }, + "deletionTimestamp": { + "type": "string", + "description": "RFC 3339 date and time at which the object will be deleted; populated by the system when a graceful deletion is requested, read-only; if not set, graceful deletion of the object has not been requested" + }, + "generateName": { + "type": "string", + "description": "an optional prefix to use to generate a unique name; has the same validation rules as name; optional, and is applied only name if is not specified" + }, + "kind": { + "type": "string", + "description": "kind of object, in CamelCase; cannot be updated" + }, + "labels": { + "type": "any", + "description": "map of string keys and values that can be used to organize and categorize objects; may match selectors of replication controllers and services" + }, + "name": { + "type": "string", + "description": "string that identifies an object. Must be unique within a namespace; cannot be updated" + }, + "namespace": { + "type": "string", + "description": "namespace of the object; cannot be updated" + }, + "resourceVersion": { + "type": "string", + "description": "string that identifies the internal version of this object that can be used by clients to determine when objects have changed; populated by the system, read-only; value must be treated as opaque by clients and passed unmodified back to the server: https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#concurrency-control-and-consistency" + }, + "selfLink": { + "type": "string", + "description": "URL for the object; populated by the system, read-only" + }, + "template": { + "$ref": "v1beta3.PodTemplateSpec", + "description": "the template of the desired behavior of the pod; https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#spec-and-status" + }, + "uid": { + "type": "string", + "description": "unique UUID across space and time; populated by the system; read-only" + } + } + }, + "v1beta3.PodTemplateList": { + "id": "v1beta3.PodTemplateList", + "required": [ + "items" + ], + "properties": { + "apiVersion": { + "type": "string", + "description": "version of the schema the object should have" + }, + "items": { + "type": "array", + "items": { + "$ref": "v1beta3.PodTemplate" + }, + "description": "list of pod templates" + }, + "kind": { + "type": "string", + "description": "kind of object, in CamelCase; cannot be updated" + }, + "resourceVersion": { + "type": "string", + "description": "string that identifies the internal version of this object that can be used by clients to determine when objects have changed; populated by the system, read-only; value must be treated as opaque by clients and passed unmodified back to the server: https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#concurrency-control-and-consistency" + }, + "selfLink": { + "type": "string", + "description": "URL for the object; populated by the system, read-only" + } + } + }, "v1beta3.PodTemplateSpec": { "id": "v1beta3.PodTemplateSpec", "properties": { @@ -9348,7 +10208,7 @@ }, "selector": { "type": "any", - "description": "label keys and values that must match in order to be controlled by this replication controller" + "description": "label keys and values that must match in order to be controlled by this replication controller, if empty defaulted to labels on Pod template" }, "template": { "$ref": "v1beta3.PodTemplateSpec", @@ -9864,16 +10724,21 @@ "id": "v1beta3.Volume", "required": [ "name", - "gcePersistentDisk", - "gitRepo", + "emptyDir", "secret", - "nfs", - "iscsi", "glusterfs", "hostPath", - "emptyDir" + "awsElasticBlockStore", + "gitRepo", + "nfs", + "iscsi", + "gcePersistentDisk" ], "properties": { + "awsElasticBlockStore": { + "$ref": "v1beta3.AWSElasticBlockStoreVolumeSource", + "description": "AWS disk resource attached to the host machine on demand" + }, "emptyDir": { "$ref": "v1beta3.EmptyDirVolumeSource", "description": "temporary directory that shares a pod's lifetime" @@ -9906,6 +10771,10 @@ "$ref": "v1beta3.NFSVolumeSource", "description": "NFS volume that will be mounted in the host machine" }, + "persistentVolumeClaim": { + "$ref": "v1beta3.PersistentVolumeClaimVolumeSource", + "description": "a reference to a PersistentVolumeClaim in the same namespace" + }, "secret": { "$ref": "v1beta3.SecretVolumeSource", "description": "secret to populate volume" From 09d86b5fdb28765cb863192c6ee819828b8b07fc Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Tue, 21 Apr 2015 17:26:29 -0400 Subject: [PATCH 38/43] Add completions --- contrib/completions/bash/kubectl | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index 5bf6c2c46e6..cc061afcd4a 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -251,6 +251,7 @@ _kubectl_get() must_have_one_noun+=("persistentvolume") must_have_one_noun+=("persistentvolumeclaim") must_have_one_noun+=("pod") + must_have_one_noun+=("podtemplate") must_have_one_noun+=("replicationcontroller") must_have_one_noun+=("resourcequota") must_have_one_noun+=("secret") From e055ee0b716e87740688e8c2051b1d92401d91ef Mon Sep 17 00:00:00 2001 From: Zach Loafman Date: Wed, 22 Apr 2015 10:06:01 -0700 Subject: [PATCH 39/43] Use KUBE_SKIP_UPDATE in Jenkins After #7146, we need to protect Jenkins from the internal gcloud bug (again). --- hack/jenkins/e2e.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hack/jenkins/e2e.sh b/hack/jenkins/e2e.sh index 32970ddab04..785f6c9e69f 100755 --- a/hack/jenkins/e2e.sh +++ b/hack/jenkins/e2e.sh @@ -102,6 +102,10 @@ else exit 1 fi + # Tell kube-up.sh to skip the update, it doesn't lock. An internal + # gcloud bug can cause racing component updates to stomp on each + # other. + export KUBE_SKIP_UPDATE=y sudo flock -x -n /var/run/lock/gcloud-components.lock -c "gcloud components update -q" || true # The "ci" bucket is for builds like "v0.15.0-468-gfa648c1" From 42e1710ccf1d7f2df5ccfd1e2860f9b6e62a0e21 Mon Sep 17 00:00:00 2001 From: Zach Loafman Date: Wed, 22 Apr 2015 10:11:08 -0700 Subject: [PATCH 40/43] Fix build after #7146 --- cluster/gce/util.sh | 2 +- cluster/gke/util.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster/gce/util.sh b/cluster/gce/util.sh index 418aedca129..192627824ce 100755 --- a/cluster/gce/util.sh +++ b/cluster/gce/util.sh @@ -49,7 +49,7 @@ function verify-prereqs { fi fi done - if [[ "${KUBE_SKIP_UPDATE} == "y" ]]; then + if [[ "${KUBE_SKIP_UPDATE}" == "y" ]]; then return fi # update and install components as needed diff --git a/cluster/gke/util.sh b/cluster/gke/util.sh index 2bb7fb01273..9895701f618 100755 --- a/cluster/gke/util.sh +++ b/cluster/gke/util.sh @@ -87,7 +87,7 @@ function verify-prereqs() { exit 1 fi fi - if [[ "${KUBE_SKIP_UPDATE} == "y" ]]; then + if [[ "${KUBE_SKIP_UPDATE}" == "y" ]]; then return fi # update and install components as needed From 0e3e502d52d6e32741a3342d9ca393466df7d6a9 Mon Sep 17 00:00:00 2001 From: Zach Loafman Date: Wed, 22 Apr 2015 10:19:46 -0700 Subject: [PATCH 41/43] Fix unbound variable after #7146 --- cluster/gce/util.sh | 1 + cluster/gke/util.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/cluster/gce/util.sh b/cluster/gce/util.sh index 192627824ce..25c7d5f8a41 100755 --- a/cluster/gce/util.sh +++ b/cluster/gce/util.sh @@ -56,6 +56,7 @@ function verify-prereqs { if [[ "${KUBE_PROMPT_FOR_UPDATE}" != "y" ]]; then gcloud_prompt="-q" fi + local sudo_prefix="" if [ ! -w $(dirname `which gcloud`) ]; then sudo_prefix="sudo" fi diff --git a/cluster/gke/util.sh b/cluster/gke/util.sh index 9895701f618..754e5fe27a2 100755 --- a/cluster/gke/util.sh +++ b/cluster/gke/util.sh @@ -94,6 +94,7 @@ function verify-prereqs() { if [[ "${KUBE_PROMPT_FOR_UPDATE}" != "y" ]]; then gcloud_prompt="-q" fi + local sudo_prefix="" if [ ! -w $(dirname `which gcloud`) ]; then sudo_prefix="sudo" fi From 86468cd29d0dd9f1724815e22ea9b3e6bec835bc Mon Sep 17 00:00:00 2001 From: Zach Loafman Date: Wed, 22 Apr 2015 10:55:08 -0700 Subject: [PATCH 42/43] Revert "Added kube-proxy token." --- .../templates/create-dynamic-salt-files.sh | 9 ++------- cluster/gce/configure-vm.sh | 19 ++++++------------- cluster/gce/util.sh | 13 +++++-------- cluster/vagrant/provision-master.sh | 5 +---- 4 files changed, 14 insertions(+), 32 deletions(-) diff --git a/cluster/aws/templates/create-dynamic-salt-files.sh b/cluster/aws/templates/create-dynamic-salt-files.sh index 031834d1125..33050b3591c 100644 --- a/cluster/aws/templates/create-dynamic-salt-files.sh +++ b/cluster/aws/templates/create-dynamic-salt-files.sh @@ -40,19 +40,14 @@ mkdir -p /srv/salt-overlay/salt/nginx echo $MASTER_HTPASSWD > /srv/salt-overlay/salt/nginx/htpasswd # Generate and distribute a shared secret (bearer token) to -# apiserver and nodes so that kubelet/kube-proxy can authenticate to +# apiserver and kubelet so that kubelet can authenticate to # apiserver to send events. # This works on CoreOS, so it should work on a lot of distros. kubelet_token=$(cat /dev/urandom | base64 | tr -d "=+/" | dd bs=32 count=1 2> /dev/null) -# Same thing for kube-proxy. -kube_proxy_token=$(cat /dev/urandom | base64 | tr -d "=+/" | dd bs=32 count=1 2> /dev/null) -# Make a list of tokens and usernames to be pushed to the apiserver mkdir -p /srv/salt-overlay/salt/kube-apiserver known_tokens_file="/srv/salt-overlay/salt/kube-apiserver/known_tokens.csv" -(umask u=rw,go= ; echo "" > $known_tokens_file) -echo "$kubelet_token,kubelet,kubelet" >> $known_tokens_file ; -echo "$kube_proxy_token,kube_proxy,kube_proxy" >> $known_tokens_file +(umask u=rw,go= ; echo "$kubelet_token,kubelet,kubelet" > $known_tokens_file) mkdir -p /srv/salt-overlay/salt/kubelet kubelet_auth_file="/srv/salt-overlay/salt/kubelet/kubernetes_auth" diff --git a/cluster/gce/configure-vm.sh b/cluster/gce/configure-vm.sh index b894f842a81..7424c445b68 100644 --- a/cluster/gce/configure-vm.sh +++ b/cluster/gce/configure-vm.sh @@ -73,23 +73,17 @@ for k,v in yaml.load(sys.stdin).iteritems(): fi } -function ensure-kube-tokens() { +function ensure-kube-token() { # We bake the KUBELET_TOKEN in separately to avoid auth information # having to be re-communicated on kube-push. (Otherwise the client # has to keep the bearer token around to handle generating a valid # kube-env.) if [[ -z "${KUBELET_TOKEN:-}" ]] && [[ ! -e "${KNOWN_TOKENS_FILE}" ]]; then - until KUBELET_TOKEN=$(curl-metadata kubelet-token); do + until KUBELET_TOKEN=$(curl-metadata kube-token); do echo 'Waiting for metadata KUBELET_TOKEN...' sleep 3 done fi - if [[ -z "${KUBE_PROXY_TOKEN:-}" ]] && [[ ! -e "${KNOWN_TOKENS_FILE}" ]]; then - until KUBE_PROXY_TOKEN=$(curl-metadata kube-proxy-token); do - echo 'Waiting for metadata KUBE_PROXY_TOKEN...' - sleep 3 - done - fi } function remove-docker-artifacts() { @@ -258,7 +252,7 @@ EOF # This should only happen on cluster initialization. Uses # MASTER_HTPASSWORD to generate the nginx/htpasswd file, and the -# KUBELET_TOKEN and KUBE_PROXY_TOKEN, to generate known_tokens.csv +# KUBELET_TOKEN, plus /dev/urandom, to generate known_tokens.csv # (KNOWN_TOKENS_FILE). After the first boot and on upgrade, these # files exist on the master-pd and should never be touched again # (except perhaps an additional service account, see NB below.) @@ -272,9 +266,8 @@ function create-salt-auth() { if [ ! -e "${KNOWN_TOKENS_FILE}" ]; then mkdir -p /srv/salt-overlay/salt/kube-apiserver - (umask 077; echo "" > "${KNOWN_TOKENS_FILE}") - echo "${KUBELET_TOKEN},kubelet,kubelet" >> "${KNOWN_TOKENS_FILE}" - echo "${KUBE_PROXY_TOKEN},kube_proxy,kube_proxy" >> "${KNOWN_TOKENS_FILE}" + (umask 077; + echo "${KUBELET_TOKEN},kubelet,kubelet" > "${KNOWN_TOKENS_FILE}") mkdir -p /srv/salt-overlay/salt/kubelet kubelet_auth_file="/srv/salt-overlay/salt/kubelet/kubernetes_auth" @@ -429,7 +422,7 @@ if [[ -z "${is_push}" ]]; then ensure-install-dir set-kube-env [[ "${KUBERNETES_MASTER}" == "true" ]] && mount-master-pd - ensure-kube-tokens + ensure-kube-token create-salt-pillar create-salt-auth download-release diff --git a/cluster/gce/util.sh b/cluster/gce/util.sh index 25c7d5f8a41..33c59c46921 100755 --- a/cluster/gce/util.sh +++ b/cluster/gce/util.sh @@ -594,12 +594,11 @@ function kube-up { --zone "${ZONE}" \ --size "10GB" - # Generate a bearer token for kubelets in this cluster. We push this - # separately from the other cluster variables so that the client (this + # Generate a bearer token for this cluster. We push this separately + # from the other cluster variables so that the client (this # computer) can forget it later. This should disappear with # https://github.com/GoogleCloudPlatform/kubernetes/issues/3168 KUBELET_TOKEN=$(dd if=/dev/urandom bs=128 count=1 2>/dev/null | base64 | tr -d "=+/" | dd bs=32 count=1 2>/dev/null) - KUBE_PROXY_TOKEN=$(dd if=/dev/urandom bs=128 count=1 2>/dev/null | base64 | tr -d "=+/" | dd bs=32 count=1 2>/dev/null) # Reserve the master's IP so that it can later be transferred to another VM # without disrupting the kubelets. IPs are associated with regions, not zones, @@ -626,8 +625,7 @@ function kube-up { # Wait for last batch of jobs wait-for-jobs - add-instance-metadata "${MASTER_NAME}" "kubelet-token=${KUBELET_TOKEN}" - add-instance-metadata "${MASTER_NAME}" "kube-proxy-token=${KUBE_PROXY_TOKEN}" + add-instance-metadata "${MASTER_NAME}" "kube-token=${KUBELET_TOKEN}" echo "Creating minions." @@ -642,8 +640,7 @@ function kube-up { create-node-template "${NODE_INSTANCE_PREFIX}-template" "${scope_flags[*]}" \ "startup-script=${KUBE_ROOT}/cluster/gce/configure-vm.sh" \ "kube-env=${KUBE_TEMP}/node-kube-env.yaml" \ - "kubelet-token=${KUBELET_TOKEN}" \ - "kube-proxy-token=${KUBE_PROXY_TOKEN}" + "kube-token=${KUBELET_TOKEN}" gcloud preview managed-instance-groups --zone "${ZONE}" \ create "${NODE_INSTANCE_PREFIX}-group" \ @@ -881,7 +878,7 @@ function kube-push { # TODO(zmerlynn): Re-create instance-template with the new # node-kube-env. This isn't important until the node-ip-range issue # is solved (because that's blocking automatic dynamic nodes from - # working). The node-kube-env has to be composed with the kube*-token + # working). The node-kube-env has to be composed with the kube-token # metadata. Ideally we would have # https://github.com/GoogleCloudPlatform/kubernetes/issues/3168 # implemented before then, though, so avoiding this mess until then. diff --git a/cluster/vagrant/provision-master.sh b/cluster/vagrant/provision-master.sh index 3f587797967..0175cf70ed2 100755 --- a/cluster/vagrant/provision-master.sh +++ b/cluster/vagrant/provision-master.sh @@ -137,13 +137,10 @@ EOF known_tokens_file="/srv/salt-overlay/salt/kube-apiserver/known_tokens.csv" if [[ ! -f "${known_tokens_file}" ]]; then kubelet_token=$(cat /dev/urandom | base64 | tr -d "=+/" | dd bs=32 count=1 2> /dev/null) - kube_proxy_token=$(cat /dev/urandom | base64 | tr -d "=+/" | dd bs=32 count=1 2> /dev/null) mkdir -p /srv/salt-overlay/salt/kube-apiserver known_tokens_file="/srv/salt-overlay/salt/kube-apiserver/known_tokens.csv" - (umask u=rw,go= ; echo "" > $known_tokens_file) - echo "$kubelet_token,kubelet,kubelet" >> $known_tokens_file - echo "$kube_proxy_token,kube-proxy,kube-proxy" >> $known_tokens_file + (umask u=rw,go= ; echo "$kubelet_token,kubelet,kubelet" > $known_tokens_file) mkdir -p /srv/salt-overlay/salt/kubelet kubelet_auth_file="/srv/salt-overlay/salt/kubelet/kubernetes_auth" From f59013410a660494d08429b89d01ec33408f5b1d Mon Sep 17 00:00:00 2001 From: Yifan Gu Date: Wed, 22 Apr 2015 11:55:04 -0700 Subject: [PATCH 43/43] kubelet/container: Move Prober/HandlerRunner interface to container/helpers.go This enables us to pass them to container runtime as parameters. --- pkg/kubelet/container/helpers.go | 32 ++++++++++++++++++++++++++++++++ pkg/kubelet/handlers.go | 7 ++----- pkg/kubelet/kubelet.go | 8 ++++---- pkg/kubelet/kubelet_test.go | 12 ++++++------ pkg/kubelet/probe.go | 23 +++++++++++------------ pkg/kubelet/probe_test.go | 2 +- 6 files changed, 56 insertions(+), 28 deletions(-) create mode 100644 pkg/kubelet/container/helpers.go diff --git a/pkg/kubelet/container/helpers.go b/pkg/kubelet/container/helpers.go new file mode 100644 index 00000000000..6776339421d --- /dev/null +++ b/pkg/kubelet/container/helpers.go @@ -0,0 +1,32 @@ +/* +Copyright 2015 Google Inc. All rights reserved. + +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 container + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/probe" +) + +// HandlerRunner runs a lifecycle handler for a container. +type HandlerRunner interface { + Run(containerID string, pod *api.Pod, container *api.Container, handler *api.Handler) error +} + +// Prober checks the healthiness of a container. +type Prober interface { + Probe(pod *api.Pod, status api.PodStatus, container api.Container, containerID string, createdAt int64) (probe.Result, error) +} diff --git a/pkg/kubelet/handlers.go b/pkg/kubelet/handlers.go index 44b9a5045d4..be8fe96399d 100644 --- a/pkg/kubelet/handlers.go +++ b/pkg/kubelet/handlers.go @@ -22,15 +22,12 @@ import ( "strconv" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + kubecontainer "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/container" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/golang/glog" ) -type HandlerRunner interface { - Run(containerID string, pod *api.Pod, container *api.Container, handler *api.Handler) error -} - type handlerRunner struct { httpGetter httpGetter commandRunner dockertools.ContainerCommandRunner @@ -38,7 +35,7 @@ type handlerRunner struct { } // TODO(yifan): Merge commandRunner and containerManager once containerManager implements the ContainerCommandRunner interface. -func NewHandlerRunner(httpGetter httpGetter, commandRunner dockertools.ContainerCommandRunner, containerManager *dockertools.DockerManager) *handlerRunner { +func newHandlerRunner(httpGetter httpGetter, commandRunner dockertools.ContainerCommandRunner, containerManager *dockertools.DockerManager) kubecontainer.HandlerRunner { return &handlerRunner{ httpGetter: httpGetter, commandRunner: commandRunner, diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 63f169f5f51..fd061339ece 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -232,8 +232,8 @@ func NewMainKubelet( } klet.podManager = newBasicPodManager(klet.kubeClient) - klet.prober = NewProber(klet.runner, klet.readinessManager, klet.containerRefManager, klet.recorder) - klet.handlerRunner = NewHandlerRunner(klet.httpClient, klet.runner, klet.containerManager) + klet.prober = newProber(klet.runner, klet.readinessManager, klet.containerRefManager, klet.recorder) + klet.handlerRunner = newHandlerRunner(klet.httpClient, klet.runner, klet.containerManager) runtimeCache, err := kubecontainer.NewRuntimeCache(containerManager) if err != nil { @@ -317,10 +317,10 @@ type Kubelet struct { networkPlugin network.NetworkPlugin // Healthy check prober. - prober *Prober + prober kubecontainer.Prober // Container lifecycle handler runner. - handlerRunner HandlerRunner + handlerRunner kubecontainer.HandlerRunner // Container readiness state manager. readinessManager *kubecontainer.ReadinessManager diff --git a/pkg/kubelet/kubelet_test.go b/pkg/kubelet/kubelet_test.go index eaa98870f61..899bad7971d 100644 --- a/pkg/kubelet/kubelet_test.go +++ b/pkg/kubelet/kubelet_test.go @@ -113,8 +113,8 @@ func newTestKubelet(t *testing.T) *TestKubelet { }, fakeRecorder) kubelet.containerManager.Puller = &dockertools.FakeDockerPuller{} - kubelet.prober = NewProber(nil, kubelet.readinessManager, kubelet.containerRefManager, kubelet.recorder) - kubelet.handlerRunner = NewHandlerRunner(&fakeHTTP{}, &fakeContainerCommandRunner{}, kubelet.containerManager) + kubelet.prober = newProber(nil, kubelet.readinessManager, kubelet.containerRefManager, kubelet.recorder) + kubelet.handlerRunner = newHandlerRunner(&fakeHTTP{}, &fakeContainerCommandRunner{}, kubelet.containerManager) return &TestKubelet{kubelet, fakeDocker, mockCadvisor, fakeKubeClient, waitGroup, fakeMirrorClient} } @@ -768,7 +768,7 @@ func TestSyncPodsWithPodInfraCreatesContainerCallsHandler(t *testing.T) { waitGroup := testKubelet.waitGroup fakeHttp := fakeHTTP{} kubelet.httpClient = &fakeHttp - kubelet.handlerRunner = NewHandlerRunner(kubelet.httpClient, &fakeContainerCommandRunner{}, kubelet.containerManager) + kubelet.handlerRunner = newHandlerRunner(kubelet.httpClient, &fakeContainerCommandRunner{}, kubelet.containerManager) pods := []*api.Pod{ { ObjectMeta: api.ObjectMeta{ @@ -1690,7 +1690,7 @@ func TestRunHandlerExec(t *testing.T) { kubelet := testKubelet.kubelet fakeDocker := testKubelet.fakeDocker kubelet.runner = &fakeCommandRunner - kubelet.handlerRunner = NewHandlerRunner(&fakeHTTP{}, kubelet.runner, kubelet.containerManager) + kubelet.handlerRunner = newHandlerRunner(&fakeHTTP{}, kubelet.runner, kubelet.containerManager) containerID := "abc1234" podName := "podFoo" @@ -1745,7 +1745,7 @@ func TestRunHandlerHttp(t *testing.T) { testKubelet := newTestKubelet(t) kubelet := testKubelet.kubelet kubelet.httpClient = &fakeHttp - kubelet.handlerRunner = NewHandlerRunner(kubelet.httpClient, &fakeContainerCommandRunner{}, kubelet.containerManager) + kubelet.handlerRunner = newHandlerRunner(kubelet.httpClient, &fakeContainerCommandRunner{}, kubelet.containerManager) containerID := "abc1234" podName := "podFoo" @@ -1813,7 +1813,7 @@ func TestSyncPodEventHandlerFails(t *testing.T) { kubelet.httpClient = &fakeHTTP{ err: fmt.Errorf("test error"), } - kubelet.handlerRunner = NewHandlerRunner(kubelet.httpClient, &fakeContainerCommandRunner{}, kubelet.containerManager) + kubelet.handlerRunner = newHandlerRunner(kubelet.httpClient, &fakeContainerCommandRunner{}, kubelet.containerManager) pods := []*api.Pod{ { diff --git a/pkg/kubelet/probe.go b/pkg/kubelet/probe.go index cc8ac163dbb..7c3bf235128 100644 --- a/pkg/kubelet/probe.go +++ b/pkg/kubelet/probe.go @@ -37,9 +37,8 @@ import ( const maxProbeRetries = 3 -// Prober helps to check the liveness/readiness of a container. -// TODO(yifan): Replace the concrete type with interface later. -type Prober struct { +// prober helps to check the liveness/readiness of a container. +type prober struct { exec execprobe.ExecProber http httprobe.HTTPProber tcp tcprobe.TCPProber @@ -52,13 +51,13 @@ type Prober struct { // NewProber creates a Prober, it takes a command runner and // several container info managers. -func NewProber( +func newProber( runner dockertools.ContainerCommandRunner, readinessManager *kubecontainer.ReadinessManager, refManager *kubecontainer.RefManager, - recorder record.EventRecorder) *Prober { + recorder record.EventRecorder) kubecontainer.Prober { - return &Prober{ + return &prober{ exec: execprobe.New(), http: httprobe.New(), tcp: tcprobe.New(), @@ -73,7 +72,7 @@ func NewProber( // Probe checks the liveness/readiness of the given container. // If the container's liveness probe is unsuccessful, set readiness to false. // If liveness is successful, do a readiness check and set readiness accordingly. -func (pb *Prober) Probe(pod *api.Pod, status api.PodStatus, container api.Container, containerID string, createdAt int64) (probe.Result, error) { +func (pb *prober) Probe(pod *api.Pod, status api.PodStatus, container api.Container, containerID string, createdAt int64) (probe.Result, error) { // Probe liveness. live, err := pb.probeLiveness(pod, status, container, containerID, createdAt) if err != nil { @@ -113,7 +112,7 @@ func (pb *Prober) Probe(pod *api.Pod, status api.PodStatus, container api.Contai // probeLiveness probes the liveness of a container. // If the initalDelay since container creation on liveness probe has not passed the probe will return probe.Success. -func (pb *Prober) probeLiveness(pod *api.Pod, status api.PodStatus, container api.Container, containerID string, createdAt int64) (probe.Result, error) { +func (pb *prober) probeLiveness(pod *api.Pod, status api.PodStatus, container api.Container, containerID string, createdAt int64) (probe.Result, error) { p := container.LivenessProbe if p == nil { return probe.Success, nil @@ -126,7 +125,7 @@ func (pb *Prober) probeLiveness(pod *api.Pod, status api.PodStatus, container ap // probeReadiness probes the readiness of a container. // If the initial delay on the readiness probe has not passed the probe will return probe.Failure. -func (pb *Prober) probeReadiness(pod *api.Pod, status api.PodStatus, container api.Container, containerID string, createdAt int64) (probe.Result, error) { +func (pb *prober) probeReadiness(pod *api.Pod, status api.PodStatus, container api.Container, containerID string, createdAt int64) (probe.Result, error) { p := container.ReadinessProbe if p == nil { return probe.Success, nil @@ -139,7 +138,7 @@ func (pb *Prober) probeReadiness(pod *api.Pod, status api.PodStatus, container a // runProbeWithRetries tries to probe the container in a finite loop, it returns the last result // if it never succeeds. -func (pb *Prober) runProbeWithRetries(p *api.Probe, pod *api.Pod, status api.PodStatus, container api.Container, containerID string, retires int) (probe.Result, error) { +func (pb *prober) runProbeWithRetries(p *api.Probe, pod *api.Pod, status api.PodStatus, container api.Container, containerID string, retires int) (probe.Result, error) { var err error var result probe.Result for i := 0; i < retires; i++ { @@ -151,7 +150,7 @@ func (pb *Prober) runProbeWithRetries(p *api.Probe, pod *api.Pod, status api.Pod return result, err } -func (pb *Prober) runProbe(p *api.Probe, pod *api.Pod, status api.PodStatus, container api.Container, containerID string) (probe.Result, error) { +func (pb *prober) runProbe(p *api.Probe, pod *api.Pod, status api.PodStatus, container api.Container, containerID string) (probe.Result, error) { timeout := time.Duration(p.TimeoutSeconds) * time.Second if p.Exec != nil { glog.V(4).Infof("Exec-Probe Pod: %v, Container: %v", pod, container) @@ -228,7 +227,7 @@ type execInContainer struct { run func() ([]byte, error) } -func (p *Prober) newExecInContainer(pod *api.Pod, container api.Container, containerID string) exec.Cmd { +func (p *prober) newExecInContainer(pod *api.Pod, container api.Container, containerID string) exec.Cmd { return execInContainer{func() ([]byte, error) { return p.runner.RunInContainer(containerID, container.LivenessProbe.Exec.Command) }} diff --git a/pkg/kubelet/probe_test.go b/pkg/kubelet/probe_test.go index b87878cb391..c0f3fe47763 100644 --- a/pkg/kubelet/probe_test.go +++ b/pkg/kubelet/probe_test.go @@ -152,7 +152,7 @@ func makeTestKubelet(result probe.Result, err error) *Kubelet { containerRefManager: kubecontainer.NewRefManager(), } - kl.prober = &Prober{ + kl.prober = &prober{ exec: fakeExecProber{ result: result, err: err,