Merge "Add a description field to all standard resources"
This commit is contained in:
@@ -149,6 +149,17 @@ class ExtensionDescriptor(object):
|
|||||||
"""Returns a list of extensions to be processed before this one."""
|
"""Returns a list of extensions to be processed before this one."""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def get_optional_extensions(self):
|
||||||
|
"""Returns a list of extensions to be processed before this one.
|
||||||
|
|
||||||
|
Unlike get_required_extensions. This will not fail the loading of
|
||||||
|
the extension if one of these extensions is not present. This is
|
||||||
|
useful for an extension that extends multiple resources across
|
||||||
|
other extensions that should still work for the remaining extensions
|
||||||
|
when one is missing.
|
||||||
|
"""
|
||||||
|
return []
|
||||||
|
|
||||||
def update_attributes_map(self, extended_attributes,
|
def update_attributes_map(self, extended_attributes,
|
||||||
extension_attrs_map=None):
|
extension_attrs_map=None):
|
||||||
"""Update attributes map for this extension.
|
"""Update attributes map for this extension.
|
||||||
@@ -432,6 +443,7 @@ class ExtensionManager(object):
|
|||||||
"""
|
"""
|
||||||
processed_exts = {}
|
processed_exts = {}
|
||||||
exts_to_process = self.extensions.copy()
|
exts_to_process = self.extensions.copy()
|
||||||
|
check_optionals = True
|
||||||
# Iterate until there are unprocessed extensions or if no progress
|
# Iterate until there are unprocessed extensions or if no progress
|
||||||
# is made in a whole iteration
|
# is made in a whole iteration
|
||||||
while exts_to_process:
|
while exts_to_process:
|
||||||
@@ -442,12 +454,21 @@ class ExtensionManager(object):
|
|||||||
required_exts_set = set(ext.get_required_extensions())
|
required_exts_set = set(ext.get_required_extensions())
|
||||||
if required_exts_set - set(processed_exts):
|
if required_exts_set - set(processed_exts):
|
||||||
continue
|
continue
|
||||||
|
optional_exts_set = set(ext.get_optional_extensions())
|
||||||
|
if check_optionals and optional_exts_set - set(processed_exts):
|
||||||
|
continue
|
||||||
extended_attrs = ext.get_extended_resources(version)
|
extended_attrs = ext.get_extended_resources(version)
|
||||||
for res, resource_attrs in six.iteritems(extended_attrs):
|
for res, resource_attrs in six.iteritems(extended_attrs):
|
||||||
attr_map.setdefault(res, {}).update(resource_attrs)
|
attr_map.setdefault(res, {}).update(resource_attrs)
|
||||||
processed_exts[ext_name] = ext
|
processed_exts[ext_name] = ext
|
||||||
del exts_to_process[ext_name]
|
del exts_to_process[ext_name]
|
||||||
if len(processed_exts) == processed_ext_count:
|
if len(processed_exts) == processed_ext_count:
|
||||||
|
# if we hit here, it means there are unsatisfied
|
||||||
|
# dependencies. try again without optionals since optionals
|
||||||
|
# are only necessary to set order if they are present.
|
||||||
|
if check_optionals:
|
||||||
|
check_optionals = False
|
||||||
|
continue
|
||||||
# Exit loop as no progress was made
|
# Exit loop as no progress was made
|
||||||
break
|
break
|
||||||
if exts_to_process:
|
if exts_to_process:
|
||||||
|
@@ -20,6 +20,7 @@ from oslo_log import log as logging
|
|||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
import six
|
import six
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
|
from sqlalchemy.ext import associationproxy
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
from sqlalchemy import sql
|
from sqlalchemy import sql
|
||||||
|
|
||||||
@@ -211,7 +212,13 @@ class CommonDbMixin(object):
|
|||||||
if not value:
|
if not value:
|
||||||
query = query.filter(sql.false())
|
query = query.filter(sql.false())
|
||||||
return query
|
return query
|
||||||
query = query.filter(column.in_(value))
|
if isinstance(column, associationproxy.AssociationProxy):
|
||||||
|
# association proxies don't support in_ so we have to
|
||||||
|
# do multiple equals matches
|
||||||
|
query = query.filter(
|
||||||
|
or_(*[column == v for v in value]))
|
||||||
|
else:
|
||||||
|
query = query.filter(column.in_(value))
|
||||||
elif key == 'shared' and hasattr(model, 'rbac_entries'):
|
elif key == 'shared' and hasattr(model, 'rbac_entries'):
|
||||||
# translate a filter on shared into a query against the
|
# translate a filter on shared into a query against the
|
||||||
# object's rbac entries
|
# object's rbac entries
|
||||||
@@ -301,9 +308,11 @@ class CommonDbMixin(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def _filter_non_model_columns(self, data, model):
|
def _filter_non_model_columns(self, data, model):
|
||||||
"""Remove all the attributes from data which are not columns of
|
"""Remove all the attributes from data which are not columns or
|
||||||
the model passed as second parameter.
|
association proxies of the model passed as second parameter
|
||||||
"""
|
"""
|
||||||
columns = [c.name for c in model.__table__.columns]
|
columns = [c.name for c in model.__table__.columns]
|
||||||
return dict((k, v) for (k, v) in
|
return dict((k, v) for (k, v) in
|
||||||
six.iteritems(data) if k in columns)
|
six.iteritems(data) if k in columns or
|
||||||
|
isinstance(getattr(model, k, None),
|
||||||
|
associationproxy.AssociationProxy))
|
||||||
|
@@ -298,7 +298,8 @@ class DbBasePluginCommon(common_db_mixin.CommonDbMixin):
|
|||||||
'cidr': str(detail.subnet_cidr),
|
'cidr': str(detail.subnet_cidr),
|
||||||
'subnetpool_id': subnetpool_id,
|
'subnetpool_id': subnetpool_id,
|
||||||
'enable_dhcp': subnet['enable_dhcp'],
|
'enable_dhcp': subnet['enable_dhcp'],
|
||||||
'gateway_ip': gateway_ip}
|
'gateway_ip': gateway_ip,
|
||||||
|
'description': subnet.get('description')}
|
||||||
if subnet['ip_version'] == 6 and subnet['enable_dhcp']:
|
if subnet['ip_version'] == 6 and subnet['enable_dhcp']:
|
||||||
if attributes.is_attr_set(subnet['ipv6_ra_mode']):
|
if attributes.is_attr_set(subnet['ipv6_ra_mode']):
|
||||||
args['ipv6_ra_mode'] = subnet['ipv6_ra_mode']
|
args['ipv6_ra_mode'] = subnet['ipv6_ra_mode']
|
||||||
|
@@ -45,6 +45,7 @@ from neutron.db import models_v2
|
|||||||
from neutron.db import rbac_db_mixin as rbac_mixin
|
from neutron.db import rbac_db_mixin as rbac_mixin
|
||||||
from neutron.db import rbac_db_models as rbac_db
|
from neutron.db import rbac_db_models as rbac_db
|
||||||
from neutron.db import sqlalchemyutils
|
from neutron.db import sqlalchemyutils
|
||||||
|
from neutron.db import standardattrdescription_db as stattr_db
|
||||||
from neutron.extensions import l3
|
from neutron.extensions import l3
|
||||||
from neutron import ipam
|
from neutron import ipam
|
||||||
from neutron.ipam import subnet_alloc
|
from neutron.ipam import subnet_alloc
|
||||||
@@ -80,7 +81,8 @@ def _check_subnet_not_used(context, subnet_id):
|
|||||||
|
|
||||||
class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
|
class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
|
||||||
neutron_plugin_base_v2.NeutronPluginBaseV2,
|
neutron_plugin_base_v2.NeutronPluginBaseV2,
|
||||||
rbac_mixin.RbacPluginMixin):
|
rbac_mixin.RbacPluginMixin,
|
||||||
|
stattr_db.StandardAttrDescriptionMixin):
|
||||||
"""V2 Neutron plugin interface implementation using SQLAlchemy models.
|
"""V2 Neutron plugin interface implementation using SQLAlchemy models.
|
||||||
|
|
||||||
Whenever a non-read call happens the plugin will call an event handler
|
Whenever a non-read call happens the plugin will call an event handler
|
||||||
@@ -319,7 +321,8 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
|
|||||||
'name': n['name'],
|
'name': n['name'],
|
||||||
'admin_state_up': n['admin_state_up'],
|
'admin_state_up': n['admin_state_up'],
|
||||||
'mtu': n.get('mtu', constants.DEFAULT_NETWORK_MTU),
|
'mtu': n.get('mtu', constants.DEFAULT_NETWORK_MTU),
|
||||||
'status': n.get('status', constants.NET_STATUS_ACTIVE)}
|
'status': n.get('status', constants.NET_STATUS_ACTIVE),
|
||||||
|
'description': n.get('description')}
|
||||||
network = models_v2.Network(**args)
|
network = models_v2.Network(**args)
|
||||||
if n['shared']:
|
if n['shared']:
|
||||||
entry = rbac_db.NetworkRBAC(
|
entry = rbac_db.NetworkRBAC(
|
||||||
@@ -988,7 +991,8 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
|
|||||||
'is_default': sp_reader.is_default,
|
'is_default': sp_reader.is_default,
|
||||||
'shared': sp_reader.shared,
|
'shared': sp_reader.shared,
|
||||||
'default_quota': sp_reader.default_quota,
|
'default_quota': sp_reader.default_quota,
|
||||||
'address_scope_id': sp_reader.address_scope_id}
|
'address_scope_id': sp_reader.address_scope_id,
|
||||||
|
'description': sp_reader.description}
|
||||||
subnetpool = models_v2.SubnetPool(**pool_args)
|
subnetpool = models_v2.SubnetPool(**pool_args)
|
||||||
context.session.add(subnetpool)
|
context.session.add(subnetpool)
|
||||||
for prefix in sp_reader.prefixes:
|
for prefix in sp_reader.prefixes:
|
||||||
@@ -1026,10 +1030,8 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
|
|||||||
for key in ['id', 'name', 'ip_version', 'min_prefixlen',
|
for key in ['id', 'name', 'ip_version', 'min_prefixlen',
|
||||||
'max_prefixlen', 'default_prefixlen', 'is_default',
|
'max_prefixlen', 'default_prefixlen', 'is_default',
|
||||||
'shared', 'default_quota', 'address_scope_id',
|
'shared', 'default_quota', 'address_scope_id',
|
||||||
'standard_attr']:
|
'standard_attr', 'description']:
|
||||||
self._write_key(key, updated, model, new_pool)
|
self._write_key(key, updated, model, new_pool)
|
||||||
self._apply_dict_extend_functions(attributes.SUBNETPOOLS,
|
|
||||||
updated, model)
|
|
||||||
return updated
|
return updated
|
||||||
|
|
||||||
def _write_key(self, key, update, orig, new_dict):
|
def _write_key(self, key, update, orig, new_dict):
|
||||||
@@ -1078,7 +1080,8 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
|
|||||||
|
|
||||||
for key in ['min_prefixlen', 'max_prefixlen', 'default_prefixlen']:
|
for key in ['min_prefixlen', 'max_prefixlen', 'default_prefixlen']:
|
||||||
updated['key'] = str(updated[key])
|
updated['key'] = str(updated[key])
|
||||||
|
self._apply_dict_extend_functions(attributes.SUBNETPOOLS,
|
||||||
|
updated, orig_sp)
|
||||||
return updated
|
return updated
|
||||||
|
|
||||||
def get_subnetpool(self, context, id, fields=None):
|
def get_subnetpool(self, context, id, fields=None):
|
||||||
@@ -1211,7 +1214,8 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
|
|||||||
admin_state_up=p['admin_state_up'],
|
admin_state_up=p['admin_state_up'],
|
||||||
status=p.get('status', constants.PORT_STATUS_ACTIVE),
|
status=p.get('status', constants.PORT_STATUS_ACTIVE),
|
||||||
device_id=p['device_id'],
|
device_id=p['device_id'],
|
||||||
device_owner=p['device_owner'])
|
device_owner=p['device_owner'],
|
||||||
|
description=p.get('description'))
|
||||||
if ('dns-integration' in self.supported_extension_aliases and
|
if ('dns-integration' in self.supported_extension_aliases and
|
||||||
'dns_name' in p):
|
'dns_name' in p):
|
||||||
request_dns_name = self._get_request_dns_name(p)
|
request_dns_name = self._get_request_dns_name(p)
|
||||||
|
@@ -38,6 +38,7 @@ from neutron.common import utils
|
|||||||
from neutron.db import l3_agentschedulers_db as l3_agt
|
from neutron.db import l3_agentschedulers_db as l3_agt
|
||||||
from neutron.db import model_base
|
from neutron.db import model_base
|
||||||
from neutron.db import models_v2
|
from neutron.db import models_v2
|
||||||
|
from neutron.db import standardattrdescription_db as st_attr
|
||||||
from neutron.extensions import external_net
|
from neutron.extensions import external_net
|
||||||
from neutron.extensions import l3
|
from neutron.extensions import l3
|
||||||
from neutron import manager
|
from neutron import manager
|
||||||
@@ -131,7 +132,8 @@ class FloatingIP(model_base.HasStandardAttributes, model_base.BASEV2,
|
|||||||
router = orm.relationship(Router, backref='floating_ips')
|
router = orm.relationship(Router, backref='floating_ips')
|
||||||
|
|
||||||
|
|
||||||
class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
|
class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
|
||||||
|
st_attr.StandardAttrDescriptionMixin):
|
||||||
"""Mixin class to add L3/NAT router methods to db_base_plugin_v2."""
|
"""Mixin class to add L3/NAT router methods to db_base_plugin_v2."""
|
||||||
|
|
||||||
router_device_owners = (
|
router_device_owners = (
|
||||||
@@ -182,7 +184,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
|
|||||||
tenant_id=tenant_id,
|
tenant_id=tenant_id,
|
||||||
name=router['name'],
|
name=router['name'],
|
||||||
admin_state_up=router['admin_state_up'],
|
admin_state_up=router['admin_state_up'],
|
||||||
status="ACTIVE")
|
status="ACTIVE",
|
||||||
|
description=router.get('description'))
|
||||||
context.session.add(router_db)
|
context.session.add(router_db)
|
||||||
return router_db
|
return router_db
|
||||||
|
|
||||||
@@ -1008,10 +1011,13 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
|
|||||||
previous_router_id = floatingip_db.router_id
|
previous_router_id = floatingip_db.router_id
|
||||||
port_id, internal_ip_address, router_id = (
|
port_id, internal_ip_address, router_id = (
|
||||||
self._check_and_get_fip_assoc(context, fip, floatingip_db))
|
self._check_and_get_fip_assoc(context, fip, floatingip_db))
|
||||||
floatingip_db.update({'fixed_ip_address': internal_ip_address,
|
update = {'fixed_ip_address': internal_ip_address,
|
||||||
'fixed_port_id': port_id,
|
'fixed_port_id': port_id,
|
||||||
'router_id': router_id,
|
'router_id': router_id,
|
||||||
'last_known_router_id': previous_router_id})
|
'last_known_router_id': previous_router_id}
|
||||||
|
if 'description' in fip:
|
||||||
|
update['description'] = fip['description']
|
||||||
|
floatingip_db.update(update)
|
||||||
next_hop = None
|
next_hop = None
|
||||||
if router_id:
|
if router_id:
|
||||||
# NOTE(tidwellr) use admin context here
|
# NOTE(tidwellr) use admin context here
|
||||||
@@ -1094,7 +1100,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
|
|||||||
status=initial_status,
|
status=initial_status,
|
||||||
floating_network_id=fip['floating_network_id'],
|
floating_network_id=fip['floating_network_id'],
|
||||||
floating_ip_address=floating_ip_address,
|
floating_ip_address=floating_ip_address,
|
||||||
floating_port_id=external_port['id'])
|
floating_port_id=external_port['id'],
|
||||||
|
description=fip.get('description'))
|
||||||
# Update association with internal port
|
# Update association with internal port
|
||||||
# and define external IP address
|
# and define external IP address
|
||||||
self._update_fip_assoc(context, fip,
|
self._update_fip_assoc(context, fip,
|
||||||
@@ -1110,6 +1117,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
|
|||||||
self._process_dns_floatingip_create_postcommit(context,
|
self._process_dns_floatingip_create_postcommit(context,
|
||||||
floatingip_dict,
|
floatingip_dict,
|
||||||
dns_data)
|
dns_data)
|
||||||
|
self._apply_dict_extend_functions(l3.FLOATINGIPS, floatingip_dict,
|
||||||
|
floatingip_db)
|
||||||
return floatingip_dict
|
return floatingip_dict
|
||||||
|
|
||||||
def create_floatingip(self, context, floatingip,
|
def create_floatingip(self, context, floatingip,
|
||||||
|
@@ -1 +1 @@
|
|||||||
5ffceebfada
|
4ffceebfcdc
|
||||||
|
@@ -1 +1 @@
|
|||||||
3894bccad37f
|
0e66c5227a8a
|
||||||
|
@@ -0,0 +1,64 @@
|
|||||||
|
# Copyright 2015 OpenStack Foundation
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""standard_desc
|
||||||
|
|
||||||
|
Revision ID: 4ffceebfcdc
|
||||||
|
Revises: 5ffceebfada
|
||||||
|
Create Date: 2016-02-10 23:12:04.012457
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '4ffceebfcdc'
|
||||||
|
down_revision = '5ffceebfada'
|
||||||
|
depends_on = ('0e66c5227a8a',)
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# A simple model of the security groups table with only the fields needed for
|
||||||
|
# the migration.
|
||||||
|
securitygroups = sa.Table('securitygroups', sa.MetaData(),
|
||||||
|
sa.Column('standard_attr_id', sa.BigInteger(),
|
||||||
|
nullable=False),
|
||||||
|
sa.Column('description', sa.String(length=255)))
|
||||||
|
|
||||||
|
standardattr = sa.Table(
|
||||||
|
'standardattributes', sa.MetaData(),
|
||||||
|
sa.Column('id', sa.BigInteger(), primary_key=True, autoincrement=True),
|
||||||
|
sa.Column('description', sa.String(length=255)))
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
migrate_values()
|
||||||
|
op.drop_column('securitygroups', 'description')
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_values():
|
||||||
|
session = sa.orm.Session(bind=op.get_bind())
|
||||||
|
values = []
|
||||||
|
for row in session.query(securitygroups):
|
||||||
|
values.append({'id': row[0],
|
||||||
|
'description': row[1]})
|
||||||
|
with session.begin(subtransactions=True):
|
||||||
|
for value in values:
|
||||||
|
session.execute(
|
||||||
|
standardattr.update().values(
|
||||||
|
description=value['description']).where(
|
||||||
|
standardattr.c.id == value['id']))
|
||||||
|
# this commit appears to be necessary to allow further operations
|
||||||
|
session.commit()
|
@@ -0,0 +1,34 @@
|
|||||||
|
# Copyright 2016 OpenStack Foundation
|
||||||
|
#
|
||||||
|
# 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 desc to standard attr table
|
||||||
|
|
||||||
|
Revision ID: 0e66c5227a8a
|
||||||
|
Revises: 3894bccad37f
|
||||||
|
Create Date: 2016-02-02 10:50:34.238563
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '0e66c5227a8a'
|
||||||
|
down_revision = '3894bccad37f'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column('standardattributes', sa.Column('description',
|
||||||
|
sa.String(length=255), nullable=True))
|
@@ -16,6 +16,7 @@
|
|||||||
from oslo_db.sqlalchemy import models
|
from oslo_db.sqlalchemy import models
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.ext.associationproxy import association_proxy
|
||||||
from sqlalchemy.ext import declarative
|
from sqlalchemy.ext import declarative
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
|
||||||
@@ -107,6 +108,7 @@ class StandardAttribute(BASEV2, models.TimestampMixin):
|
|||||||
# before a 2-byte prefix is required. We shouldn't get anywhere near this
|
# before a 2-byte prefix is required. We shouldn't get anywhere near this
|
||||||
# limit with our table names...
|
# limit with our table names...
|
||||||
resource_type = sa.Column(sa.String(255), nullable=False)
|
resource_type = sa.Column(sa.String(255), nullable=False)
|
||||||
|
description = sa.Column(sa.String(attr.DESCRIPTION_MAX_LEN))
|
||||||
|
|
||||||
|
|
||||||
class HasStandardAttributes(object):
|
class HasStandardAttributes(object):
|
||||||
@@ -130,8 +132,10 @@ class HasStandardAttributes(object):
|
|||||||
single_parent=True,
|
single_parent=True,
|
||||||
uselist=False)
|
uselist=False)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, description='', *args, **kwargs):
|
||||||
super(HasStandardAttributes, self).__init__(*args, **kwargs)
|
super(HasStandardAttributes, self).__init__(*args, **kwargs)
|
||||||
# here we automatically create the related standard attribute object
|
# here we automatically create the related standard attribute object
|
||||||
self.standard_attr = StandardAttribute(
|
self.standard_attr = StandardAttribute(
|
||||||
resource_type=self.__tablename__)
|
resource_type=self.__tablename__, description=description)
|
||||||
|
|
||||||
|
description = association_proxy('standard_attr', 'description')
|
||||||
|
@@ -142,8 +142,8 @@ class Port(model_base.HasStandardAttributes, model_base.BASEV2,
|
|||||||
def __init__(self, id=None, tenant_id=None, name=None, network_id=None,
|
def __init__(self, id=None, tenant_id=None, name=None, network_id=None,
|
||||||
mac_address=None, admin_state_up=None, status=None,
|
mac_address=None, admin_state_up=None, status=None,
|
||||||
device_id=None, device_owner=None, fixed_ips=None,
|
device_id=None, device_owner=None, fixed_ips=None,
|
||||||
dns_name=None):
|
dns_name=None, **kwargs):
|
||||||
super(Port, self).__init__()
|
super(Port, self).__init__(**kwargs)
|
||||||
self.id = id
|
self.id = id
|
||||||
self.tenant_id = tenant_id
|
self.tenant_id = tenant_id
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@@ -44,7 +44,6 @@ class SecurityGroup(model_base.HasStandardAttributes, model_base.BASEV2,
|
|||||||
"""Represents a v2 neutron security group."""
|
"""Represents a v2 neutron security group."""
|
||||||
|
|
||||||
name = sa.Column(sa.String(attributes.NAME_MAX_LEN))
|
name = sa.Column(sa.String(attributes.NAME_MAX_LEN))
|
||||||
description = sa.Column(sa.String(attributes.DESCRIPTION_MAX_LEN))
|
|
||||||
|
|
||||||
|
|
||||||
class DefaultSecurityGroup(model_base.BASEV2):
|
class DefaultSecurityGroup(model_base.BASEV2):
|
||||||
@@ -317,6 +316,8 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase):
|
|||||||
'description': security_group['description']}
|
'description': security_group['description']}
|
||||||
res['security_group_rules'] = [self._make_security_group_rule_dict(r)
|
res['security_group_rules'] = [self._make_security_group_rule_dict(r)
|
||||||
for r in security_group.rules]
|
for r in security_group.rules]
|
||||||
|
self._apply_dict_extend_functions(ext_sg.SECURITYGROUPS, res,
|
||||||
|
security_group)
|
||||||
return self._fields(res, fields)
|
return self._fields(res, fields)
|
||||||
|
|
||||||
def _make_security_group_binding_dict(self, security_group, fields=None):
|
def _make_security_group_binding_dict(self, security_group, fields=None):
|
||||||
@@ -397,7 +398,9 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase):
|
|||||||
protocol=rule_dict['protocol'],
|
protocol=rule_dict['protocol'],
|
||||||
port_range_min=rule_dict['port_range_min'],
|
port_range_min=rule_dict['port_range_min'],
|
||||||
port_range_max=rule_dict['port_range_max'],
|
port_range_max=rule_dict['port_range_max'],
|
||||||
remote_ip_prefix=rule_dict.get('remote_ip_prefix'))
|
remote_ip_prefix=rule_dict.get('remote_ip_prefix'),
|
||||||
|
description=rule_dict.get('description')
|
||||||
|
)
|
||||||
context.session.add(db)
|
context.session.add(db)
|
||||||
self._registry_notify(resources.SECURITY_GROUP_RULE,
|
self._registry_notify(resources.SECURITY_GROUP_RULE,
|
||||||
events.PRECOMMIT_CREATE,
|
events.PRECOMMIT_CREATE,
|
||||||
@@ -515,6 +518,8 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase):
|
|||||||
'remote_ip_prefix': security_group_rule['remote_ip_prefix'],
|
'remote_ip_prefix': security_group_rule['remote_ip_prefix'],
|
||||||
'remote_group_id': security_group_rule['remote_group_id']}
|
'remote_group_id': security_group_rule['remote_group_id']}
|
||||||
|
|
||||||
|
self._apply_dict_extend_functions(ext_sg.SECURITYGROUPRULES, res,
|
||||||
|
security_group_rule)
|
||||||
return self._fields(res, fields)
|
return self._fields(res, fields)
|
||||||
|
|
||||||
def _make_security_group_rule_filter_dict(self, security_group_rule):
|
def _make_security_group_rule_filter_dict(self, security_group_rule):
|
||||||
@@ -525,7 +530,7 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase):
|
|||||||
|
|
||||||
include_if_present = ['protocol', 'port_range_max', 'port_range_min',
|
include_if_present = ['protocol', 'port_range_max', 'port_range_min',
|
||||||
'ethertype', 'remote_ip_prefix',
|
'ethertype', 'remote_ip_prefix',
|
||||||
'remote_group_id']
|
'remote_group_id', 'description']
|
||||||
for key in include_if_present:
|
for key in include_if_present:
|
||||||
value = sgr.get(key)
|
value = sgr.get(key)
|
||||||
if value:
|
if value:
|
||||||
@@ -547,7 +552,9 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase):
|
|||||||
# Check in database if rule exists
|
# Check in database if rule exists
|
||||||
filters = self._make_security_group_rule_filter_dict(
|
filters = self._make_security_group_rule_filter_dict(
|
||||||
security_group_rule)
|
security_group_rule)
|
||||||
db_rules = self.get_security_group_rules(context, filters)
|
db_rules = self.get_security_group_rules(
|
||||||
|
context, filters,
|
||||||
|
fields=security_group_rule['security_group_rule'].keys())
|
||||||
# Note(arosen): the call to get_security_group_rules wildcards
|
# Note(arosen): the call to get_security_group_rules wildcards
|
||||||
# values in the filter that have a value of [None]. For
|
# values in the filter that have a value of [None]. For
|
||||||
# example, filters = {'remote_group_id': [None]} will return
|
# example, filters = {'remote_group_id': [None]} will return
|
||||||
@@ -559,7 +566,6 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase):
|
|||||||
# below to check for these corner cases.
|
# below to check for these corner cases.
|
||||||
for db_rule in db_rules:
|
for db_rule in db_rules:
|
||||||
# need to remove id from db_rule for matching
|
# need to remove id from db_rule for matching
|
||||||
id = db_rule.pop('id')
|
|
||||||
if (security_group_rule['security_group_rule'] == db_rule):
|
if (security_group_rule['security_group_rule'] == db_rule):
|
||||||
raise ext_sg.SecurityGroupRuleExists(id=id)
|
raise ext_sg.SecurityGroupRuleExists(id=id)
|
||||||
|
|
||||||
|
35
neutron/db/standardattrdescription_db.py
Normal file
35
neutron/db/standardattrdescription_db.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from neutron.api.v2 import attributes
|
||||||
|
from neutron.db import common_db_mixin
|
||||||
|
from neutron.extensions import l3
|
||||||
|
from neutron.extensions import securitygroup
|
||||||
|
|
||||||
|
|
||||||
|
class StandardAttrDescriptionMixin(object):
|
||||||
|
supported_extension_aliases = ['standard-attr-description']
|
||||||
|
|
||||||
|
def _extend_standard_attr_description(self, res, db_object):
|
||||||
|
if not hasattr(db_object, 'description'):
|
||||||
|
return
|
||||||
|
res['description'] = db_object.description
|
||||||
|
|
||||||
|
for resource in [attributes.NETWORKS, attributes.PORTS,
|
||||||
|
attributes.SUBNETS, attributes.SUBNETPOOLS,
|
||||||
|
securitygroup.SECURITYGROUPS,
|
||||||
|
securitygroup.SECURITYGROUPRULES,
|
||||||
|
l3.ROUTERS, l3.FLOATINGIPS]:
|
||||||
|
common_db_mixin.CommonDbMixin.register_dict_extend_funcs(
|
||||||
|
resource, ['_extend_standard_attr_description'])
|
@@ -213,10 +213,12 @@ attr.validators['type:name_not_default'] = _validate_name_not_default
|
|||||||
|
|
||||||
sg_supported_protocols = [None] + list(const.IP_PROTOCOL_MAP.keys())
|
sg_supported_protocols = [None] + list(const.IP_PROTOCOL_MAP.keys())
|
||||||
sg_supported_ethertypes = ['IPv4', 'IPv6']
|
sg_supported_ethertypes = ['IPv4', 'IPv6']
|
||||||
|
SECURITYGROUPS = 'security_groups'
|
||||||
|
SECURITYGROUPRULES = 'security_group_rules'
|
||||||
|
|
||||||
# Attribute Map
|
# Attribute Map
|
||||||
RESOURCE_ATTRIBUTE_MAP = {
|
RESOURCE_ATTRIBUTE_MAP = {
|
||||||
'security_groups': {
|
SECURITYGROUPS: {
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
'id': {'allow_post': False, 'allow_put': False,
|
||||||
'validate': {'type:uuid': None},
|
'validate': {'type:uuid': None},
|
||||||
'is_visible': True,
|
'is_visible': True,
|
||||||
@@ -231,10 +233,10 @@ RESOURCE_ATTRIBUTE_MAP = {
|
|||||||
'required_by_policy': True,
|
'required_by_policy': True,
|
||||||
'validate': {'type:string': attr.TENANT_ID_MAX_LEN},
|
'validate': {'type:string': attr.TENANT_ID_MAX_LEN},
|
||||||
'is_visible': True},
|
'is_visible': True},
|
||||||
'security_group_rules': {'allow_post': False, 'allow_put': False,
|
SECURITYGROUPRULES: {'allow_post': False, 'allow_put': False,
|
||||||
'is_visible': True},
|
'is_visible': True},
|
||||||
},
|
},
|
||||||
'security_group_rules': {
|
SECURITYGROUPRULES: {
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
'id': {'allow_post': False, 'allow_put': False,
|
||||||
'validate': {'type:uuid': None},
|
'validate': {'type:uuid': None},
|
||||||
'is_visible': True,
|
'is_visible': True,
|
||||||
@@ -270,7 +272,6 @@ RESOURCE_ATTRIBUTE_MAP = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SECURITYGROUPS = 'security_groups'
|
|
||||||
EXTENDED_ATTRIBUTES_2_0 = {
|
EXTENDED_ATTRIBUTES_2_0 = {
|
||||||
'ports': {SECURITYGROUPS: {'allow_post': True,
|
'ports': {SECURITYGROUPS: {'allow_post': True,
|
||||||
'allow_put': True,
|
'allow_put': True,
|
||||||
|
55
neutron/extensions/standardattrdescription.py
Normal file
55
neutron/extensions/standardattrdescription.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Copyright 2016 OpenStack Foundation
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from neutron.api import extensions
|
||||||
|
from neutron.api.v2 import attributes as attr
|
||||||
|
|
||||||
|
|
||||||
|
EXTENDED_ATTRIBUTES_2_0 = {}
|
||||||
|
|
||||||
|
for resource in ('security_group_rules', 'security_groups', 'ports', 'subnets',
|
||||||
|
'networks', 'routers', 'floatingips', 'subnetpools'):
|
||||||
|
EXTENDED_ATTRIBUTES_2_0[resource] = {
|
||||||
|
'description': {'allow_post': True, 'allow_put': True,
|
||||||
|
'validate': {'type:string': attr.DESCRIPTION_MAX_LEN},
|
||||||
|
'is_visible': True, 'default': ''},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Standardattrdescription(extensions.ExtensionDescriptor):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_name(cls):
|
||||||
|
return "standard-attr-description"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_alias(cls):
|
||||||
|
return "standard-attr-description"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_description(cls):
|
||||||
|
return "Extension to add descriptions to standard attributes"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_updated(cls):
|
||||||
|
return "2016-02-10T10:00:00-00:00"
|
||||||
|
|
||||||
|
def get_optional_extensions(self):
|
||||||
|
return ['security-group', 'router']
|
||||||
|
|
||||||
|
def get_extended_resources(self, version):
|
||||||
|
if version == "2.0":
|
||||||
|
return dict(EXTENDED_ATTRIBUTES_2_0.items())
|
||||||
|
return {}
|
@@ -229,6 +229,7 @@ class SubnetPoolReader(object):
|
|||||||
self._read_prefix_bounds(subnetpool)
|
self._read_prefix_bounds(subnetpool)
|
||||||
self._read_attrs(subnetpool,
|
self._read_attrs(subnetpool,
|
||||||
['tenant_id', 'name', 'is_default', 'shared'])
|
['tenant_id', 'name', 'is_default', 'shared'])
|
||||||
|
self.description = subnetpool.get('description')
|
||||||
self._read_address_scope(subnetpool)
|
self._read_address_scope(subnetpool)
|
||||||
self.subnetpool = {'id': self.id,
|
self.subnetpool = {'id': self.id,
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
@@ -243,7 +244,8 @@ class SubnetPoolReader(object):
|
|||||||
'default_quota': self.default_quota,
|
'default_quota': self.default_quota,
|
||||||
'address_scope_id': self.address_scope_id,
|
'address_scope_id': self.address_scope_id,
|
||||||
'is_default': self.is_default,
|
'is_default': self.is_default,
|
||||||
'shared': self.shared}
|
'shared': self.shared,
|
||||||
|
'description': self.description}
|
||||||
|
|
||||||
def _read_attrs(self, subnetpool, keys):
|
def _read_attrs(self, subnetpool, keys):
|
||||||
for key in keys:
|
for key in keys:
|
||||||
|
@@ -24,10 +24,11 @@ class BaseSecGroupTest(base.BaseNetworkTest):
|
|||||||
def resource_setup(cls):
|
def resource_setup(cls):
|
||||||
super(BaseSecGroupTest, cls).resource_setup()
|
super(BaseSecGroupTest, cls).resource_setup()
|
||||||
|
|
||||||
def _create_security_group(self):
|
def _create_security_group(self, **kwargs):
|
||||||
# Create a security group
|
# Create a security group
|
||||||
name = data_utils.rand_name('secgroup-')
|
name = data_utils.rand_name('secgroup-')
|
||||||
group_create_body = self.client.create_security_group(name=name)
|
group_create_body = self.client.create_security_group(name=name,
|
||||||
|
**kwargs)
|
||||||
self.addCleanup(self._delete_security_group,
|
self.addCleanup(self._delete_security_group,
|
||||||
group_create_body['security_group']['id'])
|
group_create_body['security_group']['id'])
|
||||||
self.assertEqual(group_create_body['security_group']['name'], name)
|
self.assertEqual(group_create_body['security_group']['name'], name)
|
||||||
|
@@ -118,6 +118,26 @@ class FloatingIPTestJSON(base.BaseNetworkTest):
|
|||||||
self.assertIsNone(updated_floating_ip['fixed_ip_address'])
|
self.assertIsNone(updated_floating_ip['fixed_ip_address'])
|
||||||
self.assertIsNone(updated_floating_ip['router_id'])
|
self.assertIsNone(updated_floating_ip['router_id'])
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('c72c1c0c-2193-4aca-eeee-b1442641ffff')
|
||||||
|
def test_create_update_floatingip_description(self):
|
||||||
|
if not test.is_extension_enabled('standard-attr-description',
|
||||||
|
'network'):
|
||||||
|
msg = "standard-attr-description not enabled."
|
||||||
|
raise self.skipException(msg)
|
||||||
|
body = self.client.create_floatingip(
|
||||||
|
floating_network_id=self.ext_net_id,
|
||||||
|
port_id=self.ports[0]['id'],
|
||||||
|
description='d1'
|
||||||
|
)['floatingip']
|
||||||
|
self.assertEqual('d1', body['description'])
|
||||||
|
body = self.client.show_floatingip(body['id'])['floatingip']
|
||||||
|
self.assertEqual('d1', body['description'])
|
||||||
|
body = self.client.update_floatingip(body['id'], description='d2')
|
||||||
|
self.assertEqual('d2', body['floatingip']['description'])
|
||||||
|
body = self.client.show_floatingip(body['floatingip']['id'])
|
||||||
|
self.assertEqual('d2', body['floatingip']['description'])
|
||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
@test.idempotent_id('e1f6bffd-442f-4668-b30e-df13f2705e77')
|
@test.idempotent_id('e1f6bffd-442f-4668-b30e-df13f2705e77')
|
||||||
def test_floating_ip_delete_port(self):
|
def test_floating_ip_delete_port(self):
|
||||||
|
@@ -236,6 +236,24 @@ class NetworksTestJSON(base.BaseNetworkTest):
|
|||||||
if network['id'] == self.network['id']]
|
if network['id'] == self.network['id']]
|
||||||
self.assertNotEmpty(networks, "Created network not found in the list")
|
self.assertNotEmpty(networks, "Created network not found in the list")
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('c72c1c0c-2193-4aca-ccc4-b1442640bbbb')
|
||||||
|
def test_create_update_network_description(self):
|
||||||
|
if not test.is_extension_enabled('standard-attr-description',
|
||||||
|
'network'):
|
||||||
|
msg = "standard-attr-description not enabled."
|
||||||
|
raise self.skipException(msg)
|
||||||
|
body = self.create_network(description='d1')
|
||||||
|
self.assertEqual('d1', body['description'])
|
||||||
|
net_id = body['id']
|
||||||
|
body = self.client.list_networks(id=net_id)['networks'][0]
|
||||||
|
self.assertEqual('d1', body['description'])
|
||||||
|
body = self.client.update_network(body['id'],
|
||||||
|
description='d2')
|
||||||
|
self.assertEqual('d2', body['network']['description'])
|
||||||
|
body = self.client.list_networks(id=net_id)['networks'][0]
|
||||||
|
self.assertEqual('d2', body['description'])
|
||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
@test.idempotent_id('6ae6d24f-9194-4869-9c85-c313cb20e080')
|
@test.idempotent_id('6ae6d24f-9194-4869-9c85-c313cb20e080')
|
||||||
def test_list_networks_fields(self):
|
def test_list_networks_fields(self):
|
||||||
@@ -272,6 +290,24 @@ class NetworksTestJSON(base.BaseNetworkTest):
|
|||||||
for field_name in fields:
|
for field_name in fields:
|
||||||
self.assertEqual(subnet[field_name], self.subnet[field_name])
|
self.assertEqual(subnet[field_name], self.subnet[field_name])
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('c72c1c0c-2193-4aca-eeee-b1442640bbbb')
|
||||||
|
def test_create_update_subnet_description(self):
|
||||||
|
if not test.is_extension_enabled('standard-attr-description',
|
||||||
|
'network'):
|
||||||
|
msg = "standard-attr-description not enabled."
|
||||||
|
raise self.skipException(msg)
|
||||||
|
body = self.create_subnet(self.network, description='d1')
|
||||||
|
self.assertEqual('d1', body['description'])
|
||||||
|
sub_id = body['id']
|
||||||
|
body = self.client.list_subnets(id=sub_id)['subnets'][0]
|
||||||
|
self.assertEqual('d1', body['description'])
|
||||||
|
body = self.client.update_subnet(body['id'],
|
||||||
|
description='d2')
|
||||||
|
self.assertEqual('d2', body['subnet']['description'])
|
||||||
|
body = self.client.list_subnets(id=sub_id)['subnets'][0]
|
||||||
|
self.assertEqual('d2', body['description'])
|
||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
@test.idempotent_id('db68ba48-f4ea-49e9-81d1-e367f6d0b20a')
|
@test.idempotent_id('db68ba48-f4ea-49e9-81d1-e367f6d0b20a')
|
||||||
def test_list_subnets(self):
|
def test_list_subnets(self):
|
||||||
|
@@ -69,6 +69,24 @@ class PortsTestJSON(sec_base.BaseSecGroupTest):
|
|||||||
self.assertEqual(updated_port['name'], new_name)
|
self.assertEqual(updated_port['name'], new_name)
|
||||||
self.assertFalse(updated_port['admin_state_up'])
|
self.assertFalse(updated_port['admin_state_up'])
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('c72c1c0c-2193-4aca-bbb4-b1442640bbbb')
|
||||||
|
def test_create_update_port_description(self):
|
||||||
|
if not test.is_extension_enabled('standard-attr-description',
|
||||||
|
'network'):
|
||||||
|
msg = "standard-attr-description not enabled."
|
||||||
|
raise self.skipException(msg)
|
||||||
|
body = self.create_port(self.network,
|
||||||
|
description='d1')
|
||||||
|
self.assertEqual('d1', body['description'])
|
||||||
|
body = self.client.list_ports(id=body['id'])['ports'][0]
|
||||||
|
self.assertEqual('d1', body['description'])
|
||||||
|
body = self.client.update_port(body['id'],
|
||||||
|
description='d2')
|
||||||
|
self.assertEqual('d2', body['port']['description'])
|
||||||
|
body = self.client.list_ports(id=body['port']['id'])['ports'][0]
|
||||||
|
self.assertEqual('d2', body['description'])
|
||||||
|
|
||||||
@test.idempotent_id('67f1b811-f8db-43e2-86bd-72c074d4a42c')
|
@test.idempotent_id('67f1b811-f8db-43e2-86bd-72c074d4a42c')
|
||||||
def test_create_bulk_port(self):
|
def test_create_bulk_port(self):
|
||||||
network1 = self.network
|
network1 = self.network
|
||||||
|
@@ -79,6 +79,22 @@ class RoutersTest(base.BaseRouterTest):
|
|||||||
create_body['router']['id'])
|
create_body['router']['id'])
|
||||||
self.assertEqual(show_body['router']['name'], updated_name)
|
self.assertEqual(show_body['router']['name'], updated_name)
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('c72c1c0c-2193-4aca-eeee-b1442640eeee')
|
||||||
|
def test_create_update_router_description(self):
|
||||||
|
if not test.is_extension_enabled('standard-attr-description',
|
||||||
|
'network'):
|
||||||
|
msg = "standard-attr-description not enabled."
|
||||||
|
raise self.skipException(msg)
|
||||||
|
body = self.create_router(description='d1', router_name='test')
|
||||||
|
self.assertEqual('d1', body['description'])
|
||||||
|
body = self.client.show_router(body['id'])['router']
|
||||||
|
self.assertEqual('d1', body['description'])
|
||||||
|
body = self.client.update_router(body['id'], description='d2')
|
||||||
|
self.assertEqual('d2', body['router']['description'])
|
||||||
|
body = self.client.show_router(body['router']['id'])['router']
|
||||||
|
self.assertEqual('d2', body['description'])
|
||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
@test.idempotent_id('e54dd3a3-4352-4921-b09d-44369ae17397')
|
@test.idempotent_id('e54dd3a3-4352-4921-b09d-44369ae17397')
|
||||||
def test_create_router_setting_tenant_id(self):
|
def test_create_router_setting_tenant_id(self):
|
||||||
|
@@ -142,6 +142,23 @@ class SecGroupTest(base.BaseSecGroupTest):
|
|||||||
self.assertIn(rule_create_body['security_group_rule']['id'],
|
self.assertIn(rule_create_body['security_group_rule']['id'],
|
||||||
rule_list)
|
rule_list)
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('c72c1c0c-2193-4aca-fff2-b1442640bbbb')
|
||||||
|
def test_create_security_group_rule_description(self):
|
||||||
|
if not test.is_extension_enabled('standard-attr-description',
|
||||||
|
'network'):
|
||||||
|
msg = "standard-attr-description not enabled."
|
||||||
|
raise self.skipException(msg)
|
||||||
|
sg = self._create_security_group()[0]['security_group']
|
||||||
|
rule = self.client.create_security_group_rule(
|
||||||
|
security_group_id=sg['id'], protocol='tcp',
|
||||||
|
direction='ingress', ethertype=self.ethertype,
|
||||||
|
description='d1'
|
||||||
|
)['security_group_rule']
|
||||||
|
self.assertEqual('d1', rule['description'])
|
||||||
|
body = self.client.show_security_group_rule(rule['id'])
|
||||||
|
self.assertEqual('d1', body['security_group_rule']['description'])
|
||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
@test.idempotent_id('87dfbcf9-1849-43ea-b1e4-efa3eeae9f71')
|
@test.idempotent_id('87dfbcf9-1849-43ea-b1e4-efa3eeae9f71')
|
||||||
def test_create_security_group_rule_with_additional_args(self):
|
def test_create_security_group_rule_with_additional_args(self):
|
||||||
|
@@ -103,6 +103,25 @@ class SubnetPoolsTest(SubnetPoolsTestBase):
|
|||||||
[sp['name'] for sp in subnetpools],
|
[sp['name'] for sp in subnetpools],
|
||||||
"Created subnetpool name should be in the list")
|
"Created subnetpool name should be in the list")
|
||||||
|
|
||||||
|
@test.attr(type='smoke')
|
||||||
|
@test.idempotent_id('c72c1c0c-2193-4aca-ddd4-b1442640bbbb')
|
||||||
|
def test_create_update_subnetpool_description(self):
|
||||||
|
if not test.is_extension_enabled('standard-attr-description',
|
||||||
|
'network'):
|
||||||
|
msg = "standard-attr-description not enabled."
|
||||||
|
raise self.skipException(msg)
|
||||||
|
body = self._create_subnetpool(description='d1')
|
||||||
|
self.assertEqual('d1', body['description'])
|
||||||
|
sub_id = body['id']
|
||||||
|
body = filter(lambda x: x['id'] == sub_id,
|
||||||
|
self.client.list_subnetpools()['subnetpools'])[0]
|
||||||
|
self.assertEqual('d1', body['description'])
|
||||||
|
body = self.client.update_subnetpool(sub_id, description='d2')
|
||||||
|
self.assertEqual('d2', body['subnetpool']['description'])
|
||||||
|
body = filter(lambda x: x['id'] == sub_id,
|
||||||
|
self.client.list_subnetpools()['subnetpools'])[0]
|
||||||
|
self.assertEqual('d2', body['description'])
|
||||||
|
|
||||||
@test.attr(type='smoke')
|
@test.attr(type='smoke')
|
||||||
@test.idempotent_id('741d08c2-1e3f-42be-99c7-0ea93c5b728c')
|
@test.idempotent_id('741d08c2-1e3f-42be-99c7-0ea93c5b728c')
|
||||||
def test_get_subnetpool(self):
|
def test_get_subnetpool(self):
|
||||||
|
@@ -434,6 +434,8 @@ class NetworkClientJSON(service_client.ServiceClient):
|
|||||||
update_body['name'] = kwargs.get('name', body['router']['name'])
|
update_body['name'] = kwargs.get('name', body['router']['name'])
|
||||||
update_body['admin_state_up'] = kwargs.get(
|
update_body['admin_state_up'] = kwargs.get(
|
||||||
'admin_state_up', body['router']['admin_state_up'])
|
'admin_state_up', body['router']['admin_state_up'])
|
||||||
|
if 'description' in kwargs:
|
||||||
|
update_body['description'] = kwargs['description']
|
||||||
cur_gw_info = body['router']['external_gateway_info']
|
cur_gw_info = body['router']['external_gateway_info']
|
||||||
if cur_gw_info:
|
if cur_gw_info:
|
||||||
# TODO(kevinbenton): setting the external gateway info is not
|
# TODO(kevinbenton): setting the external gateway info is not
|
||||||
|
@@ -579,6 +579,14 @@ class RequestExtensionTest(base.BaseTestCase):
|
|||||||
|
|
||||||
class ExtensionManagerTest(base.BaseTestCase):
|
class ExtensionManagerTest(base.BaseTestCase):
|
||||||
|
|
||||||
|
def test_optional_extensions_no_error(self):
|
||||||
|
ext_mgr = extensions.ExtensionManager('')
|
||||||
|
attr_map = {}
|
||||||
|
ext_mgr.add_extension(ext_stubs.StubExtension('foo_alias',
|
||||||
|
optional=['cats']))
|
||||||
|
ext_mgr.extend_resources("2.0", attr_map)
|
||||||
|
self.assertIn('foo_alias', ext_mgr.extensions)
|
||||||
|
|
||||||
def test_missing_required_extensions_raise_error(self):
|
def test_missing_required_extensions_raise_error(self):
|
||||||
ext_mgr = extensions.ExtensionManager('')
|
ext_mgr = extensions.ExtensionManager('')
|
||||||
attr_map = {}
|
attr_map = {}
|
||||||
|
@@ -21,8 +21,9 @@ from neutron import wsgi
|
|||||||
|
|
||||||
class StubExtension(extensions.ExtensionDescriptor):
|
class StubExtension(extensions.ExtensionDescriptor):
|
||||||
|
|
||||||
def __init__(self, alias="stub_extension"):
|
def __init__(self, alias="stub_extension", optional=None):
|
||||||
self.alias = alias
|
self.alias = alias
|
||||||
|
self.optional = optional or []
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return "Stub Extension"
|
return "Stub Extension"
|
||||||
@@ -36,6 +37,9 @@ class StubExtension(extensions.ExtensionDescriptor):
|
|||||||
def get_updated(self):
|
def get_updated(self):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def get_optional_extensions(self):
|
||||||
|
return self.optional
|
||||||
|
|
||||||
|
|
||||||
class StubExtensionWithReqs(StubExtension):
|
class StubExtensionWithReqs(StubExtension):
|
||||||
|
|
||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
prelude: >
|
||||||
|
Add description field to security group rules, networks, ports, routers,
|
||||||
|
floating IPs, and subnet pools.
|
||||||
|
features:
|
||||||
|
- Security group rules, networks, ports, routers, floating IPs, and subnet
|
||||||
|
pools may now contain an optional description which allows users to
|
||||||
|
easily store details about entities.
|
Reference in New Issue
Block a user