Merge "Implement the sandbox proposal - Part 1"
This commit is contained in:
commit
b4368be180
@ -25,7 +25,7 @@
|
||||
# userrc for us if nova service is not enabled, check
|
||||
# https://github.com/openstack-dev/devstack/blob/master/stack.sh#L1310
|
||||
|
||||
OVERRIDE_ENABLED_SERVICES="dstat,key,mysql,rabbit,n-api,n-cond,n-cpu,n-crt,n-obj,n-sch,tempest"
|
||||
OVERRIDE_ENABLED_SERVICES="dstat,key,mysql,rabbit,n-api,n-cond,n-cpu,n-crt,n-obj,n-sch,g-api,g-reg,tempest"
|
||||
export OVERRIDE_ENABLED_SERVICES
|
||||
|
||||
$BASE/new/devstack-gate/devstack-vm-gate.sh
|
||||
|
@ -64,8 +64,8 @@ else
|
||||
ZUN_BIN_DIR=$(get_python_exec_prefix)
|
||||
fi
|
||||
|
||||
DOCKER_GROUP=docker
|
||||
DEFAULT_CONTAINER_DRIVER=docker
|
||||
DOCKER_GROUP=${DOCKER_GROUP:-docker}
|
||||
ZUN_DRIVER=${DEFAULT_ZUN_DRIVER:-docker}
|
||||
|
||||
ETCD_VERSION=v3.0.13
|
||||
if is_ubuntu; then
|
||||
@ -109,11 +109,19 @@ function configure_zun {
|
||||
|
||||
create_api_paste_conf
|
||||
|
||||
if [[ ${DEFAULT_CONTAINER_DRIVER} == "docker" ]]; then
|
||||
if [[ ${ZUN_DRIVER} == "docker" ]]; then
|
||||
check_docker || install_docker
|
||||
fi
|
||||
}
|
||||
|
||||
# upload_sandbox_image() - Upload sandbox image to glance
|
||||
function upload_sandbox_image {
|
||||
if [[ ${ZUN_DRIVER} == "docker" ]]; then
|
||||
sg docker "docker pull kubernetes/pause"
|
||||
sg docker "docker save kubernetes/pause" | openstack image create kubernetes/pause --public --container-format docker --disk-format raw
|
||||
fi
|
||||
}
|
||||
|
||||
# create_zun_accounts() - Set up common required ZUN accounts
|
||||
#
|
||||
# Project User Roles
|
||||
@ -314,7 +322,11 @@ function start_zun_api {
|
||||
# start_zun_compute() - Start Zun compute agent
|
||||
function start_zun_compute {
|
||||
echo "Start zun compute..."
|
||||
run_process zun-compute "$ZUN_BIN_DIR/zun-compute" ${DOCKER_GROUP}
|
||||
if [[ ${ZUN_DRIVER} == "docker" ]]; then
|
||||
run_process zun-compute "$ZUN_BIN_DIR/zun-compute" ${DOCKER_GROUP}
|
||||
else
|
||||
run_process zun-compute "$ZUN_BIN_DIR/zun-compute"
|
||||
fi
|
||||
}
|
||||
|
||||
function start_zun_etcd {
|
||||
|
@ -32,6 +32,7 @@ if is_service_enabled zun-api zun-compute; then
|
||||
# Start the zun API and zun compute
|
||||
echo_summary "Starting zun"
|
||||
start_zun
|
||||
upload_sandbox_image
|
||||
|
||||
fi
|
||||
|
||||
|
@ -256,6 +256,20 @@ class Dict(object):
|
||||
return value
|
||||
|
||||
|
||||
class Json(object):
|
||||
type_name = 'Json'
|
||||
|
||||
@classmethod
|
||||
def validate(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
if not isinstance(value, dict):
|
||||
raise exception.InvalidValue(value=value, type=self.type_name)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class DateTime(object):
|
||||
type_name = "DateTime"
|
||||
|
||||
|
@ -120,13 +120,16 @@ class Container(base.APIBase):
|
||||
'labels': {
|
||||
'validate': types.Dict(types.String, types.String).validate,
|
||||
},
|
||||
'addresses': {
|
||||
'validate': types.Json.validate,
|
||||
},
|
||||
'image_pull_policy': {
|
||||
'validate': types.EnumType.validate,
|
||||
'validate_args': {
|
||||
'name': 'image_pull_policy',
|
||||
'values': ['never', 'always', 'ifnotpresent']
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@ -138,7 +141,7 @@ class Container(base.APIBase):
|
||||
container.unset_fields_except([
|
||||
'uuid', 'name', 'image', 'command', 'status', 'cpu', 'memory',
|
||||
'environment', 'task_state', 'workdir', 'ports', 'hostname',
|
||||
'labels', 'image_pull_policy', 'status_reason'])
|
||||
'labels', 'addresses', 'image_pull_policy', 'status_reason'])
|
||||
|
||||
container.links = [link.Link.make_link(
|
||||
'self', url,
|
||||
@ -171,6 +174,14 @@ class Container(base.APIBase):
|
||||
ports=[80, 443],
|
||||
hostname='testhost',
|
||||
labels={'key1': 'val1', 'key2': 'val2'},
|
||||
addresses={
|
||||
'private': [
|
||||
{'OS-EXT-IPS-MAC:mac_addr': 'fa:16:3e:04:da:76',
|
||||
'version': 4,
|
||||
'addr': '10.0.0.12',
|
||||
'OS-EXT-IPS:type': 'fixed'},
|
||||
],
|
||||
},
|
||||
created_at=timeutils.utcnow(),
|
||||
updated_at=timeutils.utcnow())
|
||||
return cls._convert_with_links(sample, 'http://localhost:9517', expand)
|
||||
|
@ -118,9 +118,9 @@ def wrap_controller_exception(func, func_server_error, func_client_error):
|
||||
# log the error message with its associated
|
||||
# correlation id
|
||||
log_correlation_id = uuidutils.generate_uuid()
|
||||
LOG.error(_LE("%(correlation_id)s:%(excp)s") %
|
||||
{'correlation_id': log_correlation_id,
|
||||
'excp': str(excp)})
|
||||
LOG.exception(_LE("%(correlation_id)s:%(excp)s") %
|
||||
{'correlation_id': log_correlation_id,
|
||||
'excp': str(excp)})
|
||||
# raise a client error with an obfuscated message
|
||||
return func_server_error(log_correlation_id, http_error_code)
|
||||
else:
|
||||
@ -364,3 +364,7 @@ class InvalidStateException(ZunException):
|
||||
|
||||
class DockerError(ZunException):
|
||||
message = _("Docker internal error: %(error_msg)s.")
|
||||
|
||||
|
||||
class PollTimeOut(ZunException):
|
||||
message = _("Polling request timed out.")
|
||||
|
@ -23,6 +23,7 @@ import zun.conf
|
||||
|
||||
CONF = zun.conf.CONF
|
||||
CFG_GROUP = 'keystone_auth'
|
||||
CFG_LEGACY_GROUP = 'keystone_authtoken'
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
keystone_auth_opts = (ka_loading.get_auth_common_conf_options() +
|
||||
@ -46,7 +47,9 @@ class KeystoneClientV3(object):
|
||||
|
||||
@property
|
||||
def auth_url(self):
|
||||
return CONF.keystone_authtoken.auth_uri.replace('v2.0', 'v3')
|
||||
# FIXME(pauloewerton): auth_url should be retrieved from keystone_auth
|
||||
# section by default
|
||||
return CONF[CFG_LEGACY_GROUP].auth_uri.replace('v2.0', 'v3')
|
||||
|
||||
@property
|
||||
def auth_token(self):
|
||||
|
@ -77,6 +77,23 @@ class Manager(object):
|
||||
LOG.debug('Creating container...', context=context,
|
||||
container=container)
|
||||
|
||||
container.task_state = fields.TaskState.SANDBOX_CREATING
|
||||
container.save()
|
||||
sandbox_id = None
|
||||
sandbox_image = 'kubernetes/pause'
|
||||
repo, tag = utils.parse_image_name(sandbox_image)
|
||||
try:
|
||||
image = image_driver.pull_image(context, repo, tag, 'ifnotpresent')
|
||||
sandbox_id = self.driver.create_sandbox(context, container,
|
||||
image=sandbox_image)
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception(reraise=reraise):
|
||||
LOG.exception(_LE("Unexpected exception: %s"),
|
||||
six.text_type(e))
|
||||
self._fail_container(container, six.text_type(e))
|
||||
return
|
||||
|
||||
self.driver.set_sandbox_id(container, sandbox_id)
|
||||
container.task_state = fields.TaskState.IMAGE_PULLING
|
||||
container.save()
|
||||
repo, tag = utils.parse_image_name(container.image)
|
||||
@ -107,7 +124,9 @@ class Manager(object):
|
||||
container.task_state = fields.TaskState.CONTAINER_CREATING
|
||||
container.save()
|
||||
try:
|
||||
container = self.driver.create(container, image)
|
||||
container = self.driver.create(container, sandbox_id, image)
|
||||
container.addresses = self._get_container_addresses(context,
|
||||
container)
|
||||
container.task_state = None
|
||||
container.save()
|
||||
return container
|
||||
@ -154,7 +173,6 @@ class Manager(object):
|
||||
if not force:
|
||||
self._validate_container_state(container, 'delete')
|
||||
self.driver.delete(container, force)
|
||||
return container
|
||||
except exception.DockerError as e:
|
||||
LOG.error(_LE("Error occured while calling docker delete API: %s"),
|
||||
six.text_type(e))
|
||||
@ -163,6 +181,16 @@ class Manager(object):
|
||||
LOG.exception(_LE("Unexpected exception: %s"), str(e))
|
||||
raise e
|
||||
|
||||
sandbox_id = self.driver.get_sandbox_id(container)
|
||||
if sandbox_id:
|
||||
try:
|
||||
self.driver.delete_sandbox(context, sandbox_id)
|
||||
except Exception as e:
|
||||
LOG.exception(_LE("Unexpected exception: %s"), str(e))
|
||||
raise
|
||||
|
||||
return container
|
||||
|
||||
@translate_exception
|
||||
def container_list(self, context):
|
||||
LOG.debug('Listing container...', context=context)
|
||||
@ -344,3 +372,16 @@ class Manager(object):
|
||||
except Exception as e:
|
||||
LOG.exception(_LE("Unexpected exception: %s"), str(e))
|
||||
raise e
|
||||
|
||||
def _get_container_addresses(self, context, container):
|
||||
LOG.debug('Showing container IP addresses...', context=context,
|
||||
container=container)
|
||||
try:
|
||||
return self.driver.get_addresses(context, container)
|
||||
except exception.DockerError as e:
|
||||
LOG.error(_LE("Error occured while calling docker API: %s"),
|
||||
six.text_type(e))
|
||||
raise
|
||||
except Exception as e:
|
||||
LOG.exception(_LE("Unexpected exception: %s"), str(e))
|
||||
raise e
|
||||
|
@ -28,7 +28,9 @@ Services which consume this:
|
||||
Interdependencies to other options:
|
||||
|
||||
* None
|
||||
""")
|
||||
"""),
|
||||
cfg.IntOpt('default_timeout', default=60 * 10,
|
||||
help='Maximum time (in seconds) to wait for an event.'),
|
||||
]
|
||||
|
||||
|
||||
|
@ -11,17 +11,20 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from docker import errors
|
||||
import six
|
||||
|
||||
from docker import errors
|
||||
from oslo_log import log as logging
|
||||
|
||||
from zun.common.i18n import _LW
|
||||
from zun.common.utils import check_container_id
|
||||
import zun.conf
|
||||
from zun.container.docker import utils as docker_utils
|
||||
from zun.container import driver
|
||||
from zun.objects import fields
|
||||
|
||||
|
||||
CONF = zun.conf.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -46,7 +49,7 @@ class DockerDriver(driver.ContainerDriver):
|
||||
response = docker.images(repo, quiet)
|
||||
return response
|
||||
|
||||
def create(self, container, image):
|
||||
def create(self, container, sandbox_id, image):
|
||||
with docker_utils.docker_client() as docker:
|
||||
name = container.name
|
||||
if image['path']:
|
||||
@ -67,14 +70,18 @@ class DockerDriver(driver.ContainerDriver):
|
||||
}
|
||||
|
||||
host_config = {}
|
||||
host_config['publish_all_ports'] = True
|
||||
host_config['network_mode'] = 'container:%s' % sandbox_id
|
||||
# TODO(hongbin): Uncomment this after docker-py add support for
|
||||
# container mode for pid namespace.
|
||||
# host_config['pid_mode'] = 'container:%s' % sandbox_id
|
||||
host_config['ipc_mode'] = 'container:%s' % sandbox_id
|
||||
host_config['volumes_from'] = sandbox_id
|
||||
if container.memory is not None:
|
||||
host_config['mem_limit'] = container.memory
|
||||
if container.cpu is not None:
|
||||
host_config['cpu_quota'] = int(100000 * container.cpu)
|
||||
host_config['cpu_period'] = 100000
|
||||
kwargs['host_config'] = \
|
||||
docker.create_host_config(**host_config)
|
||||
kwargs['host_config'] = docker.create_host_config(**host_config)
|
||||
|
||||
response = docker.create_container(image, **kwargs)
|
||||
container.container_id = response['Id']
|
||||
@ -204,3 +211,45 @@ class DockerDriver(driver.ContainerDriver):
|
||||
if six.PY2 and not isinstance(value, unicode):
|
||||
value = unicode(value)
|
||||
return value.encode('utf-8')
|
||||
|
||||
def create_sandbox(self, context, container, image='kubernetes/pause'):
|
||||
with docker_utils.docker_client() as docker:
|
||||
name = self.get_sandbox_name(container)
|
||||
response = docker.create_container(image, name=name)
|
||||
sandbox_id = response['Id']
|
||||
docker.start(sandbox_id)
|
||||
return sandbox_id
|
||||
|
||||
def delete_sandbox(self, context, sandbox_id):
|
||||
with docker_utils.docker_client() as docker:
|
||||
docker.remove_container(sandbox_id, force=True)
|
||||
|
||||
def get_sandbox_id(self, container):
|
||||
if container.meta:
|
||||
return container.meta.get('sandbox_id', None)
|
||||
else:
|
||||
LOG.warning(_LW("Unexpected missing of sandbox_id"))
|
||||
return None
|
||||
|
||||
def set_sandbox_id(self, container, id):
|
||||
if container.meta is None:
|
||||
container.meta = {'sandbox_id': id}
|
||||
else:
|
||||
container.meta['sandbox_id'] = id
|
||||
|
||||
def get_sandbox_name(self, container):
|
||||
return 'sandbox-' + container.uuid
|
||||
|
||||
def get_addresses(self, context, container):
|
||||
sandbox_id = self.get_sandbox_id(container)
|
||||
with docker_utils.docker_client() as docker:
|
||||
response = docker.inspect_container(sandbox_id)
|
||||
addr = response["NetworkSettings"]["IPAddress"]
|
||||
addresses = {
|
||||
'default': [
|
||||
{
|
||||
'addr': addr,
|
||||
},
|
||||
],
|
||||
}
|
||||
return addresses
|
||||
|
@ -58,7 +58,7 @@ def load_container_driver(container_driver=None):
|
||||
class ContainerDriver(object):
|
||||
'''Base class for container drivers.'''
|
||||
|
||||
def create(self, container):
|
||||
def create(self, container, sandbox_name=None):
|
||||
"""Create a container."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@ -105,3 +105,27 @@ class ContainerDriver(object):
|
||||
def kill(self, container, signal):
|
||||
"""kill signal to a container."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_sandbox(self, context, container, **kwargs):
|
||||
"""Create a sandbox."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete_sandbox(self, context, id):
|
||||
"""Delete a sandbox."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_sandbox_id(self, container):
|
||||
"""Retrieve sandbox ID."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_sandbox_id(self, container, id):
|
||||
"""Set sandbox ID."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_sandbox_name(self, container):
|
||||
"""Retrieve sandbox name."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_addresses(self, context, container):
|
||||
"""Retrieve IP addresses of the container."""
|
||||
raise NotImplementedError()
|
||||
|
@ -0,0 +1,39 @@
|
||||
# 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.
|
||||
|
||||
"""add meta addresses to container
|
||||
|
||||
Revision ID: 4a0c4f7a4a33
|
||||
Revises: 43e1088c3389
|
||||
Create Date: 2016-11-20 12:18:44.086036
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '4a0c4f7a4a33'
|
||||
down_revision = '43e1088c3389'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
from zun.db.sqlalchemy import models
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('container',
|
||||
sa.Column('meta', models.JSONEncodedDict(),
|
||||
nullable=True))
|
||||
op.add_column('container',
|
||||
sa.Column('addresses', models.JSONEncodedDict(),
|
||||
nullable=True))
|
@ -142,6 +142,8 @@ class Container(Base):
|
||||
ports = Column(JSONEncodedList)
|
||||
hostname = Column(String(255))
|
||||
labels = Column(JSONEncodedDict)
|
||||
meta = Column(JSONEncodedDict)
|
||||
addresses = Column(JSONEncodedDict)
|
||||
image_pull_policy = Column(Text, nullable=True)
|
||||
|
||||
|
||||
|
@ -25,7 +25,9 @@ class Container(base.ZunPersistentObject, base.ZunObject,
|
||||
# Version 1.2: Add memory column
|
||||
# Version 1.3: Add task_state column
|
||||
# Version 1.4: Add cpu, workdir, ports, hostname and labels columns
|
||||
VERSION = '1.4'
|
||||
# Version 1.5: Add meta column
|
||||
# Version 1.6: Add addresses column
|
||||
VERSION = '1.6'
|
||||
|
||||
fields = {
|
||||
'id': fields.IntegerField(),
|
||||
@ -46,7 +48,9 @@ class Container(base.ZunPersistentObject, base.ZunObject,
|
||||
'ports': z_fields.ListOfIntegersField(nullable=True),
|
||||
'hostname': fields.StringField(nullable=True),
|
||||
'labels': fields.DictOfStringsField(nullable=True),
|
||||
'image_pull_policy': fields.StringField(nullable=True)
|
||||
'meta': fields.DictOfStringsField(nullable=True),
|
||||
'addresses': z_fields.JsonField(nullable=True),
|
||||
'image_pull_policy': fields.StringField(nullable=True),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
@ -10,6 +10,9 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import six
|
||||
|
||||
from oslo_serialization import jsonutils as json
|
||||
from oslo_versionedobjects import fields
|
||||
|
||||
|
||||
@ -31,9 +34,9 @@ class ContainerStatusField(fields.BaseEnumField):
|
||||
|
||||
class TaskState(fields.Enum):
|
||||
ALL = (
|
||||
IMAGE_PULLING, CONTAINER_CREATING,
|
||||
IMAGE_PULLING, CONTAINER_CREATING, SANDBOX_CREATING,
|
||||
) = (
|
||||
'image_pulling', 'container_creating',
|
||||
'image_pulling', 'container_creating', 'sandbox_creating',
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
@ -47,3 +50,21 @@ class TaskStateField(fields.BaseEnumField):
|
||||
|
||||
class ListOfIntegersField(fields.AutoTypedField):
|
||||
AUTO_TYPE = fields.List(fields.Integer())
|
||||
|
||||
|
||||
class Json(fields.FieldType):
|
||||
def coerce(self, obj, attr, value):
|
||||
if isinstance(value, six.string_types):
|
||||
loaded = json.loads(value)
|
||||
return loaded
|
||||
return value
|
||||
|
||||
def from_primitive(self, obj, attr, value):
|
||||
return self.coerce(obj, attr, value)
|
||||
|
||||
def to_primitive(self, obj, attr, value):
|
||||
return json.dumps(value)
|
||||
|
||||
|
||||
class JsonField(fields.AutoTypedField):
|
||||
AUTO_TYPE = Json()
|
||||
|
@ -18,6 +18,7 @@ from tempest import manager
|
||||
|
||||
from zun.tests.tempest.api.models import container_model
|
||||
from zun.tests.tempest.api.models import service_model
|
||||
from zun.tests.tempest import utils
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
@ -105,3 +106,13 @@ class ZunClient(rest_client.RestClient):
|
||||
resp, body = self.get(self.services_uri(filters), **kwargs)
|
||||
return self.deserialize(resp, body,
|
||||
service_model.ServiceCollection)
|
||||
|
||||
def ensure_container_created(self, container_id):
|
||||
def container_created():
|
||||
_, container = self.get_container(container_id)
|
||||
if container.status == 'Creating':
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
utils.wait_for_condition(container_created)
|
||||
|
@ -10,8 +10,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import time
|
||||
|
||||
from tempest.lib import decorators
|
||||
|
||||
from zun.tests.tempest.api import clients
|
||||
@ -43,6 +41,14 @@ class TestContainer(base.BaseZunTest):
|
||||
|
||||
super(TestContainer, cls).resource_setup()
|
||||
|
||||
def tearDown(self):
|
||||
_, model = self.container_client.list_containers()
|
||||
for c in model.containers:
|
||||
self.container_client.ensure_container_created(c['uuid'])
|
||||
self.container_client.delete_container(c['uuid'])
|
||||
|
||||
super(TestContainer, self).tearDown()
|
||||
|
||||
def _create_container(self, **kwargs):
|
||||
|
||||
model = datagen.container_data(**kwargs)
|
||||
@ -52,26 +58,6 @@ class TestContainer(base.BaseZunTest):
|
||||
|
||||
self.container_client.delete_container(container_id, **kwargs)
|
||||
|
||||
def _wait_on_creation(self, container_id, timeout=60):
|
||||
def _check_status():
|
||||
resp, model = self.container_client.get_container(container_id)
|
||||
status = model.status
|
||||
if status == 'Creating':
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
time.sleep(1)
|
||||
start_time = time.time()
|
||||
end_time = time.time() + timeout
|
||||
while time.time() < end_time:
|
||||
result = _check_status()
|
||||
if result:
|
||||
return result
|
||||
time.sleep(1)
|
||||
raise Exception(("Timed out after %s seconds. Started " +
|
||||
"on %s and ended on %s") % (timeout, start_time,
|
||||
end_time))
|
||||
|
||||
@decorators.idempotent_id('a04f61f2-15ae-4200-83b7-1f311b101f35')
|
||||
def test_container_create_list_delete(self):
|
||||
|
||||
@ -82,8 +68,7 @@ class TestContainer(base.BaseZunTest):
|
||||
self.assertEqual(200, resp.status)
|
||||
self.assertGreater(len(model.containers), 0)
|
||||
# NOTE(mkrai): Check and wait for container creation to get over
|
||||
self._wait_on_creation(container.uuid)
|
||||
|
||||
self.container_client.ensure_container_created(container.uuid)
|
||||
self._delete_container(container.uuid, headers={'force': True})
|
||||
|
||||
resp, model = self.container_client.list_containers()
|
||||
|
25
zun/tests/tempest/utils.py
Normal file
25
zun/tests/tempest/utils.py
Normal file
@ -0,0 +1,25 @@
|
||||
# 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 time
|
||||
|
||||
|
||||
def wait_for_condition(condition, interval=1, timeout=60):
|
||||
start_time = time.time()
|
||||
end_time = time.time() + timeout
|
||||
while time.time() < end_time:
|
||||
result = condition()
|
||||
if result:
|
||||
return result
|
||||
time.sleep(interval)
|
||||
raise Exception(("Timed out after %s seconds. Started on %s and ended "
|
||||
"on %s") % (timeout, start_time, end_time))
|
@ -62,56 +62,67 @@ class TestManager(base.TestCase):
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch('zun.image.driver.pull_image')
|
||||
@mock.patch.object(fake_driver, 'create')
|
||||
def test_container_create(self, mock_create, mock_pull, mock_save):
|
||||
@mock.patch.object(fake_driver, 'create_sandbox')
|
||||
def test_container_create(self, mock_create_sandbox, mock_create,
|
||||
mock_pull, mock_save):
|
||||
container = Container(self.context, **utils.get_test_container())
|
||||
mock_pull.return_value = 'fake_path'
|
||||
mock_create_sandbox.return_value = 'fake_id'
|
||||
self.compute_manager._do_container_create(self.context, container)
|
||||
mock_save.assert_called_with()
|
||||
mock_pull.assert_called_once_with(self.context,
|
||||
container.image,
|
||||
'latest', 'always')
|
||||
mock_create.assert_called_once_with(container, 'fake_path')
|
||||
mock_pull.assert_any_call(self.context, container.image, 'latest',
|
||||
'always')
|
||||
mock_create.assert_called_once_with(container, 'fake_id', 'fake_path')
|
||||
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch.object(fake_driver, 'create_sandbox')
|
||||
@mock.patch('zun.image.driver.pull_image')
|
||||
@mock.patch.object(manager.Manager, '_fail_container')
|
||||
def test_container_create_pull_image_failed_docker_error(
|
||||
self, mock_fail, mock_pull, mock_save):
|
||||
self, mock_fail, mock_pull, mock_create_sandbox, mock_save):
|
||||
container = Container(self.context, **utils.get_test_container())
|
||||
mock_pull.side_effect = exception.DockerError("Pull Failed")
|
||||
mock_create_sandbox.return_value = mock.MagicMock()
|
||||
self.compute_manager._do_container_create(self.context, container)
|
||||
mock_fail.assert_called_once_with(container, "Pull Failed")
|
||||
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch.object(fake_driver, 'create_sandbox')
|
||||
@mock.patch('zun.image.driver.pull_image')
|
||||
@mock.patch.object(manager.Manager, '_fail_container')
|
||||
def test_container_create_pull_image_failed_image_not_found(
|
||||
self, mock_fail, mock_pull, mock_save):
|
||||
self, mock_fail, mock_pull, mock_create_sandbox, mock_save):
|
||||
container = Container(self.context, **utils.get_test_container())
|
||||
mock_pull.side_effect = exception.ImageNotFound("Image Not Found")
|
||||
mock_create_sandbox.return_value = mock.MagicMock()
|
||||
self.compute_manager._do_container_create(self.context, container)
|
||||
mock_fail.assert_called_once_with(container, "Image Not Found")
|
||||
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch.object(fake_driver, 'create_sandbox')
|
||||
@mock.patch('zun.image.driver.pull_image')
|
||||
@mock.patch.object(manager.Manager, '_fail_container')
|
||||
def test_container_create_pull_image_failed_zun_exception(
|
||||
self, mock_fail, mock_pull, mock_save):
|
||||
self, mock_fail, mock_pull, mock_create_sandbox, mock_save):
|
||||
container = Container(self.context, **utils.get_test_container())
|
||||
mock_pull.side_effect = exception.ZunException(
|
||||
message="Image Not Found")
|
||||
mock_create_sandbox.return_value = mock.MagicMock()
|
||||
self.compute_manager._do_container_create(self.context, container)
|
||||
mock_fail.assert_called_once_with(container, "Image Not Found")
|
||||
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch('zun.image.driver.pull_image')
|
||||
@mock.patch.object(fake_driver, 'create')
|
||||
@mock.patch.object(fake_driver, 'create_sandbox')
|
||||
@mock.patch.object(manager.Manager, '_fail_container')
|
||||
def test_container_create_docker_create_failed(self, mock_fail,
|
||||
mock_create_sandbox,
|
||||
mock_create, mock_pull,
|
||||
mock_save):
|
||||
container = Container(self.context, **utils.get_test_container())
|
||||
mock_create.side_effect = exception.DockerError("Creation Failed")
|
||||
mock_create_sandbox.return_value = mock.MagicMock()
|
||||
self.compute_manager._do_container_create(self.context, container)
|
||||
mock_fail.assert_called_once_with(container, "Creation Failed")
|
||||
|
||||
@ -127,10 +138,9 @@ class TestManager(base.TestCase):
|
||||
container.status = 'Stopped'
|
||||
self.compute_manager.container_run(self.context, container)
|
||||
mock_save.assert_called_with()
|
||||
mock_pull.assert_called_once_with(self.context,
|
||||
container.image,
|
||||
'latest', 'always')
|
||||
mock_create.assert_called_once_with(container, 'fake_path')
|
||||
mock_pull.assert_any_call(self.context, container.image, 'latest',
|
||||
'always')
|
||||
mock_create.assert_called_once_with(container, None, 'fake_path')
|
||||
mock_start.assert_called_once_with(container)
|
||||
|
||||
@mock.patch.object(Container, 'save')
|
||||
@ -147,9 +157,8 @@ class TestManager(base.TestCase):
|
||||
container)
|
||||
mock_save.assert_called_with()
|
||||
mock_fail.assert_called_with(container, 'Image Not Found')
|
||||
mock_pull.assert_called_once_with(self.context,
|
||||
container.image,
|
||||
'latest', 'always')
|
||||
mock_pull.assert_called_once_with(self.context, 'kubernetes/pause',
|
||||
'latest', 'ifnotpresent')
|
||||
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch('zun.image.driver.pull_image')
|
||||
@ -165,9 +174,8 @@ class TestManager(base.TestCase):
|
||||
container)
|
||||
mock_save.assert_called_with()
|
||||
mock_fail.assert_called_with(container, 'Image Not Found')
|
||||
mock_pull.assert_called_once_with(self.context,
|
||||
container.image,
|
||||
'latest', 'always')
|
||||
mock_pull.assert_called_once_with(self.context, 'kubernetes/pause',
|
||||
'latest', 'ifnotpresent')
|
||||
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch('zun.image.driver.pull_image')
|
||||
@ -183,9 +191,8 @@ class TestManager(base.TestCase):
|
||||
container)
|
||||
mock_save.assert_called_with()
|
||||
mock_fail.assert_called_with(container, 'Docker Error occurred')
|
||||
mock_pull.assert_called_once_with(self.context,
|
||||
container.image,
|
||||
'latest', 'always')
|
||||
mock_pull.assert_called_once_with(self.context, 'kubernetes/pause',
|
||||
'latest', 'ifnotpresent')
|
||||
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch('zun.image.driver.pull_image')
|
||||
@ -204,10 +211,9 @@ class TestManager(base.TestCase):
|
||||
container)
|
||||
mock_save.assert_called_with()
|
||||
mock_fail.assert_called_with(container, 'Docker Error occurred')
|
||||
mock_pull.assert_called_once_with(self.context,
|
||||
container.image,
|
||||
'latest', 'always')
|
||||
mock_create.assert_called_once_with(container,
|
||||
mock_pull.assert_any_call(self.context, container.image, 'latest',
|
||||
'always')
|
||||
mock_create.assert_called_once_with(container, None,
|
||||
{'name': 'nginx', 'path': None})
|
||||
|
||||
@mock.patch.object(manager.Manager, '_validate_container_state')
|
||||
|
@ -68,3 +68,18 @@ class FakeDriver(driver.ContainerDriver):
|
||||
@check_container_id
|
||||
def kill(self, container, signal=None):
|
||||
pass
|
||||
|
||||
def create_sandbox(self, context, name, **kwargs):
|
||||
pass
|
||||
|
||||
def delete_sandbox(self, context, id):
|
||||
pass
|
||||
|
||||
def get_sandbox_id(self, container):
|
||||
pass
|
||||
|
||||
def set_sandbox_id(self, container, id):
|
||||
pass
|
||||
|
||||
def get_addresses(self, context, container):
|
||||
pass
|
||||
|
@ -44,6 +44,17 @@ def get_test_container(**kw):
|
||||
'ports': kw.get('ports', [80, 443]),
|
||||
'hostname': kw.get('hostname', 'testhost'),
|
||||
'labels': kw.get('labels', {'key1': 'val1', 'key2': 'val2'}),
|
||||
'meta': kw.get('meta', {'key1': 'val1', 'key2': 'val2'}),
|
||||
'addresses': kw.get('addresses', {
|
||||
'private': [
|
||||
{
|
||||
'OS-EXT-IPS-MAC:mac_addr': 'fa:16:3e:04:da:76',
|
||||
'version': 4,
|
||||
'addr': '10.0.0.12',
|
||||
'OS-EXT-IPS:type': 'fixed'
|
||||
},
|
||||
],
|
||||
}),
|
||||
'image_pull_policy': kw.get('image_pull_policy', 'always'),
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user