From be1d242fa3efcef9e7095fbbf0e2de022fa86a45 Mon Sep 17 00:00:00 2001 From: Gal Sagie Date: Sat, 27 Jun 2015 13:16:11 +0300 Subject: [PATCH] Add Create/Destroy API to OVS QoS BW Limiting Add infrastructure needed for the implementations (CLI and native) and add API to ovs_lib Add functional tests for ovs_lib blueprint ml2-ovs-qos-with-bwlimiting Change-Id: Ided0740548987ca91f1549f251c7906e6449f91d --- neutron/agent/common/ovs_lib.py | 75 +++++++++++++++++++ neutron/agent/ovsdb/api.py | 23 ++++++ neutron/agent/ovsdb/impl_idl.py | 6 ++ neutron/agent/ovsdb/impl_vsctl.py | 16 +++- neutron/agent/ovsdb/native/commands.py | 24 ++++++ .../tests/functional/agent/test_ovs_lib.py | 11 +++ 6 files changed, 153 insertions(+), 2 deletions(-) diff --git a/neutron/agent/common/ovs_lib.py b/neutron/agent/common/ovs_lib.py index 81340c598..26df49846 100644 --- a/neutron/agent/common/ovs_lib.py +++ b/neutron/agent/common/ovs_lib.py @@ -455,6 +455,81 @@ class OVSBridge(BaseOVS): txn.add(self.ovsdb.db_set('Controller', controller_uuid, *attr)) + def _create_qos_bw_limit_queue(self, port_name, max_bw_in_bits, + max_burst_in_bits): + external_ids = {'id': port_name} + queue_other_config = {'min-rate': max_bw_in_bits, + 'max-rate': max_bw_in_bits, + 'burst': max_burst_in_bits} + + self.ovsdb.db_create( + 'Queue', external_ids=external_ids, + other_config=queue_other_config).execute(check_error=True) + + def _create_qos_bw_limit_profile(self, port_name, max_bw_in_bits): + external_ids = {'id': port_name} + queue = self.ovsdb.db_find( + 'Queue', + ('external_ids', '=', {'id': port_name}), + columns=['_uuid']).execute( + check_error=True) + queues = {} + queues[0] = queue[0]['_uuid'] + qos_other_config = {'max-rate': max_bw_in_bits} + self.ovsdb.db_create('QoS', external_ids=external_ids, + other_config=qos_other_config, + type='linux-htb', + queues=queues).execute(check_error=True) + + def create_qos_bw_limit_for_port(self, port_name, max_kbps, + max_burst_kbps): + # TODO(QoS) implement this with transactions, + # or roll back on failure + max_bw_in_bits = str(max_kbps * 1000) + max_burst_in_bits = str(max_burst_kbps * 1000) + + self._create_qos_bw_limit_queue(port_name, max_bw_in_bits, + max_burst_in_bits) + self._create_qos_bw_limit_profile(port_name, max_bw_in_bits) + + qos = self.ovsdb.db_find('QoS', + ('external_ids', '=', {'id': port_name}), + columns=['_uuid']).execute(check_error=True) + qos_profile = qos[0]['_uuid'] + self.set_db_attribute('Port', port_name, 'qos', qos_profile, + check_error=True) + + def get_qos_bw_limit_for_port(self, port_name): + + res = self.ovsdb.db_find( + 'Queue', + ('external_ids', '=', {'id': port_name}), + columns=['other_config']).execute(check_error=True) + + if res is None or len(res) == 0: + return None, None + + other_config = res[0]['other_config'] + max_kbps = int(other_config['max-rate']) / 1000 + max_burst_kbps = int(other_config['burst']) / 1000 + return max_kbps, max_burst_kbps + + def del_qos_bw_limit_for_port(self, port_name): + qos = self.ovsdb.db_find('QoS', + ('external_ids', '=', {'id': port_name}), + columns=['_uuid']).execute(check_error=True) + qos_row = qos[0]['_uuid'] + + queue = self.ovsdb.db_find('Queue', + ('external_ids', '=', {'id': port_name}), + columns=['_uuid']).execute(check_error=True) + queue_row = queue[0]['_uuid'] + + with self.ovsdb.transaction(check_error=True) as txn: + txn.add(self.ovsdb.db_set('Port', port_name, ('qos', []))) + txn.add(self.ovsdb.db_destroy('QoS', qos_row)) + txn.add(self.ovsdb.db_destroy('Queue', queue_row)) + def __enter__(self): self.create() return self diff --git a/neutron/agent/ovsdb/api.py b/neutron/agent/ovsdb/api.py index e696f8e85..b6fa02ce0 100644 --- a/neutron/agent/ovsdb/api.py +++ b/neutron/agent/ovsdb/api.py @@ -161,6 +161,29 @@ class API(object): :returns: :class:`Command` with field value result """ + @abc.abstractmethod + def db_create(self, table, **col_values): + """Create a command to create new record + + :param table: The OVS table containing the record to be created + :type table: string + :param col_values: The columns and their associated values + to be set after create + :type col_values: Dictionary of columns id's and values + :returns: :class:`Command` with no result + """ + + @abc.abstractmethod + def db_destroy(self, table, record): + """Create a command to destroy a record + + :param table: The OVS table containing the record to be destroyed + :type table: string + :param record: The record id (name/uuid) to be destroyed + :type record: uuid/string + :returns: :class:`Command` with no result + """ + @abc.abstractmethod def db_set(self, table, record, *col_values): """Create a command to set fields in a record diff --git a/neutron/agent/ovsdb/impl_idl.py b/neutron/agent/ovsdb/impl_idl.py index 5b1547287..aa2df233b 100644 --- a/neutron/agent/ovsdb/impl_idl.py +++ b/neutron/agent/ovsdb/impl_idl.py @@ -169,6 +169,12 @@ class OvsdbIdl(api.API): def br_set_external_id(self, name, field, value): return cmd.BrSetExternalIdCommand(self, name, field, value) + def db_create(self, table, **col_values): + return cmd.DbCreateCommand(self, table, **col_values) + + def db_destroy(self, table, record): + return cmd.DbDestroyCommand(self, table, record) + def db_set(self, table, record, *col_values): return cmd.DbSetCommand(self, table, record, *col_values) diff --git a/neutron/agent/ovsdb/impl_vsctl.py b/neutron/agent/ovsdb/impl_vsctl.py index 15f52529b..6c1f84e11 100644 --- a/neutron/agent/ovsdb/impl_vsctl.py +++ b/neutron/agent/ovsdb/impl_vsctl.py @@ -184,6 +184,15 @@ class OvsdbVsctl(ovsdb.API): return BaseCommand(self.context, 'br-get-external-id', args=[name, field]) + def db_create(self, table, **col_values): + args = [table] + args += _set_colval_args(*col_values.items()) + return BaseCommand(self.context, 'create', args=args) + + def db_destroy(self, table, record): + args = [table, record] + return BaseCommand(self.context, 'destroy', args=args) + def db_set(self, table, record, *col_values): args = [table, record] args += _set_colval_args(*col_values) @@ -256,8 +265,11 @@ def _set_colval_args(*col_values): col, k, op, ovsdb.py_to_val(v)) for k, v in val.items()] elif (isinstance(val, collections.Sequence) and not isinstance(val, six.string_types)): - args.append( - "%s%s%s" % (col, op, ",".join(map(ovsdb.py_to_val, val)))) + if len(val) == 0: + args.append("%s%s%s" % (col, op, "[]")) + else: + args.append( + "%s%s%s" % (col, op, ",".join(map(ovsdb.py_to_val, val)))) else: args.append("%s%s%s" % (col, op, ovsdb.py_to_val(val))) return args diff --git a/neutron/agent/ovsdb/native/commands.py b/neutron/agent/ovsdb/native/commands.py index b8bb1b117..0d5fa589d 100644 --- a/neutron/agent/ovsdb/native/commands.py +++ b/neutron/agent/ovsdb/native/commands.py @@ -148,6 +148,30 @@ class BrSetExternalIdCommand(BaseCommand): br.external_ids = external_ids +class DbCreateCommand(BaseCommand): + def __init__(self, api, table, **columns): + super(DbCreateCommand, self).__init__(api) + self.table = table + self.columns = columns + + def run_idl(self, txn): + row = txn.insert(self.api._tables[self.table]) + for col, val in self.columns.items(): + setattr(row, col, val) + self.result = row + + +class DbDestroyCommand(BaseCommand): + def __init__(self, api, table, record): + super(DbDestroyCommand, self).__init__(api) + self.table = table + self.record = record + + def run_idl(self, txn): + record = idlutils.row_by_record(self.api.idl, self.table, self.record) + record.delete() + + class DbSetCommand(BaseCommand): def __init__(self, api, table, record, *col_values): super(DbSetCommand, self).__init__(api) diff --git a/neutron/tests/functional/agent/test_ovs_lib.py b/neutron/tests/functional/agent/test_ovs_lib.py index f43048189..be71f2ef8 100644 --- a/neutron/tests/functional/agent/test_ovs_lib.py +++ b/neutron/tests/functional/agent/test_ovs_lib.py @@ -261,6 +261,17 @@ class OVSBridgeTestCase(OVSBridgeTestBase): controller, 'connection_mode')) + def test_qos_bw_limit(self): + port_name, _ = self.create_ovs_port() + self.br.create_qos_bw_limit_for_port(port_name, 700, 70) + max_rate, burst = self.br.get_qos_bw_limit_for_port(port_name) + self.assertEqual(700, max_rate) + self.assertEqual(70, burst) + self.br.del_qos_bw_limit_for_port(port_name) + max_rate, burst = self.br.get_qos_bw_limit_for_port(port_name) + self.assertIsNone(max_rate) + self.assertIsNone(burst) + class OVSLibTestCase(base.BaseOVSLinuxTestCase): -- 2.45.2