From 08cb9a36517456e66fd41e8d220b05ce814ddce6 Mon Sep 17 00:00:00 2001 From: fujioka yuuichi Date: Tue, 24 Dec 2013 17:47:07 +0900 Subject: [PATCH] Implements monitoring-network-from-opendaylight This feture is driver of the monitoring-network that collects network info from OpenDayLight via Statistics REST API, Toporogy REST API and Switch Manager REST API. Implements: blueprint monitoring-network-from-opendaylight Change-Id: Id209a05dfc83500c4aaabdece7e44241853e07dc --- ceilometer/network/statistics/__init__.py | 38 +- ceilometer/network/statistics/driver.py | 2 +- .../statistics/opendaylight/__init__.py | 0 .../network/statistics/opendaylight/client.py | 240 +++ .../network/statistics/opendaylight/driver.py | 450 +++++ .../statistics/opendaylight/__init__.py | 0 .../statistics/opendaylight/test_client.py | 174 ++ .../statistics/opendaylight/test_driver.py | 1706 +++++++++++++++++ .../network/statistics/test_statistics.py | 35 +- setup.cfg | 1 + 10 files changed, 2618 insertions(+), 28 deletions(-) create mode 100644 ceilometer/network/statistics/opendaylight/__init__.py create mode 100644 ceilometer/network/statistics/opendaylight/client.py create mode 100644 ceilometer/network/statistics/opendaylight/driver.py create mode 100644 ceilometer/tests/network/statistics/opendaylight/__init__.py create mode 100644 ceilometer/tests/network/statistics/opendaylight/test_client.py create mode 100644 ceilometer/tests/network/statistics/opendaylight/test_driver.py diff --git a/ceilometer/network/statistics/__init__.py b/ceilometer/network/statistics/__init__.py index 900bb6256..29df6e993 100644 --- a/ceilometer/network/statistics/__init__.py +++ b/ceilometer/network/statistics/__init__.py @@ -16,9 +16,11 @@ import abc import six -from stevedore import extension +from six.moves.urllib import parse as url_parse +from stevedore import driver as _driver from ceilometer.central import plugin +from ceilometer.openstack.common import network_utils from ceilometer import sample @@ -26,8 +28,7 @@ from ceilometer import sample class _Base(plugin.CentralPollster): NAMESPACE = 'network.statistics.drivers' - extension_manager = extension.ExtensionManager(namespace=NAMESPACE, - invoke_on_load=True) + drivers = {} @abc.abstractproperty def meter_name(self): @@ -41,12 +42,35 @@ class _Base(plugin.CentralPollster): def meter_unit(self): '''Return a Meter Unit.''' + @staticmethod + def _parse_my_resource(resource): + + parse_url = network_utils.urlsplit(resource) + + params = url_parse.parse_qs(parse_url.query) + parts = url_parse.ParseResult(parse_url.scheme, + parse_url.netloc, + parse_url.path, + None, + None, + None) + return parts, params + + @staticmethod + def get_driver(scheme): + if scheme not in _Base.drivers: + _Base.drivers[scheme] = _driver.DriverManager(_Base.NAMESPACE, + scheme).driver() + return _Base.drivers[scheme] + def get_samples(self, manager, cache, resources=[]): for resource in resources: - sample_data = self.extension_manager.map_method('get_sample_data', - self.meter_name, - resource, - cache) + parse_url, params = self._parse_my_resource(resource) + ext = self.get_driver(parse_url.scheme) + sample_data = ext.get_sample_data(self.meter_name, + parse_url, + params, + cache) for data in sample_data: if data is None: continue diff --git a/ceilometer/network/statistics/driver.py b/ceilometer/network/statistics/driver.py index 26af76d66..c3b527932 100644 --- a/ceilometer/network/statistics/driver.py +++ b/ceilometer/network/statistics/driver.py @@ -22,7 +22,7 @@ import six class Driver(): @abc.abstractmethod - def get_sample_data(self, meter_name, resource, cache): + def get_sample_data(self, meter_name, parse_url, params, cache): '''Return volume, resource_id, resource_metadata, timestamp in tuple. If not implemented for meter_name, returns None diff --git a/ceilometer/network/statistics/opendaylight/__init__.py b/ceilometer/network/statistics/opendaylight/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ceilometer/network/statistics/opendaylight/client.py b/ceilometer/network/statistics/opendaylight/client.py new file mode 100644 index 000000000..c9acc55d2 --- /dev/null +++ b/ceilometer/network/statistics/opendaylight/client.py @@ -0,0 +1,240 @@ +# +# Copyright 2013 NEC Corporation. All rights reserved. +# +# 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 abc + +from oslo.config import cfg +import requests +from requests import auth +import six + +from ceilometer.openstack.common.gettextutils import _ # noqa +from ceilometer.openstack.common import log + + +CONF = cfg.CONF + + +LOG = log.getLogger(__name__) + + +@six.add_metaclass(abc.ABCMeta) +class _Base(): + """Base class of OpenDaylight REST APIs Clients.""" + + @abc.abstractproperty + def base_url(self): + """Returns base url for each REST API.""" + + def __init__(self, client): + self.client = client + + def request(self, path, container_name): + return self.client.request(self.base_url + path, container_name) + + +class OpenDaylightRESTAPIFailed(Exception): + pass + + +class StatisticsAPIClient(_Base): + """OpenDaylight Statistics REST API Client + + Base URL: + {endpoint}/statistics/{containerName} + """ + + base_url = '/statistics/%(container_name)s' + + def get_port_statistics(self, container_name): + """Get port statistics + + URL: + {Base URL}/port + """ + return self.request('/port', container_name) + + def get_flow_statistics(self, container_name): + """Get flow statistics + + URL: + {Base URL}/flow + """ + return self.request('/flow', container_name) + + def get_table_statistics(self, container_name): + """Get table statistics + + URL: + {Base URL}/table + """ + return self.request('/table', container_name) + + +class TopologyAPIClient(_Base): + """OpenDaylight Topology REST API Client + + Base URL: + {endpoint}/topology/{containerName} + """ + + base_url = '/topology/%(container_name)s' + + def get_topology(self, container_name): + """Get topology + + URL: + {Base URL} + """ + return self.request('', container_name) + + def get_user_links(self, container_name): + """Get user links + + URL: + {Base URL}/userLinks + """ + return self.request('/userLinks', container_name) + + +class SwitchManagerAPIClient(_Base): + """OpenDaylight Switch Manager REST API Client + + Base URL: + {endpoint}/switchmanager/{containerName} + """ + + base_url = '/switchmanager/%(container_name)s' + + def get_nodes(self, container_name): + """Get node informations + + URL: + {Base URL}/nodes + """ + return self.request('/nodes', container_name) + + +class HostTrackerAPIClient(_Base): + """OpenDaylight Host Tracker REST API Client + + Base URL: + {endpoint}/hosttracker/{containerName} + """ + + base_url = '/hosttracker/%(container_name)s' + + def get_active_hosts(self, container_name): + """Get active hosts informatinos + + URL: + {Base URL}/hosts/active + """ + return self.request('/hosts/active', container_name) + + def get_inactive_hosts(self, container_name): + """Get inactive hosts informations + + URL: + {Base URL}/hosts/inactive + """ + return self.request('/hosts/inactive', container_name) + + +class Client(): + + def __init__(self, endpoint, params): + self.statistics = StatisticsAPIClient(self) + self.topology = TopologyAPIClient(self) + self.switch_manager = SwitchManagerAPIClient(self) + self.host_tracker = HostTrackerAPIClient(self) + + self._endpoint = endpoint + + self._req_params = self._get_req_params(params) + + @staticmethod + def _get_req_params(params): + req_params = { + 'headers': { + 'Accept': 'application/json' + } + } + + auth_way = params.get('auth') + if auth_way in ['basic', 'digest']: + user = params.get('user') + password = params.get('password') + + if auth_way == 'basic': + auth_class = auth.HTTPBasicAuth + else: + auth_class = auth.HTTPDigestAuth + + req_params['auth'] = auth_class(user, password) + return req_params + + def _log_req(self, url): + + curl_command = ['REQ: curl -i -X GET '] + curl_command.append('"%s" ' % (url)) + + if 'auth' in self._req_params: + auth_class = self._req_params['auth'] + if isinstance(auth_class, auth.HTTPBasicAuth): + curl_command.append('--basic ') + else: + curl_command.append('--digest ') + + curl_command.append('--user "%s":"%s" ' % (auth_class.username, + auth_class.password)) + + for name, value in six.iteritems(self._req_params['headers']): + curl_command.append('-H "%s: %s" ' % (name, value)) + + LOG.debug(''.join(curl_command)) + + @staticmethod + def _log_res(resp): + + dump = ['RES: \n'] + dump.append('HTTP %.1f %s %s\n' % (resp.raw.version, + resp.status_code, + resp.reason)) + dump.extend(['%s: %s\n' % (k, v) + for k, v in six.iteritems(resp.headers)]) + dump.append('\n') + if resp.content: + dump.extend([resp.content, '\n']) + + LOG.debug(''.join(dump)) + + def _http_request(self, url): + if CONF.debug: + self._log_req(url) + resp = requests.get(url, **self._req_params) + if CONF.debug: + self._log_res(resp) + if resp.status_code / 100 != 2: + raise OpenDaylightRESTAPIFailed( + _('OpenDaylitght API returned %(status)s %(reason)s') % + {'status': resp.status_code, 'reason': resp.reason}) + + return resp.json() + + def request(self, path, container_name): + + url = self._endpoint + path % {'container_name': container_name} + return self._http_request(url) diff --git a/ceilometer/network/statistics/opendaylight/driver.py b/ceilometer/network/statistics/opendaylight/driver.py new file mode 100644 index 000000000..80660dcb0 --- /dev/null +++ b/ceilometer/network/statistics/opendaylight/driver.py @@ -0,0 +1,450 @@ +# +# Copyright 2013 NEC Corporation. All rights reserved. +# +# 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 six +from six import moves +from six.moves.urllib import parse as url_parse + +from ceilometer.network.statistics import driver +from ceilometer.network.statistics.opendaylight import client +from ceilometer.openstack.common.gettextutils import _ # noqa +from ceilometer.openstack.common import log +from ceilometer.openstack.common import timeutils +from ceilometer import utils + + +LOG = log.getLogger(__name__) + + +def _get_properties(properties, prefix='properties'): + resource_meta = {} + if properties is not None: + for k, v in six.iteritems(properties): + value = v['value'] + key = prefix + '_' + k + if 'name' in v: + key += '_' + v['name'] + resource_meta[key] = value + return resource_meta + + +def _get_int_sample(key, statistic, resource_id, resource_meta): + if key not in statistic: + return None + return (int(statistic[key]), resource_id, resource_meta) + + +class OpenDayLightDriver(driver.Driver): + """Driver of network info collector from OpenDaylight. + + This driver uses resources in "pipeline.yaml". + Resource requires below conditions: + * resource is url + * scheme is "opendaylight" + + This driver can be configured via query parameters. + Supported parameters: + * scheme: + The scheme of request url to OpenDaylight REST API endpoint. + (default http) + * auth: + Auth strategy of http. + This parameter can be set basic and digest.(default None) + * user: + This is username that is used by auth.(default None) + * password: + This is password that is used by auth.(default None) + * container_name: + Name of container of OpenDaylight.(default "default") + This parameter allows multi vaues. + + e.g. + opendaylight://127.0.0.1:8080/controller/nb/v2?container_name=default& + container_name=egg&auth=basic&user=admin&password=admin&scheme=http + + In this case, the driver send request to below URL: + http://127.0.0.1:8080/controller/nb/v2/statistics/default/flow + and + http://127.0.0.1:8080/controller/nb/v2/statistics/egg/flow + """ + @staticmethod + def _prepare_cache(endpoint, params, cache): + + if 'network.statistics.opendaylight' in cache: + return cache['network.statistics.opendaylight'] + + data = {} + + container_names = params.get('container_name', ['default']) + + odl_params = {} + if 'auth' in params: + odl_params['auth'] = params['auth'][0] + if 'user' in params: + odl_params['user'] = params['user'][0] + if 'password' in params: + odl_params['password'] = params['password'][0] + cs = client.Client(endpoint, odl_params) + + for container_name in container_names: + try: + container_data = {} + + # get flow statistics + container_data['flow'] = cs.statistics.get_flow_statistics( + container_name) + + # get port statistics + container_data['port'] = cs.statistics.get_port_statistics( + container_name) + + # get table statistics + container_data['table'] = cs.statistics.get_table_statistics( + container_name) + + # get topology + container_data['topology'] = cs.topology.get_topology( + container_name) + + # get switch informations + container_data['switch'] = cs.switch_manager.get_nodes( + container_name) + + # get and optimize user links + # e.g. + # before: + # "OF|2@OF|00:00:00:00:00:00:00:02" + # after: + # { + # 'port': { + # 'type': 'OF', + # 'id': '2'}, + # 'node': { + # 'type': 'OF', + # 'id': '00:00:00:00:00:00:00:02' + # } + # } + user_links_raw = cs.topology.get_user_links(container_name) + user_links = [] + container_data['user_links'] = user_links + for user_link_row in user_links_raw['userLinks']: + user_link = {} + for k, v in six.iteritems(user_link_row): + if (k == "dstNodeConnector" or + k == "srcNodeConnector"): + port_raw, node_raw = v.split('@') + port = {} + port['type'], port['id'] = port_raw.split('|') + node = {} + node['type'], node['id'] = node_raw.split('|') + v = {'port': port, 'node': node} + user_link[k] = v + user_links.append(user_link) + + # get link status to hosts + container_data['active_hosts'] = cs.host_tracker.\ + get_active_hosts(container_name) + container_data['inactive_hosts'] = cs.host_tracker.\ + get_inactive_hosts(container_name) + + container_data['timestamp'] = timeutils.isotime() + + data[container_name] = container_data + except Exception: + LOG.exception(_('Request failed to connect to OpenDaylight' + ' with NorthBound REST API')) + + cache['network.statistics.opendaylight'] = data + + return data + + def get_sample_data(self, meter_name, parse_url, params, cache): + + extractor = self._get_extractor(meter_name) + if extractor is None: + # The way to getting meter is not implemented in this driver or + # OpenDaylight REST API has not api to getting meter. + return None + + iter = self._get_iter(meter_name) + if iter is None: + # The way to getting meter is not implemented in this driver or + # OpenDaylight REST API has not api to getting meter. + return None + + parts = url_parse.ParseResult(params.get('scheme', ['http'])[0], + parse_url.netloc, + parse_url.path, + None, + None, + None) + endpoint = url_parse.urlunparse(parts) + + data = self._prepare_cache(endpoint, params, cache) + + samples = [] + for name, value in six.iteritems(data): + timestamp = value['timestamp'] + for sample in iter(extractor, value): + if sample is not None: + # set controller name and container name + # to resource_metadata + sample[2]['controller'] = 'OpenDaylight' + sample[2]['container'] = name + + samples.append(sample + (timestamp, )) + + return samples + + def _get_iter(self, meter_name): + if meter_name == 'switch': + return self._iter_switch + elif meter_name.startswith('switch.flow'): + return self._iter_flow + elif meter_name.startswith('switch.table'): + return self._iter_table + elif meter_name.startswith('switch.port'): + return self._iter_port + + def _get_extractor(self, meter_name): + method_name = '_' + meter_name.replace('.', '_') + return getattr(self, method_name, None) + + @staticmethod + def _iter_switch(extractor, data): + for switch in data['switch']['nodeProperties']: + yield extractor(switch, switch['node']['id'], {}) + + @staticmethod + def _switch(statistic, resource_id, resource_meta): + + resource_meta.update(_get_properties(statistic.get('properties'))) + + return (1, resource_id, resource_meta) + + @staticmethod + def _iter_port(extractor, data): + for port_statistic in data['port']['portStatistics']: + for statistic in port_statistic['portStatistic']: + resource_meta = {'port': statistic['nodeConnector']['id']} + yield extractor(statistic, port_statistic['node']['id'], + resource_meta, data) + + @staticmethod + def _switch_port(statistic, resource_id, resource_meta, data): + my_node_id = resource_id + my_port_id = statistic['nodeConnector']['id'] + + # link status from topology + edge_properties = data['topology']['edgeProperties'] + for edge_property in edge_properties: + edge = edge_property['edge'] + + if (edge['headNodeConnector']['node']['id'] == my_node_id and + edge['headNodeConnector']['id'] == my_port_id): + target_node = edge['tailNodeConnector'] + elif (edge['tailNodeConnector']['node']['id'] == my_node_id and + edge['tailNodeConnector']['id'] == my_port_id): + target_node = edge['headNodeConnector'] + else: + continue + + resource_meta['topology_node_id'] = target_node['node']['id'] + resource_meta['topology_node_port'] = target_node['id'] + + resource_meta.update(_get_properties( + edge_property.get('properties'), + prefix='topology')) + + break + + # link status from user links + for user_link in data['user_links']: + if (user_link['dstNodeConnector']['node']['id'] == my_node_id and + user_link['dstNodeConnector']['port']['id'] == my_port_id): + target_node = user_link['srcNodeConnector'] + elif (user_link['srcNodeConnector']['node']['id'] == my_node_id and + user_link['srcNodeConnector']['port']['id'] == my_port_id): + target_node = user_link['dstNodeConnector'] + else: + continue + + resource_meta['user_link_node_id'] = target_node['node']['id'] + resource_meta['user_link_node_port'] = target_node['port']['id'] + resource_meta['user_link_status'] = user_link['status'] + resource_meta['user_link_name'] = user_link['name'] + + break + + # link status to hosts + for hosts, status in moves.zip( + [data['active_hosts'], data['inactive_hosts']], + ['active', 'inactive']): + for host_config in hosts['hostConfig']: + if (host_config['nodeId'] != my_node_id or + host_config['nodeConnectorId'] != my_port_id): + continue + + resource_meta['host_status'] = status + for key in ['dataLayerAddress', 'vlan', 'staticHost', + 'networkAddress']: + if key in host_config: + resource_meta['host_' + key] = host_config[key] + + break + + return (1, resource_id, resource_meta) + + @staticmethod + def _switch_port_receive_packets(statistic, resource_id, + resource_meta, data): + return _get_int_sample('receivePackets', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_port_transmit_packets(statistic, resource_id, + resource_meta, data): + return _get_int_sample('transmitPackets', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_port_receive_bytes(statistic, resource_id, + resource_meta, data): + return _get_int_sample('receiveBytes', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_port_transmit_bytes(statistic, resource_id, + resource_meta, data): + return _get_int_sample('transmitBytes', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_port_receive_drops(statistic, resource_id, + resource_meta, data): + return _get_int_sample('receiveDrops', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_port_transmit_drops(statistic, resource_id, + resource_meta, data): + return _get_int_sample('transmitDrops', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_port_receive_errors(statistic, resource_id, + resource_meta, data): + return _get_int_sample('receiveErrors', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_port_transmit_errors(statistic, resource_id, + resource_meta, data): + return _get_int_sample('transmitErrors', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_port_receive_frame_error(statistic, resource_id, + resource_meta, data): + return _get_int_sample('receiveFrameError', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_port_receive_overrun_error(statistic, resource_id, + resource_meta, data): + return _get_int_sample('receiveOverRunError', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_port_receive_crc_error(statistic, resource_id, + resource_meta, data): + return _get_int_sample('receiveCrcError', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_port_collision_count(statistic, resource_id, + resource_meta, data): + return _get_int_sample('collisionCount', statistic, resource_id, + resource_meta) + + @staticmethod + def _iter_table(extractor, data): + for table_statistic in data['table']['tableStatistics']: + for statistic in table_statistic['tableStatistic']: + resource_meta = {'table_id': statistic['nodeTable']['id']} + yield extractor(statistic, + table_statistic['node']['id'], + resource_meta) + + @staticmethod + def _switch_table(statistic, resource_id, resource_meta): + return (1, resource_id, resource_meta) + + @staticmethod + def _switch_table_active_entries(statistic, resource_id, + resource_meta): + return _get_int_sample('activeCount', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_table_lookup_packets(statistic, resource_id, + resource_meta): + return _get_int_sample('lookupCount', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_table_matched_packets(statistic, resource_id, + resource_meta): + return _get_int_sample('matchedCount', statistic, resource_id, + resource_meta) + + @staticmethod + def _iter_flow(extractor, data): + for flow_statistic in data['flow']['flowStatistics']: + for statistic in flow_statistic['flowStatistic']: + resource_meta = {'flow_id': statistic['flow']['id'], + 'table_id': statistic['tableId']} + for key, value in utils.dict_to_keyval(statistic['flow'], + 'flow'): + resource_meta[key.replace('.', '_')] = value + yield extractor(statistic, + flow_statistic['node']['id'], + resource_meta) + + @staticmethod + def _switch_flow(statistic, resource_id, resource_meta): + return (1, resource_id, resource_meta) + + @staticmethod + def _switch_flow_duration_seconds(statistic, resource_id, + resource_meta): + return _get_int_sample('durationSeconds', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_flow_duration_nanoseconds(statistic, resource_id, + resource_meta): + return _get_int_sample('durationNanoseconds', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_flow_packets(statistic, resource_id, resource_meta): + return _get_int_sample('packetCount', statistic, resource_id, + resource_meta) + + @staticmethod + def _switch_flow_bytes(statistic, resource_id, resource_meta): + return _get_int_sample('byteCount', statistic, resource_id, + resource_meta) diff --git a/ceilometer/tests/network/statistics/opendaylight/__init__.py b/ceilometer/tests/network/statistics/opendaylight/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ceilometer/tests/network/statistics/opendaylight/test_client.py b/ceilometer/tests/network/statistics/opendaylight/test_client.py new file mode 100644 index 000000000..ed506f735 --- /dev/null +++ b/ceilometer/tests/network/statistics/opendaylight/test_client.py @@ -0,0 +1,174 @@ +# +# Copyright 2013 NEC Corporation. All rights reserved. +# +# 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 requests import auth as req_auth +import six +from six.moves.urllib import parse as url_parse + +from ceilometer.network.statistics.opendaylight import client +from ceilometer.openstack.common.gettextutils import _ # noqa +from ceilometer.openstack.common import test + + +class TestClientHTTPBasicAuth(test.BaseTestCase): + + auth_way = 'basic' + scheme = 'http' + + def setUp(self): + super(TestClientHTTPBasicAuth, self).setUp() + self.parsed_url = url_parse.urlparse( + 'http://127.0.0.1:8080/controller/nb/v2?container_name=default&' + 'container_name=egg&auth=%s&user=admin&password=admin_pass&' + 'scheme=%s' % (self.auth_way, self.scheme)) + self.params = url_parse.parse_qs(self.parsed_url.query) + self.endpoint = url_parse.urlunparse( + url_parse.ParseResult(self.scheme, + self.parsed_url.netloc, + self.parsed_url.path, + None, None, None)) + odl_params = {} + odl_params['auth'] = self.params.get('auth')[0] + odl_params['user'] = self.params.get('user')[0] + odl_params['password'] = self.params.get('password')[0] + self.client = client.Client(self.endpoint, odl_params) + + self.resp = mock.MagicMock() + self.get = mock.patch('requests.get', + return_value=self.resp).start() + + self.resp.raw.version = 1.1 + self.resp.status_code = 200 + self.resp.reason = 'OK' + self.resp.headers = {} + self.resp.content = 'dummy' + + def _test_request(self, method, url): + data = method('default') + + call_args = self.get.call_args_list[0][0] + call_kwargs = self.get.call_args_list[0][1] + + # check url + real_url = url % {'container_name': 'default', + 'scheme': self.scheme} + self.assertEqual(real_url, call_args[0]) + + # check auth parameters + auth = call_kwargs.get('auth') + if self.auth_way == 'digest': + self.assertIsInstance(auth, req_auth.HTTPDigestAuth) + else: + self.assertIsInstance(auth, req_auth.HTTPBasicAuth) + self.assertEqual('admin', auth.username) + self.assertEqual('admin_pass', auth.password) + + # check header + self.assertEqual( + {'Accept': 'application/json'}, + call_kwargs['headers']) + + # check return value + self.assertEqual(self.get().json(), data) + + def test_flow_statistics(self): + self._test_request( + self.client.statistics.get_flow_statistics, + '%(scheme)s://127.0.0.1:8080/controller/nb/v2' + '/statistics/%(container_name)s/flow') + + def test_port_statistics(self): + self._test_request( + self.client.statistics.get_port_statistics, + '%(scheme)s://127.0.0.1:8080/controller/nb/v2' + '/statistics/%(container_name)s/port') + + def test_table_statistics(self): + self._test_request( + self.client.statistics.get_table_statistics, + '%(scheme)s://127.0.0.1:8080/controller/nb/v2' + '/statistics/%(container_name)s/table') + + def test_topology(self): + self._test_request( + self.client.topology.get_topology, + '%(scheme)s://127.0.0.1:8080/controller/nb/v2' + '/topology/%(container_name)s') + + def test_user_links(self): + self._test_request( + self.client.topology.get_user_links, + '%(scheme)s://127.0.0.1:8080/controller/nb/v2' + '/topology/%(container_name)s/userLinks') + + def test_switch(self): + self._test_request( + self.client.switch_manager.get_nodes, + '%(scheme)s://127.0.0.1:8080/controller/nb/v2' + '/switchmanager/%(container_name)s/nodes') + + def test_active_hosts(self): + self._test_request( + self.client.host_tracker.get_active_hosts, + '%(scheme)s://127.0.0.1:8080/controller/nb/v2' + '/hosttracker/%(container_name)s/hosts/active') + + def test_inactive_hosts(self): + self._test_request( + self.client.host_tracker.get_inactive_hosts, + '%(scheme)s://127.0.0.1:8080/controller/nb/v2' + '/hosttracker/%(container_name)s/hosts/inactive') + + def test_http_error(self): + self.resp.status_code = 404 + self.resp.reason = 'Not Found' + + try: + self.client.statistics.get_flow_statistics('default') + self.fail('') + except client.OpenDaylightRESTAPIFailed as e: + self.assertEqual( + _('OpenDaylitght API returned %(status)s %(reason)s') % + {'status': self.resp.status_code, + 'reason': self.resp.reason}, + six.text_type(e)) + + def test_other_error(self): + + class _Exception(Exception): + pass + + self.get = mock.patch('requests.get', + side_effect=_Exception).start() + + self.assertRaises(_Exception, + self.client.statistics.get_flow_statistics, + 'default') + + +class TestClientHTTPDigestAuth(TestClientHTTPBasicAuth): + + auth_way = 'digest' + + +class TestClientHTTPSBasicAuth(TestClientHTTPBasicAuth): + + scheme = 'https' + + +class TestClientHTTPSDigestAuth(TestClientHTTPDigestAuth): + + scheme = 'https' diff --git a/ceilometer/tests/network/statistics/opendaylight/test_driver.py b/ceilometer/tests/network/statistics/opendaylight/test_driver.py new file mode 100644 index 000000000..521486b22 --- /dev/null +++ b/ceilometer/tests/network/statistics/opendaylight/test_driver.py @@ -0,0 +1,1706 @@ +# +# Copyright 2013 NEC Corporation. All rights reserved. +# +# 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 abc +import mock + +import six +from six import moves +from six.moves.urllib import parse as url_parse + +from ceilometer.network.statistics.opendaylight import driver +from ceilometer.openstack.common import test + + +@six.add_metaclass(abc.ABCMeta) +class _Base(test.BaseTestCase): + + @abc.abstractproperty + def flow_data(self): + pass + + @abc.abstractproperty + def port_data(self): + pass + + @abc.abstractproperty + def table_data(self): + pass + + @abc.abstractproperty + def topology_data(self): + pass + + @abc.abstractproperty + def switch_data(self): + pass + + @abc.abstractproperty + def user_links_data(self): + pass + + @abc.abstractproperty + def active_hosts_data(self): + pass + + @abc.abstractproperty + def inactive_hosts_data(self): + pass + + fake_odl_url = url_parse.ParseResult('opendaylight', + 'localhost:8080', + 'controller/nb/v2', + None, + None, + None) + + fake_params = url_parse.parse_qs('user=admin&password=admin&scheme=http&' + 'container_name=default&auth=basic') + + fake_params_multi_container = \ + url_parse.parse_qs('user=admin&password=admin&scheme=http&' + 'container_name=first&container_name=second&' + 'auth=basic') + + def setUp(self): + super(_Base, self).setUp() + self.addCleanup(mock.patch.stopall) + + self.driver = driver.OpenDayLightDriver() + + self.get_flow_statistics = mock.patch( + 'ceilometer.network.statistics.opendaylight.client.' + 'StatisticsAPIClient.get_flow_statistics', + return_value=self.flow_data).start() + + mock.patch('ceilometer.network.statistics.opendaylight.client.' + 'StatisticsAPIClient.get_table_statistics', + return_value=self.table_data).start() + + mock.patch('ceilometer.network.statistics.opendaylight.client.' + 'StatisticsAPIClient.get_port_statistics', + return_value=self.port_data).start() + + mock.patch('ceilometer.network.statistics.opendaylight.client.' + 'TopologyAPIClient.get_topology', + return_value=self.topology_data).start() + + mock.patch('ceilometer.network.statistics.opendaylight.client.' + 'TopologyAPIClient.get_user_links', + return_value=self.user_links_data).start() + + mock.patch('ceilometer.network.statistics.opendaylight.client.' + 'SwitchManagerAPIClient.get_nodes', + return_value=self.switch_data).start() + + mock.patch('ceilometer.network.statistics.opendaylight.client.' + 'HostTrackerAPIClient.get_active_hosts', + return_value=self.active_hosts_data).start() + + mock.patch('ceilometer.network.statistics.opendaylight.client.' + 'HostTrackerAPIClient.get_inactive_hosts', + return_value=self.inactive_hosts_data).start() + + def _test_for_meter(self, meter_name, expected_data): + sample_data = self.driver.get_sample_data(meter_name, + self.fake_odl_url, + self.fake_params, + {}) + + for sample, expected in moves.zip(sample_data, expected_data): + self.assertEqual(expected[0], sample[0]) # check volume + self.assertEqual(expected[1], sample[1]) # check resource id + self.assertEqual(expected[2], sample[2]) # check resource metadata + self.assertIsNotNone(sample[3]) # timestamp + + +class TestOpenDayLightDriverSpecial(_Base): + + flow_data = {"flowStatistics": []} + port_data = {"portStatistics": []} + table_data = {"tableStatistics": []} + topology_data = {"edgeProperties": []} + switch_data = {"nodeProperties": []} + user_links_data = {"userLinks": []} + active_hosts_data = {"hostConfig": []} + inactive_hosts_data = {"hostConfig": []} + + def test_not_implemented_meter(self): + sample_data = self.driver.get_sample_data('egg', + self.fake_odl_url, + self.fake_params, + {}) + self.assertIsNone(sample_data) + + sample_data = self.driver.get_sample_data('switch.table.egg', + self.fake_odl_url, + self.fake_params, + {}) + self.assertIsNone(sample_data) + + def test_cache(self): + cache = {} + self.driver.get_sample_data('switch', + self.fake_odl_url, + self.fake_params, + cache) + self.driver.get_sample_data('switch', + self.fake_odl_url, + self.fake_params, + cache) + self.assertEqual(self.get_flow_statistics.call_count, 1) + + cache = {} + self.driver.get_sample_data('switch', + self.fake_odl_url, + self.fake_params, + cache) + self.assertEqual(self.get_flow_statistics.call_count, 2) + + def test_multi_container(self): + cache = {} + self.driver.get_sample_data('switch', + self.fake_odl_url, + self.fake_params_multi_container, + cache) + self.assertEqual(self.get_flow_statistics.call_count, 2) + + self.assertIn('network.statistics.opendaylight', cache) + + odl_data = cache['network.statistics.opendaylight'] + + self.assertIn('first', odl_data) + self.assertIn('second', odl_data) + + def test_http_error(self): + + mock.patch('ceilometer.network.statistics.opendaylight.client.' + 'StatisticsAPIClient.get_flow_statistics', + side_effect=Exception()).start() + + sample_data = self.driver.get_sample_data('switch', + self.fake_odl_url, + self.fake_params, + {}) + + self.assertEqual(0, len(sample_data)) + + mock.patch('ceilometer.network.statistics.opendaylight.client.' + 'StatisticsAPIClient.get_flow_statistics', + side_effect=[Exception(), self.flow_data]).start() + cache = {} + self.driver.get_sample_data('switch', + self.fake_odl_url, + self.fake_params_multi_container, + cache) + + self.assertIn('network.statistics.opendaylight', cache) + + odl_data = cache['network.statistics.opendaylight'] + + self.assertIn('second', odl_data) + + +class TestOpenDayLightDriverSimple(_Base): + + flow_data = { + "flowStatistics": [ + { + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "flowStatistic": [ + { + "flow": { + "match": { + "matchField": [ + { + "type": "DL_TYPE", + "value": "2048" + }, + { + "mask": "255.255.255.255", + "type": "NW_DST", + "value": "1.1.1.1" + } + ] + }, + "actions": { + "@type": "output", + "port": { + "id": "3", + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "type": "OF" + } + }, + "hardTimeout": "0", + "id": "0", + "idleTimeout": "0", + "priority": "1" + }, + "byteCount": "0", + "durationNanoseconds": "397000000", + "durationSeconds": "1828", + "packetCount": "0", + "tableId": "0" + }, + ] + } + ] + } + port_data = { + "portStatistics": [ + { + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "portStatistic": [ + { + "nodeConnector": { + "id": "4", + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "type": "OF" + }, + "collisionCount": "0", + "receiveBytes": "0", + "receiveCrcError": "0", + "receiveDrops": "0", + "receiveErrors": "0", + "receiveFrameError": "0", + "receiveOverRunError": "0", + "receivePackets": "0", + "transmitBytes": "0", + "transmitDrops": "0", + "transmitErrors": "0", + "transmitPackets": "0" + }, + ] + } + ] + } + table_data = { + "tableStatistics": [ + { + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "tableStatistic": [ + { + "activeCount": "11", + "lookupCount": "816", + "matchedCount": "220", + "nodeTable": { + "id": "0", + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + } + } + }, + ] + } + ] + } + topology_data = {"edgeProperties": []} + switch_data = { + "nodeProperties": [ + { + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "properties": { + "actions": { + "value": "4095" + }, + "timeStamp": { + "name": "connectedSince", + "value": "1377291227877" + } + } + }, + ] + } + user_links_data = {"userLinks": []} + active_hosts_data = {"hostConfig": []} + inactive_hosts_data = {"hostConfig": []} + + def test_meter_switch(self): + expected_data = [ + (1, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + "properties_actions": "4095", + "properties_timeStamp_connectedSince": "1377291227877" + }), + ] + + self._test_for_meter('switch', expected_data) + + def test_meter_switch_port(self): + expected_data = [ + (1, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4', + }), + ] + self._test_for_meter('switch.port', expected_data) + + def test_meter_switch_port_receive_packets(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + ] + self._test_for_meter('switch.port.receive.packets', expected_data) + + def test_meter_switch_port_transmit_packets(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + ] + self._test_for_meter('switch.port.transmit.packets', expected_data) + + def test_meter_switch_port_receive_bytes(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + ] + self._test_for_meter('switch.port.receive.bytes', expected_data) + + def test_meter_switch_port_transmit_bytes(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + ] + self._test_for_meter('switch.port.transmit.bytes', expected_data) + + def test_meter_switch_port_receive_drops(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + ] + self._test_for_meter('switch.port.receive.drops', expected_data) + + def test_meter_switch_port_transmit_drops(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + ] + self._test_for_meter('switch.port.transmit.drops', expected_data) + + def test_meter_switch_port_receive_errors(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + ] + self._test_for_meter('switch.port.receive.errors', expected_data) + + def test_meter_switch_port_transmit_errors(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + ] + self._test_for_meter('switch.port.transmit.errors', expected_data) + + def test_meter_switch_port_receive_frame_error(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + ] + self._test_for_meter('switch.port.receive.frame_error', expected_data) + + def test_meter_switch_port_receive_overrun_error(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + ] + self._test_for_meter('switch.port.receive.overrun_error', + expected_data) + + def test_meter_switch_port_receive_crc_error(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + ] + self._test_for_meter('switch.port.receive.crc_error', expected_data) + + def test_meter_switch_port_collision_count(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + ] + self._test_for_meter('switch.port.collision.count', expected_data) + + def test_meter_switch_table(self): + expected_data = [ + (1, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0'}), + ] + self._test_for_meter('switch.table', expected_data) + + def test_meter_switch_table_active_entries(self): + expected_data = [ + (11, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0'}), + ] + self._test_for_meter('switch.table.active.entries', expected_data) + + def test_meter_switch_table_lookup_packets(self): + expected_data = [ + (816, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0'}), + ] + self._test_for_meter('switch.table.lookup.packets', expected_data) + + def test_meter_switch_table_matched_packets(self): + expected_data = [ + (220, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0'}), + ] + self._test_for_meter('switch.table.matched.packets', expected_data) + + def test_meter_switch_flow(self): + expected_data = [ + (1, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.1", + "flow_actions_@type": "output", + "flow_actions_port_id": "3", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:02", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1" + }), + ] + self._test_for_meter('switch.flow', expected_data) + + def test_meter_switch_flow_duration_seconds(self): + expected_data = [ + (1828, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.1", + "flow_actions_@type": "output", + "flow_actions_port_id": "3", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:02", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1"}), + ] + self._test_for_meter('switch.flow.duration_seconds', expected_data) + + def test_meter_switch_flow_duration_nanoseconds(self): + expected_data = [ + (397000000, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.1", + "flow_actions_@type": "output", + "flow_actions_port_id": "3", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:02", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1"}), + ] + self._test_for_meter('switch.flow.duration_nanoseconds', expected_data) + + def test_meter_switch_flow_packets(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.1", + "flow_actions_@type": "output", + "flow_actions_port_id": "3", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:02", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1"}), + ] + self._test_for_meter('switch.flow.packets', expected_data) + + def test_meter_switch_flow_bytes(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.1", + "flow_actions_@type": "output", + "flow_actions_port_id": "3", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:02", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1"}), + ] + self._test_for_meter('switch.flow.bytes', expected_data) + + +class TestOpenDayLightDriverComplex(_Base): + + flow_data = { + "flowStatistics": [ + { + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "flowStatistic": [ + { + "flow": { + "match": { + "matchField": [ + { + "type": "DL_TYPE", + "value": "2048" + }, + { + "mask": "255.255.255.255", + "type": "NW_DST", + "value": "1.1.1.1" + } + ] + }, + "actions": { + "@type": "output", + "port": { + "id": "3", + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "type": "OF" + } + }, + "hardTimeout": "0", + "id": "0", + "idleTimeout": "0", + "priority": "1" + }, + "byteCount": "0", + "durationNanoseconds": "397000000", + "durationSeconds": "1828", + "packetCount": "0", + "tableId": "0" + }, + { + "flow": { + "match": { + "matchField": [ + { + "type": "DL_TYPE", + "value": "2048" + }, + { + "mask": "255.255.255.255", + "type": "NW_DST", + "value": "1.1.1.2" + } + ] + }, + "actions": { + "@type": "output", + "port": { + "id": "4", + "node": { + "id": "00:00:00:00:00:00:00:03", + "type": "OF" + }, + "type": "OF" + } + }, + "hardTimeout": "0", + "id": "0", + "idleTimeout": "0", + "priority": "1" + }, + "byteCount": "89", + "durationNanoseconds": "200000", + "durationSeconds": "5648", + "packetCount": "30", + "tableId": "1" + } + ] + } + ] + } + port_data = { + "portStatistics": [ + { + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "portStatistic": [ + { + "nodeConnector": { + "id": "4", + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "type": "OF" + }, + "collisionCount": "0", + "receiveBytes": "0", + "receiveCrcError": "0", + "receiveDrops": "0", + "receiveErrors": "0", + "receiveFrameError": "0", + "receiveOverRunError": "0", + "receivePackets": "0", + "transmitBytes": "0", + "transmitDrops": "0", + "transmitErrors": "0", + "transmitPackets": "0" + }, + { + "nodeConnector": { + "id": "3", + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "type": "OF" + }, + "collisionCount": "0", + "receiveBytes": "12740", + "receiveCrcError": "0", + "receiveDrops": "0", + "receiveErrors": "0", + "receiveFrameError": "0", + "receiveOverRunError": "0", + "receivePackets": "182", + "transmitBytes": "12110", + "transmitDrops": "0", + "transmitErrors": "0", + "transmitPackets": "173" + }, + { + "nodeConnector": { + "id": "2", + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "type": "OF" + }, + "collisionCount": "0", + "receiveBytes": "12180", + "receiveCrcError": "0", + "receiveDrops": "0", + "receiveErrors": "0", + "receiveFrameError": "0", + "receiveOverRunError": "0", + "receivePackets": "174", + "transmitBytes": "12670", + "transmitDrops": "0", + "transmitErrors": "0", + "transmitPackets": "181" + }, + { + "nodeConnector": { + "id": "1", + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "type": "OF" + }, + "collisionCount": "0", + "receiveBytes": "0", + "receiveCrcError": "0", + "receiveDrops": "0", + "receiveErrors": "0", + "receiveFrameError": "0", + "receiveOverRunError": "0", + "receivePackets": "0", + "transmitBytes": "0", + "transmitDrops": "0", + "transmitErrors": "0", + "transmitPackets": "0" + }, + { + "nodeConnector": { + "id": "0", + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "type": "OF" + }, + "collisionCount": "0", + "receiveBytes": "0", + "receiveCrcError": "0", + "receiveDrops": "0", + "receiveErrors": "0", + "receiveFrameError": "0", + "receiveOverRunError": "0", + "receivePackets": "0", + "transmitBytes": "0", + "transmitDrops": "0", + "transmitErrors": "0", + "transmitPackets": "0" + } + ] + } + ] + } + table_data = { + "tableStatistics": [ + { + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "tableStatistic": [ + { + "activeCount": "11", + "lookupCount": "816", + "matchedCount": "220", + "nodeTable": { + "id": "0", + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + } + } + }, + { + "activeCount": "20", + "lookupCount": "10", + "matchedCount": "5", + "nodeTable": { + "id": "1", + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + } + } + } + ] + } + ] + } + topology_data = { + "edgeProperties": [ + { + "edge": { + "headNodeConnector": { + "id": "2", + "node": { + "id": "00:00:00:00:00:00:00:03", + "type": "OF" + }, + "type": "OF" + }, + "tailNodeConnector": { + "id": "2", + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "type": "OF" + } + }, + "properties": { + "bandwidth": { + "value": 10000000000 + }, + "config": { + "value": 1 + }, + "name": { + "value": "s2-eth3" + }, + "state": { + "value": 1 + }, + "timeStamp": { + "name": "creation", + "value": 1379527162648 + } + } + }, + { + "edge": { + "headNodeConnector": { + "id": "5", + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "type": "OF" + }, + "tailNodeConnector": { + "id": "2", + "node": { + "id": "00:00:00:00:00:00:00:04", + "type": "OF" + }, + "type": "OF" + } + }, + "properties": { + "timeStamp": { + "name": "creation", + "value": 1379527162648 + } + } + } + ] + } + switch_data = { + "nodeProperties": [ + { + "node": { + "id": "00:00:00:00:00:00:00:02", + "type": "OF" + }, + "properties": { + "actions": { + "value": "4095" + }, + "buffers": { + "value": "256" + }, + "capabilities": { + "value": "199" + }, + "description": { + "value": "None" + }, + "macAddress": { + "value": "00:00:00:00:00:02" + }, + "tables": { + "value": "-1" + }, + "timeStamp": { + "name": "connectedSince", + "value": "1377291227877" + } + } + }, + { + "node": { + "id": "00:00:00:00:00:00:00:03", + "type": "OF" + }, + "properties": { + "actions": { + "value": "1024" + }, + "buffers": { + "value": "512" + }, + "capabilities": { + "value": "1000" + }, + "description": { + "value": "Foo Bar" + }, + "macAddress": { + "value": "00:00:00:00:00:03" + }, + "tables": { + "value": "10" + }, + "timeStamp": { + "name": "connectedSince", + "value": "1377291228000" + } + } + } + ] + } + user_links_data = { + "userLinks": [ + { + "dstNodeConnector": "OF|5@OF|00:00:00:00:00:00:00:05", + "name": "link1", + "srcNodeConnector": "OF|3@OF|00:00:00:00:00:00:00:02", + "status": "Success" + } + ] + } + active_hosts_data = { + "hostConfig": [ + { + "dataLayerAddress": "00:00:00:00:01:01", + "networkAddress": "1.1.1.1", + "nodeConnectorId": "9", + "nodeConnectorType": "OF", + "nodeId": "00:00:00:00:00:00:00:01", + "nodeType": "OF", + "staticHost": "false", + "vlan": "0" + }, + { + "dataLayerAddress": "00:00:00:00:02:02", + "networkAddress": "2.2.2.2", + "nodeConnectorId": "1", + "nodeConnectorType": "OF", + "nodeId": "00:00:00:00:00:00:00:02", + "nodeType": "OF", + "staticHost": "true", + "vlan": "0" + } + ] + } + inactive_hosts_data = { + "hostConfig": [ + { + "dataLayerAddress": "00:00:00:01:01:01", + "networkAddress": "1.1.1.3", + "nodeConnectorId": "8", + "nodeConnectorType": "OF", + "nodeId": "00:00:00:00:00:00:00:01", + "nodeType": "OF", + "staticHost": "false", + "vlan": "0" + }, + { + "dataLayerAddress": "00:00:00:01:02:02", + "networkAddress": "2.2.2.4", + "nodeConnectorId": "0", + "nodeConnectorType": "OF", + "nodeId": "00:00:00:00:00:00:00:02", + "nodeType": "OF", + "staticHost": "false", + "vlan": "1" + } + ] + } + + def test_meter_switch(self): + expected_data = [ + (1, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + "properties_actions": "4095", + "properties_buffers": "256", + "properties_capabilities": "199", + "properties_description": "None", + "properties_macAddress": "00:00:00:00:00:02", + "properties_tables": "-1", + "properties_timeStamp_connectedSince": "1377291227877" + }), + (1, "00:00:00:00:00:00:00:03", { + 'controller': 'OpenDaylight', + 'container': 'default', + "properties_actions": "1024", + "properties_buffers": "512", + "properties_capabilities": "1000", + "properties_description": "Foo Bar", + "properties_macAddress": "00:00:00:00:00:03", + "properties_tables": "10", + "properties_timeStamp_connectedSince": "1377291228000" + }), + ] + + self._test_for_meter('switch', expected_data) + + def test_meter_switch_port(self): + expected_data = [ + (1, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4', + }), + (1, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '3', + 'user_link_node_id': '00:00:00:00:00:00:00:05', + 'user_link_node_port': '5', + 'user_link_status': 'Success', + 'user_link_name': 'link1', + }), + (1, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '2', + 'topology_node_id': '00:00:00:00:00:00:00:03', + 'topology_node_port': '2', + "topology_bandwidth": 10000000000, + "topology_config": 1, + "topology_name": "s2-eth3", + "topology_state": 1, + "topology_timeStamp_creation": 1379527162648 + }), + (1, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '1', + 'host_status': 'active', + 'host_dataLayerAddress': '00:00:00:00:02:02', + 'host_networkAddress': '2.2.2.2', + 'host_staticHost': 'true', + 'host_vlan': '0', + }), + (1, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '0', + 'host_status': 'inactive', + 'host_dataLayerAddress': '00:00:00:01:02:02', + 'host_networkAddress': '2.2.2.4', + 'host_staticHost': 'false', + 'host_vlan': '1', + }), + ] + self._test_for_meter('switch.port', expected_data) + + def test_meter_switch_port_receive_packets(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + (182, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '3'}), + (174, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '2'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '1'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '0'}), + ] + self._test_for_meter('switch.port.receive.packets', expected_data) + + def test_meter_switch_port_transmit_packets(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + (173, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '3'}), + (181, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '2'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '1'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '0'}), + ] + self._test_for_meter('switch.port.transmit.packets', expected_data) + + def test_meter_switch_port_receive_bytes(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + (12740, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '3'}), + (12180, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '2'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '1'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '0'}), + ] + self._test_for_meter('switch.port.receive.bytes', expected_data) + + def test_meter_switch_port_transmit_bytes(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + (12110, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '3'}), + (12670, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '2'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '1'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '0'}), + ] + self._test_for_meter('switch.port.transmit.bytes', expected_data) + + def test_meter_switch_port_receive_drops(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '3'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '2'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '1'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '0'}), + ] + self._test_for_meter('switch.port.receive.drops', expected_data) + + def test_meter_switch_port_transmit_drops(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '3'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '2'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '1'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '0'}), + ] + self._test_for_meter('switch.port.transmit.drops', expected_data) + + def test_meter_switch_port_receive_errors(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '3'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '2'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '1'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '0'}), + ] + self._test_for_meter('switch.port.receive.errors', expected_data) + + def test_meter_switch_port_transmit_errors(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '3'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '2'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '1'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '0'}), + ] + self._test_for_meter('switch.port.transmit.errors', expected_data) + + def test_meter_switch_port_receive_frame_error(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '3'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '2'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '1'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '0'}), + ] + self._test_for_meter('switch.port.receive.frame_error', expected_data) + + def test_meter_switch_port_receive_overrun_error(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '3'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '2'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '1'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '0'}), + ] + self._test_for_meter('switch.port.receive.overrun_error', + expected_data) + + def test_meter_switch_port_receive_crc_error(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '3'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '2'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '1'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '0'}), + ] + self._test_for_meter('switch.port.receive.crc_error', expected_data) + + def test_meter_switch_port_collision_count(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '4'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '3'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '2'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '1'}), + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'port': '0'}), + ] + self._test_for_meter('switch.port.collision.count', expected_data) + + def test_meter_switch_table(self): + expected_data = [ + (1, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0'}), + (1, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '1'}), + ] + self._test_for_meter('switch.table', expected_data) + + def test_meter_switch_table_active_entries(self): + expected_data = [ + (11, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0'}), + (20, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '1'}), + ] + self._test_for_meter('switch.table.active.entries', expected_data) + + def test_meter_switch_table_lookup_packets(self): + expected_data = [ + (816, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0'}), + (10, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '1'}), + ] + self._test_for_meter('switch.table.lookup.packets', expected_data) + + def test_meter_switch_table_matched_packets(self): + expected_data = [ + (220, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0'}), + (5, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '1'}), + ] + self._test_for_meter('switch.table.matched.packets', expected_data) + + def test_meter_switch_flow(self): + expected_data = [ + (1, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.1", + "flow_actions_@type": "output", + "flow_actions_port_id": "3", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:02", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1" + }), + (1, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '1', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.2", + "flow_actions_@type": "output", + "flow_actions_port_id": "4", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:03", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1" + }), + ] + self._test_for_meter('switch.flow', expected_data) + + def test_meter_switch_flow_duration_seconds(self): + expected_data = [ + (1828, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.1", + "flow_actions_@type": "output", + "flow_actions_port_id": "3", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:02", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1"}), + (5648, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '1', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.2", + "flow_actions_@type": "output", + "flow_actions_port_id": "4", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:03", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1"}), + ] + self._test_for_meter('switch.flow.duration_seconds', expected_data) + + def test_meter_switch_flow_duration_nanoseconds(self): + expected_data = [ + (397000000, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.1", + "flow_actions_@type": "output", + "flow_actions_port_id": "3", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:02", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1"}), + (200000, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '1', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.2", + "flow_actions_@type": "output", + "flow_actions_port_id": "4", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:03", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1"}), + ] + self._test_for_meter('switch.flow.duration_nanoseconds', expected_data) + + def test_meter_switch_flow_packets(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.1", + "flow_actions_@type": "output", + "flow_actions_port_id": "3", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:02", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1"}), + (30, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '1', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.2", + "flow_actions_@type": "output", + "flow_actions_port_id": "4", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:03", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1"}), + ] + self._test_for_meter('switch.flow.packets', expected_data) + + def test_meter_switch_flow_bytes(self): + expected_data = [ + (0, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '0', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.1", + "flow_actions_@type": "output", + "flow_actions_port_id": "3", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:02", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1"}), + (89, "00:00:00:00:00:00:00:02", { + 'controller': 'OpenDaylight', + 'container': 'default', + 'table_id': '1', + 'flow_id': '0', + "flow_match_matchField[0]_type": "DL_TYPE", + "flow_match_matchField[0]_value": "2048", + "flow_match_matchField[1]_mask": "255.255.255.255", + "flow_match_matchField[1]_type": "NW_DST", + "flow_match_matchField[1]_value": "1.1.1.2", + "flow_actions_@type": "output", + "flow_actions_port_id": "4", + "flow_actions_port_node_id": "00:00:00:00:00:00:00:03", + "flow_actions_port_node_type": "OF", + "flow_actions_port_type": "OF", + "flow_hardTimeout": "0", + "flow_idleTimeout": "0", + "flow_priority": "1"}), + ] + self._test_for_meter('switch.flow.bytes', expected_data) diff --git a/ceilometer/tests/network/statistics/test_statistics.py b/ceilometer/tests/network/statistics/test_statistics.py index 1dac769d7..5656290cb 100644 --- a/ceilometer/tests/network/statistics/test_statistics.py +++ b/ceilometer/tests/network/statistics/test_statistics.py @@ -13,9 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from stevedore import extension -from stevedore.tests import manager as test_manager - from ceilometer.network import statistics from ceilometer.network.statistics import driver from ceilometer.openstack.common import test @@ -72,13 +69,12 @@ class TestBaseGetSamples(test.BaseTestCase): self.pollster = FakePollster() - def _setup_ext_mgr(self, *drivers): - extensions = [ - extension.Extension(str(d), None, None, d()) - for d in drivers] - ext_mgr = test_manager.TestExtensionManager( - extensions) - self.pollster.extension_manager = ext_mgr + def tearDown(self): + statistics._Base.drivers = {} + super(TestBaseGetSamples, self).tearDown() + + def _setup_ext_mgr(self, **drivers): + statistics._Base.drivers = drivers def _make_fake_driver(self, *return_values): class FakeDriver(driver.Driver): @@ -86,12 +82,12 @@ class TestBaseGetSamples(test.BaseTestCase): def __init__(self): self.index = 0 - def get_sample_data(self, meter_name, resource, cache): + def get_sample_data(self, meter_name, parse_url, params, cache): if self.index >= len(return_values): - return None + yield None retval = return_values[self.index] self.index += 1 - return retval + yield retval return FakeDriver def _make_timestamps(self, count): @@ -119,7 +115,7 @@ class TestBaseGetSamples(test.BaseTestCase): times[0]), (2, 'b', None, times[1])) - self._setup_ext_mgr(fake_driver) + self._setup_ext_mgr(http=fake_driver()) samples = self._get_samples('http://foo') @@ -133,7 +129,7 @@ class TestBaseGetSamples(test.BaseTestCase): (2, 'b', None, times[1]), (3, 'c', None, times[2])) - self._setup_ext_mgr(fake_driver) + self._setup_ext_mgr(http=fake_driver()) samples = self._get_samples('http://foo', 'http://bar') @@ -150,13 +146,12 @@ class TestBaseGetSamples(test.BaseTestCase): fake_driver2 = self._make_fake_driver((11, 'A', None, times[2]), (12, 'B', None, times[3])) - self._setup_ext_mgr(fake_driver1, fake_driver2) + self._setup_ext_mgr(http=fake_driver1(), https=fake_driver2()) samples = self._get_samples('http://foo') - self.assertEqual(len(samples), 2) + self.assertEqual(len(samples), 1) self._assert_sample(samples[0], 1, 'a', {'spam': 'egg'}, times[0]) - self._assert_sample(samples[1], 11, 'A', None, times[2]) def test_get_samples_multi_samples(self): times = self._make_timestamps(2) @@ -164,7 +159,7 @@ class TestBaseGetSamples(test.BaseTestCase): times[0]), (2, 'b', None, times[1])]) - self._setup_ext_mgr(fake_driver) + self._setup_ext_mgr(http=fake_driver()) samples = self._get_samples('http://foo') @@ -175,7 +170,7 @@ class TestBaseGetSamples(test.BaseTestCase): def test_get_samples_return_none(self): fake_driver = self._make_fake_driver(None) - self._setup_ext_mgr(fake_driver) + self._setup_ext_mgr(http=fake_driver()) samples = self._get_samples('http://foo') diff --git a/setup.cfg b/setup.cfg index 3da1b2ca9..ba5c0a036 100644 --- a/setup.cfg +++ b/setup.cfg @@ -190,6 +190,7 @@ ceilometer.dispatcher = file = ceilometer.dispatcher.file:FileDispatcher network.statistics.drivers = + opendaylight = ceilometer.network.statistics.opendaylight.driver:OpenDayLightDriver [build_sphinx]