diff --git a/neutron/db/migration/alembic_migrations/versions/CONTRACT_HEAD b/neutron/db/migration/alembic_migrations/versions/CONTRACT_HEAD index cad2398cbe4..1b0ccbcc77b 100644 --- a/neutron/db/migration/alembic_migrations/versions/CONTRACT_HEAD +++ b/neutron/db/migration/alembic_migrations/versions/CONTRACT_HEAD @@ -1 +1 @@ -b12a3ef66e62 +97c25b0d2353 diff --git a/neutron/db/migration/alembic_migrations/versions/newton/contract/97c25b0d2353_add_name_desc.py b/neutron/db/migration/alembic_migrations/versions/newton/contract/97c25b0d2353_add_name_desc.py new file mode 100644 index 00000000000..8f0e2b8f1cf --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/newton/contract/97c25b0d2353_add_name_desc.py @@ -0,0 +1,99 @@ +# Copyright 2016 NEC Technologies Limited +# +# 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. +# + +"""Add Name and Description to the networksegments table """ + +# revision identifiers, used by Alembic. +revision = '97c25b0d2353' +down_revision = 'b12a3ef66e62' +depends_on = ('89ab9a816d70',) + +# As this script depends on another migration which was a contract script, +# therefore the following column addition ( which should have been in an +# expand phase ) is also submitted in the contract phase. For information +# about the expand and contract scripts and how the depends_on works, please +# refer + +from alembic import op +import sqlalchemy as sa + +TBL = 'networksegments' + +TBL_MODEL = sa.Table(TBL, sa.MetaData(), + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('standard_attr_id', sa.BigInteger(), + nullable=True)) + + +standardattrs = sa.Table( + 'standardattributes', sa.MetaData(), + sa.Column('id', sa.BigInteger(), primary_key=True, autoincrement=True), + sa.Column('resource_type', sa.String(length=255), nullable=False)) + + +def update_existing_records(): + session = sa.orm.Session(bind=op.get_bind()) + values = [] + with session.begin(subtransactions=True): + for row in session.query(TBL_MODEL): + # NOTE from kevinbenton: without this disabled, pylint complains + # about a missing 'dml' argument. + #pylint: disable=no-value-for-parameter + res = session.execute( + standardattrs.insert().values(resource_type=TBL) + ) + session.execute( + TBL_MODEL.update().values( + standard_attr_id=res.inserted_primary_key).where( + TBL_MODEL.c.id == row[0]) + ) + # this commit is necessary to allow further operations + session.commit() + return values + + +def upgrade(): + op.add_column(TBL, sa.Column('standard_attr_id', sa.BigInteger(), + nullable=True)) + op.add_column(TBL, + sa.Column('name', sa.String(255), + nullable=True)) + update_existing_records() + # add the constraint now that everything is populated on that table + op.create_foreign_key( + constraint_name=None, source_table=TBL, + referent_table='standardattributes', + local_cols=['standard_attr_id'], remote_cols=['id'], + ondelete='CASCADE') + op.alter_column(TBL, 'standard_attr_id', nullable=False, + existing_type=sa.BigInteger(), existing_nullable=True, + existing_server_default=False) + op.create_unique_constraint( + constraint_name='uniq_%s0standard_attr_id' % TBL, + table_name=TBL, columns=['standard_attr_id']) + + +def contract_creation_exceptions(): + """ + Return create exceptions. + + These elements depend on the networksegments table which are added + in the contract branch. + """ + return { + sa.Column: ['networksegments.name', + 'networksegments.standard_attr_id'], + } diff --git a/neutron/db/segments_db.py b/neutron/db/segments_db.py index 7c99cae7f3e..737d1d0c0a7 100644 --- a/neutron/db/segments_db.py +++ b/neutron/db/segments_db.py @@ -17,9 +17,11 @@ import sqlalchemy as sa from sqlalchemy.orm import exc from neutron._i18n import _LI +from neutron.api.v2 import attributes from neutron.callbacks import events from neutron.callbacks import registry from neutron.callbacks import resources +from neutron.db import standard_attr LOG = logging.getLogger(__name__) @@ -31,7 +33,8 @@ but in Mitaka the ML2 NetworkSegment table was promoted here. """ -class NetworkSegment(model_base.BASEV2, model_base.HasId): +class NetworkSegment(standard_attr.HasStandardAttributes, + model_base.BASEV2, model_base.HasId): """Represent persistent state of a network segment. A network segment is a portion of a neutron network with a @@ -48,6 +51,8 @@ class NetworkSegment(model_base.BASEV2, model_base.HasId): is_dynamic = sa.Column(sa.Boolean, default=False, nullable=False, server_default=sa.sql.false()) segment_index = sa.Column(sa.Integer, nullable=False, server_default='0') + name = sa.Column(sa.String(attributes.NAME_MAX_LEN), + nullable=True) NETWORK_TYPE = NetworkSegment.network_type.name diff --git a/neutron/extensions/segment.py b/neutron/extensions/segment.py index 0911726d806..fd39c7107f8 100644 --- a/neutron/extensions/segment.py +++ b/neutron/extensions/segment.py @@ -31,6 +31,8 @@ SEGMENT_ID = 'segment_id' NETWORK_TYPE = 'network_type' PHYSICAL_NETWORK = 'physical_network' SEGMENTATION_ID = 'segmentation_id' +NAME_LEN = attributes.NAME_MAX_LEN +DESC_LEN = attributes.DESCRIPTION_MAX_LEN # Attribute Map RESOURCE_ATTRIBUTE_MAP = { @@ -65,6 +67,16 @@ RESOURCE_ATTRIBUTE_MAP = { 'default': constants.ATTR_NOT_SPECIFIED, 'convert_to': converters.convert_to_int, 'is_visible': True}, + 'name': {'allow_post': True, + 'allow_put': True, + 'default': constants.ATTR_NOT_SPECIFIED, + 'validate': {'type:string_or_none': NAME_LEN}, + 'is_visible': True}, + 'description': {'allow_post': True, + 'allow_put': True, + 'default': constants.ATTR_NOT_SPECIFIED, + 'validate': {'type:string_or_none': DESC_LEN}, + 'is_visible': True}, }, attributes.SUBNETS: { SEGMENT_ID: {'allow_post': True, diff --git a/neutron/objects/network/network_segment.py b/neutron/objects/network/network_segment.py index 9283a4b315d..56916286d04 100644 --- a/neutron/objects/network/network_segment.py +++ b/neutron/objects/network/network_segment.py @@ -27,6 +27,7 @@ class NetworkSegment(base.NeutronDbObject): fields = { 'id': obj_fields.UUIDField(), 'network_id': obj_fields.UUIDField(), + 'name': obj_fields.StringField(), 'network_type': obj_fields.StringField(), 'physical_network': obj_fields.StringField(nullable=True), 'segmentation_id': obj_fields.IntegerField(nullable=True), diff --git a/neutron/services/segments/db.py b/neutron/services/segments/db.py index 44bf1bf16e5..da0d4c06143 100644 --- a/neutron/services/segments/db.py +++ b/neutron/services/segments/db.py @@ -65,6 +65,8 @@ class SegmentDbMixin(common_db_mixin.CommonDbMixin): def _make_segment_dict(self, segment_db, fields=None): res = {'id': segment_db['id'], 'network_id': segment_db['network_id'], + 'name': segment_db['name'], + 'description': segment_db['description'], db.PHYSICAL_NETWORK: segment_db[db.PHYSICAL_NETWORK], db.NETWORK_TYPE: segment_db[db.NETWORK_TYPE], db.SEGMENTATION_ID: segment_db[db.SEGMENTATION_ID], @@ -103,8 +105,16 @@ class SegmentDbMixin(common_db_mixin.CommonDbMixin): segmentation_id = segment[extension.SEGMENTATION_ID] if segmentation_id == constants.ATTR_NOT_SPECIFIED: segmentation_id = None + name = segment['name'] + if name == constants.ATTR_NOT_SPECIFIED: + name = None + description = segment['description'] + if description == constants.ATTR_NOT_SPECIFIED: + description = None args = {'id': segment_id, 'network_id': network_id, + 'name': name, + 'description': description, db.PHYSICAL_NETWORK: physical_network, db.NETWORK_TYPE: network_type, db.SEGMENTATION_ID: segmentation_id} diff --git a/neutron/tests/unit/extensions/test_segment.py b/neutron/tests/unit/extensions/test_segment.py index fc358e19241..0bba352937f 100644 --- a/neutron/tests/unit/extensions/test_segment.py +++ b/neutron/tests/unit/extensions/test_segment.py @@ -80,7 +80,7 @@ class SegmentTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase): def _create_segment(self, fmt, expected_res_status=None, **kwargs): segment = {'segment': {}} for k, v in kwargs.items(): - segment['segment'][k] = str(v) + segment['segment'][k] = None if v is None else str(v) segment_req = self.new_create_request( 'segments', segment, fmt) @@ -141,6 +141,73 @@ class SegmentTestPlugin(db_base_plugin_v2.NeutronDbPluginV2, return port_dict +class TestSegmentNameDescription(SegmentTestCase): + def setUp(self): + super(TestSegmentNameDescription, self).setUp() + with self.network() as network: + self.network = network['network'] + + def _test_create_segment(self, expected=None, **kwargs): + for d in (kwargs, expected): + if d is None: + continue + d.setdefault('network_id', self.network['id']) + d.setdefault('name', None) + d.setdefault('description', None) + d.setdefault('physical_network', 'phys_net') + d.setdefault('network_type', 'net_type') + d.setdefault('segmentation_id', 200) + return super(TestSegmentNameDescription, self)._test_create_segment( + expected, **kwargs) + + def test_create_segment_no_name_description(self): + self._test_create_segment(expected={}) + + def test_create_segment_with_name(self): + expected_segment = {'name': 'segment_name'} + self._test_create_segment(name='segment_name', + expected=expected_segment) + + def test_create_segment_with_description(self): + expected_segment = {'description': 'A segment'} + self._test_create_segment(description='A segment', + expected=expected_segment) + + def test_update_segment_set_name(self): + segment = self._test_create_segment() + result = self._update('segments', + segment['segment']['id'], + {'segment': {'name': 'Segment name'}}, + expected_code=webob.exc.HTTPOk.code) + self.assertEqual('Segment name', result['segment']['name']) + + def test_update_segment_set_description(self): + segment = self._test_create_segment() + result = self._update('segments', + segment['segment']['id'], + {'segment': {'description': 'Segment desc'}}, + expected_code=webob.exc.HTTPOk.code) + self.assertEqual('Segment desc', result['segment']['description']) + + def test_update_segment_set_name_to_none(self): + segment = self._test_create_segment( + description='A segment', name='segment') + result = self._update('segments', + segment['segment']['id'], + {'segment': {'name': None}}, + expected_code=webob.exc.HTTPOk.code) + self.assertEqual(None, result['segment']['name']) + + def test_update_segment_set_description_to_none(self): + segment = self._test_create_segment( + description='A segment', name='segment') + result = self._update('segments', + segment['segment']['id'], + {'segment': {'description': None}}, + expected_code=webob.exc.HTTPOk.code) + self.assertEqual(None, result['segment']['description']) + + class TestSegment(SegmentTestCase): def test_create_segment(self): diff --git a/neutron/tests/unit/objects/test_objects.py b/neutron/tests/unit/objects/test_objects.py index 808687e16a2..efff393dbc1 100644 --- a/neutron/tests/unit/objects/test_objects.py +++ b/neutron/tests/unit/objects/test_objects.py @@ -32,7 +32,7 @@ object_data = { 'ExtraDhcpOpt': '1.0-632f689cbeb36328995a7aed1d0a78d3', 'IPAllocationPool': '1.0-371016a6480ed0b4299319cb46d9215d', 'NetworkPortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3', - 'NetworkSegment': '1.0-865567a6f70eb85cf33fb7a5575a4eab', + 'NetworkSegment': '1.0-40707ef6bd9a0bf095038158d995cc7d', 'PortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3', 'AllowedAddressPair': '1.0-9f9186b6f952fbf31d257b0458b852c0', 'QosBandwidthLimitRule': '1.2-4e44a8f5c2895ab1278399f87b40a13d', diff --git a/neutron/tests/unit/plugins/ml2/test_db.py b/neutron/tests/unit/plugins/ml2/test_db.py index d393ad7f00c..ab2271e7bd7 100644 --- a/neutron/tests/unit/plugins/ml2/test_db.py +++ b/neutron/tests/unit/plugins/ml2/test_db.py @@ -61,6 +61,10 @@ class Ml2DBTestCase(testlib_api.SqlTestCase): vif_type=vif_type, host=host)) + @staticmethod + def _sort_segments(segments): + return sorted(segments, key=lambda d: d['segmentation_id']) + def _create_segments(self, segments, is_seg_dynamic=False, network_id='foo-network-id'): self._setup_neutron_network(network_id) @@ -72,6 +76,7 @@ class Ml2DBTestCase(testlib_api.SqlTestCase): net_segments = segments_db.get_network_segments( self.ctx.session, network_id, filter_dynamic=is_seg_dynamic) + net_segments = self._sort_segments(net_segments) for segment_index, segment in enumerate(segments): self.assertEqual(segment, net_segments[segment_index]) @@ -116,8 +121,8 @@ class Ml2DBTestCase(testlib_api.SqlTestCase): net2segs = self._create_segments(segments2, network_id='net2') segs = segments_db.get_networks_segments( self.ctx.session, ['net1', 'net2']) - self.assertEqual(net1segs, segs['net1']) - self.assertEqual(net2segs, segs['net2']) + self.assertEqual(net1segs, self._sort_segments(segs['net1'])) + self.assertEqual(net2segs, self._sort_segments(segs['net2'])) def test_get_networks_segments_no_segments(self): self._create_segments([], network_id='net1') diff --git a/neutron/tests/unit/scheduler/test_dhcp_agent_scheduler.py b/neutron/tests/unit/scheduler/test_dhcp_agent_scheduler.py index f309f9e208d..ee0c2195936 100644 --- a/neutron/tests/unit/scheduler/test_dhcp_agent_scheduler.py +++ b/neutron/tests/unit/scheduler/test_dhcp_agent_scheduler.py @@ -316,6 +316,7 @@ class TestAutoScheduleSegments(test_plugin.Ml2PluginV2TestCase, seg = self.segments_plugin.create_segment( self.ctx, {'segment': {'network_id': network_id, + 'name': None, 'description': None, 'physical_network': 'physnet1', 'network_type': 'vlan', 'segmentation_id': constants.ATTR_NOT_SPECIFIED}}) @@ -529,6 +530,7 @@ class DHCPAgentWeightSchedulerTestCase(test_plugin.Ml2PluginV2TestCase): seg = self.segments_plugin.create_segment( self.ctx, {'segment': {'network_id': network_id, + 'name': None, 'description': None, 'physical_network': 'physnet1', 'network_type': 'vlan', 'segmentation_id': constants.ATTR_NOT_SPECIFIED}})