From 78d6c208b4902c2e7bc18647ae6d4cf8c7745e4a Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Wed, 5 Feb 2020 17:37:31 +0100 Subject: [PATCH] ovsdb_subordinate: Flip requires and provides Across the OpenStack Engineering team charms we are using requires and provides for subordinates in the opposite direction of what is documented in Juju. Given the end user experience is unaffected I'll bury this detail for the sake of consistency across the charms. Change-Id: Ia304cd25dbdd130338837f149a79efe59681f794 --- src/ovsdb_subordinate/provides.py | 160 +++++++---------- src/ovsdb_subordinate/requires.py | 168 +++++++++++------- unit_tests/test_ovsdb_subordinate_provides.py | 117 ++++++------ unit_tests/test_ovsdb_subordinate_requires.py | 119 +++++++------ 4 files changed, 282 insertions(+), 282 deletions(-) diff --git a/src/ovsdb_subordinate/provides.py b/src/ovsdb_subordinate/provides.py index affd9cf..b8d374f 100644 --- a/src/ovsdb_subordinate/provides.py +++ b/src/ovsdb_subordinate/provides.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import subprocess + import charms.reactive as reactive # the reactive framework unfortunately does not grok `import as` in conjunction @@ -27,107 +29,75 @@ from .ovsdb_subordinate_common import hash_hexdigest class OVSDBSubordinateProvides(Endpoint): - """This interface is used on a principle charm to connect to subordinate - """ + """This interface is used on the subordinate side of the relation""" - @property - def chassis_name(self): - """Retrieve chassis-name from relation data + def _get_ovs_value(self, tbl, col, rec=None): + """Get value of column in record in table - :returns: Chassis name as provided on subordinate relation - :rtype: str + :param tbl: Name of table + :type tbl: str + :param col: Name of column + :type col: str + :param rec: Record ID + :type rec: Optional[str] + :raises: subprocess.CalledProcessError """ - return self.all_joined_units.received.get('chassis-name', '') + cp = subprocess.run(('ovs-vsctl', 'get', tbl, rec or '.', col), + stdout=subprocess.PIPE, + check=True, universal_newlines=True) + return cp.stdout.rstrip().replace('"', '').replace("'", '') - @property - def ovn_configured(self): - """Retrieve whether OVN is configured from relation data - - :returns: True or False - :rtype: bool - """ - return self.all_joined_units.received.get('ovn-configured', False) - - def _add_interface_request(self, bridge, ifname, ifdata): - """Retrieve interface requests from relation and add/update requests - - :param bridge: Name of bridge - :type bridge: str - :param ifname: Name of interface - :type ifname: str - :param ifdata: Data to be attached to interface in Open vSwitch - :type ifdata: Dict[str,Union[str,Dict[str,str]]] - """ + def publish_chassis_name(self): + """Publish chassis name""" + ovs_hostname = self._get_ovs_value('Open_vSwitch', + 'external_ids:hostname') for relation in self.relations: - relation_ifs = relation.to_publish.get('create-interfaces', {}) - relation_ifs.update({bridge: {ifname: ifdata}}) - relation.to_publish['create-interfaces'] = relation_ifs + relation.to_publish['chassis-name'] = ovs_hostname - def _interface_requests(self): - """Retrieve interface requests from relation + def publish_ovn_configured(self): + """Publish whether OVN is configured in the local OVSDB""" + ovn_configured = False + try: + self._get_ovs_value('Open_vSwitch', 'external_ids:ovn-remote') + ovn_configured = True + except subprocess.CalledProcessError: + # No OVN + pass + + for relation in self.relations: + relation.to_publish['ovn-configured'] = ovn_configured + + @property + def interface_requests(self): + """Retrieve current interface requests :returns: Current interface requests - :rtype: Optional[Dict[str,Union[str,Dict[str,str]]]] + :rtype: Dict[str,Union[str,Dict[str,str]]] """ + return self.all_joined_units.received.get('create-interfaces', {}) + + def interface_requests_handled(self): + """Notify peer that interface requests has been dealt with + + Sets a hash of request data back on relation to signal to the other end + it has been dealt with so it can proceed. + + Note that we do not use the reactive request response pattern library + as we do not have use for per-unit granularity and we do not have + actual useful data to return. + """ + # The raw data is a json dump using sorted keys + ifreq_hexdigest = hash_hexdigest( + self.all_joined_units.received_raw['create-interfaces']) for relation in self.relations: - return relation.to_publish_raw.get('create-interfaces') - - def create_interface(self, bridge, ifname, ethaddr, ifid, - iftype=None, ifstatus=None): - """Request system interface created and attach it to CMS - - Calls to this function are additive so a principle charm can request to - have multiple interfaces created and maintained. - - The flag {endpoint_name}.{interface_name}.created will be set when - ready. - - :param bridge: Bridge the new interface should be created on - :type bridge: str - :param ifname: Interface name we want the new netdev to get - :type ifname: str - :param ethaddr: Ethernet address we want to attach to the netdev - :type ethaddr: str - :param ifid: Unique identifier for port from CMS - :type ifid: str - :param iftype: Interface type, defaults to 'internal' - :type iftype: Optional[str] - :param ifstatus: Interface status, defaults to 'active' - :type ifstatus: Optional[str] - """ - # The keys in the ifdata dictionary map directly to column names in the - # OpenvSwitch Interface table as defined in DB-SCHEMA [0] referenced in - # RFC 7047 [1] - # - # There are some established conventions for keys in the external-ids - # column of various tables, consult the OVS Integration Guide [2] for - # more details. - # - # NOTE(fnordahl): Technically the ``external-ids`` column is called - # ``external_ids`` (with an underscore) and we rely on ``ovs-vsctl``'s - # behaviour of transforming dashes to underscores for us [3] so we can - # have a more pleasant data structure. - # - # 0: http://www.openvswitch.org/ovs-vswitchd.conf.db.5.pdf - # 1: https://tools.ietf.org/html/rfc7047 - # 2: http://docs.openvswitch.org/en/latest/topics/integration/ - # 3: https://github.com/openvswitch/ovs/blob/ - # 20dac08fdcce4b7fda1d07add3b346aa9751cfbc/ - # lib/db-ctl-base.c#L189-L215 - ifdata = { - 'type': iftype or 'internal', - 'external-ids': { - 'iface-id': ifid, - 'iface-status': ifstatus or 'active', - 'attached-mac': ethaddr, - }, - } - self._add_interface_request(bridge, ifname, ifdata) + relation.to_publish['interfaces-created'] = ifreq_hexdigest reactive.clear_flag( - self.expand_name('{endpoint_name}.interfaces.created')) + self.expand_name('{endpoint_name}.interfaces.new_requests')) @when('endpoint.{endpoint_name}.joined') def joined(self): + self.publish_chassis_name() + self.publish_ovn_configured() reactive.set_flag(self.expand_name('{endpoint_name}.connected')) reactive.set_flag(self.expand_name('{endpoint_name}.available')) @@ -136,14 +106,10 @@ class OVSDBSubordinateProvides(Endpoint): reactive.clear_flag(self.expand_name('{endpoint_name}.available')) reactive.clear_flag(self.expand_name('{endpoint_name}.connected')) - @when('endpoint.{endpoint_name}.changed.interfaces-created') + @when('endpoint.{endpoint_name}.changed.create-interfaces') def new_requests(self): - ifreq = self._interface_requests() - - if ifreq is not None and self.all_joined_units.received[ - 'interfaces-created'] == hash_hexdigest(ifreq): - reactive.set_flag( - self.expand_name('{endpoint_name}.interfaces.created')) - reactive.clear_flag( - self.expand_name( - 'endpoint.{endpoint_name}.changed.interfaces-created')) + reactive.set_flag( + self.expand_name('{endpoint_name}.interfaces.new_requests')) + reactive.clear_flag( + self.expand_name( + 'endpoint.{endpoint_name}.changed.create-interfaces')) diff --git a/src/ovsdb_subordinate/requires.py b/src/ovsdb_subordinate/requires.py index 0df3d5e..ba30ebd 100644 --- a/src/ovsdb_subordinate/requires.py +++ b/src/ovsdb_subordinate/requires.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import subprocess - import charms.reactive as reactive # the reactive framework unfortunately does not grok `import as` in conjunction @@ -29,75 +27,107 @@ from .ovsdb_subordinate_common import hash_hexdigest class OVSDBSubordinateRequires(Endpoint): - """This interface is used on the subordinate side of the relation""" - - def _get_ovs_value(self, tbl, col, rec=None): - """Get value of column in record in table - - :param tbl: Name of table - :type tbl: str - :param col: Name of column - :type col: str - :param rec: Record ID - :type rec: Optional[str] - :raises: subprocess.CalledProcessError - """ - cp = subprocess.run(('ovs-vsctl', 'get', tbl, rec or '.', col), - stdout=subprocess.PIPE, - check=True, universal_newlines=True) - return cp.stdout.rstrip().replace('"', '').replace("'", '') - - def publish_chassis_name(self): - """Publish chassis name""" - ovs_hostname = self._get_ovs_value('Open_vSwitch', - 'external_ids:hostname') - for relation in self.relations: - relation.to_publish['chassis-name'] = ovs_hostname - - def publish_ovn_configured(self): - """Publish whether OVN is configured in the local OVSDB""" - ovn_configured = False - try: - self._get_ovs_value('Open_vSwitch', 'external_ids:ovn-remote') - ovn_configured = True - except subprocess.CalledProcessError: - # No OVN - pass - - for relation in self.relations: - relation.to_publish['ovn-configured'] = ovn_configured + """This interface is used on a principle charm to connect to subordinate + """ @property - def interface_requests(self): - """Retrieve current interface requests + def chassis_name(self): + """Retrieve chassis-name from relation data + + :returns: Chassis name as provided on subordinate relation + :rtype: str + """ + return self.all_joined_units.received.get('chassis-name', '') + + @property + def ovn_configured(self): + """Retrieve whether OVN is configured from relation data + + :returns: True or False + :rtype: bool + """ + return self.all_joined_units.received.get('ovn-configured', False) + + def _add_interface_request(self, bridge, ifname, ifdata): + """Retrieve interface requests from relation and add/update requests + + :param bridge: Name of bridge + :type bridge: str + :param ifname: Name of interface + :type ifname: str + :param ifdata: Data to be attached to interface in Open vSwitch + :type ifdata: Dict[str,Union[str,Dict[str,str]]] + """ + for relation in self.relations: + relation_ifs = relation.to_publish.get('create-interfaces', {}) + relation_ifs.update({bridge: {ifname: ifdata}}) + relation.to_publish['create-interfaces'] = relation_ifs + + def _interface_requests(self): + """Retrieve interface requests from relation :returns: Current interface requests - :rtype: Dict[str,Union[str,Dict[str,str]]] + :rtype: Optional[Dict[str,Union[str,Dict[str,str]]]] """ - return self.all_joined_units.received.get('create-interfaces', {}) - - def interface_requests_handled(self): - """Notify peer that interface requests has been dealt with - - Sets a hash of request data back on relation to signal to the other end - it has been dealt with so it can proceed. - - Note that we do not use the reactive request response pattern library - as we do not have use for per-unit granularity and we do not have - actual useful data to return. - """ - # The raw data is a json dump using sorted keys - ifreq_hexdigest = hash_hexdigest( - self.all_joined_units.received_raw['create-interfaces']) for relation in self.relations: - relation.to_publish['interfaces-created'] = ifreq_hexdigest + return relation.to_publish_raw.get('create-interfaces') + + def create_interface(self, bridge, ifname, ethaddr, ifid, + iftype=None, ifstatus=None): + """Request system interface created and attach it to CMS + + Calls to this function are additive so a principle charm can request to + have multiple interfaces created and maintained. + + The flag {endpoint_name}.{interface_name}.created will be set when + ready. + + :param bridge: Bridge the new interface should be created on + :type bridge: str + :param ifname: Interface name we want the new netdev to get + :type ifname: str + :param ethaddr: Ethernet address we want to attach to the netdev + :type ethaddr: str + :param ifid: Unique identifier for port from CMS + :type ifid: str + :param iftype: Interface type, defaults to 'internal' + :type iftype: Optional[str] + :param ifstatus: Interface status, defaults to 'active' + :type ifstatus: Optional[str] + """ + # The keys in the ifdata dictionary map directly to column names in the + # OpenvSwitch Interface table as defined in DB-SCHEMA [0] referenced in + # RFC 7047 [1] + # + # There are some established conventions for keys in the external-ids + # column of various tables, consult the OVS Integration Guide [2] for + # more details. + # + # NOTE(fnordahl): Technically the ``external-ids`` column is called + # ``external_ids`` (with an underscore) and we rely on ``ovs-vsctl``'s + # behaviour of transforming dashes to underscores for us [3] so we can + # have a more pleasant data structure. + # + # 0: http://www.openvswitch.org/ovs-vswitchd.conf.db.5.pdf + # 1: https://tools.ietf.org/html/rfc7047 + # 2: http://docs.openvswitch.org/en/latest/topics/integration/ + # 3: https://github.com/openvswitch/ovs/blob/ + # 20dac08fdcce4b7fda1d07add3b346aa9751cfbc/ + # lib/db-ctl-base.c#L189-L215 + ifdata = { + 'type': iftype or 'internal', + 'external-ids': { + 'iface-id': ifid, + 'iface-status': ifstatus or 'active', + 'attached-mac': ethaddr, + }, + } + self._add_interface_request(bridge, ifname, ifdata) reactive.clear_flag( - self.expand_name('{endpoint_name}.interfaces.new_requests')) + self.expand_name('{endpoint_name}.interfaces.created')) @when('endpoint.{endpoint_name}.joined') def joined(self): - self.publish_chassis_name() - self.publish_ovn_configured() reactive.set_flag(self.expand_name('{endpoint_name}.connected')) reactive.set_flag(self.expand_name('{endpoint_name}.available')) @@ -106,10 +136,14 @@ class OVSDBSubordinateRequires(Endpoint): reactive.clear_flag(self.expand_name('{endpoint_name}.available')) reactive.clear_flag(self.expand_name('{endpoint_name}.connected')) - @when('endpoint.{endpoint_name}.changed.create-interfaces') + @when('endpoint.{endpoint_name}.changed.interfaces-created') def new_requests(self): - reactive.set_flag( - self.expand_name('{endpoint_name}.interfaces.new_requests')) - reactive.clear_flag( - self.expand_name( - 'endpoint.{endpoint_name}.changed.create-interfaces')) + ifreq = self._interface_requests() + + if ifreq is not None and self.all_joined_units.received[ + 'interfaces-created'] == hash_hexdigest(ifreq): + reactive.set_flag( + self.expand_name('{endpoint_name}.interfaces.created')) + reactive.clear_flag( + self.expand_name( + 'endpoint.{endpoint_name}.changed.interfaces-created')) diff --git a/unit_tests/test_ovsdb_subordinate_provides.py b/unit_tests/test_ovsdb_subordinate_provides.py index c0d7f12..8f94409 100644 --- a/unit_tests/test_ovsdb_subordinate_provides.py +++ b/unit_tests/test_ovsdb_subordinate_provides.py @@ -54,64 +54,73 @@ class TestOVSDBSubordinateProvides(test_utils.PatchHelper): self._relations.__iter__.return_value = [relation] return relation.to_publish - def test_chassis_name(self): - self.patch_target('_all_joined_units') - self._all_joined_units.received.get.return_value = 'fakename' - self.assertEquals(self.target.chassis_name, 'fakename') - self._all_joined_units.received.get.assert_called_once_with( - 'chassis-name', '') + def test__get_ovs_value(self): + self.patch_object(provides.subprocess, 'run') + cp = mock.MagicMock() + cp.stdout = '"hostname-42"\n' + self.run.return_value = cp + self.assertEquals( + self.target._get_ovs_value('tbl', 'col'), + 'hostname-42') + self.run.assert_called_once_with( + ('ovs-vsctl', 'get', 'tbl', '.', 'col'), + stdout=mock.ANY, check=True, universal_newlines=True) + self.run.reset_mock() + self.target._get_ovs_value('tbl', 'col', rec='rec') + self.run.assert_called_once_with( + ('ovs-vsctl', 'get', 'tbl', 'rec', 'col'), + stdout=mock.ANY, check=True, universal_newlines=True) - def test_ovn_configured(self): - self.patch_target('_all_joined_units') - self._all_joined_units.received.get.return_value = True - self.assertEquals(self.target.ovn_configured, True) - self._all_joined_units.received.get.assert_called_once_with( - 'ovn-configured', False) - - def test__add_interface_request(self): + def test_publish_chassis_name(self): + self.patch_target('_get_ovs_value') to_publish = self.patch_topublish() - to_publish.get.return_value = {} - self.target._add_interface_request('br-ex', 'eth0', {'data': ''}) + self._get_ovs_value.return_value = 'aHostname' + self.target.publish_chassis_name() to_publish.__setitem__.assert_called_once_with( - 'create-interfaces', {'br-ex': {'eth0': {'data': ''}}}) + 'chassis-name', 'aHostname') - def test__interfrace_requests(self): - self.patch_target('_relations') - relation = mock.MagicMock() - self._relations.__iter__.return_value = [relation] - relation.to_publish_raw.get.return_value = 'aValue' - self.assertEquals(self.target._interface_requests(), 'aValue') - relation.to_publish_raw.get.assert_called_once_with( - 'create-interfaces') + def test_publish_ovn_configured(self): + self.patch_object(provides, 'subprocess') + self.subprocess.CalledProcessError = Exception + self.patch_target('_get_ovs_value') + to_publish = self.patch_topublish() + self._get_ovs_value.side_effect = Exception + self.target.publish_ovn_configured() + to_publish.__setitem__.assert_called_once_with('ovn-configured', False) + self._get_ovs_value.assert_called_once_with( + 'Open_vSwitch', 'external_ids:ovn-remote') + to_publish.__setitem__.reset_mock() + self._get_ovs_value.side_effect = None + self.target.publish_ovn_configured() + to_publish.__setitem__.assert_called_once_with('ovn-configured', True) - def test_create_interface(self): - self.patch_target('_add_interface_request') + def test_interface_requests(self): + self.patch_target('_all_joined_units') + self._all_joined_units.received.get.return_value = 'fakereq' + self.assertEquals( + self.target.interface_requests, 'fakereq') + + def test_interface_requests_handled(self): + self.patch_object(provides, 'hash_hexdigest') + self.hash_hexdigest.return_value = 'fakehash' + self.patch_target('_all_joined_units') + self._all_joined_units.received_raw.__getitem__.return_value = 'ifreq' + to_publish = self.patch_topublish() self.patch_object(provides.reactive, 'clear_flag') - ifdata = { - 'type': 'internal', - 'external-ids': { - 'iface-id': 'fakeuuid', - 'iface-status': 'active', - 'attached-mac': 'fakemac', - }, - } - self.target.create_interface('br-ex', 'eth0', 'fakemac', 'fakeuuid') - self._add_interface_request.assert_called_once_with( - 'br-ex', 'eth0', ifdata) + self.target.interface_requests_handled() + self.hash_hexdigest.assert_called_once_with('ifreq') + to_publish.__setitem__.assert_called_once_with( + 'interfaces-created', 'fakehash') self.clear_flag.assert_called_once_with( - 'some-relation.interfaces.created') - self._add_interface_request.reset_mock() - ifdata['type'] = 'someothervalue' - ifdata['external-ids']['iface-status'] = 'inactive' - self.target.create_interface('br-ex', 'eth0', 'fakemac', 'fakeuuid', - iftype='someothervalue', - ifstatus='inactive') - self._add_interface_request.assert_called_once_with( - 'br-ex', 'eth0', ifdata) + 'some-relation.interfaces.new_requests') def test_joined(self): + self.patch_target('publish_chassis_name') + self.patch_target('publish_ovn_configured') self.patch_object(provides.reactive, 'set_flag') self.target.joined() + self.publish_chassis_name.assert_called_once_with() + self.publish_ovn_configured.assert_called_once_with() self.set_flag.assert_has_calls([ mock.call('some-relation.connected'), mock.call('some-relation.available'), @@ -126,20 +135,10 @@ class TestOVSDBSubordinateProvides(test_utils.PatchHelper): ]) def test_new_requests(self): - self.patch_target('_interface_requests') - self.patch_target('_all_joined_units') - self.patch_object(provides, 'hash_hexdigest') - self.hash_hexdigest.return_value = 'fakehash' - self._interface_requests.return_value = 'fakerequests' self.patch_object(provides.reactive, 'set_flag') self.patch_object(provides.reactive, 'clear_flag') self.target.new_requests() - self.hash_hexdigest.assert_called_once_with('fakerequests') - self.assertFalse(self.set_flag.called) - self.assertFalse(self.clear_flag.called) - self._all_joined_units.received.__getitem__.return_value = 'fakehash' - self.target.new_requests() self.set_flag.assert_called_once_with( - 'some-relation.interfaces.created') + 'some-relation.interfaces.new_requests') self.clear_flag.assert_called_once_with( - 'endpoint.some-relation.changed.interfaces-created') + 'endpoint.some-relation.changed.create-interfaces') diff --git a/unit_tests/test_ovsdb_subordinate_requires.py b/unit_tests/test_ovsdb_subordinate_requires.py index 0d56c56..a2cccab 100644 --- a/unit_tests/test_ovsdb_subordinate_requires.py +++ b/unit_tests/test_ovsdb_subordinate_requires.py @@ -22,7 +22,7 @@ import charms_openstack.test_utils as test_utils _hook_args = {} -class TestOVSDBSubordinateProvides(test_utils.PatchHelper): +class TestOVSDBSubordinateRequires(test_utils.PatchHelper): def setUp(self): super().setUp() @@ -54,73 +54,64 @@ class TestOVSDBSubordinateProvides(test_utils.PatchHelper): self._relations.__iter__.return_value = [relation] return relation.to_publish - def test__get_ovs_value(self): - self.patch_object(requires.subprocess, 'run') - cp = mock.MagicMock() - cp.stdout = '"hostname-42"\n' - self.run.return_value = cp - self.assertEquals( - self.target._get_ovs_value('tbl', 'col'), - 'hostname-42') - self.run.assert_called_once_with( - ('ovs-vsctl', 'get', 'tbl', '.', 'col'), - stdout=mock.ANY, check=True, universal_newlines=True) - self.run.reset_mock() - self.target._get_ovs_value('tbl', 'col', rec='rec') - self.run.assert_called_once_with( - ('ovs-vsctl', 'get', 'tbl', 'rec', 'col'), - stdout=mock.ANY, check=True, universal_newlines=True) + def test_chassis_name(self): + self.patch_target('_all_joined_units') + self._all_joined_units.received.get.return_value = 'fakename' + self.assertEquals(self.target.chassis_name, 'fakename') + self._all_joined_units.received.get.assert_called_once_with( + 'chassis-name', '') - def test_publish_chassis_name(self): - self.patch_target('_get_ovs_value') + def test_ovn_configured(self): + self.patch_target('_all_joined_units') + self._all_joined_units.received.get.return_value = True + self.assertEquals(self.target.ovn_configured, True) + self._all_joined_units.received.get.assert_called_once_with( + 'ovn-configured', False) + + def test__add_interface_request(self): to_publish = self.patch_topublish() - self._get_ovs_value.return_value = 'aHostname' - self.target.publish_chassis_name() + to_publish.get.return_value = {} + self.target._add_interface_request('br-ex', 'eth0', {'data': ''}) to_publish.__setitem__.assert_called_once_with( - 'chassis-name', 'aHostname') + 'create-interfaces', {'br-ex': {'eth0': {'data': ''}}}) - def test_publish_ovn_configured(self): - self.patch_object(requires, 'subprocess') - self.subprocess.CalledProcessError = Exception - self.patch_target('_get_ovs_value') - to_publish = self.patch_topublish() - self._get_ovs_value.side_effect = Exception - self.target.publish_ovn_configured() - to_publish.__setitem__.assert_called_once_with('ovn-configured', False) - self._get_ovs_value.assert_called_once_with( - 'Open_vSwitch', 'external_ids:ovn-remote') - to_publish.__setitem__.reset_mock() - self._get_ovs_value.side_effect = None - self.target.publish_ovn_configured() - to_publish.__setitem__.assert_called_once_with('ovn-configured', True) + def test__interfrace_requests(self): + self.patch_target('_relations') + relation = mock.MagicMock() + self._relations.__iter__.return_value = [relation] + relation.to_publish_raw.get.return_value = 'aValue' + self.assertEquals(self.target._interface_requests(), 'aValue') + relation.to_publish_raw.get.assert_called_once_with( + 'create-interfaces') - def test_interface_requests(self): - self.patch_target('_all_joined_units') - self._all_joined_units.received.get.return_value = 'fakereq' - self.assertEquals( - self.target.interface_requests, 'fakereq') - - def test_interface_requests_handled(self): - self.patch_object(requires, 'hash_hexdigest') - self.hash_hexdigest.return_value = 'fakehash' - self.patch_target('_all_joined_units') - self._all_joined_units.received_raw.__getitem__.return_value = 'ifreq' - to_publish = self.patch_topublish() + def test_create_interface(self): + self.patch_target('_add_interface_request') self.patch_object(requires.reactive, 'clear_flag') - self.target.interface_requests_handled() - self.hash_hexdigest.assert_called_once_with('ifreq') - to_publish.__setitem__.assert_called_once_with( - 'interfaces-created', 'fakehash') + ifdata = { + 'type': 'internal', + 'external-ids': { + 'iface-id': 'fakeuuid', + 'iface-status': 'active', + 'attached-mac': 'fakemac', + }, + } + self.target.create_interface('br-ex', 'eth0', 'fakemac', 'fakeuuid') + self._add_interface_request.assert_called_once_with( + 'br-ex', 'eth0', ifdata) self.clear_flag.assert_called_once_with( - 'some-relation.interfaces.new_requests') + 'some-relation.interfaces.created') + self._add_interface_request.reset_mock() + ifdata['type'] = 'someothervalue' + ifdata['external-ids']['iface-status'] = 'inactive' + self.target.create_interface('br-ex', 'eth0', 'fakemac', 'fakeuuid', + iftype='someothervalue', + ifstatus='inactive') + self._add_interface_request.assert_called_once_with( + 'br-ex', 'eth0', ifdata) def test_joined(self): - self.patch_target('publish_chassis_name') - self.patch_target('publish_ovn_configured') self.patch_object(requires.reactive, 'set_flag') self.target.joined() - self.publish_chassis_name.assert_called_once_with() - self.publish_ovn_configured.assert_called_once_with() self.set_flag.assert_has_calls([ mock.call('some-relation.connected'), mock.call('some-relation.available'), @@ -135,10 +126,20 @@ class TestOVSDBSubordinateProvides(test_utils.PatchHelper): ]) def test_new_requests(self): + self.patch_target('_interface_requests') + self.patch_target('_all_joined_units') + self.patch_object(requires, 'hash_hexdigest') + self.hash_hexdigest.return_value = 'fakehash' + self._interface_requests.return_value = 'fakerequests' self.patch_object(requires.reactive, 'set_flag') self.patch_object(requires.reactive, 'clear_flag') self.target.new_requests() + self.hash_hexdigest.assert_called_once_with('fakerequests') + self.assertFalse(self.set_flag.called) + self.assertFalse(self.clear_flag.called) + self._all_joined_units.received.__getitem__.return_value = 'fakehash' + self.target.new_requests() self.set_flag.assert_called_once_with( - 'some-relation.interfaces.new_requests') + 'some-relation.interfaces.created') self.clear_flag.assert_called_once_with( - 'endpoint.some-relation.changed.create-interfaces') + 'endpoint.some-relation.changed.interfaces-created')