Merge pull request #1142 from jbeda/tweak-demo

Improve update demo, support rolling template update
This commit is contained in:
Daniel Smith 2014-09-05 10:07:58 -07:00
commit ee1c0838d8
35 changed files with 541 additions and 205 deletions

View File

@ -17,8 +17,8 @@
source $(dirname $0)/kube-env.sh
source $(dirname $0)/$KUBERNETES_PROVIDER/util.sh
CLOUDCFG=$(dirname $0)/../_output/go/bin/kubecfg
if [ ! -x $CLOUDCFG ]; then
KUBECFG=$(dirname $0)/../_output/go/bin/kubecfg
if [ ! -x $KUBECFG ]; then
echo "Could not find kubecfg binary. Run hack/build-go.sh to build it."
exit 1
fi
@ -40,4 +40,4 @@ if [ "$KUBE_MASTER_IP" != "" ] && [ "$KUBERNETES_MASTER" == "" ]; then
export KUBERNETES_MASTER=https://${KUBE_MASTER_IP}
fi
$CLOUDCFG $AUTH_CONFIG "$@"
$KUBECFG $AUTH_CONFIG "$@"

View File

@ -55,6 +55,7 @@ var (
www = flag.String("www", "", "If -proxy is true, use this directory to serve static files")
templateFile = flag.String("template_file", "", "If present, load this file as a golang template and use it for output printing")
templateStr = flag.String("template", "", "If present, parse this string as a golang template and use it for output printing")
imageName = flag.String("image", "", "Image used when updating a replicationController. Will apply to the first container in the pod template.")
)
var parser = kubecfg.NewParser(map[string]interface{}{
@ -65,17 +66,24 @@ var parser = kubecfg.NewParser(map[string]interface{}{
})
func usage() {
fmt.Fprintf(os.Stderr, `usage: kubecfg -h [-c config/file.json] [-p :,..., :] <method>
fmt.Fprintf(os.Stderr, `Usage: kubecfg -h [-c config/file.json] <method>
Kubernetes REST API:
Kubernetes REST API:
kubecfg [OPTIONS] get|list|create|delete|update <%s>[/<id>]
Manage replication controllers:
kubecfg [OPTIONS] stop|rm|rollingupdate <controller>
kubecfg [OPTIONS] run <image> <replicas> <controller>
Manage replication controllers:
kubecfg [OPTIONS] stop|rm <controller>
kubecfg [OPTIONS] [-u <time>] [-image <image>] rollingupdate <controller>
kubecfg [OPTIONS] resize <controller> <replicas>
Options:
Launch a simple ReplicationController with a single container based
on the given image:
kubecfg [OPTIONS] [-p <port spec>] run <image> <replicas> <controller>
Options:
`, prettyWireStorage())
flag.PrintDefaults()
@ -337,7 +345,7 @@ func executeControllerRequest(method string, c *client.Client) bool {
case "rm":
err = kubecfg.DeleteController(parseController(), c)
case "rollingupdate":
err = kubecfg.Update(parseController(), c, *updatePeriod)
err = kubecfg.Update(parseController(), c, *updatePeriod, *imageName)
case "run":
if len(flag.Args()) != 4 {
glog.Fatal("usage: kubecfg [OPTIONS] run <image> <replicas> <controller>")

View File

@ -81,6 +81,7 @@ The pod that you created in Step One has the label `name=redis-master`. The sele
"kind": "Service",
"apiVersion": "v1beta1",
"port": 10000,
"containerPort": 6379,
"selector": {
"name": "redis-master"
}
@ -169,6 +170,7 @@ Just like the master, we want to have a service to proxy connections to the read
"kind": "Service",
"apiVersion": "v1beta1",
"port": 10001,
"containerPort": 6379,
"labels": {
"name": "redisslave"
},

View File

@ -3,6 +3,7 @@
"kind": "Service",
"apiVersion": "v1beta1",
"port": 9998,
"containerPort": 80,
"selector": {
"name": "frontend"
}

View File

@ -3,6 +3,7 @@
"kind": "Service",
"apiVersion": "v1beta1",
"port": 10000,
"containerPort": 6379,
"selector": {
"name": "redis-master"
}

View File

@ -3,6 +3,7 @@
"kind": "Service",
"apiVersion": "v1beta1",
"port": 10001,
"containerPort": 6379,
"labels": {
"name": "redisslave"
},

View File

@ -0,0 +1,22 @@
#!/bin/bash
# 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.
echo "Running local proxy to Kubernetes API Server. Run this in a "
echo "separate terminal or run it in the background."
echo
echo " http://localhost:8001/static/"
echo
../../cluster/kubecfg.sh -proxy -www local/

View File

@ -0,0 +1,27 @@
#!/bin/bash
# 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.
if [ -z "$DOCKER_HUB_USER" ] ; then
echo "Please set DOCKER_HUB_USER to your Docker hub account"
exit 1
fi
export KUBE_REPO_ROOT=${KUBE_REPO_ROOT-$(dirname $0)/../..}
export KUBECFG=${KUBECFG-$KUBE_REPO_ROOT/cluster/kubecfg.sh}
set -x
$KUBECFG -p 8080:80 run $DOCKER_HUB_USER/update-demo:nautilus 2 update-demo

24
examples/update-demo/2-scale.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
# 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.
NEW_SIZE=${1:-4}
export KUBE_REPO_ROOT=${KUBE_REPO_ROOT-$(dirname $0)/../..}
export KUBECFG=${KUBECFG-$KUBE_REPO_ROOT/cluster/kubecfg.sh}
set -x
$KUBECFG resize update-demo $NEW_SIZE

View File

@ -0,0 +1,29 @@
#!/bin/bash
# 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.
if [ -z "$DOCKER_HUB_USER" ] ; then
echo "Please set DOCKER_HUB_USER to your Docker hub account"
exit 1
fi
NEW_IMAGE=${1:-kitten}
TIMING=${2:-10s}
export KUBE_REPO_ROOT=${KUBE_REPO_ROOT-$(dirname $0)/../..}
export KUBECFG=${KUBECFG-$KUBE_REPO_ROOT/cluster/kubecfg.sh}
set -x
$KUBECFG -image $DOCKER_HUB_USER/update-demo:$NEW_IMAGE -u $TIMING rollingupdate update-demo

23
examples/update-demo/4-down.sh Executable file
View File

@ -0,0 +1,23 @@
#!/bin/bash
# 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.
export KUBE_REPO_ROOT=${KUBE_REPO_ROOT-$(dirname $0)/../..}
export KUBECFG=${KUBECFG-$KUBE_REPO_ROOT/cluster/kubecfg.sh}
set -x
$KUBECFG stop update-demo
$KUBECFG rm update-demo

View File

@ -27,7 +27,7 @@ This example assumes that you have forked the repository and [turned up a Kubern
This example also assumes that you have [Docker](http://docker.io) installed on your local machine.
It also assumes that ```$DOCKER_USER``` is set to your docker user id.
It also assumes that `$DOCKER_HUB_USER` is set to your Docker user id. We use this to upload the docker images that are used in the demo.
You may need to open the firewall for port 8080 using the [console][cloud-console] or the `gcutil` tool. The following command will allow traffic from any source to instances tagged `kubernetes-minion`:
@ -37,57 +37,66 @@ $ gcutil addfirewall --allowed=tcp:8080 --target_tags=kubernetes-minion kubernet
### Step One: Build the image
$ cd kubernetes/examples/update-demo/image
$ docker build -t $DOCKER_USER/data .
$ docker push $DOCKER_USER/data
```shell
$ cd kubernetes/examples/update-demo
$ ./build-images.sh
```
### Step Two: Run the controller
Now we will turn up two replicas of that image. They all serve on port 8080, mapped to internal port 80
### Step Two: Turn up the UX for the demo
$ cd kubernetes
$ cluster/kubecfg.sh -p 8080:80 run $DOCKER_USER/data 2 dataController
```shell
$ ./0-run-web-proxy.sh &
```
### Step Three: Turn up the UX for the demo
In a different terminal:
This can sometimes spew to the output so you could also run it in a different terminal.
$ cd kubernetes
$ cluster/kubecfg.sh -proxy -www examples/update-demo/local/
Now visit the the [demo website](http://localhost:8001/static). You won't see anything much quite yet.
Now visit the the [demo website](http://localhost:8001/static/index.html). You should see two light blue squares with pod IDs and ip addresses.
### Step Three: Run the controller
Now we will turn up two replicas of an image. They all serve on port 8080, mapped to internal port 80
```shell
$ ./1-create-replication-controller.sh
```
After these pull the image (which may take a minute or so) you'll see a couple of squares in the UI detailing the pods that are running along with the image that they are serving up. A cute little nautilus.
### Step Four: Try resizing the controller
Now we will increase the number of replicas from two to four:
$ cd kubernetes
$ cluster/kubecfg.sh resize dataController 4
```shell
$ ./2-scale.sh
```
If you go back to the [demo website](http://localhost:8001/static/index.html) you should eventually see four boxes, one for each pod.
### Step Five: Update the docker image
We will now update the docker image to serve a different color.
We will now update the docker image to serve a different image by doing a rolling update to a new Docker image.
$ cd kubernetes/examples/update-demo/image
$ ${EDITOR} data.json
Edit the ```color``` value so that it is a new color. For example:
```js
{
"color": "#F00"
}
```shell
$ ./3-rolling-update
```
Will set the color to red.
The rollingUpdate command in kubecfg will do 2 things:
Once you are happy with the color, build a new image:
1. Update the template in the replication controller to the new image (`$DOCKER_HUB_USER/update-demo:kitten`)
2. Kill each of the pods one by one. It'll let the replication controller create new pods to replace those that were killed.
$ docker build -t $DOCKER_USER/data .
$ docker push $DOCKER_USER/data
Watch the UX, it will update one pod every 10 seconds until all of the pods have the new image.
### Step Six: Roll the update out to your servers
We will now update the servers that are running out in your cluster.
### Step Five: Bring down the pods
$ cd kubernetes
$ cluster/kubecfg.sh -u=30s rollingupdate dataController
```shell
$ ./4-down.sh
```
Watch the UX, it will update one pod every 30 seconds until all of the pods have the new color.
This will first 'stop' the replication controller by turning the target number of replicas to 0. It'll then delete that controller.
[cloud-console]: https://console.developer.google.com
### Image Copyright
Note that he images included here are public domain.
* [kitten](http://commons.wikimedia.org/wiki/File:Kitten-stare.jpg)
* [nautilus](http://commons.wikimedia.org/wiki/File:Nautilus_pompilius.jpg)

View File

@ -1,7 +0,0 @@
FROM dockerfile/nginx
ADD data.json /usr/share/nginx/html/data.json
ADD default /etc/nginx/sites-available/default
RUN chmod a+r /usr/share/nginx/html/data.json
CMD ["nginx"]

View File

@ -1,3 +0,0 @@
{
"color": "#0FF"
}

View File

@ -1,23 +0,0 @@
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /usr/share/nginx/html;
index index.html index.htm;
# Make site accessible from http://localhost/
server_name localhost;
location / {
try_files $uri $uri/ =404;
expires 0;
add_header Cache-Control private;
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
}
}

View File

@ -0,0 +1,22 @@
# 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.
FROM dockerfile/nginx
ADD default /etc/nginx/sites-available/default
ONBUILD ADD html /usr/share/nginx/html
ONBUILD RUN chmod -R a+r /usr/share/nginx/html
CMD ["nginx"]

View File

@ -0,0 +1,37 @@
# 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.
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /usr/share/nginx/html;
index index.html index.htm;
# Make site accessible from http://localhost/
server_name localhost;
location / {
try_files $uri $uri/ =404;
expires 0;
add_header Cache-Control private;
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
}
}

View File

@ -0,0 +1,32 @@
#!/bin/bash
# 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.
# This script will build and push the images necessary for the demo.
if (( $# != 1 )); then
echo "Usage: $0 <docker hub user name>"
exit 1
fi
DOCKER_USER=$1
set -x
docker build -t update-demo-base base
docker build -t $DOCKER_USER/update-demo:kitten kitten
docker build -t $DOCKER_USER/update-demo:nautilus nautilus
docker push $DOCKER_USER/update-demo

View File

@ -0,0 +1,15 @@
# 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.
FROM update-demo-base

View File

@ -0,0 +1,3 @@
{
"image": "kitten.jpg"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,15 @@
# 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.
FROM update-demo-base

View File

@ -0,0 +1,3 @@
{
"image": "nautilus.jpg"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -13,17 +13,23 @@ 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.
-->
<html ng-app>
<head>
<script src="angular.min.js"></script>
<script src="script.js"></script>
<link rel="stylesheet" href="style.css"></link>
<script src="angular.min.js"></script>
<script src="script.js"></script>
<link rel="stylesheet" href="style.css"></link>
</head>
<body ng-controller="ButtonsCtrl">
<div ng-repeat="server in servers" class="server" style="background: #FFF">
<b>{{server.id}}</b>
<div class="img" style="background: {{server.color}};"> </div>
<a href="http://{{server.ip}}:8080/data.json">{{server.ip}}</a>
</div>
<div ng-repeat="server in servers" class="pod">
<img src="http://{{server.ip}}:8080/{{server.image}}" height="100px" width="100px" />
<b>ID:</b> {{server.id}}<br>
<b>Host:</b> <a href="http://{{server.ip}}:8080/data.json">{{server.host}}</a><br>
<b>Image:</b> {{server.dockerImage}}<br>
<b>Labels:</b>
<ul>
<li ng-repeat="(key,value) in server.labels">{{key}}={{value}}</li>
</ul>
</div>
</body>
</html>

View File

@ -16,72 +16,77 @@ limitations under the License.
var base = "http://localhost:8001/api/v1beta1/";
var updateColor = function($http, server) {
$http.get("http://" + server.ip + ":8080/data.json")
var updateImage = function($http, server) {
$http.get("http://" + server.ip + ":8080/data.json")
.success(function(data) {
server.color = data.color;
console.log(data);
})
server.image = data.image;
console.log(data);
})
.error(function(data) {
server.color = "#000";
console.log(data);
});
server.image = ""
console.log(data);
});
};
var updateServer = function($http, server) {
$http.get(base + "pods/" + server.id)
.success(function(data) {
console.log(data);
server.ip = data.currentState.hostIP;
updateColor($http, server);
})
.error(function(data) {
console.log(data);
});
$http.get(base + "pods/" + server.id)
.success(function(data) {
console.log(data);
server.ip = data.currentState.hostIP;
server.labels = data.labels;
server.host = data.currentState.host.split('.')[0]
server.dockerImage = data.currentState.info["update-demo-container"].Config.Image
updateImage($http, server);
})
.error(function(data) {
console.log(data);
});
};
var updateData = function($scope, $http) {
var servers = $scope.servers
for (var i = 0; i < servers.length; ++i) {
var server = servers[i];
updateServer($http, server);
}
var servers = $scope.servers
for (var i = 0; i < servers.length; ++i) {
var server = servers[i];
updateServer($http, server);
}
};
var ButtonsCtrl = function ($scope, $http, $interval) {
$scope.servers = [];
update($scope, $http);
$interval(angular.bind({}, update, $scope, $http), 2000);
$scope.servers = [];
update($scope, $http);
$interval(angular.bind({}, update, $scope, $http), 2000);
};
var getServer = function($scope, id) {
var servers = $scope.servers;
for (var i = 0; i < servers.length; ++i) {
if (servers[i].id == id) {
return servers[i];
}
var servers = $scope.servers;
for (var i = 0; i < servers.length; ++i) {
if (servers[i].id == id) {
return servers[i];
}
return null;
}
return null;
};
var update = function($scope, $http) {
if (!$http) {
console.log("No HTTP!");
return;
}
$http.get(base + "pods")
.success(function(data) {
console.log(data);
var newServers = [];
for (var i = 0; i < data.items.length; ++i) {
var server = getServer($scope, data.items[i].id);
if (server == null) {
server = { "id": data.items[i].id };
}
newServers.push(server);
}
$scope.servers = newServers;
updateData($scope, $http)
if (!$http) {
console.log("No HTTP!");
return;
}
$http.get(base + "pods")
.success(function(data) {
console.log(data);
var newServers = [];
for (var i = 0; i < data.items.length; ++i) {
var server = getServer($scope, data.items[i].id);
if (server == null) {
server = { "id": data.items[i].id };
}
newServers.push(server);
}
$scope.servers = newServers;
updateData($scope, $http)
})
.error(function(data) {
console.log("ERROR: " + data);
})
.error(function(data) { console.log("ERROR: " + data); })
};

View File

@ -14,19 +14,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.img {
img {
height: 100px;
width: 100px;
background-size: 100px 100px;
margin-left: 25px;
float: right;
background-size: 100px 100px;
background-color: black;
margin-left: 10px;
border: none;
}
.server {
ul {
margin-top: 0;
margin-bottom: 0;
}
.pod {
font-family: Roboto, Open Sans, arial;
width: 150px;
height: 150px;
border: 1px solid black;
border-radius: 5px;
padding: 10px;
margin: 10px;
display: inline-block;
padding: 3px;
background-color: #D1D1D1;
}

View File

@ -24,7 +24,7 @@ source "${KUBE_REPO_ROOT}/cluster/kube-env.sh"
source "${KUBE_REPO_ROOT}/cluster/$KUBERNETES_PROVIDER/util.sh"
# Launch a container
$CLOUDCFG -p 8080:80 run dockerfile/nginx 2 myNginx
$KUBECFG -p 8080:80 run dockerfile/nginx 2 myNginx
function remove-quotes() {
local in=$1
@ -35,13 +35,13 @@ function remove-quotes() {
function teardown() {
echo "Cleaning up test artifacts"
$CLOUDCFG stop myNginx
$CLOUDCFG rm myNginx
$KUBECFG stop myNginx
$KUBECFG rm myNginx
}
trap "teardown" EXIT
POD_ID_LIST=$($CLOUDCFG '-template={{range.Items}}{{.ID}} {{end}}' -l name=myNginx list pods)
POD_ID_LIST=$($KUBECFG '-template={{range.Items}}{{.ID}} {{end}}' -l name=myNginx list pods)
# Container turn up on a clean cluster can take a while for the docker image pull.
ALL_RUNNING=0
while [ $ALL_RUNNING -ne 1 ]; do
@ -49,7 +49,7 @@ while [ $ALL_RUNNING -ne 1 ]; do
sleep 5
ALL_RUNNING=1
for id in $POD_ID_LIST; do
CURRENT_STATUS=$($CLOUDCFG -template '{{and .CurrentState.Info.mynginx.State.Running .CurrentState.Info.net.State.Running}}' get pods/$id)
CURRENT_STATUS=$($KUBECFG -template '{{and .CurrentState.Info.mynginx.State.Running .CurrentState.Info.net.State.Running}}' get pods/$id)
if [ "$CURRENT_STATUS" != "true" ]; then
ALL_RUNNING=0
fi

View File

@ -26,23 +26,23 @@ source "${KUBE_REPO_ROOT}/cluster/$KUBERNETES_PROVIDER/util.sh"
GUESTBOOK="${KUBE_REPO_ROOT}/examples/guestbook"
# Launch the guestbook example
$CLOUDCFG -c "${GUESTBOOK}/redis-master.json" create /pods
$CLOUDCFG -c "${GUESTBOOK}/redis-master-service.json" create /services
$CLOUDCFG -c "${GUESTBOOK}/redis-slave-controller.json" create /replicationControllers
$KUBECFG -c "${GUESTBOOK}/redis-master.json" create /pods
$KUBECFG -c "${GUESTBOOK}/redis-master-service.json" create /services
$KUBECFG -c "${GUESTBOOK}/redis-slave-controller.json" create /replicationControllers
sleep 5
POD_LIST_1=$($CLOUDCFG '-template={{range.Items}}{{.ID}} {{end}}' list pods)
POD_LIST_1=$($KUBECFG '-template={{range.Items}}{{.ID}} {{end}}' list pods)
echo "Pods running: ${POD_LIST_1}"
$CLOUDCFG stop redisSlaveController
$KUBECFG stop redisSlaveController
# Needed until issue #103 gets fixed
sleep 25
$CLOUDCFG rm redisSlaveController
$CLOUDCFG delete services/redismaster
$CLOUDCFG delete pods/redis-master-2
$KUBECFG rm redisSlaveController
$KUBECFG delete services/redismaster
$KUBECFG delete pods/redis-master-2
POD_LIST_2=$($CLOUDCFG '-template={{range.Items}}{{.ID}} {{end}}' list pods)
POD_LIST_2=$($KUBECFG '-template={{range.Items}}{{.ID}} {{end}}' list pods)
echo "Pods running after shutdown: ${POD_LIST_2}"
exit 0

View File

@ -16,65 +16,72 @@
# Launches an nginx container and verifies it can be reached. Assumes that
# we're being called by hack/e2e-test.sh (we use some env vars it sets up).
# Exit on error
set -e
set -o errexit
set -o nounset
set -o pipefail
set -x
source "${KUBE_REPO_ROOT}/cluster/kube-env.sh"
source "${KUBE_REPO_ROOT}/cluster/$KUBERNETES_PROVIDER/util.sh"
function validate() {
POD_ID_LIST=$($CLOUDCFG '-template={{range.Items}}{{.ID}} {{end}}' -l name=$controller list pods)
# Container turn up on a clean cluster can take a while for the docker image pull.
ALL_RUNNING=0
while [ $ALL_RUNNING -ne 1 ]; do
echo "Waiting for all containers in pod to come up."
sleep 5
ALL_RUNNING=1
for id in $POD_ID_LIST; do
CURRENT_STATUS=$($CLOUDCFG -template '{{and .CurrentState.Info.datacontroller.State.Running .CurrentState.Info.net.State.Running}}' get pods/$id)
if [ "$CURRENT_STATUS" != "true" ]; then
ALL_RUNNING=0
fi
done
done
ids=($POD_ID_LIST)
if [ ${#ids[@]} -ne $1 ]; then
echo "Unexpected number of pods: ${#ids[@]}. Expected $1"
exit 1
fi
CONTROLLER_NAME=update-demo
function validate() {
NUM_REPLICAS=$1
CONTAINER_IMAGE_VERSION=$2
POD_ID_LIST=$($KUBECFG '-template={{range.Items}}{{.ID}} {{end}}' -l replicationController=${CONTROLLER_NAME} list pods)
# Container turn up on a clean cluster can take a while for the docker image pull.
ALL_RUNNING=0
while [ $ALL_RUNNING -ne 1 ]; do
echo "Waiting for all containers in pod to come up."
sleep 5
ALL_RUNNING=1
for id in $POD_ID_LIST; do
TEMPLATE_STRING="{{and ((index .CurrentState.Info \"${CONTROLLER_NAME}\").State.Running) .CurrentState.Info.net.State.Running}}"
CURRENT_STATUS=$($KUBECFG -template "${TEMPLATE_STRING}" get pods/$id)
if [ "$CURRENT_STATUS" != "true" ]; then
ALL_RUNNING=0
fi
CURRENT_IMAGE=$($KUBECFG -template "{{(index .CurrentState.Info \"${CONTROLLER_NAME}\").Config.Image}}" get pods/$id)
if [ "$CURRENT_IMAGE" != "${DOCKER_HUB_USER}/update-demo:${CONTAINER_IMAGE_VERSION}" ]; then
ALL_RUNNING=0
fi
done
done
ids=($POD_ID_LIST)
if [ ${#ids[@]} -ne $NUM_REPLICAS ]; then
echo "Unexpected number of pods: ${#ids[@]}. Expected $NUM_REPLICAS"
exit 1
fi
}
controller=dataController
DOCKER_HUB_USER=jbeda
# Launch a container
$CLOUDCFG -p 8080:80 run brendanburns/data 2 $controller
${KUBE_REPO_ROOT}/examples/update-demo/1-create-replication-controller.sh
function teardown() {
echo "Cleaning up test artifacts"
$CLOUDCFG stop $controller
$CLOUDCFG rm $controller
${KUBE_REPO_ROOT}/examples/update-demo/4-down.sh
}
trap "teardown" EXIT
validate 2
validate 2 nautilus
$CLOUDCFG resize $controller 1
${KUBE_REPO_ROOT}/examples/update-demo/2-scale.sh 1
sleep 2
validate 1
validate 1 nautilus
$CLOUDCFG resize $controller 2
${KUBE_REPO_ROOT}/examples/update-demo/2-scale.sh 2
sleep 2
validate 2
validate 2 nautilus
# TODO: test rolling update here, but to do so, we need to make the update blocking
# $CLOUDCFG -u=20s rollingupdate $controller
#
# Wait for the replica controller to recreate
# sleep 10
#
# validate 2
${KUBE_REPO_ROOT}/examples/update-demo/3-rolling-update.sh kitten 1s
sleep 2
validate 2 kitten
exit 0

View File

@ -32,7 +32,7 @@ set -e
# Use testing config
export KUBE_CONFIG_FILE="config-test.sh"
export KUBE_REPO_ROOT="$(dirname $0)/.."
export CLOUDCFG="${KUBE_REPO_ROOT}/cluster/kubecfg.sh -expect_version_match"
export KUBECFG="${KUBE_REPO_ROOT}/cluster/kubecfg.sh -expect_version_match"
if [[ $TEAR_DOWN -ne 0 ]]; then
detect-project

View File

@ -19,6 +19,7 @@ package client
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
)
@ -43,7 +44,7 @@ type Fake struct {
func (c *Fake) ListPods(selector labels.Selector) (api.PodList, error) {
c.Actions = append(c.Actions, FakeAction{Action: "list-pods"})
return c.Pods, nil
return *runtime.CopyOrDie(c.Pods).(*api.PodList), nil
}
func (c *Fake) GetPod(name string) (api.Pod, error) {
@ -73,7 +74,7 @@ func (c *Fake) ListReplicationControllers(selector labels.Selector) (api.Replica
func (c *Fake) GetReplicationController(name string) (api.ReplicationController, error) {
c.Actions = append(c.Actions, FakeAction{Action: "get-controller", Value: name})
return c.Ctrl, nil
return *runtime.CopyOrDie(c.Ctrl).(*api.ReplicationController), nil
}
func (c *Fake) CreateReplicationController(controller api.ReplicationController) (api.ReplicationController, error) {

View File

@ -78,11 +78,24 @@ func LoadAuthInfo(path string, r io.Reader) (*client.AuthInfo, error) {
// 'name' points to a replication controller.
// 'client' is used for updating pods.
// 'updatePeriod' is the time between pod updates.
func Update(name string, client client.Interface, updatePeriod time.Duration) error {
// 'imageName' is the new image to update for the template. This will work
// with the first container in the pod. There is no support yet for
// updating more complex replication controllers. If this is blank then no
// update of the image is performed.
func Update(name string, client client.Interface, updatePeriod time.Duration, imageName string) error {
controller, err := client.GetReplicationController(name)
if err != nil {
return err
}
if len(imageName) != 0 {
controller.DesiredState.PodTemplate.DesiredState.Manifest.Containers[0].Image = imageName
controller, err = client.UpdateReplicationController(controller)
if err != nil {
return err
}
}
s := labels.Set(controller.DesiredState.ReplicaSelector).AsSelector()
podList, err := client.ListPods(s)
@ -168,7 +181,7 @@ func RunController(image, name string, replicas int, client client.Interface, po
DesiredState: api.ReplicationControllerState{
Replicas: replicas,
ReplicaSelector: map[string]string{
"name": name,
"replicationController": name,
},
PodTemplate: api.PodTemplate{
DesiredState: api.PodState{
@ -184,13 +197,10 @@ func RunController(image, name string, replicas int, client client.Interface, po
},
},
Labels: map[string]string{
"name": name,
"replicationController": name,
},
},
},
Labels: map[string]string{
"name": name,
},
}
controllerOut, err := client.CreateReplicationController(controller)

View File

@ -26,10 +26,11 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
func validateAction(expectedAction, actualAction client.FakeAction, t *testing.T) {
if expectedAction != actualAction {
if !reflect.DeepEqual(expectedAction, actualAction) {
t.Errorf("Unexpected Action: %#v, expected: %#v", actualAction, expectedAction)
}
}
@ -43,7 +44,7 @@ func TestUpdateWithPods(t *testing.T) {
},
},
}
Update("foo", &fakeClient, 0)
Update("foo", &fakeClient, 0, "")
if len(fakeClient.Actions) != 5 {
t.Errorf("Unexpected action list %#v", fakeClient.Actions)
}
@ -57,7 +58,7 @@ func TestUpdateWithPods(t *testing.T) {
func TestUpdateNoPods(t *testing.T) {
fakeClient := client.Fake{}
Update("foo", &fakeClient, 0)
Update("foo", &fakeClient, 0, "")
if len(fakeClient.Actions) != 2 {
t.Errorf("Unexpected action list %#v", fakeClient.Actions)
}
@ -65,6 +66,45 @@ func TestUpdateNoPods(t *testing.T) {
validateAction(client.FakeAction{Action: "list-pods"}, fakeClient.Actions[1], t)
}
func TestUpdateWithNewImage(t *testing.T) {
fakeClient := client.Fake{
Pods: api.PodList{
Items: []api.Pod{
{JSONBase: api.JSONBase{ID: "pod-1"}},
{JSONBase: api.JSONBase{ID: "pod-2"}},
},
},
Ctrl: api.ReplicationController{
DesiredState: api.ReplicationControllerState{
PodTemplate: api.PodTemplate{
DesiredState: api.PodState{
Manifest: api.ContainerManifest{
Containers: []api.Container{
{Image: "fooImage:1"},
},
},
},
},
},
},
}
Update("foo", &fakeClient, 0, "fooImage:2")
if len(fakeClient.Actions) != 6 {
t.Errorf("Unexpected action list %#v", fakeClient.Actions)
}
validateAction(client.FakeAction{Action: "get-controller", Value: "foo"}, fakeClient.Actions[0], t)
newCtrl := *runtime.CopyOrDie(fakeClient.Ctrl).(*api.ReplicationController)
newCtrl.DesiredState.PodTemplate.DesiredState.Manifest.Containers[0].Image = "fooImage:2"
validateAction(client.FakeAction{Action: "update-controller", Value: newCtrl}, fakeClient.Actions[1], t)
validateAction(client.FakeAction{Action: "list-pods"}, fakeClient.Actions[2], t)
// Update deletes the pods, it relies on the replication controller to replace them.
validateAction(client.FakeAction{Action: "delete-pod", Value: "pod-1"}, fakeClient.Actions[3], t)
validateAction(client.FakeAction{Action: "delete-pod", Value: "pod-2"}, fakeClient.Actions[4], t)
validateAction(client.FakeAction{Action: "list-pods"}, fakeClient.Actions[5], t)
}
func TestRunController(t *testing.T) {
fakeClient := client.Fake{}
name := "name"

View File

@ -194,6 +194,24 @@ func DecodeInto(data []byte, obj interface{}) error {
return conversionScheme.DecodeInto(data, obj)
}
// Does a deep copy of an API object. Useful mostly for tests.
// TODO(dbsmith): implement directly instead of via Encode/Decode
func Copy(obj interface{}) (interface{}, error) {
data, err := Encode(obj)
if err != nil {
return nil, err
}
return Decode(data)
}
func CopyOrDie(obj interface{}) interface{} {
newObj, err := Copy(obj)
if err != nil {
panic(err)
}
return newObj
}
// metaInsertion implements conversion.MetaInsertionFactory, which lets the conversion
// package figure out how to encode our object's types and versions. These fields are
// located in our JSONBase.