From 4bfed61fe3e78bfd0333376aa3c4331edb509f6e Mon Sep 17 00:00:00 2001 From: Ton Ngo Date: Thu, 30 Jul 2015 04:15:07 +0000 Subject: [PATCH] Migrate to Kubernetes Release 1 Kubernetes Release 1.0 was announced at OSCON A new Fedora Atomic image has been built with version 1.0.4 and this series of patches will update the templates, scripts, documents to work with the new image. The api has also been changed from v1beta3 to v1 and the beta api is no longer available, so the interface between Magnum and Kubernetes master are updated as well in this series of patches. This particular patch will bring up a V1 cluster with the bay-create command. Because the switch to the V1 API requires all the code changes to be applied at once, this patch pulls in portion of 3 patches together. The changes include: 1. devstack plugin downloads the new image. 2. k8s conductor and other code calls the new V1 client and k8s methods. 3. Configuration for k8s services and docker updated with new parameters. 4. Minion registration and example code updated to V1. 5. Functional tests updated to V1. 6. Gate test setup points to the new image. Co-Authored-By: Hongbin Lu Change-Id: I046931ad491e8b7ee45943852901eac5c3df913e Partially-Implements: blueprint kubernetes-v1 --- devstack/plugin.sh | 2 +- functional_creds.conf.sample | 2 +- magnum/common/exception.py | 12 +- .../swagger_client/api_client.py | 2 +- magnum/conductor/handlers/k8s_conductor.py | 100 ++++--- magnum/conductor/k8s_api.py | 8 +- magnum/conductor/scale_manager.py | 4 +- .../fragments/configure-kubernetes-master.sh | 5 +- .../fragments/configure-kubernetes-minion.sh | 2 + .../fragments/kube-examples.yaml | 35 +-- .../fragments/kube-register.yaml | 10 +- magnum/tests/contrib/post_test_hook.sh | 2 +- .../functional/test_magnum_python_client.py | 66 +++-- .../conductor/handlers/test_k8s_conductor.py | 247 ++++++++---------- .../unit/conductor/test_scale_manager.py | 4 +- requirements.txt | 1 + 16 files changed, 242 insertions(+), 260 deletions(-) diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 65d54b8f1b..da75f403e8 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -18,7 +18,7 @@ if is_service_enabled m-api m-cond; then # add image to glance if [[ "$ENABLED_SERVICES" =~ 'm-api' ]]; then - MANGUM_GUEST_IMAGE_URL=${MANGUM_GUEST_IMAGE_URL:-"https://fedorapeople.org/groups/magnum/fedora-21-atomic-3.qcow2"} + MANGUM_GUEST_IMAGE_URL=${MANGUM_GUEST_IMAGE_URL:-"https://fedorapeople.org/groups/magnum/fedora-21-atomic-5.qcow2"} IMAGE_URLS+=",${MANGUM_GUEST_IMAGE_URL}" fi diff --git a/functional_creds.conf.sample b/functional_creds.conf.sample index bf484d9cb1..e7c6bc98a0 100644 --- a/functional_creds.conf.sample +++ b/functional_creds.conf.sample @@ -7,5 +7,5 @@ user = admin tenant = admin pass = secrete [magnum] -image_id = fedora-21-atomic-3 +image_id = fedora-21-atomic-5 nic_id = public diff --git a/magnum/common/exception.py b/magnum/common/exception.py index e83ee34672..0b3518f7db 100644 --- a/magnum/common/exception.py +++ b/magnum/common/exception.py @@ -19,6 +19,7 @@ Includes decorator for re-raising Magnum-type exceptions. """ import functools +import json import sys import uuid @@ -464,8 +465,15 @@ class OSDistroFieldNotFound(ResourceNotFound): class KubernetesAPIFailed(MagnumException): - def __init__(self, message=None, **kwargs): - self.__class__.code = kwargs.get('code') + def __init__(self, message=None, err=None, **kwargs): + if err: + if err.body: + message = json.loads(err.body)['message'] + else: + message = err.reason + self.__class__.code = err.status + else: + self.__class__.code = kwargs.get('code') super(KubernetesAPIFailed, self).__init__(message, **kwargs) diff --git a/magnum/common/pythonk8sclient/swagger_client/api_client.py b/magnum/common/pythonk8sclient/swagger_client/api_client.py index 6d24a3ad39..97745eb2e1 100644 --- a/magnum/common/pythonk8sclient/swagger_client/api_client.py +++ b/magnum/common/pythonk8sclient/swagger_client/api_client.py @@ -187,7 +187,7 @@ class ApiClient(object): """ if isinstance(obj, type(None)): return None - elif isinstance(obj, (str, int, float, bool, tuple)): + elif isinstance(obj, (unicode, str, int, float, bool, tuple, file)): return obj elif isinstance(obj, list): return [self.sanitize_for_serialization(sub_obj) diff --git a/magnum/conductor/handlers/k8s_conductor.py b/magnum/conductor/handlers/k8s_conductor.py index 7efd652dd8..9b0990d75e 100644 --- a/magnum/conductor/handlers/k8s_conductor.py +++ b/magnum/conductor/handlers/k8s_conductor.py @@ -16,12 +16,11 @@ from oslo_log import log as logging from magnum.common import exception from magnum.common import k8s_manifest +from magnum.common.pythonk8sclient.swagger_client import rest from magnum.conductor import k8s_api as k8s from magnum.conductor import utils as conductor_utils from magnum import objects -import ast -from six.moves.urllib import error LOG = logging.getLogger(__name__) @@ -41,11 +40,10 @@ class Handler(object): self.k8s_api = k8s.create_k8s_api(context, service) manifest = k8s_manifest.parse(service.manifest) try: - self.k8s_api.createService(body=manifest, - namespaces='default') - except error.HTTPError as err: - message = ast.literal_eval(err.read())['message'] - raise exception.KubernetesAPIFailed(code=err.code, message=message) + self.k8s_api.create_namespaced_service(body=manifest, + namespace='default') + except rest.ApiException as err: + raise exception.KubernetesAPIFailed(err=err) # call the service object to persist in db service.create(context) return service @@ -55,12 +53,11 @@ class Handler(object): self.k8s_api = k8s.create_k8s_api(context, service) manifest = k8s_manifest.parse(service.manifest) try: - self.k8s_api.replaceService(name=service.name, - body=manifest, - namespaces='default') - except error.HTTPError as err: - message = ast.literal_eval(err.read())['message'] - raise exception.KubernetesAPIFailed(code=err.code, message=message) + self.k8s_api.replace_namespaced_service(name=str(service.name), + body=manifest, + namespace='default') + except rest.ApiException as err: + raise exception.KubernetesAPIFailed(err=err) # call the service object to persist in db service.refresh(context) service.save() @@ -72,15 +69,13 @@ class Handler(object): self.k8s_api = k8s.create_k8s_api(context, service) if conductor_utils.object_has_stack(context, service): try: - self.k8s_api.deleteService(name=service.name, - namespaces='default') - except error.HTTPError as err: - if err.code == 404: + self.k8s_api.delete_namespaced_service(name=str(service.name), + namespace='default') + except rest.ApiException as err: + if err.status == 404: pass else: - message = ast.literal_eval(err.read())['message'] - raise exception.KubernetesAPIFailed(code=err.code, - message=message) + raise exception.KubernetesAPIFailed(err=err) # call the service object to persist in db service.destroy(context) @@ -90,15 +85,15 @@ class Handler(object): self.k8s_api = k8s.create_k8s_api(context, pod) manifest = k8s_manifest.parse(pod.manifest) try: - resp = self.k8s_api.createPod(body=manifest, namespaces='default') - except error.HTTPError as err: + resp = self.k8s_api.create_namespaced_pod(body=manifest, + namespace='default') + except rest.ApiException as err: pod.status = 'failed' - if err.code != 409: + if err.status != 409: pod.create(context) - message = ast.literal_eval(err.read())['message'] - raise exception.KubernetesAPIFailed(code=err.code, message=message) + raise exception.KubernetesAPIFailed(err=err) pod.status = resp.status.phase - pod.host = resp.spec.host + pod.host = resp.spec.node_name # call the pod object to persist in db # TODO(yuanying): parse pod file and, # - extract pod name and set it @@ -112,11 +107,11 @@ class Handler(object): self.k8s_api = k8s.create_k8s_api(context, pod) manifest = k8s_manifest.parse(pod.manifest) try: - self.k8s_api.replacePod(name=pod.name, body=manifest, - namespaces='default') - except error.HTTPError as err: - message = ast.literal_eval(err.read())['message'] - raise exception.KubernetesAPIFailed(code=err.code, message=message) + self.k8s_api.replace_namespaced_pod(name=str(pod.name), + body=manifest, + namespace='default') + except rest.ApiException as err: + raise exception.KubernetesAPIFailed(err=err) # call the pod object to persist in db pod.refresh(context) pod.save() @@ -128,15 +123,13 @@ class Handler(object): self.k8s_api = k8s.create_k8s_api(context, pod) if conductor_utils.object_has_stack(context, pod): try: - self.k8s_api.deletePod(name=pod.name, - namespaces='default') - except error.HTTPError as err: - if err.code == 404: + self.k8s_api.delete_namespaced_pod(name=str(pod.name), body={}, + namespace='default') + except rest.ApiException as err: + if err.status == 404: pass else: - message = ast.literal_eval(err.read())['message'] - raise exception.KubernetesAPIFailed(code=err.code, - message=message) + raise exception.KubernetesAPIFailed(err=err) # call the pod object to persist in db pod.destroy(context) @@ -146,11 +139,10 @@ class Handler(object): self.k8s_api = k8s.create_k8s_api(context, rc) manifest = k8s_manifest.parse(rc.manifest) try: - self.k8s_api.createReplicationController(body=manifest, - namespaces='default') - except error.HTTPError as err: - message = ast.literal_eval(err.read())['message'] - raise exception.KubernetesAPIFailed(code=err.code, message=message) + self.k8s_api.create_namespaced_replication_controller( + body=manifest, namespace='default') + except rest.ApiException as err: + raise exception.KubernetesAPIFailed(err=err) # call the rc object to persist in db rc.create(context) return rc @@ -160,12 +152,10 @@ class Handler(object): self.k8s_api = k8s.create_k8s_api(context, rc) manifest = k8s_manifest.parse(rc.manifest) try: - self.k8s_api.replaceReplicationController(name=rc.name, - body=manifest, - namespaces='default') - except error.HTTPError as err: - message = ast.literal_eval(err.read())['message'] - raise exception.KubernetesAPIFailed(code=err.code, message=message) + self.k8s_api.replace_namespaced_replication_controller( + name=str(rc.name), body=manifest, namespace='default') + except rest.ApiException as err: + raise exception.KubernetesAPIFailed(err=err) # call the rc object to persist in db rc.refresh(context) rc.save() @@ -177,14 +167,12 @@ class Handler(object): self.k8s_api = k8s.create_k8s_api(context, rc) if conductor_utils.object_has_stack(context, rc): try: - self.k8s_api.deleteReplicationController(name=rc.name, - namespaces='default') - except error.HTTPError as err: - if err.code == 404: + self.k8s_api.delete_namespaced_replication_controller( + name=str(rc.name), body={}, namespace='default') + except rest.ApiException as err: + if err.status == 404: pass else: - message = ast.literal_eval(err.read())['message'] - raise exception.KubernetesAPIFailed(code=err.code, - message=message) + raise exception.KubernetesAPIFailed(err=err) # call the rc object to persist in db rc.destroy(context) diff --git a/magnum/conductor/k8s_api.py b/magnum/conductor/k8s_api.py index 8ea9ff69dd..d227893e94 100644 --- a/magnum/conductor/k8s_api.py +++ b/magnum/conductor/k8s_api.py @@ -15,8 +15,8 @@ from oslo_config import cfg from magnum.common import config -from magnum.common.pythonk8sclient.client import ApivbetaApi -from magnum.common.pythonk8sclient.client import swagger +from magnum.common.pythonk8sclient.swagger_client import api_client +from magnum.common.pythonk8sclient.swagger_client.apis import apiv_api from magnum.conductor import utils from magnum.i18n import _ @@ -35,14 +35,14 @@ kubernetes_opts = [ cfg.CONF.register_opts(kubernetes_opts, group='kubernetes') -class K8sAPI(ApivbetaApi.ApivbetaApi): +class K8sAPI(apiv_api.ApivApi): def __init__(self, context, obj): # retrieve the URL of the k8s API endpoint k8s_api_endpoint = self._retrieve_k8s_api_endpoint(context, obj) # build a connection with Kubernetes master - client = swagger.ApiClient(k8s_api_endpoint) + client = api_client.ApiClient(k8s_api_endpoint) super(K8sAPI, self).__init__(client) diff --git a/magnum/conductor/scale_manager.py b/magnum/conductor/scale_manager.py index 63c05dc12f..5f8067e467 100644 --- a/magnum/conductor/scale_manager.py +++ b/magnum/conductor/scale_manager.py @@ -48,8 +48,8 @@ class ScaleManager(object): hosts_no_container = list(hosts) k8s_api = k8s.create_k8s_api(self.context, bay) - for pod in k8s_api.listPod().items: - host = pod.spec.host + for pod in k8s_api.list_namespaced_pod(namespace='default').items: + host = pod.spec.node_name if host in hosts_no_container: hosts_no_container.remove(host) diff --git a/magnum/templates/heat-kubernetes/fragments/configure-kubernetes-master.sh b/magnum/templates/heat-kubernetes/fragments/configure-kubernetes-master.sh index c973a95588..692124cc5c 100644 --- a/magnum/templates/heat-kubernetes/fragments/configure-kubernetes-master.sh +++ b/magnum/templates/heat-kubernetes/fragments/configure-kubernetes-master.sh @@ -13,9 +13,10 @@ sed -i ' sed -i ' /^KUBE_API_ADDRESS=/ s/=.*/="--address=0.0.0.0"/ - /^KUBE_SERVICE_ADDRESSES=/ s|=.*|="--portal_net='"$PORTAL_NETWORK_CIDR"'"| - /^KUBE_API_ARGS=/ s/=.*/="--runtime_config=api\/v1beta3"/ + /^KUBE_SERVICE_ADDRESSES=/ s|=.*|="--service-cluster-ip-range='"$PORTAL_NETWORK_CIDR"'"| + /^KUBE_API_ARGS=/ s/=.*/="--runtime_config=api\/all=true"/ /^KUBE_ETCD_SERVERS=/ s/=.*/="--etcd_servers=http:\/\/127.0.0.1:2379"/ + /^KUBE_ADMISSION_CONTROL=/ s/=.*/=""/ ' /etc/kubernetes/apiserver sed -i ' diff --git a/magnum/templates/heat-kubernetes/fragments/configure-kubernetes-minion.sh b/magnum/templates/heat-kubernetes/fragments/configure-kubernetes-minion.sh index 8ae10b6211..d252a8dfe6 100644 --- a/magnum/templates/heat-kubernetes/fragments/configure-kubernetes-minion.sh +++ b/magnum/templates/heat-kubernetes/fragments/configure-kubernetes-minion.sh @@ -37,5 +37,7 @@ cat >> /etc/environment <=1.9.0 stevedore>=1.5.0 # Apache-2.0 taskflow>=1.16.0 cryptography>=1.0 # Apache-2.0 +urllib3>=1.8.3