diff --git a/neutron/plugins/cisco/db/n1kv_db_v2.py b/neutron/plugins/cisco/db/n1kv_db_v2.py index 5c237115c6..be54fc097d 100644 --- a/neutron/plugins/cisco/db/n1kv_db_v2.py +++ b/neutron/plugins/cisco/db/n1kv_db_v2.py @@ -850,6 +850,13 @@ def get_policy_profile(db_session, id): raise c_exc.PolicyProfileIdNotFound(profile_id=id) +def get_policy_profiles(): + """Retrieve all policy profiles.""" + db_session = db.get_session() + with db_session.begin(subtransactions=True): + return db_session.query(n1kv_models_v2.PolicyProfile) + + def create_profile_binding(db_session, tenant_id, profile_id, profile_type): """Create Network/Policy Profile association with a tenant.""" db_session = db_session or db.get_session() diff --git a/neutron/plugins/cisco/n1kv/n1kv_client.py b/neutron/plugins/cisco/n1kv/n1kv_client.py index 6164d96388..79abf715e9 100644 --- a/neutron/plugins/cisco/n1kv/n1kv_client.py +++ b/neutron/plugins/cisco/n1kv/n1kv_client.py @@ -156,18 +156,6 @@ class Client(object): """ return self._get(self.port_profiles_path) - def list_events(self, event_type=None, epoch=None): - """ - Fetch all events of event_type from the VSM. - - :param event_type: type of event to be listed. - :param epoch: timestamp after which the events occurred to be listed. - :returns: XML string - """ - if event_type: - self.events_path = self.events_path + '?type=' + event_type - return self._get(self.events_path) - def create_bridge_domain(self, network, overlay_subtype): """ Create a bridge domain on VSM. diff --git a/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py b/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py index badb7fe379..0e77f9029c 100644 --- a/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py +++ b/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py @@ -154,15 +154,13 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, """ LOG.debug(_('_setup_vsm')) self.agent_vsm = True - # Retrieve all the policy profiles from VSM. - self._populate_policy_profiles() - # Continue to poll VSM for any create/delete of policy profiles. + # Poll VSM for create/delete of policy profile. eventlet.spawn(self._poll_policy_profiles) def _poll_policy_profiles(self): """Start a green thread to pull policy profiles from VSM.""" while True: - self._poll_policies(event_type='port_profile') + self._populate_policy_profiles() eventlet.sleep(int(c_conf.CISCO_N1K.poll_duration)) def _populate_policy_profiles(self): @@ -177,53 +175,35 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, try: n1kvclient = n1kv_client.Client() policy_profiles = n1kvclient.list_port_profiles() - LOG.debug(_('_populate_policy_profiles %s'), policy_profiles) + vsm_profiles = {} + plugin_profiles = {} + # Fetch policy profiles from VSM if policy_profiles: for profile in policy_profiles['body'][c_const.SET]: - if c_const.ID and c_const.NAME in profile: - profile_id = profile[c_const.PROPERTIES][c_const.ID] - profile_name = profile[c_const. - PROPERTIES][c_const.NAME] - self._add_policy_profile(profile_name, profile_id) + profile_name = (profile[c_const.PROPERTIES]. + get(c_const.NAME, None)) + profile_id = (profile[c_const.PROPERTIES]. + get(c_const.ID, None)) + if profile_id and profile_name: + vsm_profiles[profile_id] = profile_name + # Fetch policy profiles previously populated + for profile in n1kv_db_v2.get_policy_profiles(): + plugin_profiles[profile.id] = profile.name + vsm_profiles_set = set(vsm_profiles) + plugin_profiles_set = set(plugin_profiles) + # Update database if the profile sets differ. + if vsm_profiles_set ^ plugin_profiles_set: + # Add profiles in database if new profiles were created in VSM + for pid in vsm_profiles_set - plugin_profiles_set: + self._add_policy_profile(vsm_profiles[pid], pid) + # Delete profiles from database if profiles were deleted in VSM + for pid in plugin_profiles_set - vsm_profiles_set: + self._delete_policy_profile(pid) self._remove_all_fake_policy_profiles() except (cisco_exceptions.VSMError, cisco_exceptions.VSMConnectionFailed): LOG.warning(_('No policy profile populated from VSM')) - def _poll_policies(self, event_type=None, epoch=None, tenant_id=None): - """ - Poll for Policy Profiles from Cisco Nexus1000V for any update/delete. - """ - LOG.debug(_('_poll_policies')) - try: - n1kvclient = n1kv_client.Client() - policy_profiles = n1kvclient.list_events(event_type, epoch) - if policy_profiles: - for profile in policy_profiles['body'][c_const.SET]: - if c_const.NAME in profile: - # Extract commands from the events XML. - cmd = profile[c_const.PROPERTIES]['cmd'] - cmds = cmd.split(';') - cmdwords = cmds[1].split() - profile_name = profile[c_const. - PROPERTIES][c_const.NAME] - # Delete the policy profile from db if deleted on VSM - if 'no' in cmdwords[0]: - p = self._get_policy_profile_by_name(profile_name) - if p: - self._delete_policy_profile(p['id']) - # Add policy profile to neutron DB idempotently - elif c_const.ID in profile[c_const.PROPERTIES]: - profile_id = profile[c_const. - PROPERTIES][c_const.ID] - self._add_policy_profile( - profile_name, profile_id, tenant_id) - # Replace tenant-id for profile bindings with admin's tenant-id - self._remove_all_fake_policy_profiles() - except (cisco_exceptions.VSMError, - cisco_exceptions.VSMConnectionFailed): - LOG.warning(_('No policy profile updated from VSM')) - def _extend_network_dict_provider(self, context, network): """Add extended network parameters.""" binding = n1kv_db_v2.get_network_binding(context.session, diff --git a/neutron/tests/unit/cisco/n1kv/fake_client.py b/neutron/tests/unit/cisco/n1kv/fake_client.py index 12a36e60b0..55f8765195 100755 --- a/neutron/tests/unit/cisco/n1kv/fake_client.py +++ b/neutron/tests/unit/cisco/n1kv/fake_client.py @@ -18,7 +18,7 @@ # @author: Sourabh Patwardhan, Cisco Systems Inc. from neutron.openstack.common import log as logging -from neutron.plugins.cisco.common import cisco_exceptions +from neutron.plugins.cisco.common import cisco_exceptions as c_exc from neutron.plugins.cisco.n1kv.n1kv_client import Client as n1kv_client LOG = logging.getLogger(__name__) @@ -36,15 +36,26 @@ class TestClient(n1kv_client): def __init__(self, **kwargs): self.broken = False self.inject_params = False + self.total_profiles = 2 super(TestClient, self).__init__() + def _get_total_profiles(self): + return self.total_profiles + def _do_request(self, method, action, body=None, headers=None): if self.broken: - raise cisco_exceptions.VSMError(reason='VSM:Internal Server Error') + raise c_exc.VSMError(reason='VSM:Internal Server Error') if self.inject_params and body: body['invalidKey'] = 'catchMeIfYouCan' if method == 'POST': return _validate_resource(action, body) + elif method == 'GET': + if 'virtual-port-profile' in action: + profiles = _policy_profile_generator_xml( + self._get_total_profiles()) + return self._deserialize(profiles, 200) + else: + raise c_exc.VSMError(reason='VSM:Internal Server Error') class TestClientInvalidRequest(TestClient): @@ -62,10 +73,33 @@ def _validate_resource(action, body=None): if 'vm-network' in action and 'port' not in action: vmnetwork_set = set(_resource_metadata['vmnetwork']) if body_set - vmnetwork_set: - raise cisco_exceptions.VSMError(reason='Invalid Request') + raise c_exc.VSMError(reason='Invalid Request') elif 'port' in action: port_set = set(_resource_metadata['port']) if body_set - port_set: - raise cisco_exceptions.VSMError(reason='Invalid Request') + raise c_exc.VSMError(reason='Invalid Request') else: return + + +def _policy_profile_generator_xml(total_profiles): + """ + Generate policy profile response in XML format. + + :param total_profiles: integer representing total number of profiles to + return + """ + xml = [""" + """] + template = ( + '' + '' + '00000000-0000-0000-0000-00000000000%(num)s' + 'pp-%(num)s' + '' + '' + ) + xml.extend(template % {'num': n} for n in range(1, total_profiles + 1)) + xml.append("") + return ''.join(xml) diff --git a/neutron/tests/unit/cisco/n1kv/test_n1kv_plugin.py b/neutron/tests/unit/cisco/n1kv/test_n1kv_plugin.py index d9dc4cf3b2..793f037492 100644 --- a/neutron/tests/unit/cisco/n1kv/test_n1kv_plugin.py +++ b/neutron/tests/unit/cisco/n1kv/test_n1kv_plugin.py @@ -25,6 +25,7 @@ from neutron.api.v2 import attributes from neutron import context import neutron.db.api as db from neutron.extensions import portbindings +from neutron import manager from neutron.plugins.cisco.common import cisco_exceptions as c_exc from neutron.plugins.cisco.db import n1kv_db_v2 from neutron.plugins.cisco.db import network_db_v2 as cdb @@ -69,7 +70,7 @@ class FakeResponse(object): def _fake_setup_vsm(self): """Fake establish Communication with Cisco Nexus1000V VSM.""" self.agent_vsm = True - self._poll_policies(event_type="port_profile") + self._populate_policy_profiles() class NetworkProfileTestExtensionManager(object): @@ -506,6 +507,55 @@ class TestN1kvPorts(test_plugin.TestPortsV2, client_patch.stop() +class TestN1kvPolicyProfiles(N1kvPluginTestCase): + def test_populate_policy_profile(self): + client_patch = patch(n1kv_client.__name__ + ".Client", + new=fake_client.TestClient) + client_patch.start() + instance = n1kv_neutron_plugin.N1kvNeutronPluginV2() + instance._populate_policy_profiles() + db_session = db.get_session() + profile = n1kv_db_v2.get_policy_profile( + db_session, '00000000-0000-0000-0000-000000000001') + self.assertEqual('pp-1', profile['name']) + client_patch.stop() + + def test_populate_policy_profile_delete(self): + # Patch the Client class with the TestClient class + with patch(n1kv_client.__name__ + ".Client", + new=fake_client.TestClient): + # Patch the _get_total_profiles() method to return a custom value + with patch(fake_client.__name__ + + '.TestClient._get_total_profiles') as obj_inst: + # Return 3 policy profiles + obj_inst.return_value = 3 + plugin = manager.NeutronManager.get_plugin() + plugin._populate_policy_profiles() + db_session = db.get_session() + profile = n1kv_db_v2.get_policy_profile( + db_session, '00000000-0000-0000-0000-000000000001') + # Verify that DB contains only 3 policy profiles + self.assertEqual('pp-1', profile['name']) + profile = n1kv_db_v2.get_policy_profile( + db_session, '00000000-0000-0000-0000-000000000002') + self.assertEqual('pp-2', profile['name']) + profile = n1kv_db_v2.get_policy_profile( + db_session, '00000000-0000-0000-0000-000000000003') + self.assertEqual('pp-3', profile['name']) + self.assertRaises(c_exc.PolicyProfileIdNotFound, + n1kv_db_v2.get_policy_profile, + db_session, + '00000000-0000-0000-0000-000000000004') + # Return 2 policy profiles + obj_inst.return_value = 2 + plugin._populate_policy_profiles() + # Verify that the third policy profile is deleted + self.assertRaises(c_exc.PolicyProfileIdNotFound, + n1kv_db_v2.get_policy_profile, + db_session, + '00000000-0000-0000-0000-000000000003') + + class TestN1kvNetworks(test_plugin.TestNetworksV2, N1kvPluginTestCase):