Add detach network server side.

Add support for detach a network from a container.

Change-Id: I36d8829ae2c36751b4101d55ee621cb280eae4b7
Partially-Implements: blueprint network-rest-api
This commit is contained in:
ShunliZhou 2017-08-15 16:37:43 +08:00
parent 6a6199500e
commit a1a7b7d624
15 changed files with 342 additions and 87 deletions

View File

@ -692,3 +692,37 @@ Response
.. rest_parameters:: parameters.yaml
- image: image
Detach a network from a container
================================
.. rest_method:: POST /v1/containers/{container_ident}/network_detach?network={network}
Detach a network from a container.
Response Codes
-------------
.. rest_status_code:: success status.yaml
- 202
.. rest_status_code:: error status.yaml
- 401
- 403
- 404
.. rest_parameters:: parameters.yaml
- container_ident: container_ident
- network: network
Response
--------
This request does not return anything in the response body.
.. rest_parameters:; parameters.yaml
- X-Openstack-Request-Id: request_id

View File

@ -30,6 +30,7 @@
"container:stats": "rule:default",
"container:commit": "rule:default",
"container:add_security_group": "rule:default",
"container:network_detach": "rule:default",
"image:pull": "rule:default",
"image:get_all": "rule:default",

View File

@ -112,7 +112,8 @@ class ContainersController(base.Controller):
'put_archive': ['POST'],
'stats': ['GET'],
'commit': ['POST'],
'add_security_group': ['POST']
'add_security_group': ['POST'],
'network_detach': ['POST']
}
@pecan.expose('json')
@ -700,3 +701,18 @@ class ContainersController(base.Controller):
return compute_api.container_commit(context, container,
kwargs.get('repository', None),
kwargs.get('tag', None))
@base.Controller.api_version("1.6")
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
@validation.validate_query_param(pecan.request, schema.network_detach)
def network_detach(self, container_id, **kwargs):
container = _get_container(container_id)
check_policy_on_container(container.as_dict(),
"container:network_detach")
context = pecan.request.context
compute_api = pecan.request.compute_api
neutron_api = neutron.NeutronAPI(context)
neutron_net = neutron_api.get_neutron_network(kwargs.get('network'))
compute_api.network_detach(context, container, neutron_net['id'])
pecan.response.status = 202

View File

@ -169,3 +169,14 @@ add_security_group = {
},
'additionalProperties': False
}
network_detach = {
'type': 'object',
'properties': {
'network': {
'type': 'string'
}
},
'required': ['network'],
'additionalProperties': False
}

View File

@ -38,10 +38,11 @@ REST_API_VERSION_HISTORY = """REST API Version History:
* 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
* 1.6 - Support detach network from a container
"""
BASE_VER = '1.1'
CURRENT_MAX_VER = '1.5'
CURRENT_MAX_VER = '1.6'
class Version(object):

View File

@ -58,3 +58,9 @@ user documentation.
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.
1.6
---
Add detach a network from a container api.
Users can use this api to detach a neutron network from a container.

View File

@ -146,3 +146,6 @@ class API(object):
return
self.rpcapi.capsule_create(context, host_state['host'], new_capsule,
requested_networks, host_state['limits'])
def network_detach(self, context, container, *args):
return self.rpcapi.network_detach(context, container, *args)

View File

@ -721,3 +721,12 @@ class Manager(periodic_task.PeriodicTasks):
limits)
if created_container:
self._do_container_start(context, created_container)
def network_detach(self, context, container, network):
LOG.debug('Detach network: %(network)s from container: %(container)s.',
{'container': container, 'network': network})
try:
self.driver.network_detach(context, container, network)
except Exception as e:
with excutils.save_and_reraise_exception(reraise=False):
LOG.exception("Unexpected exception: %s", six.text_type(e))

View File

@ -181,3 +181,7 @@ class API(rpc_service.API):
capsule=capsule,
requested_networks=requested_networks,
limits=limits)
def network_detach(self, context, container, network):
return self._call(container.host, 'network_detach',
container=container, network=network)

View File

@ -756,6 +756,23 @@ class DockerDriver(driver.ContainerDriver):
def get_available_nodes(self):
return [self._host.get_hostname()]
def network_detach(self, context, container, network):
with docker_utils.docker_client() as docker:
network_api = zun_network.api(context,
docker_api=docker)
docker_net = self._get_docker_network_name(context, network)
network_api.disconnect_container_from_network(container,
docker_net, network)
# Only clear network info related to this network
# Cannot del container.address directly which will not update
# changed fields of the container objects as the del operate on
# the addresses object, only base.getter will called.
update = container.addresses
del update[network]
container.addresses = update
container.save(context)
class NovaDockerDriver(DockerDriver):
capabilities = {

View File

@ -228,3 +228,6 @@ class ContainerDriver(object):
if nodename in self.get_available_nodes():
return True
return False
def network_detach(self, context, container, network):
raise NotImplementedError()

View File

@ -17,6 +17,8 @@ import webtest
from zun.api import app
from zun.tests.unit.api import base as api_base
CURRENT_VERSION = "container 1.6"
class TestRootController(api_base.FunctionalTest):
def setUp(self):
@ -25,7 +27,7 @@ class TestRootController(api_base.FunctionalTest):
'default_version':
{'id': 'v1',
'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}],
'max_version': '1.5',
'max_version': '1.6',
'min_version': '1.1',
'status': 'CURRENT'},
'description': 'Zun is an OpenStack project which '
@ -33,7 +35,7 @@ class TestRootController(api_base.FunctionalTest):
'versions': [{'id': 'v1',
'links': [{'href': 'http://localhost/v1/',
'rel': 'self'}],
'max_version': '1.5',
'max_version': '1.6',
'min_version': '1.1',
'status': 'CURRENT'}]}
@ -105,15 +107,16 @@ class TestRootController(api_base.FunctionalTest):
def test_noauth(self):
# Don't need to auth
paste_file = "zun/tests/unit/api/controllers/noauth-paste.ini"
headers = {'OpenStack-API-Version': CURRENT_VERSION}
app = self.make_app(paste_file)
response = app.get('/')
response = app.get('/', headers=headers)
self.assertEqual(self.root_expected, response.json)
response = app.get('/v1/')
response = app.get('/v1/', headers=headers)
self.assertEqual(self.v1_expected, response.json)
response = app.get('/v1/containers/')
response = app.get('/v1/containers/', headers=headers)
self.assertEqual(200, response.status_int)
def test_auth_with_no_public_routes(self):

View File

@ -23,6 +23,8 @@ from zun.tests.unit.api import base as api_base
from zun.tests.unit.db import utils
from zun.tests.unit.objects import utils as obj_utils
CURRENT_VERSION = "container 1.6"
class TestContainerController(api_base.FunctionalTest):
@patch('zun.network.neutron.NeutronAPI.get_available_network')
@ -35,8 +37,11 @@ class TestContainerController(api_base.FunctionalTest):
params = ('{"name": "MyDocker", "image": "ubuntu",'
'"command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"}}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers?run=true',
params=params,
headers=headers,
content_type='application/json')
self.assertEqual(202, response.status_int)
@ -52,8 +57,9 @@ class TestContainerController(api_base.FunctionalTest):
'"environment": {"key1": "val1", "key2": "val2"}}')
with self.assertRaisesRegex(AppError,
"Invalid input for query parameters"):
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers?run=xyz', params=params,
content_type='application/json')
content_type='application/json', headers=headers)
@patch('zun.network.neutron.NeutronAPI.get_available_network')
@patch('zun.compute.api.API.container_run')
@ -65,7 +71,7 @@ class TestContainerController(api_base.FunctionalTest):
'"command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"},'
'"runtime": "runc"}')
api_version = {"OpenStack-API-Version": "container 1.5"}
api_version = {"OpenStack-API-Version": CURRENT_VERSION}
response = self.app.post('/v1/containers?run=true',
params=params,
content_type='application/json',
@ -80,19 +86,20 @@ class TestContainerController(api_base.FunctionalTest):
'"command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"},'
'"runtime": "runc"}')
api_version = {"OpenStack-API-Version": "container 1.4"}
headers = {"OpenStack-API-Version": "container 1.4",
"Accept": "application/json"}
with self.assertRaisesRegex(AppError,
"Invalid param runtime"):
self.app.post('/v1/containers?run=true',
params=params, content_type='application/json',
headers=api_version)
headers=headers)
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"}
api_version = {"OpenStack-API-Version": CURRENT_VERSION}
with self.assertRaisesRegex(AppError,
"Invalid input for field"):
self.app.post('/v1/containers?run=true',
@ -110,9 +117,11 @@ class TestContainerController(api_base.FunctionalTest):
params = ('{"name": "MyDocker", "image": "ubuntu",'
'"command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"}}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers?run=false',
params=params,
content_type='application/json')
content_type='application/json',
headers=headers)
self.assertEqual(202, response.status_int)
self.assertFalse(mock_container_run.called)
mock_neutron_get_network.assert_called_once()
@ -125,8 +134,10 @@ class TestContainerController(api_base.FunctionalTest):
params = ('{"name": "MyDocker", "image": "ubuntu",'
'"command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"}}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.assertRaises(AppError, self.app.post, '/v1/containers?run=wrong',
params=params, content_type='application/json')
params=params, content_type='application/json',
headers=headers)
self.assertTrue(mock_container_run.not_called)
@patch('zun.network.neutron.NeutronAPI.get_available_network')
@ -139,8 +150,9 @@ class TestContainerController(api_base.FunctionalTest):
params = ('{"name": "MyDocker", "image": "ubuntu",'
'"command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"}}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
self.assertEqual(202, response.status_int)
@ -155,8 +167,9 @@ class TestContainerController(api_base.FunctionalTest):
'"environment": {"key1": "val1", "key2": "val2"}}')
with self.assertRaisesRegex(AppError,
"is a required property"):
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
self.assertTrue(mock_container_create.not_called)
@ -170,7 +183,9 @@ class TestContainerController(api_base.FunctionalTest):
mock_search.side_effect = exception.ImageNotFound()
params = {"name": "MyDocker", "image": "not-found"}
response = self.post_json('/containers/', params, expect_errors=True)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.post_json('/containers/', params, headers=headers,
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(404, response.status_int)
self.assertFalse(mock_container_create.called)
@ -191,8 +206,9 @@ class TestContainerController(api_base.FunctionalTest):
params = ('{"name": "MyDocker", "image": "ubuntu",'
'"command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"}}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
mock_neutron_get_network.assert_called_once()
@ -207,8 +223,9 @@ class TestContainerController(api_base.FunctionalTest):
params = ('{"name": "MyDocker", "image": "ubuntu",'
'"command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"}}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
self.assertEqual(202, response.status_int)
self.assertIn('status_reason', response.json.keys())
@ -231,8 +248,9 @@ class TestContainerController(api_base.FunctionalTest):
params = ('{"name": "MyDocker", "image": "ubuntu",'
'"command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"}}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
self.assertEqual(202, response.status_int)
# get all containers
@ -286,8 +304,9 @@ class TestContainerController(api_base.FunctionalTest):
params = ('{"name": "MyDocker", "image": "ubuntu",'
'"command": "env",'
'"environment": {"key1": "val1", "key2": "val2"}}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
self.assertEqual(202, response.status_int)
# get all containers
@ -324,8 +343,9 @@ class TestContainerController(api_base.FunctionalTest):
# Create a container with a command
params = ('{"name": "MyDocker", "image": "ubuntu",'
'"command": "env", "memory": "512"}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
self.assertEqual(202, response.status_int)
# get all containers
@ -361,8 +381,9 @@ class TestContainerController(api_base.FunctionalTest):
mock_neutron_get_network.return_value = fake_network
params = ('{"image": "ubuntu", "command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"}}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
self.assertEqual(202, response.status_int)
# get all containers
@ -403,8 +424,9 @@ class TestContainerController(api_base.FunctionalTest):
'"command": "env", "memory": "512",'
'"restart_policy": {"Name": "no",'
'"MaximumRetryCount": "0"}}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
self.assertEqual(202, response.status_int)
# get all containers
@ -445,8 +467,9 @@ class TestContainerController(api_base.FunctionalTest):
'"command": "env", "memory": "512",'
'"restart_policy": {"Name": "no",'
'"MaximumRetryCount": "6"}}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
self.assertEqual(202, response.status_int)
# get all containers
@ -486,8 +509,9 @@ class TestContainerController(api_base.FunctionalTest):
params = ('{"name": "MyDocker", "image": "ubuntu",'
'"command": "env", "memory": "512",'
'"restart_policy": {"Name": "no"}}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
self.assertEqual(202, response.status_int)
# get all containers
@ -528,8 +552,9 @@ class TestContainerController(api_base.FunctionalTest):
'"command": "env", "memory": "512",'
'"restart_policy": {"Name": "unless-stopped",'
'"MaximumRetryCount": "0"}}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
self.assertEqual(202, response.status_int)
# get all containers
@ -569,8 +594,9 @@ class TestContainerController(api_base.FunctionalTest):
'"command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"},'
'"nets": [{"port": "testport"}]}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
self.assertEqual(202, response.status_int)
# get all containers
@ -628,8 +654,9 @@ class TestContainerController(api_base.FunctionalTest):
'"MaximumRetryCount": "1"}}')
with self.assertRaisesRegex(
AppError, "maximum retry count not valid with"):
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
self.assertTrue(mock_container_create.not_called)
mock_neutron_get_network.assert_called_once()
@ -641,8 +668,10 @@ class TestContainerController(api_base.FunctionalTest):
# Long name
params = ('{"name": "' + 'i' * 256 + '", "image": "ubuntu",'
'"command": "env", "memory": "512"}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.assertRaises(AppError, self.app.post, '/v1/containers/',
params=params, content_type='application/json')
params=params, content_type='application/json',
headers=headers)
self.assertTrue(mock_container_create.not_called)
@patch('zun.compute.api.API.container_show')
@ -654,7 +683,8 @@ class TestContainerController(api_base.FunctionalTest):
mock_container_list.return_value = containers
mock_container_show.return_value = containers[0]
response = self.app.get('/v1/containers/')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.get('/v1/containers/', headers=headers)
mock_container_list.assert_called_once_with(mock.ANY,
1000, None, 'id', 'asc',
@ -676,7 +706,9 @@ class TestContainerController(api_base.FunctionalTest):
mock_container_list.return_value = containers
mock_container_show.return_value = containers[0]
response = self.app.get('/v1/containers/?all_tenants=1')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.get('/v1/containers/?all_tenants=1',
headers=headers)
mock_container_list.assert_called_once_with(mock.ANY,
1000, None, 'id', 'asc',
@ -698,7 +730,8 @@ class TestContainerController(api_base.FunctionalTest):
mock_container_list.return_value = containers
mock_container_show.return_value = containers[0]
response = self.app.get('/v1/containers/')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.get('/v1/containers/', headers=headers)
self.assertEqual(200, response.status_int)
actual_containers = response.json['containers']
self.assertEqual(1, len(actual_containers))
@ -721,8 +754,9 @@ class TestContainerController(api_base.FunctionalTest):
**test_container))
mock_container_list.return_value = container_list[-1:]
mock_container_show.return_value = container_list[-1]
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.get('/v1/containers/?limit=3&marker=%s'
% container_list[2].uuid)
% container_list[2].uuid, headers=headers)
self.assertEqual(200, response.status_int)
actual_containers = response.json['containers']
@ -739,7 +773,8 @@ class TestContainerController(api_base.FunctionalTest):
mock_container_list.return_value = containers
mock_container_show.side_effect = Exception
response = self.app.get('/v1/containers/')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.get('/v1/containers/', headers=headers)
mock_container_list.assert_called_once_with(mock.ANY,
1000, None, 'id', 'asc',
@ -762,7 +797,9 @@ class TestContainerController(api_base.FunctionalTest):
mock_container_get_by_uuid.return_value = test_container_obj
mock_container_show.return_value = test_container_obj
response = self.app.get('/v1/containers/%s/' % test_container['uuid'])
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.get('/v1/containers/%s/' % test_container['uuid'],
headers=headers)
mock_container_get_by_uuid.assert_called_once_with(
mock.ANY,
@ -782,8 +819,9 @@ class TestContainerController(api_base.FunctionalTest):
mock_container_get_by_uuid.return_value = test_container_obj
mock_container_show.return_value = test_container_obj
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.get('/v1/containers/%s/?all_tenants=1' %
test_container['uuid'])
test_container['uuid'], headers=headers)
mock_container_get_by_uuid.assert_called_once_with(
mock.ANY,
@ -804,9 +842,10 @@ class TestContainerController(api_base.FunctionalTest):
params = {'cpu': 1}
container_uuid = test_container.get('uuid')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.patch_json(
'/v1/containers/%s/' % container_uuid,
params=params)
params=params, headers=headers)
self.assertEqual(200, response.status_int)
self.assertTrue(mock_update.called)
@ -818,14 +857,16 @@ class TestContainerController(api_base.FunctionalTest):
get_by_ident_loc = 'zun.objects.Container.get_by_%s' % ident_field
with patch(get_by_ident_loc) as mock_get_by_indent:
mock_get_by_indent.return_value = test_container_obj
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers/%s/%s/?%s' %
(ident, action, query_param))
(ident, action, query_param),
headers=headers)
self.assertEqual(status_code, response.status_int)
# Only PUT should work, others like GET should fail
self.assertRaises(AppError, self.app.get,
('/v1/containers/%s/%s/' %
(ident, action)))
(ident, action)), headers=headers)
if query_param:
value = query_param.split('=')[1]
mock_container_action.assert_called_once_with(
@ -843,8 +884,10 @@ class TestContainerController(api_base.FunctionalTest):
with patch.object(test_container_obj, 'save') as mock_save:
params = {'name': 'new_name'}
container_uuid = test_container.get('uuid')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers/%s/rename' %
container_uuid, params=params)
container_uuid, params=params,
headers=headers)
mock_save.assert_called_once()
self.assertEqual(200, response.status_int)
@ -859,9 +902,10 @@ class TestContainerController(api_base.FunctionalTest):
container_name = test_container.get('name')
params = {'name': container_name}
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.assertRaises(AppError, self.app.post,
'/v1/containers/%s/rename' %
container_uuid, params=params)
container_uuid, params=params, headers=headers)
@patch('zun.objects.Container.get_by_name')
def test_rename_with_invalid_name_by_uuid(self,
@ -877,8 +921,9 @@ class TestContainerController(api_base.FunctionalTest):
params = {'name': value}
with self.assertRaisesRegex(AppError,
"Invalid input for query parameters"):
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers/%s/rename' %
container_uuid, params=params)
container_uuid, params=params, headers=headers)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.api.API.container_start')
@ -896,8 +941,9 @@ class TestContainerController(api_base.FunctionalTest):
uuid=uuid, status='Running')
with self.assertRaisesRegex(
AppError, "Cannot start container %s in Running state" % uuid):
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'start'))
'start'), headers=headers)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.api.API.container_stop')
@ -928,8 +974,9 @@ class TestContainerController(api_base.FunctionalTest):
uuid=uuid, status='Stopped')
with self.assertRaisesRegex(
AppError, "Cannot stop container %s in Stopped state" % uuid):
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'stop'))
'stop'), headers=headers)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.api.API.container_pause')
@ -947,8 +994,9 @@ class TestContainerController(api_base.FunctionalTest):
uuid=uuid, status='Stopped')
with self.assertRaisesRegex(
AppError, "Cannot pause container %s in Stopped state" % uuid):
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'pause'))
'pause'), headers=headers)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.api.API.container_unpause')
@ -967,8 +1015,10 @@ class TestContainerController(api_base.FunctionalTest):
with self.assertRaisesRegex(
AppError,
"Cannot unpause container %s in Running state" % uuid):
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'unpause'))
'unpause'),
headers=headers)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.api.API.container_reboot')
@ -987,8 +1037,10 @@ class TestContainerController(api_base.FunctionalTest):
uuid=uuid, status='Paused')
with self.assertRaisesRegex(
AppError, "Cannot reboot container %s in Paused state" % uuid):
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'reboot'))
'reboot'),
headers=headers)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.api.API.container_reboot')
@ -1010,7 +1062,9 @@ class TestContainerController(api_base.FunctionalTest):
mock_get_by_uuid.return_value = test_container_obj
container_uuid = test_container.get('uuid')
response = self.app.get('/v1/containers/%s/logs/' % container_uuid)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.get('/v1/containers/%s/logs/' % container_uuid,
headers=headers)
self.assertEqual(200, response.status_int)
mock_container_logs.assert_called_once_with(
@ -1026,9 +1080,11 @@ class TestContainerController(api_base.FunctionalTest):
mock_get_by_uuid.return_value = test_container_obj
container_uuid = test_container.get('uuid')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.get(
'/v1/containers/%s/logs?stderr=True&stdout=True'
'&timestamps=False&tail=1&since=100000000' % container_uuid)
'&timestamps=False&tail=1&since=100000000' % container_uuid,
headers=headers)
self.assertEqual(200, response.status_int)
mock_container_logs.assert_called_once_with(
mock.ANY, test_container_obj, True, True, False, '1', '100000000')
@ -1041,8 +1097,10 @@ class TestContainerController(api_base.FunctionalTest):
mock_get_by_uuid.return_value = test_container_obj
container_uuid = test_container.get('uuid')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.assertRaises(AppError, self.app.post,
'/v1/containers/%s/logs/' % container_uuid)
'/v1/containers/%s/logs/' % container_uuid,
headers=headers)
self.assertFalse(mock_container_logs.called)
@patch('zun.compute.api.API.container_logs')
@ -1059,9 +1117,10 @@ class TestContainerController(api_base.FunctionalTest):
container_uuid = test_container.get('uuid')
params = {'since': value}
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.assertRaises(AppError, self.app.post,
'/v1/containers/%s/logs' %
container_uuid, params)
container_uuid, params, headers=headers)
self.assertFalse(mock_container_logs.called)
def test_get_logs_with_invalid_state(self):
@ -1071,7 +1130,9 @@ class TestContainerController(api_base.FunctionalTest):
with self.assertRaisesRegex(
AppError,
"Cannot logs container %s in Creating state" % uuid):
self.app.get('/v1/containers/%s/logs/' % test_object.uuid)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.get('/v1/containers/%s/logs/' % test_object.uuid,
headers=headers)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.api.API.container_exec')
@ -1086,7 +1147,8 @@ class TestContainerController(api_base.FunctionalTest):
container_uuid = test_container.get('uuid')
url = '/v1/containers/%s/%s/' % (container_uuid, 'execute')
cmd = {'command': 'ls'}
response = self.app.post(url, cmd)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post(url, cmd, headers=headers)
self.assertEqual(200, response.status_int)
mock_container_exec.assert_called_once_with(
mock.ANY, test_container_obj, cmd['command'], True, False)
@ -1099,8 +1161,10 @@ class TestContainerController(api_base.FunctionalTest):
with self.assertRaisesRegex(
AppError,
"Cannot execute container %s in Stopped state" % uuid):
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'execute'), cmd)
'execute'), cmd,
headers=headers)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.api.API.container_exec')
@ -1114,9 +1178,10 @@ class TestContainerController(api_base.FunctionalTest):
container_uuid = test_container.get('uuid')
cmd = {'command': ''}
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.assertRaises(AppError, self.app.post,
'/v1/containers/%s/execute' %
container_uuid, cmd)
container_uuid, cmd, headers=headers)
self.assertFalse(mock_container_exec.called)
@patch('zun.common.utils.validate_container_state')
@ -1129,7 +1194,9 @@ class TestContainerController(api_base.FunctionalTest):
mock_get_by_uuid.return_value = test_container_obj
container_uuid = test_container.get('uuid')
response = self.app.delete('/v1/containers/%s/' % container_uuid)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.delete('/v1/containers/%s/' % container_uuid,
headers=headers)
self.assertEqual(204, response.status_int)
mock_container_delete.assert_called_once_with(
@ -1148,8 +1215,9 @@ class TestContainerController(api_base.FunctionalTest):
mock_get_by_uuid.return_value = test_container_obj
container_uuid = test_container.get('uuid')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.delete('/v1/containers/%s/?all_tenants=1' %
container_uuid)
container_uuid, headers=headers)
self.assertEqual(204, response.status_int)
mock_container_delete.assert_called_once_with(
@ -1164,7 +1232,9 @@ class TestContainerController(api_base.FunctionalTest):
with self.assertRaisesRegex(
AppError,
"Cannot delete container %s in Running state" % uuid):
self.app.delete('/v1/containers/%s' % (test_object.uuid))
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.delete('/v1/containers/%s' % (test_object.uuid),
headers=headers)
def test_delete_force_by_uuid_invalid_state(self):
uuid = uuidutils.generate_uuid()
@ -1173,15 +1243,18 @@ class TestContainerController(api_base.FunctionalTest):
with self.assertRaisesRegex(
AppError,
"Cannot delete_force container %s in Paused state" % uuid):
self.app.delete('/v1/containers/%s?force=True' % test_object.uuid)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.delete('/v1/containers/%s?force=True' % test_object.uuid,
headers=headers)
@patch('zun.compute.api.API.container_delete')
def test_delete_by_uuid_invalid_state_force_true(self, mock_delete):
uuid = uuidutils.generate_uuid()
test_object = utils.create_test_container(context=self.context,
uuid=uuid, status='Running')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.delete('/v1/containers/%s?force=True' % (
test_object.uuid))
test_object.uuid), headers=headers)
self.assertEqual(204, response.status_int)
@patch('zun.compute.api.API.container_delete')
@ -1190,14 +1263,17 @@ class TestContainerController(api_base.FunctionalTest):
test_object = utils.create_test_container(context=self.context,
uuid=uuid)
mock_delete.side_effect = exception.InvalidValue
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.assertRaises(AppError, self.app.delete,
'/v1/containers/%s?force=wrong' % test_object.uuid)
'/v1/containers/%s?force=wrong' % test_object.uuid,
headers=headers)
self.assertTrue(mock_delete.not_called)
def test_delete_container_with_uuid_not_found(self):
uuid = uuidutils.generate_uuid()
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.assertRaises(AppError, self.app.delete,
'/v1/containers/%s' % uuid)
'/v1/containers/%s' % uuid, headers=headers)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.api.API.container_kill')
@ -1215,7 +1291,8 @@ class TestContainerController(api_base.FunctionalTest):
container_uuid = test_container.get('uuid')
url = '/v1/containers/%s/%s/' % (container_uuid, 'kill')
cmd = {'signal': '9'}
response = self.app.post(url, cmd)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post(url, cmd, headers=headers)
self.assertEqual(202, response.status_int)
mock_container_kill.assert_called_once_with(
mock.ANY, test_container_obj, cmd['signal'])
@ -1227,8 +1304,10 @@ class TestContainerController(api_base.FunctionalTest):
body = {'signal': 9}
with self.assertRaisesRegex(
AppError, "Cannot kill container %s in Stopped state" % uuid):
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'kill'), body)
'kill'), body,
headers=headers)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.api.API.container_kill')
@ -1244,8 +1323,10 @@ class TestContainerController(api_base.FunctionalTest):
mock_container_kill.side_effect = Exception
container_uuid = "edfe2a25-2901-438d-8157-fffffd68d051"
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.assertRaises(AppError, self.app.post,
'/v1/containers/%s/%s/' % (container_uuid, 'kill'))
'/v1/containers/%s/%s/' % (container_uuid, 'kill'),
headers=headers)
self.assertTrue(mock_container_kill.called)
@patch('zun.common.utils.validate_container_state')
@ -1262,8 +1343,10 @@ class TestContainerController(api_base.FunctionalTest):
mock_container_kill.side_effect = Exception
container_uuid = test_container.get('uuid')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.assertRaises(AppError, self.app.post,
'/v1/containers/%s/%s/' % (container_uuid, 'kill'))
'/v1/containers/%s/%s/' % (container_uuid, 'kill'),
headers=headers)
self.assertTrue(mock_container_kill.called)
@patch('zun.compute.api.API.container_kill')
@ -1282,8 +1365,9 @@ class TestContainerController(api_base.FunctionalTest):
params = {'signal': value}
with self.assertRaisesRegex(
AppError, "Bad response: 400 Bad Request"):
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers/%s/kill/' %
container_uuid, params)
container_uuid, params, headers=headers)
self.assertFalse(mock_container_kill.called)
@patch('zun.network.neutron.NeutronAPI.get_available_network')
@ -1298,8 +1382,9 @@ class TestContainerController(api_base.FunctionalTest):
'"command": "env", "memory": "512",'
'"environment": {"key1": "val1", "key2": "val2"},'
'"image_driver": "glance"}')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post('/v1/containers/',
params=params,
params=params, headers=headers,
content_type='application/json')
self.assertEqual(202, response.status_int)
self.assertIn('image_driver', response.json.keys())
@ -1316,7 +1401,9 @@ class TestContainerController(api_base.FunctionalTest):
mock_get_by_uuid.return_value = test_container_obj
container_uuid = test_container.get('uuid')
response = self.app.get('/v1/containers/%s/attach/' % container_uuid)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.get('/v1/containers/%s/attach/' % container_uuid,
headers=headers)
self.assertEqual(200, response.status_int)
mock_container_attach.assert_called_once_with(
@ -1336,8 +1423,10 @@ class TestContainerController(api_base.FunctionalTest):
mock_container_attach.side_effect = Exception
container_uuid = test_container.get('uuid')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.assertRaises(AppError, self.app.get,
'/v1/containers/%s/attach/' % container_uuid)
'/v1/containers/%s/attach/' % container_uuid,
headers=headers)
self.assertTrue(mock_container_attach.called)
@patch('zun.common.utils.validate_container_state')
@ -1357,7 +1446,8 @@ class TestContainerController(api_base.FunctionalTest):
container_name = test_container.get('name')
url = '/v1/containers/%s/%s/' % (container_name, 'resize')
cmd = {'h': '100', 'w': '100'}
response = self.app.post(url, cmd)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post(url, cmd, headers=headers)
self.assertEqual(200, response.status_int)
mock_container_resize.assert_called_once_with(
mock.ANY, test_container_obj, cmd['h'], cmd['w'])
@ -1377,9 +1467,10 @@ class TestContainerController(api_base.FunctionalTest):
container_uuid = test_container.get('uuid')
body = {'h': '100', 'w': '100'}
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.assertRaises(AppError, self.app.post,
'/v1/containers/%s/%s/' %
(container_uuid, 'resize'), body)
(container_uuid, 'resize'), body, headers=headers)
self.assertTrue(mock_container_resize.called)
@patch('zun.common.utils.validate_container_state')
@ -1393,8 +1484,9 @@ class TestContainerController(api_base.FunctionalTest):
mock_get_by_uuid.return_value = test_container_obj
container_uuid = test_container.get('uuid')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.get('/v1/containers/%s/top?ps_args=aux' %
container_uuid)
container_uuid, headers=headers)
self.assertEqual(200, response.status_int)
self.assertTrue(mock_container_top.called)
@ -1410,9 +1502,10 @@ class TestContainerController(api_base.FunctionalTest):
mock_container_top.side_effect = Exception
container_uuid = test_container.get('uuid')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.assertRaises(AppError, self.app.get,
'/v1/containers/%s/top?ps_args=kkkk' %
container_uuid)
container_uuid, headers=headers)
self.assertTrue(mock_container_top.called)
@patch('zun.common.utils.validate_container_state')
@ -1430,7 +1523,8 @@ class TestContainerController(api_base.FunctionalTest):
container_uuid = test_container.get('uuid')
url = '/v1/containers/%s/%s/' % (container_uuid, 'get_archive')
cmd = {'path': '/home/1.txt'}
response = self.app.get(url, cmd)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.get(url, cmd, headers=headers)
self.assertEqual(200, response.status_int)
container_get_archive.assert_called_once_with(
mock.ANY, test_container_obj, cmd['path'])
@ -1442,8 +1536,10 @@ class TestContainerController(api_base.FunctionalTest):
with self.assertRaisesRegex(
AppError,
"Cannot get_archive container %s in Error state" % uuid):
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.get('/v1/containers/%s/%s/' % (test_object.uuid,
'get_archive'))
'get_archive'),
headers=headers)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.api.API.container_put_archive')
@ -1461,7 +1557,8 @@ class TestContainerController(api_base.FunctionalTest):
url = '/v1/containers/%s/%s/' % (container_uuid, 'put_archive')
cmd = {'path': '/home/',
'data': '/home/1.tar'}
response = self.app.post(url, cmd)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post(url, cmd, headers=headers)
self.assertEqual(200, response.status_int)
container_put_archive.assert_called_once_with(
mock.ANY, test_container_obj, cmd['path'], cmd['data'])
@ -1473,8 +1570,10 @@ class TestContainerController(api_base.FunctionalTest):
with self.assertRaisesRegex(
AppError,
"Cannot put_archive container %s in Error state" % uuid):
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers/%s/%s/' % (test_object.uuid,
'put_archive'))
'put_archive'),
headers=headers)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.api.API.container_stats')
@ -1489,7 +1588,8 @@ class TestContainerController(api_base.FunctionalTest):
container_uuid = test_container.get('uuid')
url = '/v1/containers/%s/stats'\
% container_uuid
response = self.app.get(url)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.get(url, headers=headers)
self.assertEqual(200, response.status_int)
self.assertTrue(mock_container_stats.called)
@ -1508,7 +1608,8 @@ class TestContainerController(api_base.FunctionalTest):
container_name = test_container.get('name')
url = '/v1/containers/%s/%s/' % (container_name, 'commit')
cmd = {'repository': 'repo', 'tag': 'tag'}
response = self.app.post(url, cmd)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post(url, cmd, headers=headers)
self.assertEqual(202, response.status_int)
mock_container_commit.assert_called_once_with(
mock.ANY, test_container_obj, cmd['repository'], cmd['tag'])
@ -1528,7 +1629,8 @@ class TestContainerController(api_base.FunctionalTest):
container_uuid = test_container.get('uuid')
url = '/v1/containers/%s/%s/' % (container_uuid, 'commit')
cmd = {'repository': 'repo', 'tag': 'tag'}
response = self.app.post(url, cmd)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post(url, cmd, headers=headers)
self.assertEqual(202, response.status_int)
mock_container_commit.assert_called_once_with(
mock.ANY, test_container_obj, cmd['repository'], cmd['tag'])
@ -1540,7 +1642,9 @@ class TestContainerController(api_base.FunctionalTest):
uuid=uuid, status='Error')
with self.assertRaisesRegex(
AppError, "Cannot commit container %s in Error state" % uuid):
self.app.post('/v1/containers/%s/commit/' % uuid, cmd)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
self.app.post('/v1/containers/%s/commit/' % uuid, cmd,
headers=headers)
@patch('zun.common.utils.validate_container_state')
@patch('zun.compute.api.API.container_exec_resize')
@ -1555,7 +1659,8 @@ class TestContainerController(api_base.FunctionalTest):
fake_exec_id = ('7df36611fa1fc855618c2c643835d41d'
'ac3fe568e7688f0bae66f7bcb3cccc6c')
kwargs = {'exec_id': fake_exec_id, 'h': '100', 'w': '100'}
response = self.app.post(url, kwargs)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post(url, kwargs, headers=headers)
self.assertEqual(200, response.status_int)
mock_exec_resize.assert_called_once_with(
mock.ANY, test_container_obj, fake_exec_id, kwargs['h'],
@ -1580,7 +1685,8 @@ class TestContainerController(api_base.FunctionalTest):
url = '/v1/containers/%s/%s?name=%s' % (container_name,
'add_security_group',
default_security_group)
response = self.app.post(url, expect_errors=True)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post(url, headers=headers, expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertEqual(
@ -1603,7 +1709,8 @@ class TestContainerController(api_base.FunctionalTest):
url = '/v1/containers/%s/%s?uuid=%s' % (container_name,
'add_security_group',
security_group_id_to_add)
response = self.app.post(url)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post(url, headers=headers)
self.assertEqual(202, response.status_int)
self.assertEqual('application/json', response.content_type)
mock_add_security_group.assert_called_once_with(
@ -1625,18 +1732,41 @@ class TestContainerController(api_base.FunctionalTest):
url = '/v1/containers/%s/%s?uuid=%s' % (container_name,
'add_security_group',
invalid_uuid)
response = self.app.post(url, expect_errors=True)
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post(url, headers=headers, expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertEqual(
"Expected a uuid but received %(uuid)s." %
{'uuid': invalid_uuid}, response.json['errors'][0]['detail'])
@patch('zun.network.neutron.NeutronAPI.get_neutron_network')
@patch('zun.compute.api.API.network_detach')
@patch('zun.objects.Container.get_by_uuid')
def test_network_detach(self, mock_by_uuid, mock_detach, mock_get_network):
test_container = utils.get_test_container()
test_container_obj = objects.Container(self.context, **test_container)
mock_by_uuid.return_value = test_container_obj
container_uuid = test_container.get('uuid')
mock_get_network.return_value = {'id': 'private'}
mock_detach.return_value = None
url = '/v1/containers/%s/%s?network=%s' % (container_uuid,
'network_detach',
'private')
headers = {'OpenStack-API-Version': CURRENT_VERSION}
response = self.app.post(url,
headers=headers)
self.assertEqual(202, response.status_int)
mock_detach.assert_called_once_with(mock.ANY, test_container_obj,
mock.ANY)
class TestContainerEnforcement(api_base.FunctionalTest):
def _common_policy_check(self, rule, func, *arg, **kwarg):
self.policy.set_rules({rule: 'project_id:non_fake'})
headers = {'OpenStack-API-Version': CURRENT_VERSION}
kwarg['headers'] = headers
response = func(*arg, **kwarg)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)
@ -1703,6 +1833,8 @@ class TestContainerEnforcement(api_base.FunctionalTest):
def _owner_check(self, rule, func, *args, **kwargs):
self.policy.set_rules({rule: "user_id:%(user_id)s"})
headers = {'OpenStack-API-Version': CURRENT_VERSION}
kwargs['headers'] = headers
response = func(*args, **kwargs)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)

View File

@ -593,3 +593,9 @@ class TestManager(base.TestCase):
self.assertRaises(exception.DockerError,
self.compute_manager._do_container_commit,
self.context, container, 'repo', 'tag')
@mock.patch.object(fake_driver, 'network_detach')
def test_container_network_detach(self, mock_detach):
container = Container(self.context, **utils.get_test_container())
self.compute_manager.network_detach(self.context, container, 'network')
mock_detach.assert_called_once_with(self.context, container, mock.ANY)

View File

@ -465,6 +465,15 @@ class TestDockerDriver(base.DriverTestCase):
self.assertEqual('10000000/0', stats_info['BLOCK I/O(B)'])
self.assertEqual('200/200', stats_info['NET I/O(B)'])
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
'.disconnect_container_from_network')
def test_network_detach(self, mock_detach):
mock_container = mock.MagicMock()
self.driver.network_detach(self.context, mock_container, 'network')
mock_detach.assert_called_once_with(mock_container,
'network-fake_project',
mock.ANY)
class TestNovaDockerDriver(base.DriverTestCase):
def setUp(self):