diff --git a/quark/ipam.py b/quark/ipam.py index 21f482d..2e630cf 100644 --- a/quark/ipam.py +++ b/quark/ipam.py @@ -21,13 +21,14 @@ import netaddr from neutron.common import exceptions from neutron.openstack.common import log as logging +from neutron.openstack.common.notifier import api as notifier_api from neutron.openstack.common import timeutils from quark.db import api as db_api from quark.db import models -LOG = logging.getLogger("neutron") +LOG = logging.getLogger("neutron.quark.ipam") class QuarkIpam(object): @@ -137,13 +138,39 @@ class QuarkIpam(object): version=subnet["ip_version"], network_id=net_id) address["deallocated"] = 0 + payload = dict(tenant_id=address["tenant_id"], + ip_block_id=address["subnet_id"], + ip_address=address["address_readable"], + device_ids=[p["device_id"] for p in address["ports"]], + created_at=address["created_at"]) + + notifier_api.notify(context, + notifier_api.publisher_id("network"), + "ip_block.address.create", + notifier_api.CONF.default_notification_level, + payload) + return address + def _deallocate_ip_address(self, context, address): + address["deallocated"] = 1 + payload = dict(tenant_id=address["tenant_id"], + ip_block_id=address["subnet_id"], + ip_address=address["address_readable"], + device_ids=[p["device_id"] for p in address["ports"]], + created_at=address["created_at"], + deleted_at=timeutils.utcnow()) + notifier_api.notify(context, + notifier_api.publisher_id("network"), + "ip_block.address.delete", + notifier_api.CONF.default_notification_level, + payload) + def deallocate_ip_address(self, context, port, **kwargs): for addr in port["ip_addresses"]: # Note: only deallocate ip if this is the only port mapped to it if len(addr["ports"]) == 1: - addr["deallocated"] = 1 + self._deallocate_ip_address(context, addr) port["ip_addresses"] = [] def deallocate_mac_address(self, context, address): diff --git a/quark/plugin.py b/quark/plugin.py index 7984604..7390a5d 100644 --- a/quark/plugin.py +++ b/quark/plugin.py @@ -86,7 +86,6 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2, neutron_session._MAKER = scoped_session(session_maker) def __init__(self): - neutron_db_api.configure_db() self._initDBMaker() neutron_db_api.register_models(base=models.BASEV2) diff --git a/quark/plugin_modules/subnets.py b/quark/plugin_modules/subnets.py index e3d2417..5bb1a5f 100644 --- a/quark/plugin_modules/subnets.py +++ b/quark/plugin_modules/subnets.py @@ -19,6 +19,9 @@ from neutron.common import config as neutron_cfg from neutron.common import exceptions from neutron.openstack.common import importutils from neutron.openstack.common import log as logging +from neutron.openstack.common.notifier import api as notifier_api +from neutron.openstack.common import timeutils + from oslo.config import cfg from quark.db import api as db_api @@ -133,6 +136,15 @@ def create_subnet(context, subnet): subnet_dict = v._make_subnet_dict(new_subnet, default_route=routes.DEFAULT_ROUTE) subnet_dict["gateway_ip"] = gateway_ip + + notifier_api.notify(context, + notifier_api.publisher_id("network"), + "ip_block.create", + notifier_api.CONF.default_notification_level, + dict(tenant_id=subnet_dict["tenant_id"], + ip_block_id=subnet_dict["id"], + created_at=new_subnet["created_at"])) + return subnet_dict @@ -283,8 +295,20 @@ def delete_subnet(context, id): subnet = db_api.subnet_find(context, id=id, scope=db_api.ONE) if not subnet: raise exceptions.SubnetNotFound(subnet_id=id) + + payload = dict(tenant_id=subnet["tenant_id"], + ip_block_id=subnet["id"], + created_at=subnet["created_at"], + deleted_at=timeutils.utcnow()) + _delete_subnet(context, subnet) + notifier_api.notify(context, + notifier_api.publisher_id("network"), + "ip_block.delete", + notifier_api.CONF.default_notification_level, + payload) + def diagnose_subnet(context, id, fields): if id == "*": diff --git a/quark/tests/plugin_modules/test_subnets.py b/quark/tests/plugin_modules/test_subnets.py index 8f2adb8..b08d2e7 100644 --- a/quark/tests/plugin_modules/test_subnets.py +++ b/quark/tests/plugin_modules/test_subnets.py @@ -20,6 +20,7 @@ import uuid import mock from neutron.api.v2 import attributes as neutron_attrs from neutron.common import exceptions +from neutron.openstack.common.notifier import api as notifier_api from oslo.config import cfg from quark.db import models @@ -694,3 +695,53 @@ class TestQuarkDeleteSubnet(test_quark_plugin.TestQuarkPlugin): with self._stubs(subnet=subnet, ips=[{}]): with self.assertRaises(exceptions.SubnetInUse): self.plugin.delete_subnet(self.context, 1) + + +class TestSubnetsNotification(test_quark_plugin.TestQuarkPlugin): + @contextlib.contextmanager + def _stubs(self, s, deleted_at=None): + s["network"] = models.Network() + subnet = models.Subnet(**s) + db_mod = "quark.db.api" + api_mod = "neutron.openstack.common.notifier.api" + time_mod = "neutron.openstack.common.timeutils" + with contextlib.nested( + mock.patch("%s.subnet_find" % db_mod), + mock.patch("%s.network_find" % db_mod), + mock.patch("%s.subnet_create" % db_mod), + mock.patch("%s.subnet_delete" % db_mod), + mock.patch("%s.notify" % api_mod), + mock.patch("%s.utcnow" % time_mod) + ) as (sub_find, net_find, sub_create, sub_del, notify, time): + sub_create.return_value = subnet + sub_find.return_value = subnet + time.return_value = deleted_at + yield notify + + def test_create_subnet_notification(self): + s = dict(network_id=1, cidr="192.168.10.0/24", + tenant_id=1, id=1, created_at="123") + with self._stubs(s) as notify: + self.plugin.create_subnet(self.context, dict(subnet=s)) + notify.assert_called_once_with( + self.context, + notifier_api.publisher_id("network"), + "ip_block.create", + notifier_api.CONF.default_notification_level, + dict(tenant_id=s["tenant_id"], + ip_block_id=s["id"], + created_at=s["created_at"])) + + def test_delete_subnet_notification(self): + s = dict(tenant_id=1, id=1, created_at="123") + with self._stubs(s, deleted_at="456") as notify: + self.plugin.delete_subnet(self.context, 1) + notify.assert_called_once_with( + self.context, + notifier_api.publisher_id("network"), + "ip_block.delete", + notifier_api.CONF.default_notification_level, + dict(tenant_id=s["tenant_id"], + created_at=s["created_at"], + ip_block_id=s["id"], + deleted_at="456")) diff --git a/quark/tests/test_ipam.py b/quark/tests/test_ipam.py index bd0e5cb..4e9838e 100644 --- a/quark/tests/test_ipam.py +++ b/quark/tests/test_ipam.py @@ -18,9 +18,11 @@ import mock from neutron.common import exceptions from neutron.db import api as neutron_db_api from neutron.openstack.common.db.sqlalchemy import session as neutron_session +from neutron.openstack.common.notifier import api as notifier_api from oslo.config import cfg from quark.db import models + import quark.ipam from quark.tests import test_base @@ -173,8 +175,9 @@ class QuarkMacAddressDeallocation(QuarkIpamBaseTest): class QuarkIPAddressDeallocation(QuarkIpamBaseTest): def test_deallocate_ip_address(self): - port = dict(ip_addresses=[]) - addr = dict(ports=[port]) + port = dict(ip_addresses=[], device_id="foo") + addr = dict(ports=[port], tenant_id=1, subnet_id=1, + address_readable=None, created_at=None) port["ip_addresses"].append(addr) self.ipam.deallocate_ip_address(self.context, port) # ORM takes care of other model if one model is modified @@ -468,3 +471,70 @@ class TestQuarkIpPoliciesIpAllocation(QuarkIpamBaseTest): address = self.ipam.allocate_ip_address( self.context, 0, 0, 0, ip_address="0.0.0.240") self.assertEqual(address["address"], 240) + + +class QuarkIPAddressAllocationNotifications(QuarkIpamBaseTest): + @contextlib.contextmanager + def _stubs(self, address, addresses=None, subnets=None, deleted_at=None): + address = models.IPAddress(**address) + if not addresses: + addresses = [None] + db_mod = "quark.db.api" + api_mod = "neutron.openstack.common.notifier.api" + time_mod = "neutron.openstack.common.timeutils" + with contextlib.nested( + mock.patch("%s.ip_address_find" % db_mod), + mock.patch("%s.ip_address_create" % db_mod), + mock.patch("%s.subnet_find_allocation_counts" % db_mod), + mock.patch("%s.notify" % api_mod), + mock.patch("%s.utcnow" % time_mod), + ) as (addr_find, addr_create, subnet_find, notify, time): + addr_find.side_effect = addresses + addr_create.return_value = address + subnet_find.return_value = subnets + time.return_value = deleted_at + yield notify + + def test_allocation_notification(self): + subnet = dict(id=1, first_ip=0, last_ip=255, + cidr="0.0.0.0/24", ip_version=4, + next_auto_assign_ip=0, network=dict(ip_policy=None), + ip_policy=None) + address = dict(tenant_id=1, address=0, created_at="123", + subnet_id=1, address_readable="0.0.0.0") + with self._stubs( + address, + subnets=[(subnet, 0)], + addresses=[None, None] + ) as notify: + self.ipam.allocate_ip_address(self.context, 0, 0, 0, + version=4) + notify.assert_called_once_with( + self.context, + notifier_api.publisher_id("network"), + "ip_block.address.create", + notifier_api.CONF.default_notification_level, + dict(tenant_id=address["tenant_id"], + ip_block_id=address["subnet_id"], + ip_address="0.0.0.0", + device_ids=[], + created_at=address["created_at"])) + + def test_deallocation_notification(self): + address = dict(tenant_id=1, address=0, created_at="123", + subnet_id=1, address_readable="0.0.0.0", + ports=[dict(device_id="foo")]) + port = dict(ip_addresses=[address]) + with self._stubs(dict(), deleted_at="456") as notify: + self.ipam.deallocate_ip_address(self.context, port) + notify.assert_called_once_with( + self.context, + notifier_api.publisher_id("network"), + "ip_block.address.delete", + notifier_api.CONF.default_notification_level, + dict(tenant_id=address["tenant_id"], + ip_block_id=address["subnet_id"], + ip_address="0.0.0.0", + device_ids=["foo"], + created_at=address["created_at"], + deleted_at="456"))