Extend addresses attribute of Zun container

The original 'addresses' field is a map from neutron network uuid
to an address list. For example:

  {'ed5c1b52-01ab-4a89-8b99-00705d9066c6': [..]}

This commit proposes to extend it to neutron network name. This
allows the address list to be hashed from uuid or name of a network.

  {'ed5c1b52-01ab-4a89-8b99-00705d9066c6': [...],
   'private': [...]}

This improvement will make the format of addresses consistent
between containers and VMs. In addition, it allows retrieving IP
addresses through either network name or network id. For example:

  {get_attr: [my-container, addresses, private, 0, addr]}

or

  {get_attr: [my-container, addresses, <uuid>, 0, addr]}

The first form is more user-friendly and the latter is able to
resolve ambiguity that two networks have the same name.

Closes-Bug: #1709191
Change-Id: Ib319e3683529fe71ee1c8dbe3087c9f30f1f1116
This commit is contained in:
Hongbin Lu 2017-08-04 20:11:11 +00:00
parent 4af7865038
commit 46c57e7ae0
2 changed files with 97 additions and 11 deletions

View File

@ -11,6 +11,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
from heat.common import exception
from heat.common.i18n import _
from heat.engine import attributes
@ -212,8 +214,32 @@ class Container(resource.Resource):
except Exception as exc:
self.client_plugin().ignore_not_found(exc)
return ''
if name == self.ADDRESSES:
return self._extend_addresses(container)
return getattr(container, name, '')
def _extend_addresses(self, container):
"""Method adds network name to list of addresses.
This method is used only for resolving attributes.
"""
nets = self.neutron().list_networks()['networks']
id_name_mapping_on_network = {net['id']: net['name']
for net in nets}
addresses = copy.deepcopy(container.addresses)
for net_uuid in container.addresses or {}:
addr_list = addresses[net_uuid]
net_name = id_name_mapping_on_network.get(net_uuid)
if not net_name:
continue
addresses.setdefault(net_name, [])
addresses[net_name] += addr_list
return addresses
def resource_mapping():
return {

View File

@ -13,9 +13,10 @@
import copy
import mock
from oslo_config import cfg
import six
from oslo_config import cfg
from heat.common import exception
from heat.common import template_format
from heat.engine.resources.openstack.zun import container
@ -67,16 +68,27 @@ class ZunContainerTest(common.HeatTestCase):
'Name': 'on-failure'}
self.fake_interactive = False
self.fake_image_driver = 'docker'
self.fake_network_id = '9c11d847-99ce-4a83-82da-9827362a68e8'
self.fake_network_name = 'private'
self.fake_networks = {
'networks': [
{
'id': self.fake_network_id,
'name': self.fake_network_name,
}
]
}
self.fake_address = {
'version': 4,
'addr': '10.0.0.12',
'port': 'ab5c12d8-f414-48a3-b765-8ce34a6714d2'
}
self.fake_addresses = {
'addresses': {
'private': [
{
'version': 4,
'addr': '10.0.0.12',
'port': 'ab5c12d8-f414-48a3-b765-8ce34a6714d2'
},
],
}
self.fake_network_id: [self.fake_address]
}
self.fake_extended_addresses = {
self.fake_network_id: [self.fake_address],
self.fake_network_name: [self.fake_address],
}
t = template_format.parse(zun_template)
@ -86,6 +98,9 @@ class ZunContainerTest(common.HeatTestCase):
self.client = mock.Mock()
self.patchobject(container.Container, 'client',
return_value=self.client)
self.neutron_client = mock.Mock()
self.patchobject(container.Container, 'neutron',
return_value=self.neutron_client)
def _mock_get_client(self):
value = mock.MagicMock()
@ -242,6 +257,7 @@ class ZunContainerTest(common.HeatTestCase):
}, reality)
def test_resolve_attributes(self):
self.neutron_client.list_networks.return_value = self.fake_networks
c = self._create_resource('container', self.rsrc_defn, self.stack)
scheduler.TaskRunner(c.create)()
self._mock_get_client()
@ -249,5 +265,49 @@ class ZunContainerTest(common.HeatTestCase):
self.fake_name,
c._resolve_attribute(container.Container.NAME))
self.assertEqual(
self.fake_addresses,
self.fake_extended_addresses,
c._resolve_attribute(container.Container.ADDRESSES))
def test_resolve_attributes_duplicate_net_name(self):
self.neutron_client.list_networks.return_value = {
'networks': [
{'id': 'fake_net_id', 'name': 'test'},
{'id': 'fake_net_id2', 'name': 'test'},
]
}
self.fake_addresses = {
'fake_net_id': [{'addr': '10.0.0.12'}],
'fake_net_id2': [{'addr': '10.100.0.12'}],
}
self.fake_extended_addresses = {
'fake_net_id': [{'addr': '10.0.0.12'}],
'fake_net_id2': [{'addr': '10.100.0.12'}],
'test': [{'addr': '10.0.0.12'}, {'addr': '10.100.0.12'}],
}
c = self._create_resource('container', self.rsrc_defn, self.stack)
scheduler.TaskRunner(c.create)()
self._mock_get_client()
self._assert_addresses(
self.fake_extended_addresses,
c._resolve_attribute(container.Container.ADDRESSES))
def _assert_addresses(self, expected, actual):
matched = True
if len(expected) != len(actual):
matched = False
for key in expected:
if key not in actual:
matched = False
break
list1 = expected[key]
list1 = sorted(list1, key=lambda x: sorted(x.values()))
list2 = actual[key]
list2 = sorted(list2, key=lambda x: sorted(x.values()))
if list1 != list2:
matched = False
break
if not matched:
raise AssertionError(
'Addresses is unmatched:\n reference = ' + str(expected) +
'\nactual = ' + str(actual))