neutron/neutron/db/servicetype_db.py
Brandon Logan 2a0f860a56 Force service provider relationships to load
A race condition was exposed in the LBaaS V2 db layer that was caused by a
hack to get around this issue.  The real issue is that since the
ProviderResourceAssociation is inserted independently, any models that were
created before this insert will not have their relationship with the
ProviderResourceAssocation loaded.  Using the session.expire_all method will
force the session to retrieve all new data and load this relationship for any
resource that uses this relationship.

Change-Id: I940b541f4ef9c489126cd2d215b1d857f0624de0
Closes-Bug: #1504465
(cherry picked from commit f218929bc5)
2015-12-18 16:00:16 +00:00

112 lines
4.5 KiB
Python

# Copyright 2013 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 itertools import chain
from oslo_log import log as logging
import sqlalchemy as sa
from neutron.db import model_base
from neutron.services import provider_configuration as pconf
LOG = logging.getLogger(__name__)
class ProviderResourceAssociation(model_base.BASEV2):
provider_name = sa.Column(sa.String(255),
nullable=False, primary_key=True)
# should be manually deleted on resource deletion
resource_id = sa.Column(sa.String(36), nullable=False, primary_key=True,
unique=True)
class ServiceTypeManager(object):
"""Manage service type objects in Neutron."""
_instance = None
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
def __init__(self):
self.config = {}
def add_provider_configuration(self, service_type, configuration):
"""Add or update the provider configuration for the service type."""
LOG.debug('Adding provider configuration for service %s', service_type)
self.config.update({service_type: configuration})
def get_service_providers(self, context, filters=None, fields=None):
if filters and 'service_type' in filters:
return list(
chain.from_iterable(self.config[svc_type].
get_service_providers(filters, fields)
for svc_type in filters['service_type']
if svc_type in self.config)
)
return list(
chain.from_iterable(
self.config[p].get_service_providers(filters, fields)
for p in self.config)
)
def get_default_service_provider(self, context, service_type):
"""Return the default provider for a given service type."""
filters = {'service_type': [service_type],
'default': [True]}
providers = self.get_service_providers(context, filters=filters)
# By construction we expect at most a single item in provider
if not providers:
raise pconf.DefaultServiceProviderNotFound(
service_type=service_type
)
return providers[0]
def add_resource_association(self, context, service_type, provider_name,
resource_id):
r = self.get_service_providers(context,
filters={'service_type': [service_type], 'name': [provider_name]})
if not r:
raise pconf.ServiceProviderNotFound(provider=provider_name,
service_type=service_type)
with context.session.begin(subtransactions=True):
# we don't actually need service type for association.
# resource_id is unique and belongs to specific service
# which knows its type
assoc = ProviderResourceAssociation(provider_name=provider_name,
resource_id=resource_id)
context.session.add(assoc)
# NOTE(blogan): the ProviderResourceAssociation relationship will not
# be populated if a resource was created before this. The expire_all
# will force the session to go retrieve the new data when that
# resource will be read again. It has been suggested that we can
# crawl through everything in the mapper to find the resource with
# the ID that matches resource_id and expire that one, but we can
# just start with this.
context.session.expire_all()
def del_resource_associations(self, context, resource_ids):
if not resource_ids:
return
with context.session.begin(subtransactions=True):
(context.session.query(ProviderResourceAssociation).
filter(
ProviderResourceAssociation.resource_id.in_(resource_ids)).
delete(synchronize_session='fetch'))