diff --git a/etc/zun/policy.json b/etc/zun/policy.json index 6195bdfc0..613840c71 100644 --- a/etc/zun/policy.json +++ b/etc/zun/policy.json @@ -43,5 +43,12 @@ "zun-service:get_all": "rule:admin_api", "host:get_all": "rule:admin_api", - "host:get": "rule:admin_api" + "host:get": "rule:admin_api", + "capsule:create": "rule:default", + "capsule:delete": "rule:default", + "capsule:delete_all_tenants": "rule:admin_api", + "capsule:get": "rule:default", + "capsule:get_one_all_tenants": "rule:admin_api", + "capsule:get_all": "rule:default", + "capsule:get_all_all_tenants": "rule:admin_api", } diff --git a/template/capsule/capsule.yaml b/template/capsule/capsule.yaml new file mode 100644 index 000000000..300529f99 --- /dev/null +++ b/template/capsule/capsule.yaml @@ -0,0 +1,62 @@ +capsule_template_version: 2017-06-21 +# use "-" because that the fields have many items +capsule_version: beta +kind: capsule +metadata: + name: capsule-example + labels: + - app: web + - nihao: baibai +restart_policy: always +spec: + containers: + - image: ubuntu + command: + - "/bin/bash" + image_pull_policy: ifnotpresent + workdir: /root + labels: + app: web + ports: + - name: nginx-port + containerPort: 80 + hostPort: 80 + protocol: TCP + resources: + allocation: + cpu: 1 + memory: 1024 + environment: + PATCH: /usr/local/bin + - image: centos + command: + - "echo" + args: + - "Hello" + - "World" + image_pull_policy: ifnotpresent + workdir: /root + labels: + app: web01 + ports: + - name: nginx-port + containerPort: 80 + hostPort: 80 + protocol: TCP + - name: mysql-port + containerPort: 3306 + hostPort: 3306 + protocol: TCP + resources: + allocation: + cpu: 1 + memory: 1024 + environment: + NWH: /usr/bin/ + volumes: + - name: volume1 + drivers: cinder + driverOptions: options + size: 5GB + volumeType: type1 + image: ubuntu-xenial diff --git a/zun/api/controllers/experimental/__init__.py b/zun/api/controllers/experimental/__init__.py new file mode 100644 index 000000000..5479ec4d6 --- /dev/null +++ b/zun/api/controllers/experimental/__init__.py @@ -0,0 +1,152 @@ +# Copyright 2017 ARM Holdings. +# +# 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. + +""" +Experimental of the Zun API + +NOTE: IN PROGRESS AND NOT FULLY IMPLEMENTED. +""" + +from oslo_log import log as logging +import pecan + +from zun.api.controllers import base as controllers_base +from zun.api.controllers.experimental import capsules as capsule_controller +from zun.api.controllers import link +from zun.api.controllers import versions as ver +from zun.api import http_error +from zun.common.i18n import _ + +LOG = logging.getLogger(__name__) + + +BASE_VERSION = 1 + +MIN_VER_STR = '%s %s' % (ver.Version.service_string, ver.BASE_VER) + +MAX_VER_STR = '%s %s' % (ver.Version.service_string, ver.CURRENT_MAX_VER) + +MIN_VER = ver.Version({ver.Version.string: MIN_VER_STR}, + MIN_VER_STR, MAX_VER_STR) +MAX_VER = ver.Version({ver.Version.string: MAX_VER_STR}, + MIN_VER_STR, MAX_VER_STR) + + +class MediaType(controllers_base.APIBase): + """A media type representation.""" + + fields = ( + 'base', + 'type', + ) + + +class Experimental(controllers_base.APIBase): + """The representation of the version experimental of the API.""" + + fields = ( + 'id', + 'media_types', + 'links', + 'capsules' + ) + + @staticmethod + def convert(): + experimental = Experimental() + experimental.id = "experimental" + experimental.links = [link.make_link('self', pecan.request.host_url, + 'experimental', '', + bookmark=True), + link.make_link('describedby', + 'https://docs.openstack.org', + 'developer/zun/dev', + 'api-spec-v1.html', + bookmark=True, + type='text/html')] + experimental.media_types = \ + [MediaType(base='application/json', + type='application/vnd.openstack.' + 'zun.experimental+json')] + experimental.capsules = [link.make_link('self', + pecan.request.host_url, + 'capsules', ''), + link.make_link('bookmark', + pecan.request.host_url, + 'capsules', '', + bookmark=True)] + return experimental + + +class Controller(controllers_base.Controller): + """Version expereimental API controller root.""" + + capsules = capsule_controller.CapsuleController() + + @pecan.expose('json') + def get(self): + return Experimental.convert() + + def _check_version(self, version, headers=None): + if headers is None: + headers = {} + # ensure that major version in the URL matches the header + if version.major != BASE_VERSION: + raise http_error.HTTPNotAcceptableAPIVersion(_( + "Mutually exclusive versions requested. Version %(ver)s " + "requested but not supported by this service. " + "The supported version range is: " + "[%(min)s, %(max)s].") % {'ver': version, + 'min': MIN_VER_STR, + 'max': MAX_VER_STR}, + headers=headers, + max_version=str(MAX_VER), + min_version=str(MIN_VER)) + # ensure the minor version is within the supported range + if version < MIN_VER or version > MAX_VER: + raise http_error.HTTPNotAcceptableAPIVersion(_( + "Version %(ver)s was requested but the minor version is not " + "supported by this service. The supported version range is: " + "[%(min)s, %(max)s].") % {'ver': version, 'min': MIN_VER_STR, + 'max': MAX_VER_STR}, + headers=headers, + max_version=str(MAX_VER), + min_version=str(MIN_VER)) + + @pecan.expose() + def _route(self, args): + version = ver.Version( + pecan.request.headers, MIN_VER_STR, MAX_VER_STR) + + # Always set the basic version headers + pecan.response.headers[ver.Version.min_string] = MIN_VER_STR + pecan.response.headers[ver.Version.max_string] = MAX_VER_STR + pecan.response.headers[ver.Version.string] = " ".join( + [ver.Version.service_string, str(version)]) + pecan.response.headers["vary"] = ver.Version.string + + # assert that requested version is supported + self._check_version(version, pecan.response.headers) + pecan.request.version = version + if pecan.request.body: + msg = ("Processing request: url: %(url)s, %(method)s, " + "body: %(body)s" % + {'url': pecan.request.url, + 'method': pecan.request.method, + 'body': pecan.request.body}) + LOG.debug(msg) + + return super(Controller, self)._route(args) + +__all__ = (Controller) diff --git a/zun/api/controllers/experimental/capsules.py b/zun/api/controllers/experimental/capsules.py new file mode 100644 index 000000000..5cc3800bc --- /dev/null +++ b/zun/api/controllers/experimental/capsules.py @@ -0,0 +1,244 @@ +# Copyright 2017 ARM Holdings. +# +# 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 oslo_log import log as logging +from oslo_utils import uuidutils +import pecan + +from zun.api.controllers import base +from zun.api.controllers.experimental import collection +from zun.api.controllers.experimental.schemas import capsules as schema +from zun.api.controllers.experimental.views import capsules_view as view +from zun.api.controllers import link +from zun.api import utils as api_utils +from zun.common import consts +from zun.common import exception +from zun.common import name_generator +from zun.common import policy +from zun.common import utils +from zun.common import validation +from zun import objects + +LOG = logging.getLogger(__name__) + + +def _get_capsule(capsule_id): + capsule = api_utils.get_resource('Capsule', capsule_id) + if not capsule: + pecan.abort(404, ('Not found; the container you requested ' + 'does not exist.')) + return capsule + + +def _get_container(container_id): + container = api_utils.get_resource('Container', container_id) + if not container: + pecan.abort(404, ('Not found; the container you requested ' + 'does not exist.')) + return container + + +def check_policy_on_capsule(capsule, action): + context = pecan.request.context + policy.enforce(context, action, capsule, action=action) + + +class CapsuleCollection(collection.Collection): + """API representation of a collection of Capsules.""" + + fields = { + 'capsules', + 'next' + } + + """A list containing capsules objects""" + + def __init__(self, **kwargs): + self._type = 'capsules' + + @staticmethod + def convert_with_links(rpc_capsules, limit, url=None, + expand=False, **kwargs): + collection = CapsuleCollection() + collection.capsules = \ + [view.format_capsule(url, p) for p in rpc_capsules] + collection.next = collection.get_next(limit, url=url, **kwargs) + return collection + + +class CapsuleController(base.Controller): + '''Controller for Capsules''' + + _custom_actions = { + + } + + @pecan.expose('json') + @api_utils.enforce_content_types(['application/json']) + @exception.wrap_pecan_controller_exception + @validation.validated(schema.capsule_create) + def post(self, **capsule_dict): + """Create a new capsule. + + :param capsule: a capsule within the request body. + """ + context = pecan.request.context + compute_api = pecan.request.compute_api + policy.enforce(context, "capsule:create", + action="capsule:create") + capsule_dict['capsule_version'] = 'alpha' + capsule_dict['kind'] = 'capsule' + + capsules_spec = capsule_dict['spec'] + containers_spec = utils.check_capsule_template(capsules_spec) + capsule_dict['uuid'] = uuidutils.generate_uuid() + new_capsule = objects.Capsule(context, **capsule_dict) + new_capsule.project_id = context.project_id + new_capsule.user_id = context.user_id + new_capsule.create(context) + new_capsule.containers = [] + new_capsule.containers_uuids = [] + new_capsule.volumes = [] + count = len(containers_spec) + + capsule_restart_policy = capsules_spec.get('restart_policy', 'always') + + metadata_info = capsules_spec.get('metadata', None) + requested_networks = capsules_spec.get('nets', []) + if metadata_info: + new_capsule.meta_name = metadata_info.get('name', None) + new_capsule.meta_labels = metadata_info.get('labels', None) + + # Generate Object for infra container + sandbox_container = objects.Container(context) + sandbox_container.project_id = context.project_id + sandbox_container.user_id = context.user_id + name = self._generate_name_for_capsule_sandbox( + capsule_dict['uuid']) + sandbox_container.name = name + sandbox_container.create(context) + new_capsule.containers.append(sandbox_container) + new_capsule.containers_uuids.append(sandbox_container.uuid) + + for k in range(count): + container_dict = containers_spec[k] + container_dict['project_id'] = context.project_id + container_dict['user_id'] = context.user_id + name = self._generate_name_for_capsule_container( + capsule_dict['uuid']) + container_dict['name'] = name + + if container_dict.get('args') and container_dict.get('command'): + container_dict = self._transfer_list_to_str(container_dict, + 'command') + container_dict = self._transfer_list_to_str(container_dict, + 'args') + container_dict['command'] = \ + container_dict['command'] + ' ' + container_dict['args'] + container_dict.pop('args') + elif container_dict.get('command'): + container_dict = self._transfer_list_to_str(container_dict, + 'command') + elif container_dict.get('args'): + container_dict = self._transfer_list_to_str(container_dict, + 'args') + container_dict['command'] = container_dict['args'] + container_dict.pop('args') + + # NOTE(kevinz): Don't support pod remapping, will find a + # easy way to implement it. + # if container need to open some port, just open it in container, + # user can change the security group and getting access to port. + if container_dict.get('ports'): + container_dict.pop('ports') + + if container_dict.get('resources'): + resources_list = container_dict.get('resources') + allocation = resources_list.get('allocation') + if allocation.get('cpu'): + container_dict['cpu'] = allocation.get('cpu') + if allocation.get('memory'): + container_dict['memory'] = \ + str(allocation['memory']) + 'M' + container_dict.pop('resources') + + if capsule_restart_policy: + container_dict['restart_policy'] = \ + {"MaximumRetryCount": "0", + "Name": capsule_restart_policy} + self._check_for_restart_policy(container_dict) + + container_dict['status'] = consts.CREATING + container_dict['interactive'] = True + new_container = objects.Container(context, **container_dict) + new_container.create(context) + new_capsule.containers.append(new_container) + new_capsule.containers_uuids.append(new_container.uuid) + + new_capsule.save(context) + compute_api.capsule_create(context, new_capsule, requested_networks) + # Set the HTTP Location Header + pecan.response.location = link.build_url('capsules', + new_capsule.uuid) + + pecan.response.status = 202 + return view.format_capsule(pecan.request.host_url, new_capsule) + + def _generate_name_for_capsule_container(self, capsule_uuid=None): + '''Generate a random name like: zeta-22-container.''' + name_gen = name_generator.NameGenerator() + name = name_gen.generate() + return 'capsule-' + capsule_uuid + '-' + name + + def _generate_name_for_capsule_sandbox(self, capsule_uuid=None): + '''Generate sandbox name inside the capsule''' + return 'capsule-' + capsule_uuid + '-' + 'sandbox' + + def _transfer_different_field(self, field_tpl, + field_container, **container_dict): + '''Transfer the template specified field to container_field''' + if container_dict.get(field_tpl): + container_dict[field_container] = api_utils.string_or_none( + container_dict.get(field_tpl)) + container_dict.pop(field_tpl) + return container_dict + + def _check_for_restart_policy(self, container_dict): + '''Check for restart policy input''' + restart_policy = container_dict.get('restart_policy') + if not restart_policy: + return + + name = restart_policy.get('Name') + num = restart_policy.setdefault('MaximumRetryCount', '0') + count = int(num) + if name in ['unless-stopped', 'always']: + if count != 0: + raise exception.InvalidValue(("maximum retry " + "count not valid " + "with restart policy " + "of %s") % name) + elif name in ['no']: + container_dict.get('restart_policy')['MaximumRetryCount'] = '0' + + def _transfer_list_to_str(self, container_dict, field): + if container_dict[field]: + dict = None + for k in range(0, len(container_dict[field])): + if dict: + dict = dict + ' ' + container_dict[field][k] + else: + dict = container_dict[field][k] + container_dict[field] = dict + return container_dict diff --git a/zun/api/controllers/experimental/collection.py b/zun/api/controllers/experimental/collection.py new file mode 100644 index 000000000..a2540078f --- /dev/null +++ b/zun/api/controllers/experimental/collection.py @@ -0,0 +1,43 @@ +# Copyright 2017 ARM Holdings. +# +# 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. + +import pecan + +from zun.api.controllers import base +from zun.api.controllers import link + + +class Collection(base.APIBase): + + @property + def collection(self): + return getattr(self, self._type) + + def has_next(self, limit): + """Return whether collection has more items.""" + return len(self.collection) and len(self.collection) == limit + + def get_next(self, limit, url=None, **kwargs): + """Return a link to the next subset of the collection.""" + if not self.has_next(limit): + return None + + resource_url = url or self._type + q_args = ''.join(['%s=%s&' % (key, kwargs[key]) for key in kwargs]) + next_args = '?%(args)slimit=%(limit)d&marker=%(marker)s' % { + 'args': q_args, 'limit': limit, + 'marker': self.collection[-1]['uuid']} + + return link.make_link('next', pecan.request.host_url, + resource_url, next_args)['href'] diff --git a/zun/api/controllers/experimental/schemas/__init__.py b/zun/api/controllers/experimental/schemas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/zun/api/controllers/experimental/schemas/capsules.py b/zun/api/controllers/experimental/schemas/capsules.py new file mode 100644 index 000000000..3e9abb7a5 --- /dev/null +++ b/zun/api/controllers/experimental/schemas/capsules.py @@ -0,0 +1,26 @@ +# Copyright 2017 ARM Holdings. +# +# 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 zun.common.validation import parameter_types + +_capsule_properties = { + 'spec': parameter_types.spec +} + +capsule_create = { + 'type': 'object', + 'properties': _capsule_properties, + 'required': ['spec'], + 'additionalProperties': False +} diff --git a/zun/api/controllers/experimental/views/__init__.py b/zun/api/controllers/experimental/views/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/zun/api/controllers/experimental/views/capsules_view.py b/zun/api/controllers/experimental/views/capsules_view.py new file mode 100644 index 000000000..a3145fa07 --- /dev/null +++ b/zun/api/controllers/experimental/views/capsules_view.py @@ -0,0 +1,49 @@ +# Copyright 2017 ARM Holdings. +# +# 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. + +import itertools + +from zun.api.controllers import link + + +_basic_keys = ( + 'id', + 'uuid', + 'created_at', + 'status', + 'restart_policy', + 'meta_name', + 'meta_labels', + 'containers_uuids', + 'capsule_version' +) + + +def format_capsule(url, capsule): + def transform(key, value): + if key not in _basic_keys: + return + if key == 'uuid': + yield ('uuid', value) + yield ('links', [link.make_link( + 'self', url, 'capsules', value), + link.make_link( + 'bookmark', url, + 'capsules', value, + bookmark=True)]) + else: + yield (key, value) + + return dict(itertools.chain.from_iterable( + transform(k, v) for k, v in capsule.as_dict().items())) diff --git a/zun/api/controllers/root.py b/zun/api/controllers/root.py index adf3b97d8..b290f1ea2 100644 --- a/zun/api/controllers/root.py +++ b/zun/api/controllers/root.py @@ -14,6 +14,7 @@ import pecan from pecan import rest from zun.api.controllers import base +from zun.api.controllers import experimental from zun.api.controllers import link from zun.api.controllers import v1 from zun.api.controllers import versions @@ -69,13 +70,14 @@ class Root(base.APIBase): class RootController(rest.RestController): - _versions = ['v1'] + _versions = ['v1', 'experimental'] """All supported API versions""" _default_version = 'v1' """The default API version""" v1 = v1.Controller() + experimental = experimental.Controller() @pecan.expose('json') def get(self): diff --git a/zun/api/controllers/v1/__init__.py b/zun/api/controllers/v1/__init__.py index 2509d959f..6cd86a708 100644 --- a/zun/api/controllers/v1/__init__.py +++ b/zun/api/controllers/v1/__init__.py @@ -22,6 +22,7 @@ from oslo_log import log as logging import pecan from zun.api.controllers import base as controllers_base +from zun.api.controllers.experimental import capsules as capsule_controller from zun.api.controllers import link from zun.api.controllers.v1 import containers as container_controller from zun.api.controllers.v1 import hosts as host_controller @@ -115,6 +116,7 @@ class Controller(controllers_base.Controller): containers = container_controller.ContainersController() images = image_controller.ImagesController() hosts = host_controller.HostController() + capsules = capsule_controller.CapsuleController() @pecan.expose('json') def get(self): diff --git a/zun/common/exception.py b/zun/common/exception.py index 32a1c4883..2a3ac5281 100644 --- a/zun/common/exception.py +++ b/zun/common/exception.py @@ -542,3 +542,15 @@ class PciDeviceNotFoundById(NotFound): class PciDeviceNotFound(NotFound): message = _("PCI Device %(node_id)s:%(address)s not found.") + + +class CapsuleAlreadyExists(ResourceExists): + message = _("A capsule with %(field)s %(value)s already exists.") + + +class CapsuleNotFound(HTTPNotFound): + message = _("Capsule %(capsule)s could not be found.") + + +class InvalidCapsuleTemplate(ZunException): + message = _("Invalid capsule template: %(reason)s.") diff --git a/zun/common/utils.py b/zun/common/utils.py index fd4c922ec..a256bee81 100644 --- a/zun/common/utils.py +++ b/zun/common/utils.py @@ -313,3 +313,28 @@ def get_security_group_ids(context, security_groups, **kwargs): raise exception.ZunException(_( "Any of the security group in %s is not found ") % security_groups) + + +def check_capsule_template(tpl): + # TODO(kevinz): add volume spec check + kind_field = tpl.get('kind', None) + if kind_field != 'capsule' or kind_field != 'Capsule': + raise exception.InvalidCapsuleTemplate("kind fields need to " + "be set as capsule") + spec_field = tpl.get('spec', None) + if spec_field is None: + raise exception.InvalidCapsuleTemplate("No Spec found") + if spec_field.get('containers', None) is None: + raise exception.InvalidCapsuleTemplate("No valid containers field") + containers_spec = spec_field.get('containers', None) + containers_num = len(containers_spec) + if containers_num == 0: + raise exception.InvalidCapsuleTemplate("Capsule need to have one " + "container at least") + + for i in range(0, containers_num): + container_image = containers_spec[i].get('image', None) + if container_image is None: + raise exception.InvalidCapsuleTemplate("Container " + "image is needed") + return containers_spec diff --git a/zun/common/validation/parameter_types.py b/zun/common/validation/parameter_types.py index fe1599990..c8fa55676 100644 --- a/zun/common/validation/parameter_types.py +++ b/zun/common/validation/parameter_types.py @@ -225,3 +225,7 @@ security_groups = { 'maxLength': 255 } } + +spec = { + 'type': ['object'], +} diff --git a/zun/compute/api.py b/zun/compute/api.py index 76792df18..fd0d6add7 100644 --- a/zun/compute/api.py +++ b/zun/compute/api.py @@ -132,3 +132,17 @@ class API(object): def image_search(self, context, image, image_driver, *args): return self.rpcapi.image_search(context, image, image_driver, *args) + + def capsule_create(self, context, new_capsule, + requested_networks=None, extra_spec=None): + host_state = None + try: + host_state = self._schedule_container(context, new_capsule, + extra_spec) + except Exception as exc: + new_capsule.status = consts.ERROR + new_capsule.status_reason = str(exc) + new_capsule.save(context) + return + self.rpcapi.capsule_create(context, host_state['host'], new_capsule, + requested_networks, host_state['limits']) diff --git a/zun/compute/manager.py b/zun/compute/manager.py index e471f3c31..d9350a562 100644 --- a/zun/compute/manager.py +++ b/zun/compute/manager.py @@ -88,26 +88,8 @@ class Manager(periodic_task.PeriodicTasks): container.task_state = task_state container.save(context) - def _do_container_create(self, context, container, requested_networks, - limits=None, reraise=False): - LOG.debug('Creating container: %s', container.uuid) - - # check if container driver is NovaDockerDriver and - # security_groups is non empty, then return by setting - # the error message in database - if ('NovaDockerDriver' in CONF.container_driver and - container.security_groups): - msg = "security_groups can not be provided with NovaDockerDriver" - self._fail_container(self, context, container, msg) - return - - sandbox_id = None - if self.use_sandbox: - sandbox_id = self._create_sandbox(context, container, - requested_networks, reraise) - if sandbox_id is None: - return - + def _do_container_create_base(self, context, container, requested_networks, + sandbox=None, limits=None, reraise=False): self._update_task_state(context, container, consts.IMAGE_PULLING) repo, tag = utils.parse_image_name(container.image) image_pull_policy = utils.get_image_pull_policy( @@ -172,6 +154,33 @@ class Manager(periodic_task.PeriodicTasks): unset_host=True) return + def _do_container_create(self, context, container, requested_networks, + limits=None, reraise=False): + LOG.debug('Creating container: %s', container.uuid) + + # check if container driver is NovaDockerDriver and + # security_groups is non empty, then return by setting + # the error message in database + if ('NovaDockerDriver' in CONF.container_driver and + container.security_groups): + msg = "security_groups can not be provided with NovaDockerDriver" + self._fail_container(self, context, container, msg) + return + + sandbox = None + if self.use_sandbox: + sandbox = self._create_sandbox(context, container, + requested_networks, reraise) + if sandbox is None: + return + + created_container = self._do_container_create_base(context, + container, + requested_networks, + sandbox, limits, + reraise) + return created_container + def _use_sandbox(self): if CONF.use_sandbox and self.driver.capabilities["support_sandbox"]: return True @@ -681,3 +690,34 @@ class Manager(periodic_task.PeriodicTasks): return except Exception: return + + def capsule_create(self, context, capsule, requested_networks, limits): + utils.spawn_n(self._do_capsule_create, context, + capsule, requested_networks, limits) + + def _do_capsule_create(self, context, capsule, requested_networks=None, + limits=None, reraise=False): + capsule.containers[0].image = CONF.sandbox_image + capsule.containers[0].image_driver = CONF.sandbox_image_driver + capsule.containers[0].image_pull_policy = \ + CONF.sandbox_image_pull_policy + capsule.containers[0].save(context) + sandbox = self._create_sandbox(context, + capsule.containers[0], + requested_networks, reraise) + self._update_task_state(context, capsule.containers[0], None) + capsule.containers[0].status = consts.RUNNING + capsule.containers[0].save(context) + sandbox_id = capsule.containers[0].get_sandbox_id() + count = len(capsule.containers) + for k in range(1, count): + capsule.containers[k].set_sandbox_id(sandbox_id) + capsule.containers[k].addresses = capsule.containers[0].addresses + created_container = \ + self._do_container_create_base(context, + capsule.containers[k], + requested_networks, + sandbox, + limits) + if created_container: + self._do_container_start(context, created_container) diff --git a/zun/compute/rpcapi.py b/zun/compute/rpcapi.py index bdf27c2a3..a4b272b83 100644 --- a/zun/compute/rpcapi.py +++ b/zun/compute/rpcapi.py @@ -174,3 +174,10 @@ class API(rpc_service.API): return self._call(host, 'image_search', image=image, image_driver_name=image_driver, exact_match=exact_match) + + def capsule_create(self, context, host, capsule, + requested_networks, limits): + self._cast(host, 'capsule_create', + capsule=capsule, + requested_networks=requested_networks, + limits=limits)