From daf92b6bf47c92ecfbf754e81ff51827652e3199 Mon Sep 17 00:00:00 2001 From: Pradeep Kilambi Date: Mon, 14 Jul 2014 14:35:40 -0700 Subject: [PATCH] Support for metering FWaaS Partially-Implements: blueprint ceilometer-meter-fwaas Change-Id: I3b0e40fa3bbddc304d3adf936626334342288a2d --- ceilometer/network/services/discovery.py | 24 +++ ceilometer/network/services/fwaas.py | 84 +++++++++ ceilometer/neutron_client.py | 10 ++ .../tests/network/services/test_fwaas.py | 166 ++++++++++++++++++ etc/ceilometer/pipeline.yaml | 16 ++ setup.cfg | 4 + 6 files changed, 304 insertions(+) create mode 100644 ceilometer/network/services/fwaas.py create mode 100644 ceilometer/tests/network/services/test_fwaas.py diff --git a/ceilometer/network/services/discovery.py b/ceilometer/network/services/discovery.py index 71f97c24..2924b590 100644 --- a/ceilometer/network/services/discovery.py +++ b/ceilometer/network/services/discovery.py @@ -101,3 +101,27 @@ class IPSecConnectionsDiscovery(_BaseServicesDiscovery): conns = self.neutron_cli.ipsec_site_connections_get_all() return conns + + +class FirewallDiscovery(_BaseServicesDiscovery): + + def __init__(self): + super(FirewallDiscovery, self).__init__() + + def discover(self, param=None): + """Discover resources to monitor.""" + + fw = self.neutron_cli.firewall_get_all() + return [i for i in fw + if i.get('status', None) != 'error'] + + +class FirewallPolicyDiscovery(_BaseServicesDiscovery): + + def __init__(self): + super(FirewallPolicyDiscovery, self).__init__() + + def discover(self, param=None): + """Discover resources to monitor.""" + + return self.neutron_cli.fw_policy_get_all() diff --git a/ceilometer/network/services/fwaas.py b/ceilometer/network/services/fwaas.py new file mode 100644 index 00000000..27ed88e9 --- /dev/null +++ b/ceilometer/network/services/fwaas.py @@ -0,0 +1,84 @@ +# +# Copyright 2014 Cisco Systems,Inc. +# +# Author: Pradeep Kilambi +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from ceilometer.network.services import base +from ceilometer.openstack.common import log +from ceilometer.openstack.common import timeutils +from ceilometer import sample + +LOG = log.getLogger(__name__) + + +class FirewallPollster(base.BaseServicesPollster): + """Pollster to capture firewalls status samples.""" + + FIELDS = ['admin_state_up', + 'description', + 'name', + 'status', + 'firewall_policy_id', + ] + + def get_samples(self, manager, cache, resources=None): + for fw in resources: + LOG.debug("Firewall : %s" % fw) + status = self.get_status_id(fw['status']) + if status == -1: + # unknown status, skip this sample + LOG.warn("Unknown status %s received on firewall %s, " + "skipping sample" % (fw['status'], fw['id'])) + continue + + yield sample.Sample( + name='network.services.firewall', + type=sample.TYPE_GAUGE, + unit='firewall', + volume=status, + user_id=None, + project_id=fw['tenant_id'], + resource_id=fw['id'], + timestamp=timeutils.utcnow().isoformat(), + resource_metadata=self.extract_metadata(fw) + ) + + +class FirewallPolicyPollster(base.BaseServicesPollster): + """Pollster to capture firewalls status samples.""" + + FIELDS = ['name', + 'description', + 'name', + 'firewall_rules', + 'shared', + 'audited', + ] + + def get_samples(self, manager, cache, resources=None): + for fw in resources: + LOG.debug("Firewall Policy: %s" % fw) + + yield sample.Sample( + name='network.services.firewall.policy', + type=sample.TYPE_GAUGE, + unit='policy', + volume=1, + user_id=None, + project_id=fw['tenant_id'], + resource_id=fw['id'], + timestamp=timeutils.utcnow().isoformat(), + resource_metadata=self.extract_metadata(fw) + ) diff --git a/ceilometer/neutron_client.py b/ceilometer/neutron_client.py index 4f64afd5..c70aa6dd 100644 --- a/ceilometer/neutron_client.py +++ b/ceilometer/neutron_client.py @@ -106,3 +106,13 @@ class Client(object): def ipsec_site_connections_get_all(self): resp = self.client.list_ipsec_site_connections() return resp.get('ipsec_site_connections') + + @logged + def firewall_get_all(self): + resp = self.client.list_firewalls() + return resp.get('firewalls') + + @logged + def fw_policy_get_all(self): + resp = self.client.list_firewall_policies() + return resp.get('firewall_policies') diff --git a/ceilometer/tests/network/services/test_fwaas.py b/ceilometer/tests/network/services/test_fwaas.py new file mode 100644 index 00000000..23b47be9 --- /dev/null +++ b/ceilometer/tests/network/services/test_fwaas.py @@ -0,0 +1,166 @@ +# +# Copyright 2014 Cisco Systems,Inc. +# +# Author: Pradeep Kilambi +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock + +from ceilometer.central import manager +from ceilometer.network.services import discovery +from ceilometer.network.services import fwaas +from ceilometer.openstack.common import context +from ceilometer.openstack.common.fixture import mockpatch +from ceilometer.openstack.common import test + + +class _BaseTestFWPollster(test.BaseTestCase): + + @mock.patch('ceilometer.pipeline.setup_pipeline', mock.MagicMock()) + def setUp(self): + super(_BaseTestFWPollster, self).setUp() + self.addCleanup(mock.patch.stopall) + self.context = context.get_admin_context() + self.manager = manager.AgentManager() + + +class TestFirewallPollster(_BaseTestFWPollster): + + def setUp(self): + super(TestFirewallPollster, self).setUp() + self.pollster = fwaas.FirewallPollster() + fake_fw = self.fake_get_fw_service() + self.useFixture(mockpatch.Patch('ceilometer.neutron_client.Client.' + 'firewall_get_all', + return_value=fake_fw)) + + @staticmethod + def fake_get_fw_service(): + return [{'status': 'ACTIVE', + 'name': 'myfw', + 'description': '', + 'admin_state_up': True, + 'id': 'fdde3d818-fdcb-fg4b-de7f-6750dc8a9d7a', + 'firewall_policy_id': 'bbe3d818-bdcb-4e4b-b47f-5650dc8a9d7a', + 'tenant_id': 'a4eb9f4938bb418bbc4f8eb31802fefa'}, + {'status': 'INACTIVE', + 'name': 'myfw', + 'description': '', + 'admin_state_up': True, + 'id': 'fdde3d818-fdcb-fg4b-de7f-6750dc8a9d7a', + 'firewall_policy_id': 'bbe3d818-bdcb-4e4b-b47f-5650dc8a9d7a', + 'tenant_id': 'a4eb9f4938bb418bbc4f8eb31802fefa'}, + {'status': 'PENDING_CREATE', + 'name': 'myfw', + 'description': '', + 'admin_state_up': True, + 'id': 'fdde3d818-fdcb-fg4b-de7f-6750dc8a9d7a', + 'firewall_policy_id': 'bbe3d818-bdcb-4e4b-b47f-5650dc8a9d7a', + 'tenant_id': 'a4eb9f4938bb418bbc4f8eb31802fefa'}, + {'status': 'error', + 'name': 'myfw', + 'description': '', + 'admin_state_up': True, + 'id': 'fdde3d818-fdcb-fg4b-de7f-6750dc8a9d7a', + 'firewall_policy_id': 'bbe3d818-bdcb-4e4b-b47f-5650dc8a9d7a', + 'tenant_id': 'a4eb9f4938bb418bbc4f8eb31802fefa'}, + ] + + def test_fw_get_samples(self): + samples = list(self.pollster.get_samples( + self.manager, {}, + resources=self.fake_get_fw_service())) + self.assertEqual(3, len(samples)) + for field in self.pollster.FIELDS: + self.assertEqual(self.fake_get_fw_service()[0][field], + samples[0].resource_metadata[field]) + + def test_vpn_volume(self): + samples = list(self.pollster.get_samples( + self.manager, {}, + resources=self.fake_get_fw_service())) + self.assertEqual(1, samples[0].volume) + self.assertEqual(0, samples[1].volume) + self.assertEqual(2, samples[2].volume) + + def test_get_vpn_meter_names(self): + samples = list(self.pollster.get_samples( + self.manager, {}, + resources=self.fake_get_fw_service())) + self.assertEqual(set(['network.services.firewall']), + set([s.name for s in samples])) + + def test_vpn_discovery(self): + discovered_fws = discovery.FirewallDiscovery().discover() + self.assertEqual(3, len(discovered_fws)) + + for vpn in self.fake_get_fw_service(): + if vpn['status'] == 'error': + self.assertTrue(vpn not in discovered_fws) + else: + self.assertTrue(vpn in discovered_fws) + + +class TestIPSecConnectionsPollster(_BaseTestFWPollster): + + def setUp(self): + super(TestIPSecConnectionsPollster, self).setUp() + self.pollster = fwaas.FirewallPolicyPollster() + fake_fw_policy = self.fake_get_fw_policy() + self.useFixture(mockpatch.Patch('ceilometer.neutron_client.Client.' + 'fw_policy_get_all', + return_value=fake_fw_policy)) + + @staticmethod + def fake_get_fw_policy(): + return [{'name': 'my_fw_policy', + 'description': 'fw_policy', + 'admin_state_up': True, + 'tenant_id': 'abe3d818-fdcb-fg4b-de7f-6650dc8a9d7a', + 'firewall_rules': [{'enabled': True, + 'action': 'allow', + 'ip_version': 4, + 'protocol': 'tcp', + 'destination_port': '80', + 'source_ip_address': '10.24.4.2'}, + {'enabled': True, + 'action': 'deny', + 'ip_version': 4, + 'protocol': 'tcp', + 'destination_port': '22'}], + 'shared': True, + 'audited': True, + 'id': 'fdfbcec-fdcb-fg4b-de7f-6650dc8a9d7a'} + ] + + def test_policy_get_samples(self): + samples = list(self.pollster.get_samples( + self.manager, {}, + resources=self.fake_get_fw_policy())) + self.assertEqual(1, len(samples)) + for field in self.pollster.FIELDS: + self.assertEqual(self.fake_get_fw_policy()[0][field], + samples[0].resource_metadata[field]) + + def test_get_policy_meter_names(self): + samples = list(self.pollster.get_samples( + self.manager, {}, + resources=self.fake_get_fw_policy())) + self.assertEqual(set(['network.services.firewall.policy']), + set([s.name for s in samples])) + + def test_fw_policy_discovery(self): + discovered_policy = discovery.FirewallPolicyDiscovery().discover() + self.assertEqual(1, len(discovered_policy)) + self.assertEqual(self.fake_get_fw_policy(), discovered_policy) diff --git a/etc/ceilometer/pipeline.yaml b/etc/ceilometer/pipeline.yaml index 22fa31d1..29fd0283 100644 --- a/etc/ceilometer/pipeline.yaml +++ b/etc/ceilometer/pipeline.yaml @@ -78,6 +78,22 @@ sources: - "ipsec_connections" sinks: - "meter_sink" + - name: firewall_source + interval: 600 + meters: + - "network.services.firewall" + discovery: + - "fw_services" + sinks: + - "meter_sink" + - name: fw_policy_source + interval: 600 + meters: + - "network.services.firewall.policy" + discovery: + - "fw_policy" + sinks: + - "meter_sink" sinks: - name: meter_sink transformers: diff --git a/setup.cfg b/setup.cfg index bddd51dd..69a5e7f2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,6 +75,8 @@ ceilometer.discover = lb_health_probes = ceilometer.network.services.discovery:LBHealthMonitorsDiscovery vpn_services = ceilometer.network.services.discovery:VPNServicesDiscovery ipsec_connections = ceilometer.network.services.discovery:IPSecConnectionsDiscovery + fw_services = ceilometer.network.services.discovery:FirewallDiscovery + fw_policy = ceilometer.network.services.discovery:FirewallPolicyDiscovery ceilometer.poll.compute = disk.read.requests = ceilometer.compute.pollsters.disk:ReadRequestsPollster @@ -151,6 +153,8 @@ ceilometer.poll.central = network.services.lb.outgoing.bytes = ceilometer.network.services.lbaas:LBBytesOutPollster network.services.vpn = ceilometer.network.services.vpnaas:VPNServicesPollster network.services.vpn.connections = ceilometer.network.services.vpnaas:IPSecConnectionsPollster + network.services.firewall = ceilometer.network.services.fwaas:FirewallPollster + network.services.firewall.policy = ceilometer.network.services.fwaas:FirewallPolicyPollster ceilometer.alarm.storage =