zun: add property 'networks' to container
Add a new property 'networks' to resource OS::Zun::Container. This property is an ordered list of nics to be added to this container, with information about connected networks, fixed ips, port etc. This property can be updated without replacement. Story: 2003106 Task: 23222 Change-Id: I4b8c0257b83e97444dd8ff6ce88e240d12278ec2
This commit is contained in:
parent
de549a931c
commit
9b5de23ad8
@ -11,6 +11,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
import tenacity
|
||||
from zunclient import client as zun_client
|
||||
from zunclient import exceptions as zc_exc
|
||||
|
||||
@ -58,6 +60,56 @@ class ZunClientPlugin(client_plugin.ClientPlugin):
|
||||
if prop_diff:
|
||||
self.client().containers.update(container_id, **prop_diff)
|
||||
|
||||
def network_detach(self, container_id, port_id):
|
||||
with self.ignore_not_found:
|
||||
self.client(version=self.V1_18).containers.network_detach(
|
||||
container_id, port=port_id)
|
||||
return True
|
||||
|
||||
def network_attach(self, container_id, port_id=None, net_id=None, fip=None,
|
||||
security_groups=None):
|
||||
with self.ignore_not_found:
|
||||
kwargs = {}
|
||||
if port_id:
|
||||
kwargs['port'] = port_id
|
||||
if net_id:
|
||||
kwargs['network'] = net_id
|
||||
if fip:
|
||||
kwargs['fixed_ip'] = fip
|
||||
self.client(version=self.V1_18).containers.network_attach(
|
||||
container_id, **kwargs)
|
||||
return True
|
||||
|
||||
@tenacity.retry(
|
||||
stop=tenacity.stop_after_attempt(
|
||||
cfg.CONF.max_interface_check_attempts),
|
||||
wait=tenacity.wait_exponential(multiplier=0.5, max=12.0),
|
||||
retry=tenacity.retry_if_result(client_plugin.retry_if_result_is_false))
|
||||
def check_network_detach(self, container_id, port_id):
|
||||
with self.ignore_not_found:
|
||||
interfaces = self.client(
|
||||
version=self.V1_18).containers.network_list(container_id)
|
||||
for iface in interfaces:
|
||||
if iface.port_id == port_id:
|
||||
return False
|
||||
return True
|
||||
|
||||
@tenacity.retry(
|
||||
stop=tenacity.stop_after_attempt(
|
||||
cfg.CONF.max_interface_check_attempts),
|
||||
wait=tenacity.wait_exponential(multiplier=0.5, max=12.0),
|
||||
retry=tenacity.retry_if_result(client_plugin.retry_if_result_is_false))
|
||||
def check_network_attach(self, container_id, port_id):
|
||||
if not port_id:
|
||||
return True
|
||||
|
||||
interfaces = self.client(version=self.V1_18).containers.network_list(
|
||||
container_id)
|
||||
for iface in interfaces:
|
||||
if iface.port_id == port_id:
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_not_found(self, ex):
|
||||
return isinstance(ex, zc_exc.NotFound)
|
||||
|
||||
|
@ -27,18 +27,18 @@ class ServerCreateProgress(object):
|
||||
self.server_id = server_id
|
||||
|
||||
|
||||
class ServerUpdateProgress(ServerCreateProgress):
|
||||
class UpdateProgressBase(object):
|
||||
"""Keeps track on particular server update task.
|
||||
|
||||
``handler`` is a method of client plugin performing
|
||||
required update operation.
|
||||
Its first positional argument must be ``server_id``
|
||||
Its first positional argument must be ``resource_id``
|
||||
and this method must be resilent to intermittent failures,
|
||||
returning ``True`` if API was successfully called, ``False`` otherwise.
|
||||
|
||||
If result of API call is asynchronous, client plugin must have
|
||||
corresponding ``check_<handler>`` method.
|
||||
Its first positional argument must be ``server_id``
|
||||
Its first positional argument must be ``resource_id``
|
||||
and it must return ``True`` or ``False`` indicating completeness
|
||||
of the update operation.
|
||||
|
||||
@ -52,28 +52,46 @@ class ServerUpdateProgress(ServerCreateProgress):
|
||||
|
||||
structure and contain parameters with which corresponding ``handler`` and
|
||||
``check_<handler>`` methods of client plugin must be called.
|
||||
``args`` is automatically prepended with ``server_id``.
|
||||
``args`` is automatically prepended with ``resource_id``.
|
||||
Missing ``args`` or ``kwargs`` are interpreted
|
||||
as empty tuple/dict respectively.
|
||||
Defaults are interpreted as both ``args`` and ``kwargs`` being empty.
|
||||
"""
|
||||
def __init__(self, server_id, handler, complete=False, called=False,
|
||||
def __init__(self, resource_id, handler, complete=False, called=False,
|
||||
handler_extra=None, checker_extra=None):
|
||||
super(ServerUpdateProgress, self).__init__(server_id, complete)
|
||||
self.complete = complete
|
||||
self.called = called
|
||||
self.handler = handler
|
||||
self.checker = 'check_%s' % handler
|
||||
|
||||
# set call arguments basing on incomplete values and defaults
|
||||
hargs = handler_extra or {}
|
||||
self.handler_args = (server_id,) + (hargs.get('args') or ())
|
||||
self.handler_args = (resource_id,) + (hargs.get('args') or ())
|
||||
self.handler_kwargs = hargs.get('kwargs') or {}
|
||||
|
||||
cargs = checker_extra or {}
|
||||
self.checker_args = (server_id,) + (cargs.get('args') or ())
|
||||
self.checker_args = (resource_id,) + (cargs.get('args') or ())
|
||||
self.checker_kwargs = cargs.get('kwargs') or {}
|
||||
|
||||
|
||||
class ServerUpdateProgress(UpdateProgressBase):
|
||||
def __init__(self, server_id, handler, complete=False, called=False,
|
||||
handler_extra=None, checker_extra=None):
|
||||
super(ServerUpdateProgress, self).__init__(
|
||||
server_id, handler, complete=complete, called=called,
|
||||
handler_extra=handler_extra, checker_extra=checker_extra)
|
||||
self.server_id = server_id
|
||||
|
||||
|
||||
class ContainerUpdateProgress(UpdateProgressBase):
|
||||
def __init__(self, container_id, handler, complete=False, called=False,
|
||||
handler_extra=None, checker_extra=None):
|
||||
super(ContainerUpdateProgress, self).__init__(
|
||||
container_id, handler, complete=complete, called=called,
|
||||
handler_extra=handler_extra, checker_extra=checker_extra)
|
||||
self.container_id = container_id
|
||||
|
||||
|
||||
class ServerDeleteProgress(object):
|
||||
|
||||
def __init__(self, server_id, image_id=None, image_complete=True):
|
||||
|
@ -16,13 +16,17 @@ import copy
|
||||
from heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.engine import attributes
|
||||
from heat.engine.clients import progress
|
||||
from heat.engine import constraints
|
||||
from heat.engine import properties
|
||||
from heat.engine import resource
|
||||
from heat.engine.resources.openstack.nova import server_network_mixin
|
||||
from heat.engine import support
|
||||
from heat.engine import translation
|
||||
|
||||
|
||||
class Container(resource.Resource):
|
||||
class Container(resource.Resource,
|
||||
server_network_mixin.ServerNetworkMixin):
|
||||
"""A resource that creates a Zun Container.
|
||||
|
||||
This resource creates a Zun container.
|
||||
@ -34,14 +38,27 @@ class Container(resource.Resource):
|
||||
NAME, IMAGE, COMMAND, CPU, MEMORY,
|
||||
ENVIRONMENT, WORKDIR, LABELS, IMAGE_PULL_POLICY,
|
||||
RESTART_POLICY, INTERACTIVE, IMAGE_DRIVER, HINTS,
|
||||
HOSTNAME, SECURITY_GROUPS, MOUNTS,
|
||||
HOSTNAME, SECURITY_GROUPS, MOUNTS, NETWORKS,
|
||||
) = (
|
||||
'name', 'image', 'command', 'cpu', 'memory',
|
||||
'environment', 'workdir', 'labels', 'image_pull_policy',
|
||||
'restart_policy', 'interactive', 'image_driver', 'hints',
|
||||
'hostname', 'security_groups', 'mounts',
|
||||
'hostname', 'security_groups', 'mounts', 'networks',
|
||||
)
|
||||
|
||||
_NETWORK_KEYS = (
|
||||
NETWORK_UUID, NETWORK_ID, NETWORK_FIXED_IP, NETWORK_PORT,
|
||||
NETWORK_SUBNET, NETWORK_PORT_EXTRA, NETWORK_FLOATING_IP,
|
||||
ALLOCATE_NETWORK, NIC_TAG,
|
||||
) = (
|
||||
'uuid', 'network', 'fixed_ip', 'port',
|
||||
'subnet', 'port_extra_properties', 'floating_ip',
|
||||
'allocate_network', 'tag',
|
||||
)
|
||||
|
||||
_IFACE_MANAGED_KEYS = (NETWORK_PORT, NETWORK_ID,
|
||||
NETWORK_FIXED_IP, NETWORK_SUBNET)
|
||||
|
||||
_MOUNT_KEYS = (
|
||||
VOLUME_ID, MOUNT_PATH, VOLUME_SIZE
|
||||
) = (
|
||||
@ -160,6 +177,41 @@ class Container(resource.Resource):
|
||||
},
|
||||
)
|
||||
),
|
||||
NETWORKS: properties.Schema(
|
||||
properties.Schema.LIST,
|
||||
_('An ordered list of nics to be added to this server, with '
|
||||
'information about connected networks, fixed ips, port etc.'),
|
||||
support_status=support.SupportStatus(version='11.0.0'),
|
||||
schema=properties.Schema(
|
||||
properties.Schema.MAP,
|
||||
schema={
|
||||
NETWORK_ID: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Name or ID of network to create a port on.'),
|
||||
constraints=[
|
||||
constraints.CustomConstraint('neutron.network')
|
||||
]
|
||||
),
|
||||
NETWORK_FIXED_IP: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Fixed IP address to specify for the port '
|
||||
'created on the requested network.'),
|
||||
constraints=[
|
||||
constraints.CustomConstraint('ip_addr')
|
||||
]
|
||||
),
|
||||
NETWORK_PORT: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('ID of an existing port to associate with this '
|
||||
'container.'),
|
||||
constraints=[
|
||||
constraints.CustomConstraint('neutron.port')
|
||||
]
|
||||
),
|
||||
},
|
||||
),
|
||||
update_allowed=True,
|
||||
),
|
||||
}
|
||||
|
||||
attributes_schema = {
|
||||
@ -182,6 +234,24 @@ class Container(resource.Resource):
|
||||
|
||||
entity = 'containers'
|
||||
|
||||
def translation_rules(self, props):
|
||||
rules = [
|
||||
translation.TranslationRule(
|
||||
props,
|
||||
translation.TranslationRule.RESOLVE,
|
||||
translation_path=[self.NETWORKS, self.NETWORK_ID],
|
||||
client_plugin=self.client_plugin('neutron'),
|
||||
finder='find_resourceid_by_name_or_id',
|
||||
entity='network'),
|
||||
translation.TranslationRule(
|
||||
props,
|
||||
translation.TranslationRule.RESOLVE,
|
||||
translation_path=[self.NETWORKS, self.NETWORK_PORT],
|
||||
client_plugin=self.client_plugin('neutron'),
|
||||
finder='find_resourceid_by_name_or_id',
|
||||
entity='port')]
|
||||
return rules
|
||||
|
||||
def validate(self):
|
||||
super(Container, self).validate()
|
||||
|
||||
@ -196,6 +266,10 @@ class Container(resource.Resource):
|
||||
for mount in mounts:
|
||||
self._validate_mount(mount)
|
||||
|
||||
networks = self.properties[self.NETWORKS] or []
|
||||
for network in networks:
|
||||
self._validate_network(network)
|
||||
|
||||
def _validate_mount(self, mount):
|
||||
volume_id = mount.get(self.VOLUME_ID)
|
||||
volume_size = mount.get(self.VOLUME_SIZE)
|
||||
@ -215,6 +289,21 @@ class Container(resource.Resource):
|
||||
"/".join([self.NETWORKS, self.VOLUME_ID]),
|
||||
"/".join([self.NETWORKS, self.VOLUME_SIZE]))
|
||||
|
||||
def _validate_network(self, network):
|
||||
net_id = network.get(self.NETWORK_ID)
|
||||
port = network.get(self.NETWORK_PORT)
|
||||
fixed_ip = network.get(self.NETWORK_FIXED_IP)
|
||||
|
||||
if net_id is None and port is None:
|
||||
raise exception.PropertyUnspecifiedError(
|
||||
self.NETWORK_ID, self.NETWORK_PORT)
|
||||
|
||||
# Don't allow specify ip and port at the same time
|
||||
if fixed_ip and port is not None:
|
||||
raise exception.ResourcePropertyConflict(
|
||||
".".join([self.NETWORKS, self.NETWORK_FIXED_IP]),
|
||||
".".join([self.NETWORKS, self.NETWORK_PORT]))
|
||||
|
||||
def handle_create(self):
|
||||
args = dict((k, v) for k, v in self.properties.items()
|
||||
if v is not None)
|
||||
@ -224,6 +313,9 @@ class Container(resource.Resource):
|
||||
mounts = args.pop(self.MOUNTS, None)
|
||||
if mounts:
|
||||
args[self.MOUNTS] = self._build_mounts(mounts)
|
||||
networks = args.pop(self.NETWORKS, None)
|
||||
if networks:
|
||||
args['nets'] = self._build_nets(networks)
|
||||
container = self.client().containers.run(**args)
|
||||
self.resource_id_set(container.uuid)
|
||||
return container.uuid
|
||||
@ -252,6 +344,18 @@ class Container(resource.Resource):
|
||||
mnts.append(mnt_info)
|
||||
return mnts
|
||||
|
||||
def _build_nets(self, networks):
|
||||
nics = self._build_nics(networks)
|
||||
for nic in nics:
|
||||
net_id = nic.pop('net-id', None)
|
||||
if net_id:
|
||||
nic[self.NETWORK_ID] = net_id
|
||||
port_id = nic.pop('port-id', None)
|
||||
if port_id:
|
||||
nic[self.NETWORK_PORT] = port_id
|
||||
|
||||
return nics
|
||||
|
||||
def check_create_complete(self, id):
|
||||
container = self.client().containers.get(id)
|
||||
if container.status in ('Creating', 'Created'):
|
||||
@ -279,8 +383,65 @@ class Container(resource.Resource):
|
||||
.status)
|
||||
|
||||
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||
updaters = []
|
||||
container = None
|
||||
|
||||
after_props = json_snippet.properties(self.properties_schema,
|
||||
self.context)
|
||||
if self.NETWORKS in prop_diff:
|
||||
prop_diff.pop(self.NETWORKS)
|
||||
container = self.client().containers.get(self.resource_id)
|
||||
updaters.extend(self._update_networks(container, after_props))
|
||||
|
||||
self.client_plugin().update_container(self.resource_id, **prop_diff)
|
||||
|
||||
return updaters
|
||||
|
||||
def _update_networks(self, container, after_props):
|
||||
updaters = []
|
||||
new_networks = after_props[self.NETWORKS]
|
||||
old_networks = self.properties[self.NETWORKS]
|
||||
security_groups = after_props[self.SECURITY_GROUPS]
|
||||
|
||||
interfaces = self.client(version=self.client_plugin().V1_18).\
|
||||
containers.network_list(self.resource_id)
|
||||
remove_ports, add_nets = self.calculate_networks(
|
||||
old_networks, new_networks, interfaces, security_groups)
|
||||
|
||||
for port in remove_ports:
|
||||
updaters.append(
|
||||
progress.ContainerUpdateProgress(
|
||||
self.resource_id, 'network_detach',
|
||||
handler_extra={'args': (port,)},
|
||||
checker_extra={'args': (port,)})
|
||||
)
|
||||
|
||||
for args in add_nets:
|
||||
updaters.append(
|
||||
progress.ContainerUpdateProgress(
|
||||
self.resource_id, 'network_attach',
|
||||
handler_extra={'kwargs': args},
|
||||
checker_extra={'args': (args['port_id'],)})
|
||||
)
|
||||
|
||||
return updaters
|
||||
|
||||
def check_update_complete(self, updaters):
|
||||
"""Push all updaters to completion in list order."""
|
||||
for prg in updaters:
|
||||
if not prg.called:
|
||||
handler = getattr(self.client_plugin(), prg.handler)
|
||||
prg.called = handler(*prg.handler_args,
|
||||
**prg.handler_kwargs)
|
||||
return False
|
||||
if not prg.complete:
|
||||
check_complete = getattr(self.client_plugin(), prg.checker)
|
||||
prg.complete = check_complete(*prg.checker_args,
|
||||
**prg.checker_kwargs)
|
||||
break
|
||||
status = all(prg.complete for prg in updaters)
|
||||
return status
|
||||
|
||||
def handle_delete(self):
|
||||
if not self.resource_id:
|
||||
return
|
||||
|
@ -20,6 +20,7 @@ from zunclient import exceptions as zc_exc
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common import template_format
|
||||
from heat.engine.clients.os import neutron
|
||||
from heat.engine.clients.os import zun
|
||||
from heat.engine.resources.openstack.zun import container
|
||||
from heat.engine import scheduler
|
||||
@ -58,8 +59,36 @@ resources:
|
||||
mount_path: /data
|
||||
- volume_id: 6ec29ba3-bf2c-4276-a88e-3670ea5abc80
|
||||
mount_path: /data2
|
||||
networks:
|
||||
- network: mynet
|
||||
fixed_ip: 10.0.0.4
|
||||
- network: mynet2
|
||||
fixed_ip: fe80::3
|
||||
- port: myport
|
||||
'''
|
||||
|
||||
zun_template_minimum = '''
|
||||
heat_template_version: 2017-09-01
|
||||
|
||||
resources:
|
||||
test_container:
|
||||
type: OS::Zun::Container
|
||||
properties:
|
||||
name: test_container
|
||||
image: "cirros:latest"
|
||||
'''
|
||||
|
||||
|
||||
def create_fake_iface(port=None, net=None, mac=None, ip=None, subnet=None):
|
||||
class fake_interface(object):
|
||||
def __init__(self, port_id, net_id, mac_addr, fixed_ip, subnet_id):
|
||||
self.port_id = port_id
|
||||
self.net_id = net_id
|
||||
self.mac_addr = mac_addr
|
||||
self.fixed_ips = [{'ip_address': fixed_ip, 'subnet_id': subnet_id}]
|
||||
|
||||
return fake_interface(port, net, mac, ip, subnet)
|
||||
|
||||
|
||||
class ZunContainerTest(common.HeatTestCase):
|
||||
|
||||
@ -91,10 +120,18 @@ class ZunContainerTest(common.HeatTestCase):
|
||||
{'size': 1, 'destination': '/data'},
|
||||
{'source': '6ec29ba3-bf2c-4276-a88e-3670ea5abc80',
|
||||
'destination': '/data2'}]
|
||||
self.fake_networks = [
|
||||
{'network': 'mynet', 'port': None, 'fixed_ip': '10.0.0.4'},
|
||||
{'network': 'mynet2', 'port': None, 'fixed_ip': 'fe80::3'},
|
||||
{'network': None, 'port': 'myport', 'fixed_ip': None}]
|
||||
self.fake_networks_args = [
|
||||
{'network': 'mynet', 'v4-fixed-ip': '10.0.0.4'},
|
||||
{'network': 'mynet2', 'v6-fixed-ip': 'fe80::3'},
|
||||
{'port': 'myport'}]
|
||||
|
||||
self.fake_network_id = '9c11d847-99ce-4a83-82da-9827362a68e8'
|
||||
self.fake_network_name = 'private'
|
||||
self.fake_networks = {
|
||||
self.fake_networks_attr = {
|
||||
'networks': [
|
||||
{
|
||||
'id': self.fake_network_id,
|
||||
@ -128,6 +165,21 @@ class ZunContainerTest(common.HeatTestCase):
|
||||
self.stub_VolumeConstraint_validate()
|
||||
self.mock_update = self.patchobject(zun.ZunClientPlugin,
|
||||
'update_container')
|
||||
self.stub_PortConstraint_validate()
|
||||
self.mock_find = self.patchobject(
|
||||
neutron.NeutronClientPlugin,
|
||||
'find_resourceid_by_name_or_id',
|
||||
side_effect=lambda x, y: y)
|
||||
self.mock_attach = self.patchobject(zun.ZunClientPlugin,
|
||||
'network_attach')
|
||||
self.mock_detach = self.patchobject(zun.ZunClientPlugin,
|
||||
'network_detach')
|
||||
self.mock_attach_check = self.patchobject(zun.ZunClientPlugin,
|
||||
'check_network_attach',
|
||||
return_value=True)
|
||||
self.mock_detach_check = self.patchobject(zun.ZunClientPlugin,
|
||||
'check_network_detach',
|
||||
return_value=True)
|
||||
|
||||
def _mock_get_client(self):
|
||||
value = mock.MagicMock()
|
||||
@ -211,6 +263,9 @@ class ZunContainerTest(common.HeatTestCase):
|
||||
self.assertEqual(
|
||||
self.fake_mounts,
|
||||
c.properties.get(container.Container.MOUNTS))
|
||||
self.assertEqual(
|
||||
self.fake_networks,
|
||||
c.properties.get(container.Container.NETWORKS))
|
||||
|
||||
scheduler.TaskRunner(c.create)()
|
||||
self.assertEqual(self.resource_id, c.resource_id)
|
||||
@ -233,6 +288,7 @@ class ZunContainerTest(common.HeatTestCase):
|
||||
hostname=self.fake_hostname,
|
||||
security_groups=self.fake_security_groups,
|
||||
mounts=self.fake_mounts_args,
|
||||
nets=self.fake_networks_args,
|
||||
)
|
||||
|
||||
def test_container_create_failed(self):
|
||||
@ -271,6 +327,130 @@ class ZunContainerTest(common.HeatTestCase):
|
||||
cpu=10, memory=10, name='fake-container')
|
||||
self.assertEqual((c.UPDATE, c.COMPLETE), c.state)
|
||||
|
||||
def _test_container_update_None_networks(self, new_networks):
|
||||
t = template_format.parse(zun_template_minimum)
|
||||
stack = utils.parse_stack(t)
|
||||
resource_defns = stack.t.resource_definitions(stack)
|
||||
rsrc_defn = resource_defns[self.fake_name]
|
||||
c = self._create_resource('container', rsrc_defn, stack)
|
||||
scheduler.TaskRunner(c.create)()
|
||||
|
||||
new_t = copy.deepcopy(t)
|
||||
new_t['resources'][self.fake_name]['properties']['networks'] = \
|
||||
new_networks
|
||||
rsrc_defns = template.Template(new_t).resource_definitions(stack)
|
||||
new_c = rsrc_defns[self.fake_name]
|
||||
iface = create_fake_iface(
|
||||
port='aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
net='450abbc9-9b6d-4d6f-8c3a-c47ac34100ef',
|
||||
ip='1.2.3.4')
|
||||
self.client.containers.network_list.return_value = [iface]
|
||||
scheduler.TaskRunner(c.update, new_c)()
|
||||
self.assertEqual((c.UPDATE, c.COMPLETE), c.state)
|
||||
self.client.containers.network_list.assert_called_once_with(
|
||||
self.resource_id)
|
||||
|
||||
def test_container_update_None_networks_with_port(self):
|
||||
new_networks = [{'port': '2a60cbaa-3d33-4af6-a9ce-83594ac546fc'}]
|
||||
self._test_container_update_None_networks(new_networks)
|
||||
self.assertEqual(1, self.mock_attach.call_count)
|
||||
self.assertEqual(1, self.mock_detach.call_count)
|
||||
self.assertEqual(1, self.mock_attach_check.call_count)
|
||||
self.assertEqual(1, self.mock_detach_check.call_count)
|
||||
|
||||
def test_container_update_None_networks_with_network_id(self):
|
||||
new_networks = [{'network': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
'fixed_ip': '1.2.3.4'}]
|
||||
self._test_container_update_None_networks(new_networks)
|
||||
self.assertEqual(1, self.mock_attach.call_count)
|
||||
self.assertEqual(1, self.mock_detach.call_count)
|
||||
self.assertEqual(1, self.mock_attach_check.call_count)
|
||||
self.assertEqual(1, self.mock_detach_check.call_count)
|
||||
|
||||
def test_container_update_None_networks_with_complex_parameters(self):
|
||||
new_networks = [{'network': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
'fixed_ip': '1.2.3.4',
|
||||
'port': '2a60cbaa-3d33-4af6-a9ce-83594ac546fc'}]
|
||||
self._test_container_update_None_networks(new_networks)
|
||||
self.assertEqual(1, self.mock_attach.call_count)
|
||||
self.assertEqual(1, self.mock_detach.call_count)
|
||||
self.assertEqual(1, self.mock_attach_check.call_count)
|
||||
self.assertEqual(1, self.mock_detach_check.call_count)
|
||||
|
||||
def test_server_update_empty_networks_to_None(self):
|
||||
new_networks = None
|
||||
self._test_container_update_None_networks(new_networks)
|
||||
self.assertEqual(0, self.mock_attach.call_count)
|
||||
self.assertEqual(0, self.mock_detach.call_count)
|
||||
self.assertEqual(0, self.mock_attach_check.call_count)
|
||||
self.assertEqual(0, self.mock_detach_check.call_count)
|
||||
|
||||
def _test_container_update_networks(self, new_networks):
|
||||
c = self._create_resource('container', self.rsrc_defn, self.stack)
|
||||
scheduler.TaskRunner(c.create)()
|
||||
t = template_format.parse(zun_template)
|
||||
new_t = copy.deepcopy(t)
|
||||
new_t['resources'][self.fake_name]['properties']['networks'] = \
|
||||
new_networks
|
||||
rsrc_defns = template.Template(new_t).resource_definitions(self.stack)
|
||||
new_c = rsrc_defns[self.fake_name]
|
||||
sec_uuids = ['86c0f8ae-23a8-464f-8603-c54113ef5467']
|
||||
self.patchobject(neutron.NeutronClientPlugin,
|
||||
'get_secgroup_uuids', return_value=sec_uuids)
|
||||
ifaces = [
|
||||
create_fake_iface(port='95e25541-d26a-478d-8f36-ae1c8f6b74dc',
|
||||
net='mynet',
|
||||
ip='10.0.0.4'),
|
||||
create_fake_iface(port='450abbc9-9b6d-4d6f-8c3a-c47ac34100ef',
|
||||
net='mynet2',
|
||||
ip='fe80::3'),
|
||||
create_fake_iface(port='myport',
|
||||
net='aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
ip='21.22.23.24')]
|
||||
self.client.containers.network_list.return_value = ifaces
|
||||
scheduler.TaskRunner(c.update, new_c)()
|
||||
self.assertEqual((c.UPDATE, c.COMPLETE), c.state)
|
||||
self.client.containers.network_list.assert_called_once_with(
|
||||
self.resource_id)
|
||||
|
||||
def test_container_update_networks_with_complex_parameters(self):
|
||||
new_networks = [
|
||||
{'network': 'mynet',
|
||||
'fixed_ip': '10.0.0.4'},
|
||||
{'port': '2a60cbaa-3d33-4af6-a9ce-83594ac546fc'}]
|
||||
self._test_container_update_networks(new_networks)
|
||||
self.assertEqual(2, self.mock_detach.call_count)
|
||||
self.assertEqual(1, self.mock_attach.call_count)
|
||||
self.assertEqual(2, self.mock_detach_check.call_count)
|
||||
self.assertEqual(1, self.mock_attach_check.call_count)
|
||||
|
||||
def test_container_update_networks_with_None(self):
|
||||
new_networks = None
|
||||
self._test_container_update_networks(new_networks)
|
||||
self.assertEqual(3, self.mock_detach.call_count)
|
||||
self.assertEqual(1, self.mock_attach.call_count)
|
||||
self.assertEqual(3, self.mock_detach_check.call_count)
|
||||
self.assertEqual(1, self.mock_attach_check.call_count)
|
||||
|
||||
def test_container_update_old_networks_to_empty_list(self):
|
||||
new_networks = []
|
||||
self._test_container_update_networks(new_networks)
|
||||
self.assertEqual(3, self.mock_detach.call_count)
|
||||
self.assertEqual(1, self.mock_attach.call_count)
|
||||
self.assertEqual(3, self.mock_detach_check.call_count)
|
||||
self.assertEqual(1, self.mock_attach_check.call_count)
|
||||
|
||||
def test_container_update_remove_network_non_empty(self):
|
||||
new_networks = [
|
||||
{'network': 'mynet',
|
||||
'fixed_ip': '10.0.0.4'},
|
||||
{'port': 'myport'}]
|
||||
self._test_container_update_networks(new_networks)
|
||||
self.assertEqual(1, self.mock_detach.call_count)
|
||||
self.assertEqual(0, self.mock_attach.call_count)
|
||||
self.assertEqual(1, self.mock_detach_check.call_count)
|
||||
self.assertEqual(0, self.mock_attach_check.call_count)
|
||||
|
||||
def test_container_delete(self):
|
||||
c = self._create_resource('container', self.rsrc_defn, self.stack)
|
||||
scheduler.TaskRunner(c.create)()
|
||||
@ -306,7 +486,8 @@ class ZunContainerTest(common.HeatTestCase):
|
||||
}, reality)
|
||||
|
||||
def test_resolve_attributes(self):
|
||||
self.neutron_client.list_networks.return_value = self.fake_networks
|
||||
self.neutron_client.list_networks.return_value = \
|
||||
self.fake_networks_attr
|
||||
c = self._create_resource('container', self.rsrc_defn, self.stack)
|
||||
scheduler.TaskRunner(c.create)()
|
||||
self._mock_get_client()
|
||||
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add a new property ``networks`` to resource OS::Zun::Container.
|
||||
This property is an ordered list of nics to be added to this container,
|
||||
with information about connected networks, fixed ips, and port.
|
||||
This property can be updated without replacement.
|
Loading…
x
Reference in New Issue
Block a user