Merge "Add attach a network to a container server side."
This commit is contained in:
commit
fe28a063f3
@ -774,3 +774,38 @@ This request does not return anything in the response body.
|
|||||||
.. rest_parameters:: parameters.yaml
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
- X-Openstack-Request-Id: request_id
|
- X-Openstack-Request-Id: request_id
|
||||||
|
|
||||||
|
|
||||||
|
Attach a network to a container
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. rest_method:: POST /v1/containers/{container_ident}/network_attach?network={network}
|
||||||
|
|
||||||
|
Attach a network to 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
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
"container:commit": "rule:default",
|
"container:commit": "rule:default",
|
||||||
"container:add_security_group": "rule:default",
|
"container:add_security_group": "rule:default",
|
||||||
"container:network_detach": "rule:default",
|
"container:network_detach": "rule:default",
|
||||||
|
"container:network_attach": "rule:default",
|
||||||
|
|
||||||
"image:pull": "rule:default",
|
"image:pull": "rule:default",
|
||||||
"image:get_all": "rule:default",
|
"image:get_all": "rule:default",
|
||||||
|
@ -114,7 +114,8 @@ class ContainersController(base.Controller):
|
|||||||
'stats': ['GET'],
|
'stats': ['GET'],
|
||||||
'commit': ['POST'],
|
'commit': ['POST'],
|
||||||
'add_security_group': ['POST'],
|
'add_security_group': ['POST'],
|
||||||
'network_detach': ['POST']
|
'network_detach': ['POST'],
|
||||||
|
'network_attach': ['POST']
|
||||||
}
|
}
|
||||||
|
|
||||||
@pecan.expose('json')
|
@pecan.expose('json')
|
||||||
@ -765,3 +766,17 @@ class ContainersController(base.Controller):
|
|||||||
neutron_net = neutron_api.get_neutron_network(kwargs.get('network'))
|
neutron_net = neutron_api.get_neutron_network(kwargs.get('network'))
|
||||||
compute_api.network_detach(context, container, neutron_net['id'])
|
compute_api.network_detach(context, container, neutron_net['id'])
|
||||||
pecan.response.status = 202
|
pecan.response.status = 202
|
||||||
|
|
||||||
|
@base.Controller.api_version("1.8")
|
||||||
|
@pecan.expose('json')
|
||||||
|
@exception.wrap_pecan_controller_exception
|
||||||
|
@validation.validate_query_param(pecan.request, schema.network_attach)
|
||||||
|
def network_attach(self, container_id, **kwargs):
|
||||||
|
container = _get_container(container_id)
|
||||||
|
check_policy_on_container(container.as_dict(),
|
||||||
|
"container:network_attach")
|
||||||
|
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_attach(context, container, neutron_net['id'])
|
||||||
|
@ -180,3 +180,5 @@ network_detach = {
|
|||||||
'required': ['network'],
|
'required': ['network'],
|
||||||
'additionalProperties': False
|
'additionalProperties': False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
network_attach = copy.deepcopy(network_detach)
|
||||||
|
@ -40,10 +40,11 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
|||||||
* 1.5 - Add runtime to container
|
* 1.5 - Add runtime to container
|
||||||
* 1.6 - Support detach network from a container
|
* 1.6 - Support detach network from a container
|
||||||
* 1.7 - Disallow non-admin users to force delete containers
|
* 1.7 - Disallow non-admin users to force delete containers
|
||||||
|
* 1.8 - Support attach a network to a container
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BASE_VER = '1.1'
|
BASE_VER = '1.1'
|
||||||
CURRENT_MAX_VER = '1.7'
|
CURRENT_MAX_VER = '1.8'
|
||||||
|
|
||||||
|
|
||||||
class Version(object):
|
class Version(object):
|
||||||
|
@ -70,3 +70,9 @@ user documentation.
|
|||||||
|
|
||||||
Disallow non-admin users to force delete containers
|
Disallow non-admin users to force delete containers
|
||||||
Only Admin User can use "delete --force" to force delete a container.
|
Only Admin User can use "delete --force" to force delete a container.
|
||||||
|
|
||||||
|
1.8
|
||||||
|
---
|
||||||
|
|
||||||
|
Add attach a network to a container.
|
||||||
|
Users can use this api to attach a neutron network to a container.
|
||||||
|
@ -134,3 +134,6 @@ class API(object):
|
|||||||
|
|
||||||
def network_detach(self, context, container, *args):
|
def network_detach(self, context, container, *args):
|
||||||
return self.rpcapi.network_detach(context, container, *args)
|
return self.rpcapi.network_detach(context, container, *args)
|
||||||
|
|
||||||
|
def network_attach(self, context, container, *args):
|
||||||
|
return self.rpcapi.network_attach(context, container, *args)
|
||||||
|
@ -722,3 +722,12 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
with excutils.save_and_reraise_exception(reraise=False):
|
with excutils.save_and_reraise_exception(reraise=False):
|
||||||
LOG.exception("Unexpected exception: %s", six.text_type(e))
|
LOG.exception("Unexpected exception: %s", six.text_type(e))
|
||||||
|
|
||||||
|
def network_attach(self, context, container, network):
|
||||||
|
LOG.debug('Attach network: %(network)s to container: %(container)s.',
|
||||||
|
{'container': container, 'network': network})
|
||||||
|
try:
|
||||||
|
self.driver.network_attach(context, container, network)
|
||||||
|
except Exception as e:
|
||||||
|
with excutils.save_and_reraise_exception(reraise=False):
|
||||||
|
LOG.exception("Unexpected exception: %s", six.text_type(e))
|
||||||
|
@ -181,3 +181,7 @@ class API(rpc_service.API):
|
|||||||
def network_detach(self, context, container, network):
|
def network_detach(self, context, container, network):
|
||||||
return self._call(container.host, 'network_detach',
|
return self._call(container.host, 'network_detach',
|
||||||
container=container, network=network)
|
container=container, network=network)
|
||||||
|
|
||||||
|
def network_attach(self, context, container, network):
|
||||||
|
return self._call(container.host, 'network_attach',
|
||||||
|
container=container, network=network)
|
||||||
|
@ -771,6 +771,35 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
container.addresses = update
|
container.addresses = update
|
||||||
container.save(context)
|
container.save(context)
|
||||||
|
|
||||||
|
def network_attach(self, context, container, network):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
network_api = zun_network.api(context,
|
||||||
|
docker_api=docker)
|
||||||
|
if network in container.addresses:
|
||||||
|
raise exception.ZunException('Container %(container)s has'
|
||||||
|
' alreay connected to the network'
|
||||||
|
'%(network)s.'
|
||||||
|
% {'container': container['uuid'],
|
||||||
|
'network': network})
|
||||||
|
self._get_or_create_docker_network(context, network_api, network)
|
||||||
|
requested_network = {'network': network,
|
||||||
|
'port': '',
|
||||||
|
'v4-fixed-ip': '',
|
||||||
|
'v6-fixed-ip': ''}
|
||||||
|
docker_net_name = self._get_docker_network_name(context, network)
|
||||||
|
addrs = network_api.connect_container_to_network(
|
||||||
|
container, docker_net_name, requested_network,
|
||||||
|
security_groups=None)
|
||||||
|
if addrs is None:
|
||||||
|
raise exception.ZunException(_(
|
||||||
|
'Unexpected missing of addresses'))
|
||||||
|
update = {}
|
||||||
|
update[network] = addrs
|
||||||
|
addresses = container.addresses
|
||||||
|
addresses.update(update)
|
||||||
|
container.addresses = addresses
|
||||||
|
container.save(context)
|
||||||
|
|
||||||
|
|
||||||
class NovaDockerDriver(DockerDriver):
|
class NovaDockerDriver(DockerDriver):
|
||||||
capabilities = {
|
capabilities = {
|
||||||
|
@ -231,3 +231,6 @@ class ContainerDriver(object):
|
|||||||
|
|
||||||
def network_detach(self, context, container, network):
|
def network_detach(self, context, container, network):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def network_attach(self, context, container, network):
|
||||||
|
raise NotImplementedError()
|
||||||
|
@ -17,7 +17,7 @@ import webtest
|
|||||||
from zun.api import app
|
from zun.api import app
|
||||||
from zun.tests.unit.api import base as api_base
|
from zun.tests.unit.api import base as api_base
|
||||||
|
|
||||||
CURRENT_VERSION = "container 1.7"
|
CURRENT_VERSION = "container 1.8"
|
||||||
|
|
||||||
|
|
||||||
class TestRootController(api_base.FunctionalTest):
|
class TestRootController(api_base.FunctionalTest):
|
||||||
@ -27,7 +27,7 @@ class TestRootController(api_base.FunctionalTest):
|
|||||||
'default_version':
|
'default_version':
|
||||||
{'id': 'v1',
|
{'id': 'v1',
|
||||||
'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}],
|
'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}],
|
||||||
'max_version': '1.7',
|
'max_version': '1.8',
|
||||||
'min_version': '1.1',
|
'min_version': '1.1',
|
||||||
'status': 'CURRENT'},
|
'status': 'CURRENT'},
|
||||||
'description': 'Zun is an OpenStack project which '
|
'description': 'Zun is an OpenStack project which '
|
||||||
@ -35,7 +35,7 @@ class TestRootController(api_base.FunctionalTest):
|
|||||||
'versions': [{'id': 'v1',
|
'versions': [{'id': 'v1',
|
||||||
'links': [{'href': 'http://localhost/v1/',
|
'links': [{'href': 'http://localhost/v1/',
|
||||||
'rel': 'self'}],
|
'rel': 'self'}],
|
||||||
'max_version': '1.7',
|
'max_version': '1.8',
|
||||||
'min_version': '1.1',
|
'min_version': '1.1',
|
||||||
'status': 'CURRENT'}]}
|
'status': 'CURRENT'}]}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ from zun.tests.unit.api import base as api_base
|
|||||||
from zun.tests.unit.db import utils
|
from zun.tests.unit.db import utils
|
||||||
from zun.tests.unit.objects import utils as obj_utils
|
from zun.tests.unit.objects import utils as obj_utils
|
||||||
|
|
||||||
CURRENT_VERSION = "container 1.7"
|
CURRENT_VERSION = "container 1.8"
|
||||||
|
|
||||||
|
|
||||||
class TestContainerController(api_base.FunctionalTest):
|
class TestContainerController(api_base.FunctionalTest):
|
||||||
|
@ -624,3 +624,8 @@ class TestManager(base.TestCase):
|
|||||||
container = Container(self.context, **utils.get_test_container())
|
container = Container(self.context, **utils.get_test_container())
|
||||||
self.compute_manager.network_detach(self.context, container, 'network')
|
self.compute_manager.network_detach(self.context, container, 'network')
|
||||||
mock_detach.assert_called_once_with(self.context, container, mock.ANY)
|
mock_detach.assert_called_once_with(self.context, container, mock.ANY)
|
||||||
|
|
||||||
|
@mock.patch.object(fake_driver, 'network_attach')
|
||||||
|
def test_container_network_attach(self, mock_attach):
|
||||||
|
container = Container(self.context, **utils.get_test_container())
|
||||||
|
self.compute_manager.network_attach(self.context, container, 'network')
|
||||||
|
@ -475,6 +475,26 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
'network-fake_project',
|
'network-fake_project',
|
||||||
mock.ANY)
|
mock.ANY)
|
||||||
|
|
||||||
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
|
'.connect_container_to_network')
|
||||||
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
|
'.disconnect_container_from_network')
|
||||||
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
|
'.list_networks')
|
||||||
|
def test_network_attach(self, mock_list, mock_disconnect, mock_connect):
|
||||||
|
mock_container = mock.MagicMock()
|
||||||
|
mock_container.security_groups = None
|
||||||
|
mock_list.return_value = {'network': 'network'}
|
||||||
|
requested_network = [{'network': 'network',
|
||||||
|
'port': '',
|
||||||
|
'v4-fixed-ip': '',
|
||||||
|
'v6-fixed-ip': ''}]
|
||||||
|
self.driver.network_attach(self.context, mock_container, 'network')
|
||||||
|
mock_connect.assert_called_once_with(mock_container,
|
||||||
|
'network-fake_project',
|
||||||
|
requested_network[0],
|
||||||
|
security_groups=None)
|
||||||
|
|
||||||
|
|
||||||
class TestNovaDockerDriver(base.DriverTestCase):
|
class TestNovaDockerDriver(base.DriverTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user