From b03fbe6dfddea496bc156ee45c44f972d4ebd580 Mon Sep 17 00:00:00 2001 From: rajat29 Date: Fri, 15 Jun 2018 14:50:48 +0530 Subject: [PATCH] Convert type of 'command' from string to list Zun is currently using a string representation of 'command', but Docker is returning 'Cmd' in string representation. This requires a conversion of commands between list and string, which is complicated and error-prone (it might causes bugs like https://review.openstack.org/#/c/551795/). It is better to switch from string to list Co-Authored-By: Hongbin Lu Change-Id: Ie0df504a67cc20907d35e3513fd80d3f6be687fd Closes-Bug: #1755019 --- zun/api/controllers/v1/containers.py | 25 +++++++ zun/api/controllers/v1/schemas/containers.py | 12 ++- .../controllers/v1/schemas/parameter_types.py | 4 + zun/api/controllers/versions.py | 3 +- zun/api/rest_api_version_history.rst | 5 ++ zun/api/utils.py | 2 + zun/container/docker/driver.py | 13 +--- ...convert_type_of_command_from_string_to_.py | 55 ++++++++++++++ zun/db/sqlalchemy/models.py | 2 +- zun/objects/container.py | 4 +- zun/tests/unit/api/base.py | 2 +- zun/tests/unit/api/controllers/test_root.py | 4 +- .../api/controllers/v1/test_containers.py | 74 +++++++++---------- zun/tests/unit/api/test_validations.py | 5 +- .../container/docker/test_docker_driver.py | 6 +- zun/tests/unit/db/utils.py | 2 +- zun/tests/unit/objects/test_objects.py | 2 +- 17 files changed, 156 insertions(+), 64 deletions(-) create mode 100644 zun/db/sqlalchemy/alembic/versions/3e80bbfd8da7_convert_type_of_command_from_string_to_.py diff --git a/zun/api/controllers/v1/containers.py b/zun/api/controllers/v1/containers.py index 511cfa4f8..e59a66c12 100644 --- a/zun/api/controllers/v1/containers.py +++ b/zun/api/controllers/v1/containers.py @@ -13,11 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. +import shlex + from neutronclient.common import exceptions as n_exc from oslo_log import log as logging from oslo_utils import strutils from oslo_utils import uuidutils import pecan +import six from zun.api.controllers import base from zun.api.controllers import link @@ -269,12 +272,34 @@ class ContainersController(base.Controller): name = name_gen.generate() return name + '-container' + @base.Controller.api_version("1.1", "1.19") + @pecan.expose('json') + @api_utils.enforce_content_types(['application/json']) + @exception.wrap_pecan_controller_exception + @validation.validate_query_param(pecan.request, schema.query_param_create) + @validation.validated(schema.legacy_container_create) + def post(self, run=False, **container_dict): + # NOTE(hongbin): We convert the representation of 'command' from + # string to list. For example: + # '"nginx" "-g" "daemon off;"' -> ["nginx", "-g", "daemon off;"] + command = container_dict.pop('command', None) + if command is not None: + if isinstance(command, six.string_types): + command = shlex.split(command) + container_dict['command'] = command + + return self._do_post(run, **container_dict) + + @base.Controller.api_version("1.20") # noqa @pecan.expose('json') @api_utils.enforce_content_types(['application/json']) @exception.wrap_pecan_controller_exception @validation.validate_query_param(pecan.request, schema.query_param_create) @validation.validated(schema.container_create) def post(self, run=False, **container_dict): + return self._do_post(run, **container_dict) + + def _do_post(self, run=False, **container_dict): """Create or run a new container. :param run: if true, starts the container diff --git a/zun/api/controllers/v1/schemas/containers.py b/zun/api/controllers/v1/schemas/containers.py index 952956552..ab9b05603 100644 --- a/zun/api/controllers/v1/schemas/containers.py +++ b/zun/api/controllers/v1/schemas/containers.py @@ -14,7 +14,7 @@ import copy from zun.api.controllers.v1.schemas import parameter_types -_container_properties = { +_legacy_container_properties = { 'name': parameter_types.container_name, 'image': parameter_types.image_name, 'command': parameter_types.command, @@ -39,6 +39,16 @@ _container_properties = { 'auto_heal': parameter_types.boolean, } +legacy_container_create = { + 'type': 'object', + 'properties': _legacy_container_properties, + 'required': ['image'], + 'additionalProperties': False +} + +_container_properties = copy.deepcopy(_legacy_container_properties) +_container_properties['command'] = parameter_types.command_list + container_create = { 'type': 'object', 'properties': _container_properties, diff --git a/zun/api/controllers/v1/schemas/parameter_types.py b/zun/api/controllers/v1/schemas/parameter_types.py index 165b2e71c..524463316 100644 --- a/zun/api/controllers/v1/schemas/parameter_types.py +++ b/zun/api/controllers/v1/schemas/parameter_types.py @@ -81,6 +81,10 @@ command = { 'type': ['string', 'null'] } +command_list = { + 'type': ['array', 'null'] +} + auto_remove = { 'type': ['boolean', 'null'] } diff --git a/zun/api/controllers/versions.py b/zun/api/controllers/versions.py index 07cf5e6e7..7fa599d59 100644 --- a/zun/api/controllers/versions.py +++ b/zun/api/controllers/versions.py @@ -52,10 +52,11 @@ REST_API_VERSION_HISTORY = """REST API Version History: * 1.17 - Add support for detaching ports * 1.18 - Modify the response of network list * 1.19 - Intoduce container resize API + * 1.20 - Convert type of 'command' from string to list """ BASE_VER = '1.1' -CURRENT_MAX_VER = '1.19' +CURRENT_MAX_VER = '1.20' class Version(object): diff --git a/zun/api/rest_api_version_history.rst b/zun/api/rest_api_version_history.rst index a77819ae2..72cc5d109 100644 --- a/zun/api/rest_api_version_history.rst +++ b/zun/api/rest_api_version_history.rst @@ -167,3 +167,8 @@ user documentation. Introduce an API endpoint for resizing a container, such as changing the CPU or memory of the container. + +1.20 +---- + + Convert type of 'command' from string to list diff --git a/zun/api/utils.py b/zun/api/utils.py index cc953b741..6cd14b834 100644 --- a/zun/api/utils.py +++ b/zun/api/utils.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import functools from oslo_utils import uuidutils import pecan @@ -96,6 +97,7 @@ def enforce_content_types(valid_content_types): def content_types_decorator(fn): + @functools.wraps(fn) def content_types_enforcer(inst, *args, **kwargs): _do_enforce_content_types(pecan.request, valid_content_types) return fn(inst, *args, **kwargs) diff --git a/zun/container/docker/driver.py b/zun/container/docker/driver.py index 3eae8d92e..eae46bf70 100644 --- a/zun/container/docker/driver.py +++ b/zun/container/docker/driver.py @@ -639,18 +639,7 @@ class DockerDriver(driver.ContainerDriver): def _populate_command(self, container, config): command_list = config.get('Cmd') - command_str = None - if command_list: - # NOTE(hongbin): We convert the representation of the command - # from list to string. For example: - # * list: ["nginx", "-g", "daemon off;"] - # * string: '"nginx" "-g" "daemon off;"' - # In the string representation, we quote each command's token - # to avoid potential ambiguity (without quoting, the string - # representation will be 'nginx -g daemon off;' so we don't - # how the original command arguments were tokenized). - command_str = ' '.join('"%s"' % x for x in command_list) - container.command = command_str + container.command = command_list def _populate_hostname_and_ports(self, container, config): # populate hostname only when container.hostname wasn't set diff --git a/zun/db/sqlalchemy/alembic/versions/3e80bbfd8da7_convert_type_of_command_from_string_to_.py b/zun/db/sqlalchemy/alembic/versions/3e80bbfd8da7_convert_type_of_command_from_string_to_.py new file mode 100644 index 000000000..073dbfc6e --- /dev/null +++ b/zun/db/sqlalchemy/alembic/versions/3e80bbfd8da7_convert_type_of_command_from_string_to_.py @@ -0,0 +1,55 @@ +# 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. + + +"""Convert type of 'command' from string to list + +Revision ID: 3e80bbfd8da7 +Revises: 26896d5f9053 +Create Date: 2018-06-20 11:21:38.077673 + +""" + +import json +import shlex + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3e80bbfd8da7' +down_revision = '26896d5f9053' +branch_labels = None +depends_on = None + + +TABLE_MODEL = sa.Table( + 'container', sa.MetaData(), + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('command', sa.Text())) + + +def upgrade(): + op.alter_column('container', 'command', type_=sa.Text()) + # Convert 'command' from string to json-encoded list + session = sa.orm.Session(bind=op.get_bind()) + with session.begin(subtransactions=True): + for row in session.query(TABLE_MODEL): + if row[1]: + command = shlex.split(row[1]) + command = json.dumps(command) + session.execute( + TABLE_MODEL.update().values( + command=command).where( + TABLE_MODEL.c.id == row[0])) + session.commit() diff --git a/zun/db/sqlalchemy/models.py b/zun/db/sqlalchemy/models.py index c34df6f79..bf5189059 100644 --- a/zun/db/sqlalchemy/models.py +++ b/zun/db/sqlalchemy/models.py @@ -142,7 +142,7 @@ class Container(Base): name = Column(String(255)) image = Column(String(255)) cpu = Column(Float) - command = Column(String(255)) + command = Column(JSONEncodedList) memory = Column(String(255)) status = Column(String(20)) status_reason = Column(Text, nullable=True) diff --git a/zun/objects/container.py b/zun/objects/container.py index eaaecec29..687847cd9 100644 --- a/zun/objects/container.py +++ b/zun/objects/container.py @@ -62,7 +62,7 @@ class Container(base.ZunPersistentObject, base.ZunObject): # Version 1.30: Add capsule_id attribute # Version 1.31: Add 'started_at' attribute # Version 1.32: Add 'exec_instances' attribute - VERSION = '1.32' + VERSION = '1.33' fields = { 'id': fields.IntegerField(), @@ -74,7 +74,7 @@ class Container(base.ZunPersistentObject, base.ZunObject): 'image': fields.StringField(nullable=True), 'cpu': fields.FloatField(nullable=True), 'memory': fields.StringField(nullable=True), - 'command': fields.StringField(nullable=True), + 'command': fields.ListOfStringsField(nullable=True), 'status': z_fields.ContainerStatusField(nullable=True), 'status_reason': fields.StringField(nullable=True), 'task_state': z_fields.TaskStateField(nullable=True), diff --git a/zun/tests/unit/api/base.py b/zun/tests/unit/api/base.py index db985e73e..d4638de90 100644 --- a/zun/tests/unit/api/base.py +++ b/zun/tests/unit/api/base.py @@ -26,7 +26,7 @@ from zun.tests.unit.db import base PATH_PREFIX = '/v1' -CURRENT_VERSION = "container 1.19" +CURRENT_VERSION = "container 1.20" class FunctionalTest(base.DbTestCase): diff --git a/zun/tests/unit/api/controllers/test_root.py b/zun/tests/unit/api/controllers/test_root.py index 1605957c9..33e607124 100644 --- a/zun/tests/unit/api/controllers/test_root.py +++ b/zun/tests/unit/api/controllers/test_root.py @@ -28,7 +28,7 @@ class TestRootController(api_base.FunctionalTest): 'default_version': {'id': 'v1', 'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}], - 'max_version': '1.19', + 'max_version': '1.20', 'min_version': '1.1', 'status': 'CURRENT'}, 'description': 'Zun is an OpenStack project which ' @@ -37,7 +37,7 @@ class TestRootController(api_base.FunctionalTest): 'versions': [{'id': 'v1', 'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}], - 'max_version': '1.19', + 'max_version': '1.20', 'min_version': '1.1', 'status': 'CURRENT'}]} diff --git a/zun/tests/unit/api/controllers/v1/test_containers.py b/zun/tests/unit/api/controllers/v1/test_containers.py index 070c24dba..2202cd45f 100644 --- a/zun/tests/unit/api/controllers/v1/test_containers.py +++ b/zun/tests/unit/api/controllers/v1/test_containers.py @@ -33,7 +33,7 @@ class TestContainerController(api_base.FunctionalTest): mock_container_create.side_effect = lambda x, y, **z: y params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"}}') response = self.post('/v1/containers?run=true', @@ -50,7 +50,7 @@ class TestContainerController(api_base.FunctionalTest): def test_run_container_wrong_run_value(self, mock_search, mock_container_create): params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"}}') with self.assertRaisesRegex(AppError, "Invalid input for query parameters"): @@ -60,7 +60,7 @@ class TestContainerController(api_base.FunctionalTest): @patch('zun.compute.api.API.container_create') def test_run_container_wrong_memory_value(self, mock_container_create): params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "3",' + '"command": ["env"], "memory": "3",' '"environment": {"key1": "val1", "key2": "val2"}}') with self.assertRaisesRegex(AppError, "Invalid input for query parameters"): @@ -70,7 +70,7 @@ class TestContainerController(api_base.FunctionalTest): @patch('zun.compute.api.API.container_create') def test_run_container_wrong_cpu_value(self, mock_container_create): params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512", "cpu": "100"' + '"command": ["env"], "memory": "512", "cpu": "100"' '"environment": {"key1": "val1", "key2": "val2"}}') with self.assertRaisesRegex(AppError, "Invalid input for query parameters"): @@ -80,7 +80,7 @@ class TestContainerController(api_base.FunctionalTest): @patch('zun.compute.api.API.container_create') def test_run_container_wrong_disk_value(self, mock_container_create): params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512", "disk": "0"' + '"command": ["env"], "memory": "512", "disk": "0"' '"environment": {"key1": "val1", "key2": "val2"}}') with self.assertRaisesRegex(AppError, "Invalid input for query parameters"): @@ -101,7 +101,7 @@ class TestContainerController(api_base.FunctionalTest): def test_run_container_runtime_wrong_value(self): params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"},' '"runtime": 1234}') with self.assertRaisesRegex(AppError, @@ -130,7 +130,7 @@ class TestContainerController(api_base.FunctionalTest): mock_container_create.side_effect = lambda x, y, **z: y params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"}}') response = self.post('/v1/containers?run=false', params=params, @@ -146,7 +146,7 @@ class TestContainerController(api_base.FunctionalTest): mock_container_create): mock_container_create.side_effect = exception.InvalidValue params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"}}') self.assertRaises(AppError, self.post, '/v1/containers?run=wrong', params=params, content_type='application/json') @@ -159,7 +159,7 @@ class TestContainerController(api_base.FunctionalTest): mock_neutron_get_network): mock_container_create.side_effect = lambda x, y, **z: y params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"}}') response = self.post('/v1/containers/', params=params, @@ -181,7 +181,7 @@ class TestContainerController(api_base.FunctionalTest): mock_container_create.side_effect = lambda x, y, **z: y mock_can.return_value = True params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"}}') response = self.post('/v1/containers/', params=params, @@ -197,7 +197,7 @@ class TestContainerController(api_base.FunctionalTest): def test_create_container_image_not_specified(self, mock_container_create): params = ('{"name": "MyDocker",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"}}') with self.assertRaisesRegex(AppError, "is a required property"): @@ -219,7 +219,7 @@ class TestContainerController(api_base.FunctionalTest): mock_container_create.side_effect = _create_side_effect params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"}}') self.post('/v1/containers/', params=params, @@ -235,7 +235,7 @@ class TestContainerController(api_base.FunctionalTest): mock_container_create.side_effect = lambda x, y, **z: y # Create a container with a command params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"}}') response = self.post('/v1/containers/', params=params, @@ -267,7 +267,7 @@ class TestContainerController(api_base.FunctionalTest): mock_search_volume.return_value = fake_volume # Create a container with a command params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"},' '"runtime": "runc", "hostname": "testhost",' '"disk": 20, "restart_policy": {"Name": "no"},' @@ -283,7 +283,7 @@ class TestContainerController(api_base.FunctionalTest): c = response.json['containers'][0] self.assertIsNotNone(c.get('uuid')) self.assertEqual('MyDocker', c.get('name')) - self.assertEqual('env', c.get('command')) + self.assertEqual(["env"], c.get('command')) self.assertEqual('512', c.get('memory')) self.assertEqual({"key1": "val1", "key2": "val2"}, c.get('environment')) @@ -341,7 +341,7 @@ class TestContainerController(api_base.FunctionalTest): c = response.json['containers'][0] self.assertIsNotNone(c.get('uuid')) self.assertIsNotNone(c.get('name')) - self.assertIsNone(c.get('command')) + self.assertIsNone(None, c.get('command')) self.assertEqual('2048', c.get('memory')) self.assertEqual(1.0, c.get('cpu')) # TODO(kiennt): Uncomment it when bug [1] be resolved. @@ -370,7 +370,7 @@ class TestContainerController(api_base.FunctionalTest): mock_neutron_get_network.return_value = fake_network # Create a container with a command params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env",' + '"command": ["env"],' '"availability_zone": "test-az"}') response = self.post('/v1/containers/', params=params, @@ -382,7 +382,7 @@ class TestContainerController(api_base.FunctionalTest): c = response.json['containers'][0] self.assertIsNotNone(c.get('uuid')) self.assertEqual('MyDocker', c.get('name')) - self.assertEqual('env', c.get('command')) + self.assertEqual(["env"], c.get('command')) self.assertEqual('2048', c.get('memory')) self.assertEqual(1.0, c.get('cpu')) # TODO(kiennt): Uncomment it when bug [1] be resolved. @@ -409,7 +409,7 @@ class TestContainerController(api_base.FunctionalTest): mock_neutron_get_network.return_value = fake_network # Create a container with a command params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"restart_policy": {"Name": "no",' '"MaximumRetryCount": "0"}}') response = self.post('/v1/containers/', @@ -422,7 +422,7 @@ class TestContainerController(api_base.FunctionalTest): c = response.json['containers'][0] self.assertIsNotNone(c.get('uuid')) self.assertEqual('MyDocker', c.get('name')) - self.assertEqual('env', c.get('command')) + self.assertEqual(["env"], c.get('command')) self.assertEqual('512', c.get('memory')) self.assertEqual({"Name": "no", "MaximumRetryCount": "0"}, c.get('restart_policy')) @@ -446,7 +446,7 @@ class TestContainerController(api_base.FunctionalTest): mock_neutron_get_network.return_value = fake_network # Create a container with a command params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"restart_policy": {"Name": "no",' '"MaximumRetryCount": "6"}}') response = self.post('/v1/containers/', @@ -459,7 +459,7 @@ class TestContainerController(api_base.FunctionalTest): c = response.json['containers'][0] self.assertIsNotNone(c.get('uuid')) self.assertEqual('MyDocker', c.get('name')) - self.assertEqual('env', c.get('command')) + self.assertEqual(["env"], c.get('command')) self.assertEqual('512', c.get('memory')) self.assertEqual({"Name": "no", "MaximumRetryCount": "0"}, c.get('restart_policy')) @@ -483,7 +483,7 @@ class TestContainerController(api_base.FunctionalTest): mock_neutron_get_network.return_value = fake_network # Create a container with a command params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"restart_policy": {"Name": "unless-stopped",' '"MaximumRetryCount": "0"}}') response = self.post('/v1/containers/', @@ -496,7 +496,7 @@ class TestContainerController(api_base.FunctionalTest): c = response.json['containers'][0] self.assertIsNotNone(c.get('uuid')) self.assertEqual('MyDocker', c.get('name')) - self.assertEqual('env', c.get('command')) + self.assertEqual(["env"], c.get('command')) self.assertEqual('512', c.get('memory')) self.assertEqual({"Name": "unless-stopped", "MaximumRetryCount": "0"}, c.get('restart_policy')) @@ -528,7 +528,7 @@ class TestContainerController(api_base.FunctionalTest): mock_show_port.return_value = {'port': fake_port} # Create a container with a command params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"},' '"nets": [{"port": "testport"}]}') response = self.post('/v1/containers/', @@ -541,7 +541,7 @@ class TestContainerController(api_base.FunctionalTest): c = response.json['containers'][0] self.assertIsNotNone(c.get('uuid')) self.assertEqual('MyDocker', c.get('name')) - self.assertEqual('env', c.get('command')) + self.assertEqual(["env"], c.get('command')) self.assertEqual('512', c.get('memory')) self.assertEqual({"key1": "val1", "key2": "val2"}, c.get('environment')) @@ -584,7 +584,7 @@ class TestContainerController(api_base.FunctionalTest): mock_get_network.return_value = fake_public_network # Create a container with a command params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"},' '"nets": [{"network": "testpublicnet"}]}') response = self.post('/v1/containers/', @@ -619,13 +619,13 @@ class TestContainerController(api_base.FunctionalTest): mock_get_network.return_value = fake_network # Create a container with a command params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"},' '"nets": [{"network": "fakenetid", "v4-fixed-ip": ' '"10.0.0.10"}]}') - response = self.app.post('/v1/containers/', - params=params, - content_type='application/json') + response = self.post('/v1/containers/', + params=params, + content_type='application/json') fake_admin_authorize = True mock_authorize.return_value = fake_admin_authorize self.assertEqual(202, response.status_int) @@ -648,7 +648,7 @@ class TestContainerController(api_base.FunctionalTest): mock_create_volume.return_value = fake_volume # Create a container with a command params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"mounts": [{"destination": "d", ' '"size": "5"}]}') response = self.post('/v1/containers/', @@ -665,7 +665,7 @@ class TestContainerController(api_base.FunctionalTest): c = response.json['containers'][0] self.assertIsNotNone(c.get('uuid')) self.assertEqual('MyDocker', c.get('name')) - self.assertEqual('env', c.get('command')) + self.assertEqual(["env"], c.get('command')) self.assertEqual('Creating', c.get('status')) self.assertEqual('512', c.get('memory')) self.assertIn('host', c) @@ -692,7 +692,7 @@ class TestContainerController(api_base.FunctionalTest): mock_container_create.side_effect = lambda x, y, **z: y # Create a container with a command params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"restart_policy": {"Name": "always",' '"MaximumRetryCount": "1"}}') with self.assertRaisesRegex( @@ -709,7 +709,7 @@ class TestContainerController(api_base.FunctionalTest): mock_container_create): # Long name params = ('{"name": "' + 'i' * 256 + '", "image": "ubuntu",' - '"command": "env", "memory": "512"}') + '"command": ["env"], "memory": "512"}') self.assertRaises(AppError, self.post, '/v1/containers/', params=params, content_type='application/json') self.assertTrue(mock_container_create.not_called) @@ -1476,7 +1476,7 @@ class TestContainerController(api_base.FunctionalTest): mock_container_create.side_effect = lambda x, y, **z: y # Create a container with a command params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512",' + '"command": ["env"], "memory": "512",' '"environment": {"key1": "val1", "key2": "val2"},' '"image_driver": "glance"}') response = self.post('/v1/containers/', @@ -2049,7 +2049,7 @@ class TestContainerEnforcement(api_base.FunctionalTest): def test_policy_disallow_create(self): params = ('{"name": "MyDocker", "image": "ubuntu",' - '"command": "env", "memory": "512"}') + '"command": ["env"], "memory": "512"}') self._common_policy_check( 'container:create', self.post, '/v1/containers/', diff --git a/zun/tests/unit/api/test_validations.py b/zun/tests/unit/api/test_validations.py index 987668d42..6b63f18ce 100644 --- a/zun/tests/unit/api/test_validations.py +++ b/zun/tests/unit/api/test_validations.py @@ -21,7 +21,7 @@ CONTAINER_CREATE = { 'properties': { 'name': parameter_types.container_name, 'image': parameter_types.image_name, - 'command': parameter_types.command, + 'command': parameter_types.command_list, 'cpu': parameter_types.cpu, 'memory': parameter_types.memory, 'workdir': parameter_types.workdir, @@ -55,7 +55,8 @@ class TestSchemaValidations(base.BaseTestCase): def test_create_schema_with_all_valid_parameters(self): request_to_validate = {'name': 'test1', 'image': 'nginx', - 'command': '/bin/sh', 'cpu': 1.0, + 'command': ["/bin/sh"], + 'cpu': 1.0, 'memory': '5', 'workdir': '/workdir', 'image_pull_policy': 'never', 'labels': {'abc': 12, 'bcd': 'xyz'}, diff --git a/zun/tests/unit/container/docker/test_docker_driver.py b/zun/tests/unit/container/docker/test_docker_driver.py index d279fcf89..a2f762dab 100644 --- a/zun/tests/unit/container/docker/test_docker_driver.py +++ b/zun/tests/unit/container/docker/test_docker_driver.py @@ -138,7 +138,7 @@ class TestDockerDriver(base.DriverTestCase): kwargs = { 'name': '%sea8e2a25-2901-438d-8157-de7ffd68d051' % consts.NAME_PREFIX, - 'command': 'fake_command', + 'command': ['fake_command'], 'environment': {'key1': 'val1', 'key2': 'val2'}, 'working_dir': '/home/ubuntu', 'labels': {'key1': 'val1', 'key2': 'val2'}, @@ -204,7 +204,7 @@ class TestDockerDriver(base.DriverTestCase): kwargs = { 'name': '%sea8e2a25-2901-438d-8157-de7ffd68d051' % consts.NAME_PREFIX, - 'command': 'fake_command', + 'command': ['fake_command'], 'environment': {'key1': 'val1', 'key2': 'val2'}, 'working_dir': '/home/ubuntu', 'labels': {'key1': 'val1', 'key2': 'val2'}, @@ -389,7 +389,7 @@ class TestDockerDriver(base.DriverTestCase): self.driver.show(self.context, mock_container) self.mock_docker.inspect_container.assert_called_once_with( mock_container.container_id) - self.assertEqual('"fake_command"', mock_container.command) + self.assertEqual(['fake_command'], mock_container.command) def test_show_without_command(self): self.mock_docker.inspect_container = mock.Mock( diff --git a/zun/tests/unit/db/utils.py b/zun/tests/unit/db/utils.py index 2c0694b4e..ca50ed5ab 100644 --- a/zun/tests/unit/db/utils.py +++ b/zun/tests/unit/db/utils.py @@ -54,7 +54,7 @@ def get_test_container(**kwargs): 'image': kwargs.get('image', 'ubuntu'), 'created_at': kwargs.get('created_at'), 'updated_at': kwargs.get('updated_at'), - 'command': kwargs.get('command', 'fake_command'), + 'command': kwargs.get('command', ['fake_command']), 'status': kwargs.get('status', 'Running'), 'status_reason': kwargs.get('status_reason', 'Created Successfully'), 'task_state': kwargs.get('task_state', None), diff --git a/zun/tests/unit/objects/test_objects.py b/zun/tests/unit/objects/test_objects.py index 206598c38..8ed5bef4c 100644 --- a/zun/tests/unit/objects/test_objects.py +++ b/zun/tests/unit/objects/test_objects.py @@ -344,7 +344,7 @@ class TestObject(test_base.TestCase, _TestObject): # For more information on object version testing, read # https://docs.openstack.org/zun/latest/ object_data = { - 'Container': '1.32-9e9a594ca58e978fb7b580292692a1a7', + 'Container': '1.33-5eac0a995f25329ca566fdddde45c759', 'VolumeMapping': '1.1-50df6202f7846a136a91444c38eba841', 'Image': '1.1-330e6205c80b99b59717e1cfc6a79935', 'MyObj': '1.0-34c4b1aadefd177b13f9a2f894cc23cd',