Add runtime parameter to container API

This commit adds a new parameter `runtime` to
container create and run APIs so that Zun can
support passing multiple runtimes in future.
Currently Zun only supports `runc` tool.

Change-Id: I2795a4555032e52707e0344daa9dc97ad832650b
Partial-Implements: blueprint support-secure-container
This commit is contained in:
Madhuri Kumari 2017-08-04 16:01:19 +05:30
parent bd0d0de761
commit 6522a75c51
24 changed files with 207 additions and 32 deletions

View File

@ -47,6 +47,7 @@ Request
- image_driver: image_driver
- security_groups: security_groups
- nets: nets
- runtime: runtime
Request Example
----------------

View File

@ -210,6 +210,12 @@ restart_policy:
no, on-failure[:max-retry], always, unless-stopped.
in: body
type: string
runtime:
description: |
The container runtime tool to create container with. Allowed values
are runc.
in: body
type: string
security_groups:
description: |
Security groups to be added to the container.

View File

@ -26,5 +26,6 @@
"v6-fixed-ip": "",
"port": "890699a9-4690-4bd6-8b70-3a9c1be77ecb"
}
]
],
"runtime": "runc"
}

View File

@ -33,5 +33,6 @@
"ports": null,
"command": "/bin/sh -c 'echo hello'",
"cpu": 2.0,
"interactive": false
"interactive": false,
"runtime": "runc"
}

View File

@ -235,6 +235,39 @@ class ContainersController(base.Controller):
'"false", True, False, "True" and "False"')
raise exception.InvalidValue(msg)
auto_remove = container_dict.pop('auto_remove', None)
if auto_remove is not None:
req_version = pecan.request.version
min_version = versions.Version('', '', '', '1.3')
if req_version >= min_version:
try:
container_dict['auto_remove'] = strutils.bool_from_string(
auto_remove, strict=True)
except ValueError:
msg = _('Auto_remove value are true or false')
raise exception.InvalidValue(msg)
else:
msg = _('Invalid param auto_remove because current request '
'version is %(req_version)s. Auto_remove is only '
'supported from version %(min_version)s') % \
{'req_version': req_version,
'min_version': min_version}
raise exception.InvalidParam(msg)
runtime = container_dict.pop('runtime', None)
if runtime is not None:
req_version = pecan.request.version
min_version = versions.Version('', '', '', '1.5')
if req_version >= min_version:
container_dict['runtime'] = runtime
else:
msg = _('Invalid param runtime because current request '
'version is %(req_version)s. `runtime` is only '
'supported from version %(min_version)s') % \
{'req_version': req_version,
'min_version': min_version}
raise exception.InvalidParam(msg)
nets = container_dict.get('nets', [])
requested_networks = self._build_requested_networks(context, nets)
@ -262,25 +295,6 @@ class ContainersController(base.Controller):
if container_dict.get('restart_policy'):
self._check_for_restart_policy(container_dict)
auto_remove = container_dict.pop('auto_remove', None)
if auto_remove is not None:
req_version = pecan.request.version
min_version = versions.Version('', '', '', '1.3')
if req_version >= min_version:
try:
container_dict['auto_remove'] = strutils.bool_from_string(
auto_remove, strict=True)
except ValueError:
msg = _('Auto_remove value are true or false')
raise exception.InvalidValue(msg)
else:
msg = _('Invalid param auto_remove because current request '
'version is %(req_version)s. Auto_remove is only '
'supported from version %(min_version)s') % \
{'req_version': req_version,
'min_version': min_version}
raise exception.InvalidParam(msg)
container_dict['status'] = consts.CREATING
extra_spec = container_dict.get('hints', None)
new_container = objects.Container(context, **container_dict)

View File

@ -30,7 +30,8 @@ _container_properties = {
'image_driver': parameter_types.image_driver,
'security_groups': parameter_types.security_groups,
'hints': parameter_types.hints,
'nets': parameter_types.nets
'nets': parameter_types.nets,
'runtime': parameter_types.runtime
}
container_create = {

View File

@ -40,6 +40,7 @@ _basic_keys = (
'image_driver',
'security_groups',
'auto_remove',
'runtime'
)

View File

@ -37,10 +37,11 @@ REST_API_VERSION_HISTORY = """REST API Version History:
* 1.2 - Support user specify pre created networks
* 1.3 - Add auto_remove to container
* 1.4 - Support list all container host and show a container host
* 1.5 - Add runtime to container
"""
BASE_VER = '1.1'
CURRENT_MAX_VER = '1.4'
CURRENT_MAX_VER = '1.5'
class Version(object):

View File

@ -50,3 +50,11 @@ user documentation.
Users can use this api to list all the zun compute hosts.
Add get host api
Users can use this api to get details of a zun compute host.
1.5
---
Add a new attribure 'runtime' to the request to create a container.
Users can use this attribute to choose runtime for their containers.
The specified runtime should be configured by admin to run with Zun.
The default runtime for Zun is runc.

View File

@ -38,3 +38,9 @@ RESOURCE_CLASSES = (
'NUMA_SOCKET', 'NUMA_CORE', 'NUMA_THREAD', 'NUMA_MEMORY_MB',
'IPV4_ADDRESS'
)
CONTAINER_RUNTIME = (
RUNC,
) = (
'runc',
)

View File

@ -122,6 +122,11 @@ environment = {
},
}
runtime = {
'type': ['string', 'null'],
'enum': ['runc', None]
}
image_id = {
'type': ['string', 'null'],
'minLength': 2,

View File

@ -46,7 +46,10 @@ could join the namespaces of the infra container thus sharing resources inside
the sandbox (i.e. the network interface). This is typically used to group
a set of high-coupled containers into a unit. If set to False, infra container
won't be created.
""")
"""),
cfg.StrOpt('container_runtime', default='runc',
help="""Define the runtime to create container with. Current
supported values in Zun is ``runc``.""")
]

View File

@ -123,7 +123,6 @@ class DockerDriver(driver.ContainerDriver):
if network_standalone:
self._provision_network(context, network_api,
requested_networks)
kwargs = {
'name': self.get_container_name(container),
'command': container.command,
@ -134,7 +133,11 @@ class DockerDriver(driver.ContainerDriver):
'stdin_open': container.interactive,
}
runtime = container.runtime if container.runtime\
else CONF.container_runtime
host_config = {}
host_config['runtime'] = runtime
if sandbox_id:
host_config['network_mode'] = 'container:%s' % sandbox_id
# TODO(hongbin): Uncomment this after docker-py add support for

View File

@ -0,0 +1,33 @@
# 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 runtime column
Revision ID: 945569b3669f
Revises: a251f1f61217
Create Date: 2017-08-04 09:10:47.810568
"""
# revision identifiers, used by Alembic.
revision = '945569b3669f'
down_revision = 'a251f1f61217'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('container',
sa.Column('runtime', sa.String(32), nullable=True))

View File

@ -155,6 +155,7 @@ class Container(Base):
websocket_token = Column(String(255))
security_groups = Column(JSONEncodedList)
auto_remove = Column(Boolean, default=False)
runtime = Column(String(20))
class Image(Base):

View File

@ -38,7 +38,8 @@ class Container(base.ZunPersistentObject, base.ZunObject):
# Version 1.16: Add websocket_url and token
# Version 1.17: Add security_groups
# Version 1.18: Add auto_remove
VERSION = '1.18'
# Version 1.19: Add runtime column
VERSION = '1.19'
fields = {
'id': fields.IntegerField(),
@ -71,6 +72,7 @@ class Container(base.ZunPersistentObject, base.ZunObject):
'websocket_url': fields.StringField(nullable=True),
'websocket_token': fields.StringField(nullable=True),
'security_groups': fields.ListOfStringsField(nullable=True),
'runtime': z_fields.ContainerRuntimeField(nullable=True)
}
@staticmethod

View File

@ -76,3 +76,15 @@ class ResourceClass(fields.Enum):
class ResourceClassField(fields.AutoTypedField):
AUTO_TYPE = ResourceClass()
class ContainerRuntime(fields.Enum):
ALL = consts.CONTAINER_RUNTIME
def __init__(self):
super(ContainerRuntime, self).__init__(
valid_values=ContainerRuntime.ALL)
class ContainerRuntimeField(fields.BaseEnumField):
AUTO_TYPE = ContainerRuntime()

View File

@ -25,7 +25,7 @@ class TestRootController(api_base.FunctionalTest):
'default_version':
{'id': 'v1',
'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}],
'max_version': '1.4',
'max_version': '1.5',
'min_version': '1.1',
'status': 'CURRENT'},
'description': 'Zun is an OpenStack project which '
@ -33,7 +33,7 @@ class TestRootController(api_base.FunctionalTest):
'versions': [{'id': 'v1',
'links': [{'href': 'http://localhost/v1/',
'rel': 'self'}],
'max_version': '1.4',
'max_version': '1.5',
'min_version': '1.1',
'status': 'CURRENT'}]}

View File

@ -55,6 +55,50 @@ class TestContainerController(api_base.FunctionalTest):
self.app.post('/v1/containers?run=xyz', params=params,
content_type='application/json')
@patch('zun.network.neutron.NeutronAPI.get_available_network')
@patch('zun.compute.api.API.container_run')
@patch('zun.compute.api.API.image_search')
def test_run_container_runtime(self, mock_search,
mock_container_run,
mock_neutron_get_network):
params = ('{"name": "MyDocker", "image": "ubuntu",'
'"command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"},'
'"runtime": "runc"}')
api_version = {"OpenStack-API-Version": "container 1.5"}
response = self.app.post('/v1/containers?run=true',
params=params,
content_type='application/json',
headers=api_version)
self.assertEqual(202, response.status_int)
self.assertTrue(mock_container_run.called)
mock_neutron_get_network.assert_called_once()
def test_run_container_runtime_wrong_api_version(self):
params = ('{"name": "MyDocker", "image": "ubuntu",'
'"command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"},'
'"runtime": "runc"}')
api_version = {"OpenStack-API-Version": "container 1.4"}
with self.assertRaisesRegex(AppError,
"Invalid param runtime"):
self.app.post('/v1/containers?run=true',
params=params, content_type='application/json',
headers=api_version)
def test_run_container_runtime_wrong_value(self):
params = ('{"name": "MyDocker", "image": "ubuntu",'
'"command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"},'
'"runtime": "wrong_value"}')
api_version = {"OpenStack-API-Version": "container 1.4"}
with self.assertRaisesRegex(AppError,
"Invalid input for field"):
self.app.post('/v1/containers?run=true',
params=params, content_type='application/json',
headers=api_version)
@patch('zun.network.neutron.NeutronAPI.get_available_network')
@patch('zun.compute.api.API.container_run')
@patch('zun.compute.api.API.image_search')

View File

@ -30,7 +30,8 @@ CONTAINER_CREATE = {
'environment': parameter_types.environment,
'restart_policy': parameter_types.restart_policy,
'image_driver': parameter_types.image_driver,
'security_groups': parameter_types.security_groups
'security_groups': parameter_types.security_groups,
'runtime': parameter_types.runtime
},
'required': ['image'],
'additionalProperties': False,
@ -52,7 +53,8 @@ class TestSchemaValidations(base.BaseTestCase):
'restart_policy': {'Name': 'no',
'MaximumRetryCount': '0'},
'image_driver': 'docker',
'security_groups': ['abc']}
'security_groups': ['abc'],
'runtime': 'runc'}
self.schema_validator.validate(request_to_validate)
def test_create_schema_with_all_parameters_none(self):
@ -64,7 +66,8 @@ class TestSchemaValidations(base.BaseTestCase):
'environment': None,
'restart_policy': None,
'image_driver': None,
'security_groups': None
'security_groups': None,
'runtime': None
}
self.schema_validator.validate(request_to_validate)
@ -183,3 +186,11 @@ class TestSchemaValidations(base.BaseTestCase):
"Invalid input for field"
" 'pqr'"):
self.schema_validator.validate(request_to_validate)
def test_create_schema_wrong_runtime(self):
request_to_validate = {'image': 'nginx',
'runtime': 'invalid'}
with self.assertRaisesRegex(exception.SchemaValidationError,
"Invalid input for field"
" 'runtime'"):
self.schema_validator.validate(request_to_validate)

View File

@ -99,6 +99,7 @@ class TestDockerDriver(base.DriverTestCase):
host_config['cpu_quota'] = 100000
host_config['cpu_period'] = 100000
host_config['restart_policy'] = {'Name': 'no', 'MaximumRetryCount': 0}
host_config['runtime'] = 'runc'
self.mock_docker.create_host_config.assert_called_once_with(
**host_config)

View File

@ -68,6 +68,7 @@ def get_test_container(**kwargs):
'websocket_token': '7878038e-957c-4d52-ae19-1e9561784e7b',
'security_groups': kwargs.get('security_groups', ['default']),
'auto_remove': kwargs.get('auto_remove', False),
'runtime': kwargs.get('runtime', 'runc'),
}

View File

@ -87,3 +87,22 @@ class TestResourceClass(test_fields.TestField):
def test_stringify_invalid(self):
self.assertRaises(ValueError, self.field.stringify, 'bad_value')
class TestContainerRuntime(test_fields.TestField):
def setUp(self):
super(TestContainerRuntime, self).setUp()
self.field = fields.ContainerRuntime()
self.coerce_good_values = [
('runc', 'runc'),
]
self.coerce_bad_values = ['bad_value']
self.to_primitive_values = self.coerce_good_values[0:1]
self.from_primitive_values = self.coerce_good_values[0:1]
def test_stringify(self):
self.assertEqual("'runc'",
self.field.stringify('runc'))
def test_stringify_invalid(self):
self.assertRaises(ValueError, self.field.stringify, 'bad_value')

View File

@ -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.18-e611f6b0fbc9f5f5c1164545b1630268',
'Container': '1.19-5f8a734d3b0cc2a47f48205944672ab5',
'Image': '1.0-0b976be24f4f6ee0d526e5c981ce0633',
'MyObj': '1.0-34c4b1aadefd177b13f9a2f894cc23cd',
'NUMANode': '1.0-cba878b70b2f8b52f1e031b41ac13b4e',