Add standard attributes to qospolicy

This adds the standard attributes relationship to QoSPolicy.
This has the advantage of providing created_at and updated_at,
however, the primary reason is to ensure it has a revision number
so QoSPolicy objects can be safely used in push notifications.

Partially-Implements: blueprint push-notifications
Change-Id: I8c0c33eef5d53c704b609e5bc503f46f5caad1bb
This commit is contained in:
Kevin Benton 2016-08-17 13:56:57 -07:00 committed by Ihar Hrachyshka
parent ff44cb825d
commit f3f90027b1
9 changed files with 217 additions and 9 deletions

View File

@ -1 +1 @@
3b935b28e7a0
b12a3ef66e62

View File

@ -1 +1 @@
0f5bef0f87d4
67daae611b6e

View File

@ -0,0 +1,86 @@
#
# 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 standardattr to qos policies
Revision ID: b12a3ef66e62
Revises: 3b935b28e7a0
Create Date: 2016-08-18 14:10:30.021055
"""
# revision identifiers, used by Alembic.
revision = 'b12a3ef66e62'
down_revision = '3b935b28e7a0'
depends_on = ('67daae611b6e',)
from alembic import op
import sqlalchemy as sa
# basic model of the tables with required field for migration
TABLE = 'qos_policies'
TABLE_MODEL = sa.Table(TABLE, sa.MetaData(),
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('description', sa.String(length=255),
nullable=True),
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),
sa.Column('description', sa.String(length=255), nullable=True))
def upgrade():
generate_records_for_existing()
# add the constraint now that everything is populated on that table
op.create_foreign_key(
constraint_name=None, source_table=TABLE,
referent_table='standardattributes',
local_cols=['standard_attr_id'], remote_cols=['id'],
ondelete='CASCADE')
op.alter_column(TABLE, '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' % TABLE,
table_name=TABLE, columns=['standard_attr_id'])
op.drop_column(TABLE, 'description')
def generate_records_for_existing():
session = sa.orm.Session(bind=op.get_bind())
values = []
with session.begin(subtransactions=True):
for row in session.query(TABLE_MODEL):
# NOTE(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=TABLE,
description=row[1])
)
session.execute(
TABLE_MODEL.update().values(
standard_attr_id=res.inserted_primary_key[0]).where(
TABLE_MODEL.c.id == row[0])
)
# this commit is necessary to allow further operations
session.commit()
return values

View File

@ -0,0 +1,35 @@
#
# 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 standardattr to qos policies
Revision ID: 67daae611b6e
Revises: a5648cfeeadf
Create Date: 2016-08-18 14:10:30.021015
"""
revision = '67daae611b6e'
down_revision = '0f5bef0f87d4'
from alembic import op
import sqlalchemy as sa
TABLE = 'qos_policies'
def upgrade():
op.add_column(TABLE, sa.Column('standard_attr_id', sa.BigInteger(),
nullable=True))

View File

@ -20,12 +20,13 @@ from neutron.common import constants
from neutron.db import model_base
from neutron.db import models_v2
from neutron.db import rbac_db_models
from neutron.db import standard_attr
class QosPolicy(model_base.BASEV2, model_base.HasId, model_base.HasProject):
class QosPolicy(standard_attr.HasStandardAttributes, model_base.BASEV2,
model_base.HasId, model_base.HasProject):
__tablename__ = 'qos_policies'
name = sa.Column(sa.String(attrs.NAME_MAX_LEN))
description = sa.Column(sa.String(attrs.DESCRIPTION_MAX_LEN))
rbac_entries = sa.orm.relationship(rbac_db_models.QosPolicyRBAC,
backref='qos_policy', lazy='joined',
cascade='all, delete, delete-orphan')
@ -78,6 +79,8 @@ class QosBandwidthLimitRule(model_base.HasId, model_base.BASEV2):
unique=True)
max_kbps = sa.Column(sa.Integer)
max_burst_kbps = sa.Column(sa.Integer)
revises_on_change = ('qos_policy', )
qos_policy = sa.orm.relationship(QosPolicy)
class QosDscpMarkingRule(models_v2.HasId, model_base.BASEV2):
@ -88,6 +91,8 @@ class QosDscpMarkingRule(models_v2.HasId, model_base.BASEV2):
nullable=False,
unique=True)
dscp_mark = sa.Column(sa.Integer)
revises_on_change = ('qos_policy', )
qos_policy = sa.orm.relationship(QosPolicy)
class QosMinimumBandwidthRule(models_v2.HasId, model_base.BASEV2):
@ -103,6 +108,9 @@ class QosMinimumBandwidthRule(models_v2.HasId, model_base.BASEV2):
name='directions'),
nullable=False,
server_default=constants.EGRESS_DIRECTION)
revises_on_change = ('qos_policy', )
qos_policy = sa.orm.relationship(QosPolicy)
__table_args__ = (
sa.UniqueConstraint(
qos_policy_id, direction,

View File

@ -17,6 +17,7 @@ import itertools
from oslo_utils import versionutils
from oslo_versionedobjects import base as obj_base
from oslo_versionedobjects import exception
from oslo_versionedobjects import fields as obj_fields
from six import add_metaclass
@ -39,7 +40,8 @@ class QosPolicy(base.NeutronDbObject):
# Version 1.0: Initial version
# Version 1.1: QosDscpMarkingRule introduced
# Version 1.2: Added QosMinimumBandwidthRule
VERSION = '1.2'
# Version 1.3: Added standard attributes (created_at, revision, etc)
VERSION = '1.3'
# required by RbacNeutronMetaclass
rbac_db_model = QosPolicyRBAC
@ -52,7 +54,6 @@ class QosPolicy(base.NeutronDbObject):
'id': obj_fields.UUIDField(),
'tenant_id': obj_fields.StringField(),
'name': obj_fields.StringField(),
'description': obj_fields.StringField(),
'shared': obj_fields.BooleanField(default=False),
'rules': obj_fields.ListOfObjectsField('QosRule', subclasses=True),
}
@ -225,3 +226,12 @@ class QosPolicy(base.NeutronDbObject):
names.append(rule_obj_impl.QosDscpMarkingRule.obj_name())
if 'rules' in primitive and names:
primitive['rules'] = filter_rules(names, primitive['rules'])
if _target_version < (1, 3):
standard_fields = ['revision_number', 'created_at', 'updated_at']
for f in standard_fields:
primitive.pop(f)
if primitive['description'] is None:
# description was not nullable before
raise exception.IncompatibleObjectVersion(
objver=target_version, objname='QoSPolicy')

View File

@ -0,0 +1,60 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from oslo_db.sqlalchemy import utils as db_utils
from oslo_utils import uuidutils
from neutron.tests.functional.db import test_migrations
class QosStandardAttrMixin(object):
"""Validates qos standard attr migration."""
def _create_qos_pol(self, pol_id, description):
otable = db_utils.get_table(self.engine, 'qos_policies')
values = {'id': pol_id, 'description': description}
self.engine.execute(otable.insert().values(values))
def _create_policies_with_descriptions(self, engine):
for i in range(10):
pol_id = uuidutils.generate_uuid()
self._create_qos_pol(pol_id, 'description-%s' % pol_id)
def _pre_upgrade_b12a3ef66e62(self, engine):
self._create_policies_with_descriptions(engine)
return True # return True so check function is invoked after migrate
def _check_b12a3ef66e62(self, engine, data):
qp = db_utils.get_table(engine, 'qos_policies')
sa = db_utils.get_table(engine, 'standardattributes')
for qos_pol in engine.execute(qp.select()).fetchall():
# ensure standard attributes model was created
standard_id = qos_pol.standard_attr_id
rows = engine.execute(
sa.select().where(sa.c.id == standard_id)).fetchall()
self.assertEqual(1, len(rows))
# ensure description got moved over
self.assertEqual('description-%s' % qos_pol.id,
rows[0].description)
class TestQosStandardAttrMysql(QosStandardAttrMixin,
test_migrations.TestWalkMigrationsMysql):
pass
class TestQosStandardAttrPsql(QosStandardAttrMixin,
test_migrations.TestWalkMigrationsPsql):
pass

View File

@ -11,6 +11,8 @@
# under the License.
import mock
from oslo_versionedobjects import exception
import testtools
from neutron.common import exceptions as n_exc
from neutron.db import models_v2
@ -389,12 +391,19 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
policy_obj, rule_objs = self._create_test_policy_with_rules(
RULE_OBJ_CLS.keys(), reload_rules=True)
policy_obj_v1_2 = self._policy_through_version(policy_obj, '1.2')
policy_obj_v1_2 = self._policy_through_version(
policy_obj, policy.QosPolicy.VERSION)
for rule_obj in rule_objs:
self.assertIn(rule_obj, policy_obj_v1_2.rules)
def test_object_version_degradation_1_1_to_1_0(self):
def test_object_version_degradation_1_3_to_1_2_null_description(self):
policy_obj = self._create_test_policy()
policy_obj.description = None
with testtools.ExpectedException(exception.IncompatibleObjectVersion):
policy_obj.obj_to_primitive('1.2')
def test_object_version_degradation_to_1_0(self):
#NOTE(mangelajo): we should not check .VERSION, since that's the
# local version on the class definition
policy_obj, rule_objs = self._create_test_policy_with_rules(

View File

@ -39,7 +39,7 @@ object_data = {
'QosDscpMarkingRule': '1.2-0313c6554b34fd10c753cb63d638256c',
'QosMinimumBandwidthRule': '1.2-314c3419f4799067cc31cc319080adff',
'QosRuleType': '1.2-e6fd08fcca152c339cbd5e9b94b1b8e7',
'QosPolicy': '1.2-7c5659e1c1f64395223592d3d3293e22',
'QosPolicy': '1.3-2eb3494f990acae59cb51381e7f99443',
'Route': '1.0-a9883a63b416126f9e345523ec09483b',
'SecurityGroup': '1.0-e26b90c409b31fd2e3c6fcec402ac0b9',
'SecurityGroupRule': '1.0-e9b8dace9d48b936c62ad40fe1f339d5',