group-based-policy/gbpservice/neutron/db/servicechain_db.py

626 lines
29 KiB
Python

# 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 ast
from neutron.db import common_db_mixin
from neutron.plugins.common import constants as pconst
from neutron_lib.db import model_base
from neutron_lib import exceptions as n_exc
from neutron_lib.plugins import directory
from oslo_log import helpers as log
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
import sqlalchemy as sa
from sqlalchemy.ext.orderinglist import ordering_list
from sqlalchemy import orm
from sqlalchemy.orm import exc
from gbpservice.neutron.extensions import servicechain as schain
from gbpservice.neutron.services.servicechain.common import exceptions as s_exc
LOG = logging.getLogger(__name__)
MAX_IPV4_SUBNET_PREFIX_LENGTH = 31
MAX_IPV6_SUBNET_PREFIX_LENGTH = 127
class BaseSCResource(model_base.HasId, model_base.HasProject):
name = sa.Column(sa.String(255))
description = sa.Column(sa.String(255))
status = sa.Column(sa.String(length=16), nullable=True)
status_details = sa.Column(sa.String(length=4096), nullable=True)
class BaseSharedSCResource(BaseSCResource):
shared = sa.Column(sa.Boolean)
class SpecNodeAssociation(model_base.BASEV2):
"""Models one to many providing relation between Specs and Nodes."""
__tablename__ = 'sc_spec_node_associations'
servicechain_spec_id = sa.Column(
sa.String(36), sa.ForeignKey('sc_specs.id'), primary_key=True)
node_id = sa.Column(sa.String(36),
sa.ForeignKey('sc_nodes.id'),
primary_key=True)
position = sa.Column(sa.Integer)
class InstanceSpecAssociation(model_base.BASEV2):
"""Models one to many providing relation between Instance and Specs."""
__tablename__ = 'sc_instance_spec_mappings'
servicechain_instance_id = sa.Column(
sa.String(36), sa.ForeignKey('sc_instances.id'), primary_key=True)
servicechain_spec_id = sa.Column(sa.String(36),
sa.ForeignKey('sc_specs.id'),
primary_key=True)
position = sa.Column(sa.Integer)
class ServiceChainNode(model_base.BASEV2, BaseSharedSCResource):
"""ServiceChain Node"""
__tablename__ = 'sc_nodes'
config = sa.Column(sa.TEXT)
specs = orm.relationship(SpecNodeAssociation,
backref="nodes",
cascade='all, delete, delete-orphan')
service_type = sa.Column(sa.String(50), nullable=True)
service_profile_id = sa.Column(
sa.String(36), sa.ForeignKey('service_profiles.id'),
nullable=True)
class ServiceChainInstance(model_base.BASEV2, BaseSCResource):
"""Service chain instances"""
__tablename__ = 'sc_instances'
config_param_values = sa.Column(sa.String(4096))
specs = orm.relationship(
InstanceSpecAssociation,
backref='instances',
cascade='all,delete, delete-orphan',
order_by='InstanceSpecAssociation.position',
collection_class=ordering_list('position', count_from=1))
provider_ptg_id = sa.Column(sa.String(36),
# FixMe(Magesh) Issue with cascade on Delete
# sa.ForeignKey('gp_policy_target_groups.id'),
nullable=True)
consumer_ptg_id = sa.Column(sa.String(36),
# sa.ForeignKey('gp_policy_target_groups.id'),
nullable=True)
management_ptg_id = sa.Column(sa.String(36),
# sa.ForeignKey('gp_policy_target_groups.id'),
nullable=True)
classifier_id = sa.Column(sa.String(36),
# sa.ForeignKey('gp_policy_classifiers.id'),
nullable=True)
class ServiceChainSpec(model_base.BASEV2, BaseSharedSCResource):
""" ServiceChain Spec
"""
__tablename__ = 'sc_specs'
nodes = orm.relationship(
SpecNodeAssociation,
backref='specs', cascade='all, delete, delete-orphan',
order_by='SpecNodeAssociation.position',
collection_class=ordering_list('position', count_from=1))
config_param_names = sa.Column(sa.String(4096))
instances = orm.relationship(InstanceSpecAssociation,
backref="specs",
cascade='all, delete, delete-orphan')
class ServiceProfile(model_base.BASEV2, BaseSharedSCResource):
""" Service Profile
"""
__tablename__ = 'service_profiles'
vendor = sa.Column(sa.String(50))
# Not using ENUM for less painful upgrades. Validation will happen at the
# API level
insertion_mode = sa.Column(sa.String(50))
service_type = sa.Column(sa.String(50))
service_flavor = sa.Column(sa.String(1024))
nodes = orm.relationship(ServiceChainNode, backref="service_profile")
class ServiceChainDbPlugin(schain.ServiceChainPluginBase,
common_db_mixin.CommonDbMixin):
"""ServiceChain plugin interface implementation using SQLAlchemy models."""
# TODO(osms69): native bulk support
__native_bulk_support = False
__native_pagination_support = True
__native_sorting_support = True
def __init__(self, *args, **kwargs):
super(ServiceChainDbPlugin, self).__init__(*args, **kwargs)
@property
def _grouppolicy_plugin(self):
# REVISIT(Magesh): Need initialization method after all
# plugins are loaded to grab and store plugin.
grouppolicy_plugin = directory.get_plugin(pconst.GROUP_POLICY)
if not grouppolicy_plugin:
LOG.error("No Grouppolicy service plugin found.")
raise s_exc.ServiceChainDeploymentError()
return grouppolicy_plugin
# REVISIT: This is temporary, the correct fix is to use the
# project_id in the context. Moreover, patch.py already patches
# thi, so it should not be required here.
def _get_tenant_id_for_create(self, context, resource):
if context.is_admin and 'tenant_id' in resource:
tenant_id = resource['tenant_id']
return tenant_id
elif ('tenant_id' in resource and
resource['tenant_id'] != context.tenant_id):
reason = _('Cannot create resource for another tenant')
raise n_exc.AdminRequired(reason=reason)
else:
tenant_id = context.tenant_id
return tenant_id
def _get_servicechain_node(self, context, node_id):
try:
return self._get_by_id(context, ServiceChainNode, node_id)
except exc.NoResultFound:
raise schain.ServiceChainNodeNotFound(sc_node_id=node_id)
def _get_servicechain_spec(self, context, spec_id):
try:
return self._get_by_id(context, ServiceChainSpec, spec_id)
except exc.NoResultFound:
raise schain.ServiceChainSpecNotFound(sc_spec_id=spec_id)
def _get_servicechain_instance(self, context, instance_id):
try:
return self._get_by_id(context, ServiceChainInstance, instance_id)
except exc.NoResultFound:
raise schain.ServiceChainInstanceNotFound(
sc_instance_id=instance_id)
def _get_service_profile(self, context, profile_id):
try:
return self._get_by_id(context, ServiceProfile, profile_id)
except exc.NoResultFound:
raise schain.ServiceProfileNotFound(
profile_id=profile_id)
def _populate_common_fields_in_dict(self, db_ref):
res = {'id': db_ref['id'],
'tenant_id': db_ref['tenant_id'],
'name': db_ref['name'],
'description': db_ref['description'],
'status': db_ref['status'],
'status_details': db_ref['status_details'],
'shared': db_ref.get('shared', False)}
return res
def _make_sc_node_dict(self, sc_node, fields=None):
res = self._populate_common_fields_in_dict(sc_node)
res['service_profile_id'] = sc_node['service_profile_id']
res['service_type'] = sc_node['service_type']
res['config'] = sc_node['config']
res['servicechain_specs'] = [sc_spec['servicechain_spec_id']
for sc_spec in sc_node['specs']]
return self._fields(res, fields)
def _make_sc_spec_dict(self, spec, fields=None):
res = self._populate_common_fields_in_dict(spec)
res['config_param_names'] = spec.get('config_param_names')
res['nodes'] = [sc_node['node_id'] for sc_node in spec['nodes']]
res['instances'] = [x['servicechain_instance_id'] for x in
spec['instances']]
return self._fields(res, fields)
def _make_sc_instance_dict(self, instance, fields=None):
res = {'id': instance['id'],
'tenant_id': instance['tenant_id'],
'name': instance['name'],
'description': instance['description'],
'config_param_values': instance['config_param_values'],
'provider_ptg_id': instance['provider_ptg_id'],
'consumer_ptg_id': instance['consumer_ptg_id'],
'management_ptg_id': instance['management_ptg_id'],
'classifier_id': instance['classifier_id'],
'status': instance['status'],
'status_details': instance['status_details']}
res['servicechain_specs'] = [sc_spec['servicechain_spec_id']
for sc_spec in instance['specs']]
return self._fields(res, fields)
def _make_service_profile_dict(self, profile, fields=None):
res = self._populate_common_fields_in_dict(profile)
res['service_type'] = profile['service_type']
res['service_flavor'] = profile['service_flavor']
res['vendor'] = profile['vendor']
res['insertion_mode'] = profile['insertion_mode']
res['nodes'] = [node['id'] for node in profile['nodes']]
return self._fields(res, fields)
@staticmethod
def validate_service_type(service_type):
if service_type not in schain.sc_supported_type:
raise schain.ServiceTypeNotSupported(sc_service_type=service_type)
@log.log_method_call
def create_servicechain_node(self, context, servicechain_node):
node = servicechain_node['servicechain_node']
tenant_id = self._get_tenant_id_for_create(context, node)
with context.session.begin(subtransactions=True):
node_db = ServiceChainNode(
id=uuidutils.generate_uuid(), tenant_id=tenant_id,
name=node['name'], description=node['description'],
service_profile_id=node.get('service_profile_id'),
service_type=node.get('service_type'),
config=node['config'], shared=node['shared'],
status=node.get('status'),
status_details=node.get('status_details'))
context.session.add(node_db)
return self._make_sc_node_dict(node_db)
@log.log_method_call
def update_servicechain_node(self, context, servicechain_node_id,
servicechain_node, set_params=False):
node = servicechain_node['servicechain_node']
with context.session.begin(subtransactions=True):
node_db = self._get_servicechain_node(context,
servicechain_node_id)
node_db.update(node)
# Update the config param names derived for the associated specs
spec_node_associations = node_db.specs
for node_spec in spec_node_associations:
spec_id = node_spec.servicechain_spec_id
spec_db = self._get_servicechain_spec(context, spec_id)
self._process_nodes_for_spec(
context, spec_db, self._make_sc_spec_dict(spec_db),
set_params=set_params)
return self._make_sc_node_dict(node_db)
@log.log_method_call
def delete_servicechain_node(self, context, servicechain_node_id):
with context.session.begin(subtransactions=True):
node_db = self._get_servicechain_node(context,
servicechain_node_id)
if node_db.specs:
raise schain.ServiceChainNodeInUse(
node_id=servicechain_node_id)
context.session.delete(node_db)
@log.log_method_call
def get_servicechain_node(self, context, servicechain_node_id,
fields=None):
node = self._get_servicechain_node(context, servicechain_node_id)
return self._make_sc_node_dict(node, fields)
@log.log_method_call
def get_servicechain_nodes(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
marker_obj = self._get_marker_obj(context, 'servicechain_node', limit,
marker)
return self._get_collection(context, ServiceChainNode,
self._make_sc_node_dict,
filters=filters, fields=fields,
sorts=sorts, limit=limit,
marker_obj=marker_obj,
page_reverse=page_reverse)
@log.log_method_call
def get_servicechain_nodes_count(self, context, filters=None):
return self._get_collection_count(context, ServiceChainNode,
filters=filters)
def _process_nodes_for_spec(self, context, spec_db, spec,
set_params=True):
if 'nodes' in spec:
self._set_nodes_for_spec(context, spec_db, spec['nodes'],
set_params=set_params)
del spec['nodes']
return spec
def _set_nodes_for_spec(self, context, spec_db, nodes_id_list,
set_params=True):
if not nodes_id_list:
spec_db.nodes = []
spec_db.config_param_names = '[]'
return
with context.session.begin(subtransactions=True):
# We will first check if the new list of nodes is valid
filters = {'id': [n_id for n_id in nodes_id_list]}
nodes_in_db = self._get_collection_query(context, ServiceChainNode,
filters=filters)
nodes_list = [n_db['id'] for n_db in nodes_in_db]
for node_id in nodes_id_list:
if node_id not in nodes_list:
# If we find an invalid node id in the list we
# do not perform the update
raise schain.ServiceChainNodeNotFound(sc_node_id=node_id)
# New list of nodes is valid so we will first reset the
# existing list and then add each node in order.
# Note that the list could be empty in which case we interpret
# it as clearing existing nodes.
spec_db.nodes = []
if set_params:
spec_db.config_param_names = '[]'
for node_id in nodes_id_list:
if set_params:
sc_node = self.get_servicechain_node(context, node_id)
node_dict = jsonutils.loads(sc_node['config'])
config_params = (node_dict.get('parameters') or
node_dict.get('Parameters'))
if config_params:
if not spec_db.config_param_names:
spec_db.config_param_names = str(
config_params.keys())
else:
config_param_names = ast.literal_eval(
spec_db.config_param_names)
config_param_names.extend(config_params.keys())
spec_db.config_param_names = str(
config_param_names)
assoc = SpecNodeAssociation(servicechain_spec_id=spec_db.id,
node_id=node_id)
spec_db.nodes.append(assoc)
def _process_specs_for_instance(self, context, instance_db, instance):
if 'servicechain_specs' in instance:
self._set_specs_for_instance(context, instance_db,
instance['servicechain_specs'])
del instance['servicechain_specs']
return instance
def _set_specs_for_instance(self, context, instance_db, spec_id_list):
if not spec_id_list:
instance_db.spec_ids = []
return
with context.session.begin(subtransactions=True):
filters = {'id': spec_id_list}
specs_in_db = self._get_collection_query(context, ServiceChainSpec,
filters=filters)
specs_list = set(spec_db['id'] for spec_db in specs_in_db)
for spec_id in spec_id_list:
if spec_id not in specs_list:
# Do not update if spec ID is invalid
raise schain.ServiceChainSpecNotFound(sc_spec_id=spec_id)
# Reset the existing list and then add each spec in order. The list
# could be empty in which case we clear the existing specs.
instance_db.specs = []
for spec_id in spec_id_list:
assoc = InstanceSpecAssociation(
servicechain_instance_id=instance_db.id,
servicechain_spec_id=spec_id)
instance_db.specs.append(assoc)
def _get_instances_from_policy_target(self, context, policy_target):
with context.session.begin(subtransactions=True):
ptg_id = policy_target['policy_target_group_id']
scis_p = self.get_servicechain_instances(
context, {'provider_ptg_id': [ptg_id]})
scis_c = self.get_servicechain_instances(
context, {'consumer_ptg_id': [ptg_id]})
# Don't return duplicates
result = []
seen = set()
for sci in scis_p + scis_c:
if sci['id'] not in seen:
seen.add(sci['id'])
result.append(sci)
return result
@log.log_method_call
def create_servicechain_spec(self, context, servicechain_spec,
set_params=True):
spec = servicechain_spec['servicechain_spec']
tenant_id = self._get_tenant_id_for_create(context, spec)
with context.session.begin(subtransactions=True):
spec_db = ServiceChainSpec(id=uuidutils.generate_uuid(),
tenant_id=tenant_id,
name=spec['name'],
description=spec['description'],
shared=spec['shared'],
status=spec.get('status'),
status_details=
spec.get('status_details'))
self._process_nodes_for_spec(context, spec_db, spec,
set_params=set_params)
context.session.add(spec_db)
return self._make_sc_spec_dict(spec_db)
@log.log_method_call
def update_servicechain_spec(self, context, spec_id,
servicechain_spec, set_params=True):
spec = servicechain_spec['servicechain_spec']
with context.session.begin(subtransactions=True):
spec_db = self._get_servicechain_spec(context,
spec_id)
spec = self._process_nodes_for_spec(context, spec_db, spec,
set_params=set_params)
spec_db.update(spec)
return self._make_sc_spec_dict(spec_db)
@log.log_method_call
def delete_servicechain_spec(self, context, spec_id):
policy_actions = self._grouppolicy_plugin.get_policy_actions(
context, filters={"action_value": [spec_id]})
if policy_actions:
raise schain.ServiceChainSpecInUse(spec_id=spec_id)
with context.session.begin(subtransactions=True):
spec_db = self._get_servicechain_spec(context,
spec_id)
if spec_db.instances:
raise schain.ServiceChainSpecInUse(spec_id=spec_id)
context.session.delete(spec_db)
@log.log_method_call
def get_servicechain_spec(self, context, spec_id,
fields=None):
spec = self._get_servicechain_spec(context, spec_id)
return self._make_sc_spec_dict(spec, fields)
@log.log_method_call
def get_servicechain_specs(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
marker_obj = self._get_marker_obj(context, 'servicechain_spec', limit,
marker)
return self._get_collection(context, ServiceChainSpec,
self._make_sc_spec_dict,
filters=filters, fields=fields,
sorts=sorts, limit=limit,
marker_obj=marker_obj,
page_reverse=page_reverse)
@log.log_method_call
def get_servicechain_specs_count(self, context, filters=None):
return self._get_collection_count(context, ServiceChainSpec,
filters=filters)
@log.log_method_call
def create_servicechain_instance(self, context, servicechain_instance):
instance = servicechain_instance['servicechain_instance']
tenant_id = self._get_tenant_id_for_create(context, instance)
with context.session.begin(subtransactions=True):
if not instance.get('management_ptg_id'):
management_groups = (
self._grouppolicy_plugin.get_policy_target_groups(
context, {'service_management': [True],
'tenant_id': [instance.get('tenant_id')]}))
if not management_groups:
# Fall back on shared service management
management_groups = (
self._grouppolicy_plugin.get_policy_target_groups(
context, {'service_management': [True]}))
if management_groups:
instance['management_ptg_id'] = management_groups[0]['id']
instance_db = ServiceChainInstance(
id=uuidutils.generate_uuid(),
tenant_id=tenant_id, name=instance['name'],
description=instance['description'],
config_param_values=instance['config_param_values'],
provider_ptg_id=instance.get('provider_ptg_id'),
consumer_ptg_id=instance.get('consumer_ptg_id'),
management_ptg_id=instance.get('management_ptg_id'),
classifier_id=instance.get('classifier_id'),
status=instance.get('status'),
status_details=instance.get('status_details'))
self._process_specs_for_instance(context, instance_db, instance)
context.session.add(instance_db)
return self._make_sc_instance_dict(instance_db)
@log.log_method_call
def update_servicechain_instance(self, context, servicechain_instance_id,
servicechain_instance):
instance = servicechain_instance['servicechain_instance']
with context.session.begin(subtransactions=True):
instance_db = self._get_servicechain_instance(
context, servicechain_instance_id)
instance = self._process_specs_for_instance(context, instance_db,
instance)
instance_db.update(instance)
return self._make_sc_instance_dict(instance_db)
@log.log_method_call
def delete_servicechain_instance(self, context, servicechain_instance_id):
with context.session.begin(subtransactions=True):
instance_db = self._get_servicechain_instance(
context, servicechain_instance_id)
context.session.delete(instance_db)
@log.log_method_call
def get_servicechain_instance(self, context, sc_instance_id, fields=None):
instance_db = self._get_servicechain_instance(context, sc_instance_id)
return self._make_sc_instance_dict(instance_db, fields)
@log.log_method_call
def get_servicechain_instances(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
marker_obj = self._get_marker_obj(context, 'servicechain_instance',
limit, marker)
return self._get_collection(context, ServiceChainInstance,
self._make_sc_instance_dict,
filters=filters, fields=fields,
sorts=sorts, limit=limit,
marker_obj=marker_obj,
page_reverse=page_reverse)
@log.log_method_call
def get_servicechain_instances_count(self, context, filters=None):
return self._get_collection_count(context, ServiceChainInstance,
filters=filters)
@log.log_method_call
def get_service_profiles_count(self, context, filters=None):
return self._get_collection_count(context, ServiceProfile,
filters=filters)
@log.log_method_call
def create_service_profile(self, context, service_profile):
profile = service_profile['service_profile']
tenant_id = self._get_tenant_id_for_create(context, profile)
with context.session.begin(subtransactions=True):
profile_db = ServiceProfile(
id=uuidutils.generate_uuid(), tenant_id=tenant_id,
name=profile['name'], description=profile['description'],
service_type=profile.get('service_type'),
insertion_mode=profile.get('insertion_mode'),
vendor=profile.get('vendor'),
service_flavor=profile.get('service_flavor'),
shared=profile.get('shared'),
status=profile.get('status'),
status_details=profile.get('status_details'))
context.session.add(profile_db)
return self._make_service_profile_dict(profile_db)
@log.log_method_call
def update_service_profile(self, context, service_profile_id,
service_profile):
profile = service_profile['service_profile']
with context.session.begin(subtransactions=True):
profile_db = self._get_service_profile(context,
service_profile_id)
profile_db.update(profile)
return self._make_service_profile_dict(profile_db)
@log.log_method_call
def delete_service_profile(self, context, service_profile_id):
with context.session.begin(subtransactions=True):
profile_db = self._get_service_profile(context,
service_profile_id)
if profile_db.nodes:
raise schain.ServiceProfileInUse(
profile_id=service_profile_id)
context.session.delete(profile_db)
@log.log_method_call
def get_service_profile(self, context, service_profile_id, fields=None):
profile_db = self._get_service_profile(
context, service_profile_id)
return self._make_service_profile_dict(profile_db, fields)
@log.log_method_call
def get_service_profiles(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
marker_obj = self._get_marker_obj(context, 'service_profile',
limit, marker)
return self._get_collection(context, ServiceProfile,
self._make_service_profile_dict,
filters=filters, fields=fields,
sorts=sorts, limit=limit,
marker_obj=marker_obj,
page_reverse=page_reverse)