diff --git a/neutron/api/v2/attributes.py b/neutron/api/v2/attributes.py index 512255f0ca7..a32c8c2a5a5 100644 --- a/neutron/api/v2/attributes.py +++ b/neutron/api/v2/attributes.py @@ -405,6 +405,51 @@ def _validate_dict_or_nodata(data, key_specs=None): if data: return _validate_dict(data, key_specs) +def _validate_list_item(validator, item): + # Find validator function + val_func = val_params = None + for (k, v) in validator.iteritems(): + if k.startswith('type:'): + # ask forgiveness, not permission + try: + val_func = validators[k] + except KeyError: + return _("Validator '%s' does not exist.") % k + val_params = v + break + # Process validation + if val_func: + return val_func(item, val_params) + + +def _validate_list(data, key_specs=None): + if not isinstance(data, list): + msg = _("'%s' is not a list") % data + LOG.debug(msg) + return msg + # Do not perform any further validation, if no constraints are supplied + if not key_specs: + return + + # In case when list is a list of primitives and values should be converted + # replace the items with converted items inside the original list + conv_func = key_specs.get('convert_to') + if (len(data) > 0 and not + (isinstance(data[0], list) or (isinstance(data[0], dict))) and + conv_func): + for index in range(0, len(data)): + data[index] = conv_func(data[index]) + + for item in data: + msg = _validate_list_item(key_specs, item) + if msg: + return msg +def _validate_list_or_empty(data, key_specs=None): + if data != []: + return _validate_list(data, key_specs) + + + def _validate_non_negative(data, valid_values=None): try: @@ -510,6 +555,8 @@ def convert_to_list(data): 'type:dict_or_none': _validate_dict_or_none, 'type:dict_or_empty': _validate_dict_or_empty, 'type:dict_or_nodata': _validate_dict_or_nodata, + 'type:list': _validate_list, + 'type:list_or_empty': _validate_list_or_empty, 'type:fixed_ips': _validate_fixed_ips, 'type:hostroutes': _validate_hostroutes, 'type:ip_address': _validate_ip_address, diff --git a/neutron/api/v2/resource_helper.py b/neutron/api/v2/resource_helper.py new file mode 100755 index 00000000000..0ec7eef7143 --- /dev/null +++ b/neutron/api/v2/resource_helper.py @@ -0,0 +1,101 @@ +# (c) Copyright 2014 Cisco Systems Inc. +# 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. +# @author: Paul Michali Cisco Systems, Inc. + +from oslo.config import cfg + +from neutron.api import extensions +from neutron.api.v2 import attributes +from neutron.api.v2 import base +from neutron import manager +from neutron.plugins.common import constants +from neutron import quota + + +def build_plural_mappings(special_mappings, resource_map): + """Create plural to singular mapping for all resources. + + Allows for special mappings to be provided, like policies -> policy. + Otherwise, will strip off the last character for normal mappings, like + routers -> router. + """ + plural_mappings = {} + for plural in resource_map: + singular = special_mappings.get(plural, plural[:-1]) + plural_mappings[plural] = singular + return plural_mappings + + +def build_resource_info(plural_mappings, resource_map, which_service, + ext_plugin=None, action_map=None, register_quota=False, + translate_name=False, allow_bulk=False): + """Build resources for advanced services. + + Takes the resource information, and singular/plural mappings, and creates + API resource objects for advanced services extensions. Will optionally + translate underscores to dashes in resource names, register the resource, + and accept action information for resources. + + :param plural_mappings: mappings between singular and plural forms + :param resource_map: attribute map for the WSGI resources to create + :param which_service: The name of the service for which the WSGI resources + are being created. This name will be used to pass + the appropriate plugin to the WSGI resource. + It can be set to None or "CORE"to create WSGI + resources for the the core plugin + :param action_map: custom resource actions + :param register_quota: it can be set to True to register quotas for the + resource(s) being created + :param translate_name: replaces underscores with dashes + :param allow_bulk: True if bulk create are allowed + """ + resources = [] + if not which_service: + which_service = constants.CORE + if action_map is None: + action_map = {} + + if ext_plugin: + plugin = ext_plugin + elif which_service != constants.CORE: + plugin = manager.NeutronManager.get_service_plugins()[which_service] + else: + plugin = manager.NeutronManager.get_plugin() + for collection_name in resource_map: + resource_name = plural_mappings[collection_name] + params = resource_map.get(collection_name, {}) + extended_params = attributes.RESOURCE_ATTRIBUTE_MAP.get( + collection_name) + if (extended_params): + params = extended_params + if translate_name: + collection_name = collection_name.replace('_', '-') + if register_quota: + quota.QUOTAS.register_resource_by_name(resource_name) + member_actions = action_map.get(resource_name, {}) + controller = base.create_resource( + collection_name, resource_name, plugin, params, + member_actions=member_actions, + allow_bulk=allow_bulk, + allow_pagination=cfg.CONF.allow_pagination, + allow_sorting=cfg.CONF.allow_sorting) + resource = extensions.ResourceExtension( + collection_name, + controller, + path_prefix=constants.COMMON_PREFIXES[which_service], + member_actions=member_actions, + attr_map=params) + resources.append(resource) + return resources diff --git a/neutron/common/driver_manager.py b/neutron/common/driver_manager.py new file mode 100755 index 00000000000..1abe560d846 --- /dev/null +++ b/neutron/common/driver_manager.py @@ -0,0 +1,81 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013, 2014 Intel Corporation. +# Copyright 2013, 2014 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel Corporation. + +import logging as log + +import stevedore.named + +LOG = log.getLogger(__name__) + + +class DriverManager(object): + def __init__(self, namespace, driver_list, **kwargs): + super(DriverManager, self).__init__() + manager = stevedore.named.NamedExtensionManager( + namespace, driver_list, invoke_on_load=True, **kwargs) + + drivers = {} + for ext in manager: + type_ = ext.obj.get_type() + if type_ in drivers: + msg = _("driver '%(new_driver)s' ignored because " + "driver '%(old_driver)s' is already " + "registered for driver '%(type)s'") % { + 'new_driver': ext.name, + 'old_driver': drivers[type].name, + 'type': type_} + LOG.error(msg) + raise SystemExit(msg) + drivers[type_] = ext + + self._drivers = dict((type_, ext.obj) + for (type_, ext) in drivers.items()) + LOG.info(_("Registered drivers from %(namespace)s: %(keys)s"), + {'namespace': namespace, 'keys': self._drivers.keys()}) + + @staticmethod + def _driver_name(driver): + return driver.__module__ + '.' + driver.__class__.__name__ + + def register(self, type_, driver): + if type_ in self._drivers: + new_driver = self._driver_name(driver) + old_driver = self._driver_name(self._drivers[type_]) + msg = _("can't load driver '%(new_driver)s' because " + "driver '%(old_driver)s' is already " + "registered for driver '%(type)s'") % { + 'new_driver': new_driver, + 'old_driver': old_driver, + 'type': type_} + LOG.error(msg) + raise SystemExit(msg) + self._drivers[type_] = driver + + def invoke(self, type_, method_name, **kwargs): + driver = self._drivers[type_] + return getattr(driver, method_name)(**kwargs) + + def __getitem__(self, type_): + return self._drivers[type_] + + def __contains__(self, type_): + return type_ in self._drivers diff --git a/neutron/common/exceptions.py b/neutron/common/exceptions.py index 80a75d1222a..f5161a2bae0 100644 --- a/neutron/common/exceptions.py +++ b/neutron/common/exceptions.py @@ -310,3 +310,8 @@ class DeviceIDNotOwnedByTenant(Conflict): class InvalidCIDR(BadRequest): message = _("Invalid CIDR %(input)s given as IP prefix") + + +class ExtensionNotSupportedByProvider(NeutronException): + message = _("Extension %(extension_name)s is not supported by " + " %(provider_name)s provider") diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index 86a23275517..d975b196b32 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -210,6 +210,12 @@ def _get_marker_obj(self, context, resource, limit, marker): return getattr(self, '_get_%s' % resource)(context, marker) return None + @classmethod + def register_dict_extend_funcs(cls, resource, funcs): + cur_funcs = cls._dict_extend_functions.get(resource, []) + cur_funcs.extend(funcs) + cls._dict_extend_functions[resource] = cur_funcs + class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2, CommonDbMixin): diff --git a/neutron/db/loadbalancer/lbaas_ssl_db.py b/neutron/db/loadbalancer/lbaas_ssl_db.py new file mode 100644 index 00000000000..bb01cab1662 --- /dev/null +++ b/neutron/db/loadbalancer/lbaas_ssl_db.py @@ -0,0 +1,584 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2014 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. +# +# @author: Evgeny Fedoruk, Radware, Inc + + +import sqlalchemy as sa +from sqlalchemy import orm +from sqlalchemy.orm import exc + +from neutron.db import db_base_plugin_v2 as base_db +from neutron.db.loadbalancer import loadbalancer_db as lbaas_db +from neutron.db import model_base +from neutron.db import models_v2 +from neutron.extensions import lbaas_ssl +from neutron.extensions import loadbalancer +from neutron.openstack.common import uuidutils +from neutron.plugins.common import constants +from neutron.openstack.common import log as logging + + +LOG = logging.getLogger(__name__) + + +class SSLPolicy(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant): + + """Represents a v2 neutron SSL Policy. + + SSL Policy may be associated to several vips. + Vip can be associated with one SSL Policy only. + """ + __tablename__ = 'sslpolicies' + + name = sa.Column(sa.String(255)) + description = sa.Column(sa.String(255)) + front_end_enabled = sa.Column(sa.Boolean(), nullable=False) + front_end_protocols = sa.Column(sa.String(64)) + front_end_cipher_suites = sa.Column(sa.String(512)) + back_end_enabled = sa.Column(sa.Boolean(), nullable=False) + back_end_protocols = sa.Column(sa.String(64)) + back_end_cipher_suites = sa.Column(sa.String(512)) + + vips = orm.relationship(lbaas_db.Vip, + cascade='all', lazy="joined", + primaryjoin="Vip.ssl_policy_id==SSLPolicy.id") + + +class SSLCertificate(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant): + + """Represents a v2 neutron SSL Certificate. + + SSL Certificate may be associated to 0..N vips + Vip can be associated with 0..M certificates. + """ + + name = sa.Column(sa.String(255)) + description = sa.Column(sa.String(255)) + certificate = sa.Column(sa.Text(4096), nullable=False) + passphrase = sa.Column(sa.String(128)) + certificate_chain = sa.Column(sa.Text(20480)) + + vips = orm.relationship( + "VipSSLCertificateAssociation", + cascade="all", lazy="joined" + ) + + +class SSLTrustedCertificate(model_base.BASEV2, + models_v2.HasId, models_v2.HasTenant): + + """Represents a v2 neutron SSL Trusted Certificate. + + SSL Trusted Certificate may be associated to 0..N vips + Vip can be associated with 0..M trusted certificates. + """ + + name = sa.Column(sa.String(255)) + description = sa.Column(sa.String(255)) + certificate = sa.Column(sa.String(4096)) + + vips = orm.relationship( + "VipSSLTrustedCertificateAssociation", + cascade="all", lazy="joined" + ) + + +class VipSSLCertificateAssociation(model_base.BASEV2, + models_v2.HasStatusDescription): + + """Many-to-many association between Vip and SSL certificate classes.""" + + vip_id = sa.Column(sa.String(36), + sa.ForeignKey("vips.id"), + primary_key=True) + ssl_certificate_id = sa.Column(sa.String(36), + sa.ForeignKey("sslcertificates.id"), + primary_key=True) + + +class VipSSLTrustedCertificateAssociation(model_base.BASEV2, + models_v2.HasStatusDescription): + + """Many-to-many association between Vip + and SSLTrustedCertificate classes. + """ + + vip_id = sa.Column(sa.String(36), + sa.ForeignKey("vips.id"), + primary_key=True) + ssl_trusted_certificate_id = sa.Column( + sa.String(36), + sa.ForeignKey("ssltrustedcertificates.id"), + primary_key=True + ) + +setattr(lbaas_db.Vip, 'ssl_policy_id', + sa.Column(sa.String(36), sa.ForeignKey('sslpolicies.id'), + default=None, nullable=True)) + +setattr(lbaas_db.Vip, 'ssl_certificate_ids', + orm.relationship( + VipSSLCertificateAssociation, + uselist=True, + lazy="joined", + primaryjoin="Vip.id==VipSSLCertificateAssociation.vip_id", + foreign_keys=[VipSSLCertificateAssociation.vip_id] + )) + +setattr(lbaas_db.Vip, 'ssl_trusted_certificate_ids', + orm.relationship( + VipSSLTrustedCertificateAssociation, + uselist=True, + lazy="joined", + primaryjoin="Vip.id==VipSSLTrustedCertificateAssociation.vip_id", + foreign_keys=[VipSSLTrustedCertificateAssociation.vip_id] + )) + + +class LBaasSSLDbMixin(lbaas_ssl.LbaasSSLPluginBase, base_db.CommonDbMixin): + def _extend_vip_dict_ssl(self, vip_res, vip_db): + vip_res['ssl_policy_id'] = vip_db['ssl_policy_id'] + + vip_res['ssl_certificate_ids'] = [ + cert['ssl_certificate_id'] for cert + in vip_db['ssl_certificate_ids']] + + vip_res['ssl_trusted_certificate_ids'] = [ + cert['ssl_trusted_certificate_id'] for cert + in vip_db['ssl_trusted_certificate_ids']] + + lbaas_db.LoadBalancerPluginDb.register_dict_extend_funcs( + loadbalancer.VIPS, ['_extend_vip_dict_ssl']) + + def update_vip_ssl_assocs_status(self, context, vip_id, + status, status_description=None): + with context.session.begin(subtransactions=True): + assocs = self._get_vip_ssl_assocs( + context, VipSSLCertificateAssociation, vip_id) + assocs.extend(self._get_vip_ssl_assocs( + context, VipSSLTrustedCertificateAssociation, vip_id)) + for assoc in assocs: + if assoc.status != status: + assoc.status = status + if status_description or assoc['status_description']: + assoc.status_description = status_description + + def _get_ssl_resource(self, context, model, id): + try: + r = self._get_by_id(context, model, id) + except exc.NoResultFound: + if issubclass(model, lbaas_db.Vip): + raise loadbalancer.VipNotFound(vip_id=id) + if issubclass(model, lbaas_db.Pool): + raise loadbalancer.PoolNotFound(pool_id=id) + if issubclass(model, SSLPolicy): + raise lbaas_ssl.SSLPolicyNotFound(policy_id=id) + elif issubclass(model, SSLCertificate): + raise lbaas_ssl.SSLCertificateNotFound(certificate_id=id) + elif issubclass(model, SSLTrustedCertificate): + raise lbaas_ssl.SSLTrustedCertificateNotFound( + certificate_id=id) + else: + raise + return r + + def _make_ssl_policy_dict(self, ssl_policy, fields=None): + res = {'id': ssl_policy['id'], + 'tenant_id': ssl_policy['tenant_id'], + 'name': ssl_policy['name'], + 'description': ssl_policy['description'], + 'front_end_enabled': ssl_policy['front_end_enabled'], + 'front_end_protocols': ssl_policy['front_end_protocols'], + 'front_end_cipher_suites': + ssl_policy['front_end_cipher_suites'], + 'back_end_enabled': ssl_policy['back_end_enabled'], + 'back_end_protocols': ssl_policy['back_end_protocols'], + 'back_end_cipher_suites': ssl_policy['back_end_cipher_suites']} + res['vips'] = [{'vip_id': v['id'], + 'status': v['status'], + 'status_description': v['status_description']} + for v in ssl_policy.vips] + return self._fields(res, fields) + + def create_ssl_policy(self, context, ssl_policy): + p = ssl_policy['ssl_policy'] + tenant_id = self._get_tenant_id_for_create(context, p) + with context.session.begin(subtransactions=True): + policy_db = SSLPolicy(id=uuidutils.generate_uuid(), + tenant_id=tenant_id, + name=p['name'], + front_end_enabled=p['front_end_enabled'], + front_end_protocols=p['front_end_protocols'], + front_end_cipher_suites= + p['front_end_cipher_suites'], + back_end_enabled=p['back_end_enabled'], + back_end_protocols=p['back_end_protocols'], + back_end_cipher_suites= + p['back_end_cipher_suites']) + context.session.add(policy_db) + return self._make_ssl_policy_dict(policy_db) + + def update_ssl_policy(self, context, id, ssl_policy): + p = ssl_policy['ssl_policy'] + with context.session.begin(subtransactions=True): + policy_db = self._get_ssl_resource(context, SSLPolicy, id) + self.assert_modification_allowed(policy_db) + if p: + policy_db.update(p) + return self._make_ssl_policy_dict(policy_db) + + def delete_ssl_policy(self, context, id): + with context.session.begin(subtransactions=True): + policy_db = self._get_ssl_resource(context, SSLPolicy, id) + + # Ensure that the policy is not used + try: + context.session.delete(policy_db) + context.session.flush() + except sa.IntegrityError: + raise lbaas_ssl.SSLPolicyInUse(policy_id=id) + + def get_ssl_policy(self, context, id, fields=None): + policy = self._get_ssl_resource(context, SSLPolicy, id) + return self._make_ssl_policy_dict(policy, fields) + + def get_ssl_policies(self, context, filters=None, fields=None): + return self._get_collection(context, SSLPolicy, + self._make_ssl_policy_dict, + filters=filters, fields=fields) + + def _make_ssl_certificate_dict(self, ssl_certificate, fields=None): + res = {'id': ssl_certificate['id'], + 'name': ssl_certificate['name'], + 'description': ssl_certificate['description'], + 'tenant_id': ssl_certificate['tenant_id'], + 'certificate': ssl_certificate['certificate'], + 'passphrase': ssl_certificate['passphrase'], + 'certificate_chain': ssl_certificate['certificate_chain']} + res['vips'] = [{'vip_id': v['vip_id'], + 'status': v['status'], + 'status_description': v['status_description']} + for v in ssl_certificate.vips] + + return self._fields(res, fields) + + def create_ssl_certificate(self, context, ssl_certificate): + cert = ssl_certificate['ssl_certificate'] + tenant_id = self._get_tenant_id_for_create(context, cert) + with context.session.begin(subtransactions=True): + certificate_db = SSLCertificate( + id=uuidutils.generate_uuid(), + tenant_id=tenant_id, + name=cert['name'], + certificate=cert['certificate'], + passphrase=cert['passphrase'], + certificate_chain=cert['certificate_chain'] + ) + context.session.add(certificate_db) + return self._make_ssl_certificate_dict(certificate_db) + + def update_ssl_certificate(self, context, id, ssl_certificate): + c = ssl_certificate['ssl_certificate'] + with context.session.begin(subtransactions=True): + certificate_db = self._get_ssl_resource(context, + SSLCertificate, id) + if c: + certificate_db.update(c) + return self._make_ssl_certificate_dict(certificate_db) + + def delete_ssl_certificate(self, context, id): + with context.session.begin(subtransactions=True): + certificate = self._get_ssl_resource(context, SSLCertificate, id) + + # Ensure that the certificate is not used + try: + context.session.delete(certificate) + context.session.flush() + except sa.IntegrityError: + raise lbaas_ssl.SSLCertificateInUse(certificate_id=id) + + def get_ssl_certificate(self, context, id, fields=None): + cert = self._get_ssl_resource(context, SSLCertificate, id) + return self._make_ssl_certificate_dict(cert, fields) + + def get_ssl_certificates(self, context, filters=None, fields=None): + return self._get_collection(context, SSLCertificate, + self._make_ssl_certificate_dict, + filters=filters, fields=fields) + + def get_vip_ssl_certificates(self, context, vip_id): + assocs = self._get_vip_ssl_assocs(context, + VipSSLCertificateAssociation, + vip_id) + return self.get_ssl_certificates( + context, + filters={'id': [assoc['ssl_certificate_id'] for assoc in assocs]}) + + def _make_ssl_trusted_certificate_dict(self, ssl_trusted_certificate, + fields=None): + res = {'id': ssl_trusted_certificate['id'], + 'tenant_id': ssl_trusted_certificate['tenant_id'], + 'name': ssl_trusted_certificate['name'], + 'description': ssl_trusted_certificate['description'], + 'certificate': ssl_trusted_certificate['certificate']} + res['vips'] = [{'vip_id': v['vip_id'], + 'status': v['status'], + 'status_description': v['status_description']} + for v in ssl_trusted_certificate.vips] + return self._fields(res, fields) + + def create_ssl_trusted_certificate(self, context, + ssl_trusted_certificate): + c = ssl_trusted_certificate['ssl_trusted_certificate'] + tenant_id = self._get_tenant_id_for_create(context, c) + with context.session.begin(subtransactions=True): + cert_db = SSLTrustedCertificate(id=uuidutils.generate_uuid(), + tenant_id=tenant_id, + name=c['name'], + certificate=c['certificate']) + context.session.add(cert_db) + return self._make_ssl_trusted_certificate_dict(cert_db) + + def update_ssl_trusted_certificate(self, context, id, + ssl_trusted_certificate): + c = ssl_trusted_certificate['ssl_trusted_certificate'] + with context.session.begin(subtransactions=True): + trusted_cert_db = self._get_ssl_resource(context, + SSLTrustedCertificate, id) + self.assert_modification_allowed(trusted_cert_db) + if c: + trusted_cert_db.update(c) + return self._make_ssl_trusted_certificate_dict( + trusted_cert_db) + + def delete_ssl_trusted_certificate(self, context, id): + with context.session.begin(subtransactions=True): + cert_db = self._get_ssl_resource(context, + SSLTrustedCertificate, id) + + # Ensure that the trusted certificate is not used + try: + context.session.delete(cert_db) + context.session.flush() + except sa.IntegrityError: + raise lbaas_ssl.SSLTrustedCertificateInUse(certificate_id=id) + + def get_ssl_trusted_certificate(self, context, id, fields=None): + cert = self._get_ssl_resource(context, SSLTrustedCertificate, id) + return self._make_ssl_trusted_certificate_dict(cert, fields) + + def get_ssl_trusted_certificates(self, context, + filters=None, fields=None): + return self._get_collection(context, SSLTrustedCertificate, + self._make_ssl_trusted_certificate_dict, + filters=filters, fields=fields) + + def get_vip_ssl_trusted_certificates(self, context, vip_id): + assocs = self._get_vip_ssl_assocs(context, + VipSSLTrustedCertificateAssociation, + vip_id) + return self.get_ssl_trusted_certificates( + context, + filters={'id': [assoc['ssl_trusted_certificate_id'] + for assoc in assocs]}) + + #VIP-SSL Association DB access + def create_vip_ssl_association(self, context, + ssl_association, vip_id): + if ssl_association['ssl_association']['ssl_policy']: + policy_id = ssl_association['ssl_association'][ + 'ssl_policy']['id'] + else: + policy_id = None + + cert_ids = [cert['id'] + for cert in ssl_association[ + 'ssl_association']['ssl_certificates']] + trusted_cert_ids = [cert['id'] for cert + in ssl_association['ssl_association'] + ['ssl_trusted_certificates']] + + policy = self._update_vip_ssl_policy(context, + policy_id, vip_id) + certificates = self._update_vip_ssl_certificates(context, + cert_ids, vip_id) + trusted_certificates = self._update_vip_ssl_trusted_certificates( + context, trusted_cert_ids, vip_id + ) + + res = {'ssl_policy': policy, + 'ssl_certificates': certificates, + 'ssl_trusted_certificates': trusted_certificates} + return res + + def delete_vip_ssl_association(self, context, id, vip_id): + policy = self._update_vip_ssl_policy(context, + None, vip_id) + certificates = self._update_vip_ssl_certificates(context, + [], vip_id) + trusted_certificates = self._update_vip_ssl_trusted_certificates( + context, [], vip_id + ) + + res = {'ssl_policy': policy, + 'ssl_certificates': certificates, + 'ssl_trusted_certificates': trusted_certificates} + return res + + def get_vip_ssl_associations(self, context, id, vip_id, fields=None): + pass + + def get_vip_ssl_association(self, context,id, vip_id, fields=None): + vip = self._get_ssl_resource(context, lbaas_db.Vip, vip_id) + + cert_assocs = self._get_vip_ssl_assocs( + context, + VipSSLCertificateAssociation, + vip_id) + trust_cert_assocs = self._get_vip_ssl_assocs( + context, + VipSSLTrustedCertificateAssociation, + vip_id) + + if vip.ssl_policy_id is not None: + policy = {'id': vip.ssl_policy_id} + else: + policy = {'id': None} + + certs = [ + {'id': assoc['ssl_certificate_id']} for assoc in cert_assocs] + trust_certs = [ + {'id': assoc['ssl_trusted_certificate_id']} for assoc + in trust_cert_assocs] + + tenant_id = vip['tenant_id'] + + x = {'ssl_association': { + 'ssl_policy': policy, + 'ssl_certificates': certs, + 'ssl_trusted_certificates': trust_certs}, + 'tenant_id': tenant_id + } + LOG.debug(x) + return x + + def _get_vip_ssl_assocs(self, context, assoc_type, vip_id): + assoc_qry = context.session.query(assoc_type) + return assoc_qry.filter_by(vip_id=vip_id).all() + + def _get_vip_ssl_assocs_for_deletion(self, context, assoc_type, vip_id): + assoc_qry = context.session.query(assoc_type) + return assoc_qry.filter_by(vip_id=vip_id, + status=constants.PENDING_DELETE).all() + + def _make_vip_ssl_policy_assoc_dict(self, assoc, fields=None): + res = {'vip_id': assoc['vip_id'], + 'ssl_policy_id': assoc['ssl_policy_id']} + return self._fields(res, fields) + + def _update_vip_ssl_policy(self, context, policy_id, vip_id): + with context.session.begin(subtransactions=True): + res = {'id': policy_id} + vip = self._get_ssl_resource(context, lbaas_db.Vip, vip_id) + + if vip.ssl_policy_id is not None: + if vip.ssl_policy_id == policy_id: + return res + else: + self.assert_modification_allowed(vip) + vip.ssl_policy_id = None + + if policy_id is not None: + self.assert_modification_allowed(vip) + vip.ssl_policy_id = policy_id + + return res + + def _update_vip_ssl_certificates(self, context, new_cert_ids, vip_id): + with context.session.begin(subtransactions=True): + vip = self._get_ssl_resource(context, lbaas_db.Vip, vip_id) + assocs = self._get_vip_ssl_assocs(context, + VipSSLCertificateAssociation, + vip_id) + + new_certs = [] + for assoc in assocs: + self.assert_modification_allowed(assoc) + if assoc.ssl_certificate_id in new_cert_ids: + assoc.status = constants.PENDING_CREATE + new_cert_ids.remove(assoc.ssl_certificate_id) + new_certs.append({'id': assoc.ssl_certificate_id}) + else: + assoc.status = constants.PENDING_DELETE + + for cert_id in new_cert_ids: + assoc = VipSSLCertificateAssociation( + vip_id=vip_id, + ssl_certificate_id=cert_id, + status=constants.PENDING_CREATE + ) + vip.ssl_certificate_ids.append(assoc) + new_certs.append({'id': cert_id}) + + return new_certs + + def _update_vip_ssl_trusted_certificates(self, context, cert_ids, vip_id): + with context.session.begin(subtransactions=True): + vip = self._get_ssl_resource(context, lbaas_db.Vip, vip_id) + assocs = self._get_vip_ssl_assocs( + context, + VipSSLTrustedCertificateAssociation, + vip_id + ) + + certificates = [] + for assoc in assocs: + self.assert_modification_allowed(assoc) + if assoc.ssl_trusted_certificate_id in cert_ids: + assoc.status = constants.PENDING_CREATE + cert_ids.remove(assoc.ssl_trusted_certificate_id) + certificates.append( + {'id': assoc.ssl_trusted_certificate_id}) + else: + assoc.status = constants.PENDING_DELETE + + for cert_id in cert_ids: + assoc = VipSSLTrustedCertificateAssociation( + vip_id=vip_id, + ssl_trusted_certificate_id=cert_id, + status=constants.PENDING_CREATE + ) + vip.ssl_trusted_certificate_ids.append(assoc) + certificates.append({'id': cert_id}) + + res = certificates + return res + + def _remove_pending_delete_vip_ssl(self, context, vip_id): + with context.session.begin(subtransactions=True): + assocs = self._get_vip_ssl_assocs_for_deletion( + context, VipSSLCertificateAssociation, vip_id + ) + for assoc in assocs: + context.session.delete(assoc) + + assocs = self._get_vip_ssl_assocs_for_deletion( + context, VipSSLTrustedCertificateAssociation, vip_id + ) + for assoc in assocs: + context.session.delete(assoc) diff --git a/neutron/db/loadbalancer/loadbalancer_db.py b/neutron/db/loadbalancer/loadbalancer_db.py index 02d2a7b15be..81cab96becc 100644 --- a/neutron/db/loadbalancer/loadbalancer_db.py +++ b/neutron/db/loadbalancer/loadbalancer_db.py @@ -220,7 +220,7 @@ def assert_modification_allowed(self, obj): ######################################################## # VIP DB access - def _make_vip_dict(self, vip, fields=None): + def _make_vip_dict(self, vip, fields=None, process_extensions=True): fixed_ip = (vip.port.fixed_ips or [{}])[0] res = {'id': vip['id'], @@ -238,6 +238,10 @@ def _make_vip_dict(self, vip, fields=None): 'status': vip['status'], 'status_description': vip['status_description']} + if process_extensions: + self._apply_dict_extend_functions( + loadbalancer.VIPS, res, vip) + if vip['session_persistence']: s_p = { 'type': vip['session_persistence']['type'] @@ -338,9 +342,15 @@ def create_vip(self, context, vip): raise q_exc.NotAuthorized() # validate that the pool has same protocol if pool['protocol'] != v['protocol']: - raise loadbalancer.ProtocolMismatch( - vip_proto=v['protocol'], - pool_proto=pool['protocol']) + #mmadhavs + #SSL extension change:Allow VIP to + #be HTTP or HTTPS if pool is on HTTP + if (pool['protocol'] != 'HTTP' or + v['protocol'] !='HTTPS'): + #mmadhavs + raise loadbalancer.ProtocolMismatch( + vip_proto=v['protocol'], + pool_proto=pool['protocol']) else: pool = None @@ -415,9 +425,15 @@ def update_vip(self, context, id, vip): raise q_exc.NotAuthorized() # validate that the pool has same protocol if new_pool['protocol'] != vip_db['protocol']: - raise loadbalancer.ProtocolMismatch( - vip_proto=vip_db['protocol'], - pool_proto=new_pool['protocol']) + #mmadhavs + #SSL extension change:Allow VIP to + #be HTTP or HTTPS if pool is on HTTP + if (new_pool['protocol'] != 'HTTP' or + vip_db['protocol'] !='HTTPS'): + #mmadhavs + raise loadbalancer.ProtocolMismatch( + vip_proto=vip_db['protocol'], + pool_proto=new_pool['protocol']) if old_pool_id: old_pool = self._get_resource( @@ -499,6 +515,16 @@ def update_pool_stats(self, context, pool_id, data=None): if stats_status: self.update_status(context, Member, member, stats_status) + def _ensure_pool_delete_conditions(self, context, pool_id): + if context.session.query(Vip).filter_by(pool_id=pool_id).first(): + raise loadbalancer.PoolInUse(pool_id=pool_id) + + def _ensure_vip_delete_conditions(self, context, vip_id): + vip = self._get_resource(context, Vip, vip_id) + if vip['ssl_policy_id'] != None: + raise lbaas_ssl.SSLPolicyInUse( + policy_id=vip['ssl_policy_id']) + def _create_pool_stats(self, context, pool_id, data=None): # This is internal method to add pool statistics. It won't # be exposed to API diff --git a/neutron/db/migration/alembic_migrations/versions/1c6b0d82afcd_servicevm_framework.py b/neutron/db/migration/alembic_migrations/versions/1c6b0d82afcd_servicevm_framework.py new file mode 100755 index 00000000000..04ef69d8e0e --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/1c6b0d82afcd_servicevm_framework.py @@ -0,0 +1,159 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 Intel Corporation. +# Copyright 2013 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel Corporation. + +"""add tables for servicevm framework + +Revision ID: 1c6b0d82afcd +Revises: 538732fa21e1 +Create Date: 2013-11-25 18:06:13.980301 + +""" + +# revision identifiers, used by Alembic. +revision = '1c6b0d82afcd' +down_revision = '538732fa21e1' + +# Change to ['*'] if this migration applies to all plugins + +migration_for_plugins = [ + 'neutron.vm.plugin.ServiceVMPlugin' +] + +from alembic import op +import sqlalchemy as sa + +from neutron.db import migration + + +def upgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.create_table( + 'devicetemplates', + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('tenant_id', sa.String(length=255), nullable=True), + sa.Column('name', sa.String(length=255), nullable=True), + sa.Column('description', sa.String(length=255), nullable=True), + sa.Column('device_driver', sa.String(length=255), nullable=True), + sa.Column('mgmt_driver', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id'), + ) + op.create_table( + 'servicetypes', + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('tenant_id', sa.String(length=255), nullable=True), + sa.Column('template_id', sa.String(length=36), nullable=False), + sa.Column('service_type', sa.String(length=255), nullable=False), + sa.PrimaryKeyConstraint('id'), + ) + op.create_table( + 'devicetemplateattributes', + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('template_id', sa.String(length=36), nullable=False), + sa.Column('key', sa.String(length=255), nullable=False), + sa.Column('value', sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint(['template_id'], ['devicetemplates.id'], ), + sa.PrimaryKeyConstraint('id'), + ) + op.create_table( + 'devices', + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('tenant_id', sa.String(length=255), nullable=True), + sa.Column('template_id', sa.String(length=36), nullable=True), + sa.Column('instance_id', sa.String(length=255), nullable=True), + sa.Column('mgmt_address', sa.String(length=255), nullable=True), + sa.Column('status', sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint(['template_id'], ['devicetemplates.id'], ), + sa.PrimaryKeyConstraint('id'), + ) + op.create_table( + 'deviceargs', + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('device_id', sa.String(length=36)), + sa.Column('key', sa.String(length=255), nullable=False), + sa.Column('value', sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint(['device_id'], ['devices.id'], ), + sa.PrimaryKeyConstraint('id'), + ) + op.create_table( + 'deviceservicecontexts', + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('device_id', sa.String(length=36)), + sa.Column('network_id', sa.String(length=36), nullable=True), + sa.Column('subnet_id', sa.String(length=36), nullable=True), + sa.Column('port_id', sa.String(length=36), nullable=True), + sa.Column('router_id', sa.String(length=36), nullable=True), + sa.Column('role', sa.String(length=256), nullable=True), + sa.Column('index', sa.Integer(), nullable=True), + sa.PrimaryKeyConstraint('id'), + ) + op.create_table( + 'serviceinstances', + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('tenant_id', sa.String(length=255), nullable=True), + sa.Column('name', sa.String(length=255), nullable=True), + sa.Column('service_type_id', sa.String(length=36), nullable=True), + sa.Column('service_table_id', sa.String(length=36), nullable=True), + sa.Column('managed_by_user', sa.Boolean(), nullable=False), + sa.Column('mgmt_driver', sa.String(length=255), nullable=True), + sa.Column('mgmt_address', sa.String(length=255), nullable=True), + sa.Column('status', sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint(['service_type_id'], ['servicetypes.id'], ), + sa.PrimaryKeyConstraint('id'), + ) + op.create_table( + 'servicecontexts', + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('service_instance_id', sa.String(length=36)), + sa.Column('network_id', sa.String(length=36), nullable=True), + sa.Column('subnet_id', sa.String(length=36), nullable=True), + sa.Column('port_id', sa.String(length=36), nullable=True), + sa.Column('router_id', sa.String(length=36), nullable=True), + sa.Column('role', sa.String(length=256), nullable=True), + sa.Column('index', sa.Integer(), nullable=True), + sa.PrimaryKeyConstraint('id'), + ) + op.create_table( + 'servicedevicebindings', + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('service_instance_id', sa.String(length=36), nullable=True), + sa.Column('device_id', sa.String(length=36), nullable=True), + sa.ForeignKeyConstraint(['device_id'], ['devices.id'], ), + sa.ForeignKeyConstraint(['service_instance_id'], + ['serviceinstances.id'], ), + sa.PrimaryKeyConstraint('id'), + ) + + +def downgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.drop_table('devicetemplates') + op.drop_table('servicetypes') + op.drop_table('devicetemplateattributes') + op.drop_table('devices') + op.drop_table('deviceargs') + op.drop_table('deviceservicecontexts') + op.drop_table('serviceinstances') + op.drop_table('servicecontexts') + op.drop_table('servicedevicebindings') diff --git a/neutron/db/migration/alembic_migrations/versions/6815e9450v77_ssl_extension.py b/neutron/db/migration/alembic_migrations/versions/6815e9450v77_ssl_extension.py new file mode 100644 index 00000000000..ccaadf94735 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/6815e9450v77_ssl_extension.py @@ -0,0 +1,125 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2014 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. +# + +"""Adds SSL extension tables and modifies vip model with ssl_policy_id + +Revision ID: 6815e9450v77 +Revises: 1b837a7125a9 +Create Date: 2014-04-30 08:00:39.585946 + +""" + +# revision identifiers, used by Alembic. +revision = '6815e9450v77' +down_revision = 'havana' + +# Change to ['*'] if this migration applies to all plugins + +migration_for_plugins = [ + 'neutron.services.loadbalancer.plugin.LoadBalancerPlugin' +] + +from alembic import op +import sqlalchemy as sa + + +from neutron.db import migration + + +def upgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.create_table( + u'sslpolicies', + sa.Column(u'tenant_id', sa.String(255), nullable=True), + sa.Column(u'id', sa.String(36), nullable=False), + sa.Column(u'name', sa.String(255), nullable=True), + sa.Column(u'description', sa.String(255), nullable=True), + sa.Column(u'front_end_enabled', sa.Boolean(), nullable=False), + sa.Column(u'front_end_protocols', sa.String(64)), + sa.Column(u'front_end_cipher_suites', sa.String(512)), + sa.Column(u'back_end_enabled', sa.Boolean(), nullable=False), + sa.Column(u'back_end_protocols', sa.String(64)), + sa.Column(u'back_end_cipher_suites', sa.String(512)), + sa.PrimaryKeyConstraint(u'id'), + ) + op.create_table( + u'sslcertificates', + sa.Column(u'tenant_id', sa.String(255), nullable=True), + sa.Column(u'id', sa.String(36), nullable=False), + sa.Column(u'name', sa.String(255), nullable=True), + sa.Column(u'description', sa.String(255), nullable=True), + sa.Column(u'certificate', sa.Text, nullable=False), + sa.Column(u'passphrase', sa.String(128)), + sa.Column(u'certificate_chain', sa.Text), + sa.PrimaryKeyConstraint(u'id'), + ) + op.create_table( + u'ssltrustedcertificates', + sa.Column(u'tenant_id', sa.String(255), nullable=True), + sa.Column(u'id', sa.String(36), nullable=False), + sa.Column(u'name', sa.String(255), nullable=True), + sa.Column(u'description', sa.String(255), nullable=True), + sa.Column(u'certificate', sa.Text, nullable=False), + sa.PrimaryKeyConstraint(u'id'), + ) + op.create_table( + u'vipsslcertificateassociations', + sa.Column(u'vip_id', sa.String(36), nullable=False), + sa.Column(u'ssl_certificate_id', sa.String(36), nullable=False), + sa.Column(u'status', sa.String(16), nullable=False), + sa.Column(u'status_description', sa.String(255)), + sa.ForeignKeyConstraint(['vip_id'], [u'vips.id'], ), + sa.ForeignKeyConstraint(['ssl_certificate_id'], + [u'sslcertificates.id'], ), + sa.PrimaryKeyConstraint(u'vip_id', u'ssl_certificate_id') + ) + op.create_table( + u'vipssltrustedcertificateassociations', + sa.Column(u'vip_id', sa.String(36), nullable=False), + sa.Column(u'ssl_trusted_certificate_id', sa.String(36), + nullable=False), + sa.Column(u'status', sa.String(16), nullable=False), + sa.Column(u'status_description', sa.String(255)), + sa.ForeignKeyConstraint(['vip_id'], [u'vips.id'], ), + sa.ForeignKeyConstraint(['ssl_trusted_certificate_id'], + [u'ssltrustedcertificates.id'], ), + sa.PrimaryKeyConstraint(u'vip_id', u'ssl_trusted_certificate_id') + ) + + op.add_column('vips', + sa.Column(u'ssl_policy_id', sa.String(36), + sa.ForeignKey('sslpolicies.id', + name='vip_ssl_policy_id_fk'), + nullable=True)) + + # Create default SSL policy + # TODO(Evgeny Fedoruk) insert default SSL policy + + +def downgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.drop_table('vipsslcertificateassociations') + op.drop_table('vipssltrustedcertificateassociations') + op.drop_table('sslcertificates') + op.drop_table('ssltrustedcertificates') + op.drop_constraint('vip_ssl_policy_id_fk', 'vips', type_='foreignkey') + op.drop_column('vips', 'ssl_policy_id') + op.drop_table('sslpolicies') diff --git a/neutron/db/migration/alembic_migrations/versions/81ffa86020d_rpc_proxy.py b/neutron/db/migration/alembic_migrations/versions/81ffa86020d_rpc_proxy.py new file mode 100755 index 00000000000..b5607f959c2 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/81ffa86020d_rpc_proxy.py @@ -0,0 +1,71 @@ +# Copyright 2014 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. +# + +"""rpc_proxy + +Revision ID: 81ffa86020d +Revises: 1c6b0d82afcd +Create Date: 2014-03-19 15:50:11.712686 + +""" + +# revision identifiers, used by Alembic. +revision = '81ffa86020d' +down_revision = '1c6b0d82afcd' + +# Change to ['*'] if this migration applies to all plugins + +migration_for_plugins = [ + 'neutron.vm.plugin.ServiceVMPlugin' +] + +from alembic import op +import sqlalchemy as sa + +from neutron.db import migration + + +def upgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.create_table( + 'proxymgmtports', + sa.Column('device_id', sa.String(255)), + sa.Column('port_id', sa.String(36), nullable=False), + sa.Column('dst_transport_url', sa.String(255)), + sa.Column('svr_proxy_id', sa.String(36)), + sa.Column('svr_ns_proxy_id', sa.String(36)), + sa.Column('clt_proxy_id', sa.String(36)), + sa.Column('clt_ns_proxy_id', sa.String(36)), + sa.PrimaryKeyConstraint('device_id'), + ) + op.create_table( + 'proxyserviceports', + sa.Column('service_instance_id', sa.String(255)), + sa.Column('svr_proxy_id', sa.String(36)), + sa.Column('svr_ns_proxy_id', sa.String(36)), + sa.Column('clt_proxy_id', sa.String(36)), + sa.Column('clt_ns_proxy_id', sa.String(36)), + sa.PrimaryKeyConstraint('service_instance_id'), + ) + + +def downgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.drop_table('proxymgmtport') + op.drop_table('proxyserviceport') diff --git a/neutron/db/vm/__init__.py b/neutron/db/vm/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/db/vm/proxy_db.py b/neutron/db/vm/proxy_db.py new file mode 100644 index 00000000000..0a66df771af --- /dev/null +++ b/neutron/db/vm/proxy_db.py @@ -0,0 +1,101 @@ +# Copyright 2014 Intel Corporation. +# Copyright 2014 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel Corporation. + +import sqlalchemy as sa + +from neutron.db import db_base_plugin_v2 as base_db +from neutron.db import model_base + + +class ProxyMgmtPort(model_base.BASEV2): + device_id = sa.Column(sa.String(255), primary_key=True) + port_id = sa.Column(sa.String(36), nullable=False) + dst_transport_url = sa.Column(sa.String(255)) + svr_proxy_id = sa.Column(sa.String(36)) + svr_ns_proxy_id = sa.Column(sa.String(36)) + clt_proxy_id = sa.Column(sa.String(36)) + clt_ns_proxy_id = sa.Column(sa.String(36)) + + +class ProxyServicePort(model_base.BASEV2): + service_instance_id = sa.Column(sa.String(255), primary_key=True) + svr_proxy_id = sa.Column(sa.String(36)) + svr_ns_proxy_id = sa.Column(sa.String(36)) + clt_proxy_id = sa.Column(sa.String(36)) + clt_ns_proxy_id = sa.Column(sa.String(36)) + + +class RpcProxyDb(base_db.CommonDbMixin): + def _make_proxy_mgmt_port(self, proxy_mgmt_port): + key_list = ('device_id', 'port_id', 'dst_transport_url', + 'svr_proxy_id', 'svr_ns_proxy_id', + 'clt_proxy_id', 'clt_ns_proxy_id') + return dict((key, getattr(proxy_mgmt_port, key)) for key in key_list) + + def _make_proxy_service_port(self, proxy_service_port): + key_list = ('service_instance_id', 'svr_proxy_id', 'svr_ns_proxy_id', + 'clt_proxy_id', 'clt_ns_proxy_id') + return dict((key, getattr(proxy_service_port, key)) + for key in key_list) + + def create_proxy_mgmt_port(self, context, device_id, port_id, + dst_transport_url, + svr_proxy_id, svr_ns_proxy_id, + clt_proxy_id, clt_ns_proxy_id): + with context.session.begin(subtransactions=True): + proxy_mgmt_port = ProxyMgmtPort( + device_id=device_id, port_id=port_id, + dst_transport_url=dst_transport_url, + svr_proxy_id=svr_proxy_id, svr_ns_proxy_id=svr_ns_proxy_id, + clt_proxy_id=clt_proxy_id, clt_ns_proxy_id=clt_ns_proxy_id) + context.session.add(proxy_mgmt_port) + + def delete_proxy_mgmt_port(self, context, port_id): + with context.session.begin(subtransactions=True): + context.session.query(ProxyMgmtPort).filter_by( + port_id=port_id).delete() + + def get_proxy_mgmt_port(self, context, device_id): + with context.session.begin(subtransactions=True): + proxy_mgmt_port = context.session.query(ProxyMgmtPort).filter_by( + device_id=device_id).one() + return self._make_proxy_mgmt_port(proxy_mgmt_port) + + def create_proxy_service_port(self, context, service_instance_id, + svr_proxy_id, svr_ns_proxy_id, + clt_proxy_id, clt_ns_proxy_id): + with context.session.begin(subtransactions=True): + proxy_service_port = ProxyServicePort( + service_instance_id=service_instance_id, + svr_proxy_id=svr_proxy_id, svr_ns_proxy_id=svr_ns_proxy_id, + clt_proxy_id=clt_proxy_id, clt_ns_proxy_id=clt_ns_proxy_id) + context.session.add(proxy_service_port) + + def delete_proxy_service_port(self, context, service_instance_id): + with context.session.begin(subtransactions=True): + context.session.query(ProxyServicePort).filter_by( + service_instance_id=service_instance_id).delete() + + def get_proxy_service_port(self, context, service_instance_id): + with context.session.begin(subtransactions=True): + proxy_service_port = context.session.query( + ProxyServicePort).filter_by( + service_instance_id=service_instance_id).one() + return self._make_proxy_service_port(proxy_service_port) diff --git a/neutron/db/vm/vm_db.py b/neutron/db/vm/vm_db.py new file mode 100644 index 00000000000..8aa51f4cbe3 --- /dev/null +++ b/neutron/db/vm/vm_db.py @@ -0,0 +1,868 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013, 2014 Intel Corporation. +# Copyright 2013, 2014 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel Corporation. + +import uuid + +import sqlalchemy as sa +from sqlalchemy import orm +from sqlalchemy.orm import exc as orm_exc + +from neutron.api.v2 import attributes +from neutron.db import api as qdbapi +from neutron.db import db_base_plugin_v2 as base_db +from neutron.db import model_base +from neutron.db import models_v2 +from neutron.extensions import servicevm +from neutron import manager +from neutron.openstack.common import jsonutils +from neutron.openstack.common import log as logging +from neutron.plugins.common import constants +LOG = logging.getLogger(__name__) +_ACTIVE_UPDATE = (constants.ACTIVE, constants.PENDING_UPDATE) + + +########################################################################### +# db tables + +# This table corresponds to SeviceVM of the origial spec of Blueprint +# https://docs.google.com/document/d/ +# 1pwFVV8UavvQkBz92bT-BweBAiIZoMJP0NPAO4-60XFY/edit?pli=1 +class DeviceTemplate(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant): + """Represents template to create hosting device + """ + # Descriptive name + name = sa.Column(sa.String(255)) + description = sa.Column(sa.String(255)) + + # service type that this service vm provides. + # At first phase, this includes only single service + # In future, single service VM may accomodate multiple services. + service_types = orm.relationship('ServiceType', backref='template') + + # driver to create hosting device. e.g. noop, nova, heat, etc... + device_driver = sa.Column(sa.String(255)) + + # mgmt driver to communicate with hosting device. + # e.g. noop, OpenStack MGMT, OpenStack notification, netconf, snmp, + # ssh, etc... + mgmt_driver = sa.Column(sa.String(255)) + + # (key, value) pair to spin up + attributes = orm.relationship('DeviceTemplateAttribute', + backref='template') + + # TODO(yamahata): re-think the necessity of following columns + # They are all commented out for minimalism for now. + # They will be added when it is found really necessary. + # + # the name of the interface inside the VM + # For agent in hosting device + # (or something responsible for it in hosting device) to recieve + # requests from management tools + # vm_mgmt_if = sa.Column(sa.String(255), default='eth0') + # + # security_group = sa.Column(sa.String(36)) + # multi_tenant = sa.Column(sa.Boolean(), default=False) + # + # max_lsi = sa.Column(sa.Integer, default=0) + # dp_if_types = sa.Column(sa.String(255)) + # + # classification = sa.Column(sa.String(255), nullable=True) + # availability_zone = sa.Column(sa.String(36), nullable=True) + # host_aggregate = sa.Column(sa.String(36), nullable=True) + # allow_mgmt_access = sa.Column(sa.Boolean(), default=False) + # access_cred_uname = sa.Column(sa.String(255), nullable=True) + # access_cred_passwd = sa.Column(sa.String(255), nullable=True) + + +# this table corresponds to Service_Type in ServiceVM of the original spec +# TODO(yamahata): reach consensus for naming. +# 'service' might be too generic terminology. +class ServiceType(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant): + """Represents service type which hosting device provides. + Since a device may provide many services, This is one-to-many + relationship. + """ + template_id = sa.Column(sa.String(36), sa.ForeignKey('devicetemplates.id'), + nullable=False) + service_type = sa.Column(sa.String(255), nullable=False) + + +# this table corresponds to image, flavor in ServiceVM of the original spec +# Or just string long enough? +class DeviceTemplateAttribute(model_base.BASEV2, models_v2.HasId): + """Represents attributes necessary for spinning up VM in (key, value) pair + key value pair is adopted for being agnostic to actuall manager of VMs + like nova, heat or others. e.g. image-id, flavor-id for Nova. + The interpretation is up to actual driver of hosting device. + """ + template_id = sa.Column(sa.String(36), sa.ForeignKey('devicetemplates.id'), + nullable=False) + key = sa.Column(sa.String(255), nullable=False) + value = sa.Column(sa.String(255), nullable=True) + + +class Device(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant): + """Represents devices that hosts services. + Here the term, 'VM', is intentionally avoided because it can be + VM or other container. + """ + template_id = sa.Column(sa.String(36), sa.ForeignKey('devicetemplates.id')) + template = orm.relationship('DeviceTemplate') + + # sufficient information to uniquely identify hosting device. + # In case of service VM, it's UUID of nova VM. + instance_id = sa.Column(sa.String(255), nullable=True) + + # For a management tool to talk to manage this hosting device. + # opaque string. mgmt_driver interprets it. + # e.g. (driver, mgmt_address) = (ssh, ip address), ... + mgmt_address = sa.Column(sa.String(255), nullable=True) + + service_context = orm.relationship('DeviceServiceContext') + services = orm.relationship('ServiceDeviceBinding', backref='device') + + status = sa.Column(sa.String(255), nullable=False) + + +class DeviceArg(model_base.BASEV2, models_v2.HasId): + """Represents kwargs necessary for spinning up VM in (key, value) pair + key value pair is adopted for being agnostic to actuall manager of VMs + like nova, heat or others. e.g. image-id, flavor-id for Nova. + The interpretation is up to actual driver of hosting device. + """ + device_id = sa.Column(sa.String(36), sa.ForeignKey('devices.id'), + nullable=False) + device = orm.relationship('Device', backref='kwargs') + key = sa.Column(sa.String(255), nullable=False) + # json encoded value. example + # "nic": [{"net-id": }, {"port-id": }] + value = sa.Column(sa.String(4096), nullable=True) + + +# TODO(yamahata): This is tentative. +# In the future, this will be replaced with db models of +# service insertion/chain. +# Since such models are under discussion/development as of +# this time, this models is just for lbaas driver of hosting +# device +# This corresponds to the instantiation of DP_IF_Types +class DeviceServiceContext(model_base.BASEV2, models_v2.HasId): + """Represents service context of Device for scheduler. + This represents service insertion/chainging of a given device. + """ + device_id = sa.Column(sa.String(36), sa.ForeignKey('devices.id')) + network_id = sa.Column(sa.String(36), nullable=True) + subnet_id = sa.Column(sa.String(36), nullable=True) + port_id = sa.Column(sa.String(36), nullable=True) + router_id = sa.Column(sa.String(36), nullable=True) + + role = sa.Column(sa.String(255), nullable=True) + # disambiguation between same roles + index = sa.Column(sa.Integer, nullable=True) + + +# this table corresponds to ServiceInstance of the original spec +class ServiceInstance(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant): + """Represents logical service instance + This table is only to tell what logical service instances exists. + There will be service specific tables for each service types which holds + actuall parameters necessary for specific service type. + For example, tables for "Routers", "LBaaS", "FW", tables. which table + is implicitly determined by service_type_id. + """ + name = sa.Column(sa.String(255), nullable=True) + service_type_id = sa.Column(sa.String(36), + sa.ForeignKey('servicetypes.id')) + service_type = orm.relationship('ServiceType') + # points to row in service specific table if any. + service_table_id = sa.Column(sa.String(36), nullable=True) + + # True: This service is managed by user so that user is able to + # change its configurations + # False: This service is manged by other neutron service like lbaas + # so that user can't change the configuration directly via + # servicevm API, but via API for the service. + managed_by_user = sa.Column(sa.Boolean(), default=False) + + # mgmt driver to communicate with logical service instance in + # hosting device. + # e.g. noop, OpenStack MGMT, OpenStack notification, netconf, snmp, + # ssh, etc... + mgmt_driver = sa.Column(sa.String(255)) + + # For a management tool to talk to manage this service instance. + # opaque string. mgmt_driver interprets it. + mgmt_address = sa.Column(sa.String(255), nullable=True) + + service_context = orm.relationship('ServiceContext') + devices = orm.relationship('ServiceDeviceBinding') + + status = sa.Column(sa.String(255), nullable=False) + + # TODO(yamahata): re-think the necessity of following columns + # They are all commented out for minimalism for now. + # They will be added when it is found really necessary. + # + # multi_tenant = sa.Column(sa.Boolean()) + # state = sa.Column(sa.Enum('UP', 'DOWN', + # name='service_instance_state')) + # For a logical service instance in hosting device to recieve + # requests from management tools. + # opaque string. mgmt_driver interprets it. + # e.g. the name of the interface inside the VM + protocol + # vm_mgmt_if = sa.Column(sa.String(255), default=None, nullable=True) + # networks = + # obj_store = + # cost_factor = + + +# TODO(yamahata): This is tentative. +# In the future, this will be replaced with db models of +# service insertion/chain. +# Since such models are under discussion/development as of +# this time, this models is just for lbaas driver of hosting +# device +# This corresponds to networks of Logical Service Instance in the origianl spec +class ServiceContext(model_base.BASEV2, models_v2.HasId): + """Represents service context of logical service instance. + This represents service insertion/chainging of a given device. + This is equal or subset of DeviceServiceContext of the + corresponding Device. + """ + service_instance_id = sa.Column(sa.String(36), + sa.ForeignKey('serviceinstances.id')) + network_id = sa.Column(sa.String(36), nullable=True) + subnet_id = sa.Column(sa.String(36), nullable=True) + port_id = sa.Column(sa.String(36), nullable=True) + router_id = sa.Column(sa.String(36), nullable=True) + + role = sa.Column(sa.String(255), nullable=True) + index = sa.Column(sa.Integer, nullable=True) # disambiguation + + +class ServiceDeviceBinding(model_base.BASEV2): + """Represents binding with Device and LogicalResource. + Since Device can accomodate multiple services, it's many-to-one + relationship. + """ + service_instance_id = sa.Column( + sa.String(36), sa.ForeignKey('serviceinstances.id'), primary_key=True) + device_id = sa.Column(sa.String(36), sa.ForeignKey('devices.id'), + primary_key=True) + + +########################################################################### +# actual code to manage those tables +class ServiceContextEntry(dict): + @classmethod + def create(cls, network_id, subnet_id, port_id, router_id, role, index): + return cls({ + 'network_id': network_id, + 'subnet_id': subnet_id, + 'port_id': port_id, + 'router_id': router_id, + 'role': role, + 'index': index, + }) + + +class ServiceResourcePluginDb(servicevm.ServiceVMPluginBase, + base_db.CommonDbMixin): + + @property + def _core_plugin(self): + return manager.NeutronManager.get_plugin() + + def subnet_id_to_network_id(self, context, subnet_id): + subnet = self._core_plugin.get_subnet(context, subnet_id) + return subnet['network_id'] + + def __init__(self): + qdbapi.register_models() + super(ServiceResourcePluginDb, self).__init__() + + def _get_resource(self, context, model, id): + try: + return self._get_by_id(context, model, id) + except orm_exc.NoResultFound: + if issubclass(model, DeviceTemplate): + raise servicevm.DeviceTemplateNotFound(device_template_id=id) + elif issubclass(model, ServiceType): + raise servicevm.ServiceTypeNotFound(service_type_id=id) + elif issubclass(model, ServiceInstance): + raise servicevm.ServiceInstanceNotFound(service_instance_id=id) + if issubclass(model, Device): + raise servicevm.DeviceNotFound(device_id=id) + else: + raise + + def _make_attributes_dict(self, attributes_db): + return dict((attr.key, attr.value) for attr in attributes_db) + + def _make_service_types_list(self, service_types): + return [{'id': service_type.id, + 'service_type': service_type.service_type} + for service_type in service_types] + + def _make_template_dict(self, template, fields=None): + res = { + 'attributes': self._make_attributes_dict(template['attributes']), + 'service_types': + self._make_service_types_list(template['service_types']), + } + key_list = ('id', 'tenant_id', 'name', 'description', + 'device_driver', 'mgmt_driver') + res.update((key, template[key]) for key in key_list) + return self._fields(res, fields) + + def _make_services_list(self, binding_db): + return [binding.service_instance_id for binding in binding_db] + + def _make_kwargs_dict(self, kwargs_db): + return dict((arg.key, jsonutils.loads(arg.value)) for arg in kwargs_db) + + def _make_device_service_context_dict(self, service_context): + key_list = ('id', 'network_id', 'subnet_id', 'port_id', 'router_id', + 'role', 'index') + return [self._fields(dict((key, entry[key]) for key in key_list), None) + for entry in service_context] + + def _make_device_dict(self, device_db, fields=None): + LOG.debug(_('device_db %s'), device_db) + res = { + 'services': + self._make_services_list(getattr(device_db, 'services', [])), + 'device_template': + self._make_template_dict(device_db.template), + 'kwargs': self._make_kwargs_dict(device_db.kwargs), + 'service_context': + self._make_device_service_context_dict(device_db.service_context), + } + key_list = ('id', 'tenant_id', 'instance_id', 'template_id', 'status', + 'mgmt_address') + res.update((key, device_db[key]) for key in key_list) + return self._fields(res, fields) + + def _make_service_context_dict(self, service_context): + key_list = ('id', 'network_id', 'subnet_id', 'port_id', 'router_id', + 'role', 'index') + return [self._fields(dict((key, entry[key]) for key in key_list), None) + for entry in service_context] + + def _make_service_device_list(self, devices): + return [binding.device_id for binding in devices] + + def _make_service_instance_dict(self, instance_db, fields=None): + res = { + 'service_context': + self._make_service_context_dict(instance_db.service_context), + 'devices': + self._make_service_device_list(instance_db.devices) + } + key_list = ('id', 'tenant_id', 'name', 'service_type_id', + 'service_table_id', 'mgmt_driver', 'mgmt_address', + 'status') + res.update((key, instance_db[key]) for key in key_list) + return self._fields(res, fields) + + @staticmethod + def _device_driver_name(device_dict): + return device_dict['device_template']['device_driver'] + + @staticmethod + def _mgmt_device_driver(device_dict): + return device_dict['device_template']['mgmt_driver'] + + @staticmethod + def _mgmt_service_driver(service_instance_dict): + return service_instance_dict['mgmt_driver'] + + @staticmethod + def _instance_id(device_dict): + return device_dict['instance_id'] + + ########################################################################### + # hosting device template + + def create_device_template(self, context, device_template): + template = device_template['device_template'] + LOG.debug(_('template %s'), template) + isglobal= template.get('scope') + if isglobal=='global': + tenant_id = constants.GLOBAL_DEVICE_TEMPLATE_TID + else: + tenant_id = self._get_tenant_id_for_create(context, template) + device_driver = template.get('device_driver') + mgmt_driver = template.get('mgmt_driver') + service_types = template.get('service_types') + + if (not attributes.is_attr_set(device_driver)): + LOG.debug(_('hosting device driver unspecified')) + raise servicevm.DeviceDriverNotSpecified() + if (not attributes.is_attr_set(mgmt_driver)): + LOG.debug(_('mgmt driver unspecified')) + raise servicevm.MGMTDriverNotSpecified() + if (not attributes.is_attr_set(service_types)): + LOG.debug(_('service types unspecified')) + raise servicevm.SeviceTypesNotSpecified() + + with context.session.begin(subtransactions=True): + template_id = str(uuid.uuid4()) + template_db = DeviceTemplate( + id=template_id, + tenant_id=tenant_id, + name=template.get('name'), + description=template.get('description'), + device_driver=device_driver, + mgmt_driver=mgmt_driver) + context.session.add(template_db) + for (key, value) in template.get('attributes', {}).items(): + attribute_db = DeviceTemplateAttribute( + id=str(uuid.uuid4()), + template_id=template_id, + key=key, + value=value) + context.session.add(attribute_db) + for service_type in (item['service_type'] + for item in template['service_types']): + service_type_db = ServiceType( + id=str(uuid.uuid4()), + tenant_id=tenant_id, + template_id=template_id, + service_type=service_type) + context.session.add(service_type_db) + + LOG.debug(_('template_db %(template_db)s %(attributes)s ' + '%(service_types)s'), + {'template_db': template_db, + 'attributes': template_db.attributes, + 'service_types': template_db.service_types}) + return self._make_template_dict(template_db) + + def update_device_template(self, context, device_template_id, + device_template): + with context.session.begin(subtransactions=True): + template_db = self._get_resource(context, DeviceTemplate, + device_template_id) + template_db.update(device_template['device_template']) + return self._make_template_dict(template_db) + + def delete_device_template(self, context, device_template_id): + with context.session.begin(subtransactions=True): + # TODO(yamahata): race. prevent from newly inserting hosting device + # that refers to this template + devices_db = context.session.query(Device).filter_by( + template_id=device_template_id).first() + if devices_db is not None: + raise servicevm.DeviceTemplateInUse( + device_template_id=device_template_id) + + context.session.query(ServiceType).filter_by( + template_id=device_template_id).delete() + context.session.query(DeviceTemplateAttribute).filter_by( + template_id=device_template_id).delete() + template_db = self._get_resource(context, DeviceTemplate, + device_template_id) + context.session.delete(template_db) + + def get_device_template(self, context, device_template_id, fields=None): + template_db = self._get_resource(context, DeviceTemplate, + device_template_id) + return self._make_template_dict(template_db) + + def get_device_templates(self, context, filters, fields=None): + return self._get_collection(context, DeviceTemplate, + self._make_template_dict, + filters=filters, fields=fields) + + # called internally, not by REST API + # need enhancement? + def choose_device_template(self, context, service_type, + required_attributes=None): + required_attributes = required_attributes or [] + with context.session.begin(subtransactions=True): + query = ( + context.session.query(DeviceTemplate). + filter( + sa.exists(). + where(sa.and_( + DeviceTemplate.id == ServiceType.template_id, + ServiceType.service_type == service_type)))) + for key in required_attributes: + query = query.filter( + sa.exists(). + where(sa.and_( + DeviceTemplate.id == + DeviceTemplateAttribute.template_id, + DeviceTemplateAttribute.key == key))) + template_db = query.first() + if template_db: + return self._make_template_dict(template_db) + + ########################################################################### + # hosting device + + # called internally, not by REST API + def _create_device_pre(self, context, device): + device = device['device'] + LOG.debug(_('device %s'), device) + tenant_id = self._get_tenant_id_for_create(context, device) + template_id = device['template_id'] + device_id = str(uuid.uuid4()) + kwargs = device.get('kwargs', {}) + service_context = device.get('service_context', []) + with context.session.begin(subtransactions=True): + device_db = Device(id=device_id, + tenant_id=tenant_id, + instance_id=None, + template_id=template_id, + status=constants.PENDING_CREATE) + context.session.add(device_db) + for key, value in kwargs.items(): + arg = DeviceArg(id=str(uuid.uuid4()), device_id=device_id, + key=key, value=jsonutils.dumps(value)) + context.session.add(arg) + + LOG.debug(_('service_context %s'), service_context) + for sc_entry in service_context: + LOG.debug(_('sc_entry %s'), sc_entry) + network_id = sc_entry.get('network_id') + subnet_id = sc_entry.get('subnet_id') + port_id = sc_entry.get('port_id') + router_id = sc_entry.get('router_id') + role = sc_entry.get('role') + index = sc_entry.get('index') + network_binding = DeviceServiceContext( + id=str(uuid.uuid4()), device_id=device_id, + network_id=network_id, subnet_id=subnet_id, + port_id=port_id, router_id=router_id, role=role, + index=index) + context.session.add(network_binding) + + return self._make_device_dict(device_db) + + # called internally, not by REST API + # intsance_id = None means error on creation + def _create_device_post(self, context, device_id, instance_id, + mgmt_address, service_context): + with context.session.begin(subtransactions=True): + query = (self._model_query(context, Device). + filter(Device.id == device_id). + filter(Device.status == constants.PENDING_CREATE). + one()) + query.update({'instance_id': instance_id, + 'mgmt_address': mgmt_address}) + if instance_id is None: + query.update({'status': constants.ERROR}) + + for sc_entry in service_context: + # some member of service context is determined during + # creating hosting device. + (self._model_query(context, DeviceServiceContext). + filter(DeviceServiceContext.id == sc_entry['id']). + update({'network_id': sc_entry['network_id'], + 'subnet_id': sc_entry['subnet_id'], + 'port_id': sc_entry['port_id'], + 'router_id': sc_entry['router_id'], + 'role': sc_entry['role'], + 'index': sc_entry['index']})) + + def _create_device_status(self, context, device_id, new_status): + with context.session.begin(subtransactions=True): + (self._model_query(context, Device). + filter(Device.id == device_id). + filter(Device.status == constants.PENDING_CREATE). + update({'status': new_status})) + + def _get_device_db(self, context, device_id, new_status): + try: + device_db = ( + self._model_query(context, Device). + filter(Device.id == device_id). + filter(Device.status.in_(_ACTIVE_UPDATE)). + filter(Device.status == constants.ACTIVE). + with_lockmode('update').one()) + except orm_exc.NoResultFound: + raise servicevm.DeviceNotFound(device_id=device_id) + if device_db.status == constants.PENDING_UPDATE: + raise servicevm.DeviceInUse(device_id=device_id) + device_db.update({'status': new_status}) + return device_db + + def _update_device_pre(self, context, device_id): + with context.session.begin(subtransactions=True): + device_db = self._get_device_db(context, device_id, + constants.PENDING_UPDATE) + return self._make_device_dict(device_db) + + def _update_device_post(self, context, device_id, new_status): + with context.session.begin(subtransactions=True): + (self._model_query(context, Device). + filter(Device.id == device_id). + filter(Device.status == constants.PENDING_UPDATE). + update({'status': new_status})) + + def _delete_device_pre(self, context, device_id): + with context.session.begin(subtransactions=True): + # TODO(yamahata): race. keep others from inserting new binding + binding_db = (context.session.query(ServiceDeviceBinding). + filter_by(device_id=device_id).first()) + if binding_db is not None: + raise servicevm.DeviceInUse(device_id=device_id) + device_db = self._get_device_db(context, device_id, + constants.PENDING_DELETE) + + return self._make_device_dict(device_db) + + def _delete_device_post(self, context, device_id, error): + with context.session.begin(subtransactions=True): + query = ( + self._model_query(context, Device). + filter(Device.id == device_id). + filter(Device.status == constants.PENDING_DELETE)) + if error: + query.update({'status': constants.ERROR}) + else: + (self._model_query(context, DeviceArg). + filter(DeviceArg.device_id == device_id).delete()) + (self._model_query(context, DeviceServiceContext). + filter(DeviceServiceContext.device_id == device_id).delete()) + query.delete() + + # reference implementation. needs to be overrided by subclass + def create_device(self, context, device): + device_dict = self._create_device_pre(context, device) + # start actual creation of hosting device. + # Waiting for completion of creation should be done backgroundly + # by another thread if it takes a while. + instance_id = str(uuid.uuid4()) + device_dict['instance_id'] = instance_id + self._create_device_post(context, device_dict['id'], instance_id, None, + device_dict['service_context']) + self._create_device_status(context, device_dict['id'], + constants.ACTIVE) + return device_dict + + # reference implementation. needs to be overrided by subclass + def update_device(self, context, device_id, device): + device_dict = self._update_device_pre(context, device_id) + # start actual update of hosting device + # waiting for completion of update should be done backgroundly + # by another thread if it takes a while + self._update_device_post(context, device_id, constants.ACTIVE) + return device_dict + + # reference implementation. needs to be overrided by subclass + def delete_device(self, context, device_id): + self._delete_device_pre(context, device_id) + # start actual deletion of hosting device. + # Waiting for completion of deletion should be done backgroundly + # by another thread if it takes a while. + self._delete_device_post(context, device_id, False) + + def get_device(self, context, device_id, fields=None): + device_db = self._get_resource(context, Device, device_id) + return self._make_device_dict(device_db, fields) + + def get_devices(self, context, filters=None, fields=None): + return self._get_collection(context, Device, self._make_device_dict, + filters=filters, fields=fields) + + ########################################################################### + # logical service instance + + # called internally, not by REST API + def _create_service_instance(self, context, device_id, + service_instance_param, managed_by_user): + """ + :param service_instance_param: dictionary to create + instance of ServiceInstance. The following keys are used. + name, service_type_id, service_table_id, mgmt_driver, mgmt_address + mgmt_driver, mgmt_address can be determined later. + """ + name = service_instance_param['name'] + service_type_id = service_instance_param['service_type_id'] + service_table_id = service_instance_param['service_table_id'] + mgmt_driver = service_instance_param.get('mgmt_driver') + mgmt_address = service_instance_param.get('mgmt_address') + + service_instance_id = str(uuid.uuid4()) + with context.session.begin(subtransactions=True): + # TODO(yamahata): race. prevent modifying/deleting service_type + # with_lockmode("update") + device_db = self._get_resource(context, Device, device_id) + device_dict = self._make_device_dict(device_db) + tenant_id = self._get_tenant_id_for_create(context, device_dict) + instance_db = ServiceInstance( + id=service_instance_id, + tenant_id=tenant_id, + name=name, + service_type_id=service_type_id, + service_table_id=service_table_id, + managed_by_user=managed_by_user, + status=constants.PENDING_CREATE, + mgmt_driver=mgmt_driver, + mgmt_address=mgmt_address) + context.session.add(instance_db) + context.session.flush() + + binding_db = ServiceDeviceBinding( + service_instance_id=service_instance_id, device_id=device_id) + context.session.add(binding_db) + + return self._make_service_instance_dict(instance_db) + + # reference implementation. must be overriden by subclass + def create_service_instance(self, context, service_instance): + self._create_service_instance( + context, service_instance['service_instance'], True) + + def _update_service_instance_mgmt(self, context, service_instance_id, + mgmt_driver, mgmt_address): + with context.session.begin(subtransactions=True): + (self._model_query(context, ServiceInstance). + filter(ServiceInstance.id == service_instance_id). + filter(ServiceInstance.status == constants.PENDING_CREATE). + one(). + update({'mgmt_driver': mgmt_driver, + 'mgmt_address': mgmt_address})) + + def _update_service_instance_pre(self, context, service_instance_id, + service_instance): + with context.session.begin(subtransactions=True): + instance_db = ( + self._model_query(context, ServiceInstance). + filter(ServiceInstance.id == service_instance_id). + filter(Device.status == constants.ACTIVE). + with_lockmode('update').one()) + instance_db.update(service_instance) + instance_db.update({'status': constants.PENDING_UPDATE}) + return self._make_service_instance_dict(instance_db) + + def _update_service_instance_post(self, context, service_instance_id, + status): + with context.session.begin(subtransactions=True): + (self._model_query(context, ServiceInstance). + filter(ServiceInstance.id == service_instance_id). + filter(ServiceInstance.status.in_( + [constants.PENDING_CREATE, constants.PENDING_UPDATE])).one(). + update({'status': status})) + + # reference implementation + def update_service_instance(self, context, service_instance_id, + service_instance): + service_instance_dict = self._update_service_instance_pre( + context, service_instance_id, service_instance) + self._update_service_instance_post( + context, service_instance_id, service_instance, constants.ACTIVE) + return service_instance_dict + + def _delete_service_instance_pre(self, context, service_instance_id, + managed_by_user): + with context.session.begin(subtransactions=True): + service_instance = ( + self._model_query(context, ServiceInstance). + filter(ServiceInstance.id == service_instance_id). + filter(ServiceInstance.status == constants.ACTIVE). + with_lockmode('update').one()) + + if service_instance.managed_by_user != managed_by_user: + raise servicevm.ServiceInstanceNotManagedByUser( + service_instance_id=service_instance_id) + + service_instance.status = constants.PENDING_DELETE + + binding_db = ( + self._model_query(context, ServiceDeviceBinding). + filter(ServiceDeviceBinding.service_instance_id == + service_instance_id). + all()) + assert binding_db + # check only. _post method will delete it. + if len(binding_db) > 1: + raise servicevm.ServiceInstanceInUse( + service_instance_id=service_instance_id) + + def _delete_service_instance_post(self, context, service_instance_id): + with context.session.begin(subtransactions=True): + binding_db = ( + self._model_query(context, ServiceDeviceBinding). + filter(ServiceDeviceBinding. service_instance_id == + service_instance_id). + all()) + assert binding_db + assert len(binding_db) == 1 + context.session.delete(binding_db[0]) + + (self._model_query(context, ServiceInstance). + filter(ServiceInstance.id == service_instance_id). + filter(ServiceInstance.status == constants.PENDING_DELETE). + delete()) + + # reference implementation. needs to be overriden by subclass + def _delete_service_instance(self, context, service_instance_id, + managed_by_user): + self._delete_service_instance_pre(context, service_instance_id, + managed_by_user) + self._delete_service_instance_post(context, service_instance_id) + + # reference implementation. needs to be overriden by subclass + def delete_service_instance(self, context, service_instance_id): + self._delete_service_instance(context, service_instance_id, True) + + def get_by_service_table_id(self, context, service_table_id): + with context.session.begin(subtransactions=True): + instance_db = (self._model_query(context, ServiceInstance). + filter(ServiceInstance.service_table_id == + service_table_id).one()) + device_db = ( + self._model_query(context, Device). + filter(sa.exists().where(sa.and_( + ServiceDeviceBinding.device_id == Device.id, + ServiceDeviceBinding.service_instance_id == + instance_db.id))).one()) + return (self._make_device_dict(device_db), + self._make_service_instance_dict(instance_db)) + + def get_by_service_instance_id(self, context, service_instance_id): + with context.session.begin(subtransactions=True): + instance_db = self._get_resource(context, ServiceInstance, + service_instance_id) + device_db = ( + self._model_query(context, Device). + filter(sa.exists().where(sa.and_( + ServiceDeviceBinding.device_id == Device.id, + ServiceDeviceBinding.service_instance_id == + instance_db.id))).one()) + return (self._make_device_dict(device_db), + self._make_service_instance_dict(instance_db)) + + def get_service_instance(self, context, service_instance_id, fields=None): + instance_db = self._get_resource(context, ServiceInstance, + service_instance_id) + return self._make_service_instance_dict(instance_db, fields) + + def get_service_instances(self, context, filters=None, fields=None): + return self._get_collection( + context, ServiceInstance, self._make_service_instance_dict, + filters=filters, fields=fields) diff --git a/neutron/extensions/lbaas_ssl.py b/neutron/extensions/lbaas_ssl.py new file mode 100644 index 00000000000..d9e3ecb3faf --- /dev/null +++ b/neutron/extensions/lbaas_ssl.py @@ -0,0 +1,437 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2014 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. +# +# @author: Evgeny Fedoruk, Radware, Inc + +import abc + +import six + +from neutron.api import extensions +from neutron.api.v2 import attributes as attr +from neutron.api.v2 import base +from neutron.api.v2 import resource_helper +from neutron.common import exceptions as qexception +from neutron.extensions import loadbalancer +from neutron import manager +from neutron.plugins.common import constants +from neutron.services.service_base import ServicePluginBase + + +# SSL Exceptions +class SSLPolicyNotFound(qexception.NotFound): + message = _("SSL Policy %(policy_id)s could not be found") + + +class SSLPolicyInUse(qexception.InUse): + message = _("SSL Policy %(policy_id)s is still associated with vips") + + +class SSLCertificateNotFound(qexception.NotFound): + message = _("SSL Certificate %(certificate_id)s could not be found") + + +class SSLCertificateInUse(qexception.InUse): + message = _("SSL Certificate %(certificate_id)s is still associated " + "with vips") + + +class SSLTrustedCertificateNotFound(qexception.NotFound): + message = _("SSL Trusted Certificate %(certificate_id)s " + "could not be found") + + +class SSLTrustedCertificateInUse(qexception.InUse): + message = _("SSL Trusted Certificate %(certificate_id)s " + "is still associated with vips") + + +class VipSSLCertificateAssociationNotFound(qexception.NotFound): + message = _("Vip %(vip_id)s is not associated " + "with SSL Certificate %(certificate_id)s") + + +class VipSSLCertificateAssociationExists(qexception.Conflict): + message = _("SSL Certificate %(certificate_id)s " + "is already associated with vip %(vip_id)s") + + +class VipSSLTrustedCertificateAssociationNotFound(qexception.NotFound): + message = _("Vip %(vip_id)s is not associated " + "with SSL Trusted Certificate %(certificate_id)s") + + +class VipSSLTrustedCertificateAssociationExists(qexception.Conflict): + message = _("SSL Trusted Certificate %(certificate_id)s " + "is already associated with vip %(vip_id)s") + +RESOURCE_ATTRIBUTE_MAP = { + 'ssl_policies': { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True}, + 'name': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'default': '', + 'is_visible': True}, + 'description': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'default': '', + 'is_visible': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True}, + 'front_end_enabled': {'allow_post': True, 'allow_put': True, + 'default': True, + 'convert_to': attr.convert_to_boolean, + 'is_visible': True}, + 'front_end_protocols': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True}, + 'front_end_cipher_suites': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'default': '', + 'is_visible': True}, + 'back_end_enabled': {'allow_post': True, 'allow_put': True, + 'default': True, + 'convert_to': attr.convert_to_boolean, + 'is_visible': True}, + 'back_end_protocols': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True}, + 'back_end_cipher_suites': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'default': '', + 'is_visible': True}, + 'vips': {'allow_post': False, 'allow_put': False, + 'default': None, + 'validate': {'type:uuid_list': None}, + 'convert_to': attr.convert_to_list, + 'is_visible': True} + }, + 'ssl_certificates': { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True}, + 'name': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'description': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'default': '', + 'is_visible': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True}, + 'certificate': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'passphrase': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'certificate_chain': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'vips': {'allow_post': False, 'allow_put': False, + 'default': None, + 'validate': {'type:uuid_list': None}, + 'convert_to': attr.convert_to_list, + 'is_visible': True} + }, + 'ssl_trusted_certificates': { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True}, + 'name': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'default': '', + 'is_visible': True}, + 'description': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'default': '', + 'is_visible': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True}, + 'certificate': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'default': '', + 'is_visible': True}, + 'vips': {'allow_post': False, 'allow_put': False, + 'default': None, + 'validate': {'type:uuid_list': None}, + 'convert_to': attr.convert_to_list, + 'is_visible': True} + } +} + +SUB_RESOURCE_ATTRIBUTE_MAP = { + 'ssl_associations': { + 'parent': {'collection_name': 'vips', + 'member_name': 'vip'}, + 'parameters': { + 'id': { + 'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True}, + 'tenant_id': { + 'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True + }, + 'ssl_policy': { + 'allow_post': True, 'allow_put': False, + 'is_visible': True, + 'validate': { + 'type:dict': { + 'id': {'type:uuid': None} + } + } + }, + 'ssl_certificates': { + 'allow_post': True, 'allow_put': False, + 'is_visible': True, + 'validate': { + 'type:list_or_empty': { + 'is_visible': True, + 'type:dict': { + 'id': {'type:uuid': None}, + 'private_key': {'type:string': None} + } + } + } + }, + 'ssl_trusted_certificates': { + 'allow_post': True, 'allow_put': False, + 'is_visible': True, + 'validate': { + 'type:list_or_empty': { + 'is_visible': True, + 'type:dict': { + 'id': {'type:uuid': None} + } + } + } + } + } + } +} + +EXTENDED_ATTRIBUTES_2_0 = { + 'vips': { + 'ssl_policy_id': + {'allow_post': True, 'allow_put': True, + 'default': None, + 'validate': {'type:uuid_or_none': None}, + 'is_visible': True}, + 'ssl_certificate_ids': + {'allow_post': True, 'allow_put': True, + 'default': [], + 'validate': {'type:uuid_list': None}, + 'convert_to': attr.convert_to_list, + 'is_visible': True}, + 'ssl_trusted_certificate_ids': + {'allow_post': True, 'allow_put': True, + 'default': [], + 'validate': {'type:uuid_list': None}, + 'convert_to': attr.convert_to_list, + 'is_visible': True} + } +} + + +class Lbaas_ssl(extensions.ExtensionDescriptor): + + @classmethod + def get_name(cls): + return "Loadbalancing service SSL extension" + + @classmethod + def get_alias(cls): + return "lbaas-ssl" + + @classmethod + def get_description(cls): + #TODO(Evgeny Fedoruk) + return ("Extension for Loadbalancing service SSL") + + @classmethod + def get_namespace(cls): + #TODO(Evgeny Fedoruk) should be changed to specific link + return "http://wiki.openstack.org/neutron/LBaaS/API_1.0" + + @classmethod + def get_updated(cls): + return "2014-13-01T10:00:00-00:00" + + @classmethod + def get_resources(cls, ext_plugin=None): + special_plurals = { + 'ssl_policies': 'ssl_policy', + 'ssl_certificates': 'ssl_certificate', + 'ssl_trusted_certificates': 'ssl_trusted_certificate', + 'ssl_associations': 'ssl_association' + } + attr.PLURALS.update(special_plurals) + + plugin = ext_plugin or manager.NeutronManager.get_service_plugins()[ + constants.LOADBALANCER] + + plural_mappings = resource_helper.build_plural_mappings( + special_plurals, RESOURCE_ATTRIBUTE_MAP) + + resources = resource_helper.build_resource_info(plural_mappings, + RESOURCE_ATTRIBUTE_MAP, + constants.LOADBALANCER, + ext_plugin=plugin) + + for collection_name in SUB_RESOURCE_ATTRIBUTE_MAP: + resource_name = special_plurals[collection_name] + parent = SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get('parent') + params = SUB_RESOURCE_ATTRIBUTE_MAP[collection_name].get( + 'parameters') + + controller = base.create_resource(collection_name, resource_name, + plugin, params, + allow_bulk=True, + parent=parent) + + resource = extensions.ResourceExtension( + collection_name, + controller, parent, + path_prefix=constants.COMMON_PREFIXES[constants.LOADBALANCER], + attr_map=params) + resources.append(resource) + + return resources + + @classmethod + def get_plugin_interface(cls): + return loadbalancer.LoadBalancerPluginBase + + def update_attributes_map(self, attributes): + super(Lbaas_ssl, self).update_attributes_map( + attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP) + + def get_extended_resources(self, version): + if version == "2.0": + #return EXTENDED_ATTRIBUTES_2_0 + return dict(EXTENDED_ATTRIBUTES_2_0.items() + + RESOURCE_ATTRIBUTE_MAP.items()) + else: + return {} + + +#@six.add_metaclass(abc.ABCMeta) +class LbaasSSLPluginBase(ServicePluginBase): + __metaclass__ = abc.ABCMeta + + def get_plugin_name(self): + return constants.LOADBALANCER + + def get_plugin_type(self): + return constants.LOADBALANCER + + def get_plugin_description(self): + return 'LoadBalancer ssl extension service plugin' + + @abc.abstractmethod + def create_ssl_policy(self, context, ssl_policy): + pass + + @abc.abstractmethod + def update_ssl_policy(self, context, id, ssl_policy): + pass + + @abc.abstractmethod + def delete_ssl_policy(self, context, id): + pass + + @abc.abstractmethod + def get_ssl_policy(self, context, id, fields=None): + pass + + @abc.abstractmethod + def get_ssl_policies(self, context, filters=None, fields=None): + pass + + @abc.abstractmethod + def create_ssl_certificate(self, context, ssl_certificate): + pass + + @abc.abstractmethod + def update_ssl_certificate(self, context, id, ssl_certificate): + pass + + @abc.abstractmethod + def delete_ssl_certificate(self, context, id): + pass + + @abc.abstractmethod + def get_ssl_certificate(self, context, id, fields=None): + pass + + @abc.abstractmethod + def get_ssl_certificates(self, context, filters=None, fields=None): + pass + + @abc.abstractmethod + def create_ssl_trusted_certificate(self, context, + ssl_trusted_certificate): + pass + + @abc.abstractmethod + def update_ssl_trusted_certificate(self, context, id, + ssl_trusted_certificate): + pass + + @abc.abstractmethod + def delete_ssl_trusted_certificate(self, context, id): + pass + + @abc.abstractmethod + def get_ssl_trusted_certificate(self, context, id, fields=None): + pass + + @abc.abstractmethod + def get_ssl_trusted_certificates(self, context, + filters=None, fields=None): + pass + + @abc.abstractmethod + def create_vip_ssl_association(self, context, + ssl_association, vip_id): + pass + + @abc.abstractmethod + def delete_vip_ssl_association(self, context, id, vip_id): + pass + + @abc.abstractmethod + def get_vip_ssl_association(self, context, id, vip_id, fields=None): + pass + + @abc.abstractmethod + def get_vip_ssl_associations(self, context, vip_id, fields=None): + pass diff --git a/neutron/extensions/loadbalancer.py b/neutron/extensions/loadbalancer.py index d2dde8adb7f..80cc6c6e2b8 100644 --- a/neutron/extensions/loadbalancer.py +++ b/neutron/extensions/loadbalancer.py @@ -18,10 +18,12 @@ import abc from oslo.config import cfg +import six from neutron.api import extensions from neutron.api.v2 import attributes as attr from neutron.api.v2 import base +from neutron.api.v2 import resource_helper from neutron.common import exceptions as qexception from neutron import manager from neutron.plugins.common import constants @@ -29,6 +31,10 @@ # Loadbalancer Exceptions +class NoEligibleBackend(qexception.NotFound): + message = _("No eligible backend for pool %(pool_id)s") + + class VipNotFound(qexception.NotFound): message = _("Vip %(vip_id)s could not be found") @@ -67,6 +73,11 @@ class PoolInUse(qexception.InUse): message = _("Pool %(pool_id)s is still in use") +class HealthMonitorInUse(qexception.InUse): + message = _("Health monitor %(monitor_id)s still has associations with " + "pools") + + class PoolStatsNotFound(qexception.NotFound): message = _("Statistics of Pool %(pool_id)s could not be found") @@ -76,8 +87,18 @@ class ProtocolMismatch(qexception.BadRequest): "pool protocol %(pool_proto)s") +class MemberExists(qexception.NeutronException): + message = _("Member with address %(address)s and port %(port)s " + "already present in pool %(pool)s") + + +VIPS = 'vips' +POOLS = 'pools' +MEMBERS = 'members' +HEALTH_MONITORS = 'health_monitors' + RESOURCE_ATTRIBUTE_MAP = { - 'vips': { + VIPS: { 'id': {'allow_post': False, 'allow_put': False, 'validate': {'type:uuid': None}, 'is_visible': True, @@ -138,7 +159,7 @@ class ProtocolMismatch(qexception.BadRequest): 'status_description': {'allow_post': False, 'allow_put': False, 'is_visible': True} }, - 'pools': { + POOLS: { 'id': {'allow_post': False, 'allow_put': False, 'validate': {'type:uuid': None}, 'is_visible': True, @@ -186,7 +207,7 @@ class ProtocolMismatch(qexception.BadRequest): 'status_description': {'allow_post': False, 'allow_put': False, 'is_visible': True} }, - 'members': { + MEMBERS: { 'id': {'allow_post': False, 'allow_put': False, 'validate': {'type:uuid': None}, 'is_visible': True, @@ -219,7 +240,7 @@ class ProtocolMismatch(qexception.BadRequest): 'status_description': {'allow_post': False, 'allow_put': False, 'is_visible': True} }, - 'health_monitors': { + HEALTH_MONITORS: { 'id': {'allow_post': False, 'allow_put': False, 'validate': {'type:uuid': None}, 'is_visible': True, @@ -270,7 +291,7 @@ class ProtocolMismatch(qexception.BadRequest): } SUB_RESOURCE_ATTRIBUTE_MAP = { - 'health_monitors': { + HEALTH_MONITORS: { 'parent': {'collection_name': 'pools', 'member_name': 'pool'}, 'parameters': {'id': {'allow_post': True, 'allow_put': False, @@ -284,6 +305,26 @@ class ProtocolMismatch(qexception.BadRequest): } } +lbaas_quota_opts = [ + cfg.IntOpt('quota_vip', + default=10, + help=_('Number of vips allowed per tenant. ' + 'A negative value means unlimited.')), + cfg.IntOpt('quota_pool', + default=10, + help=_('Number of pools allowed per tenant. ' + 'A negative value means unlimited.')), + cfg.IntOpt('quota_member', + default=-1, + help=_('Number of pool members allowed per tenant. ' + 'A negative value means unlimited.')), + cfg.IntOpt('quota_health_monitor', + default=-1, + help=_('Number of health monitors allowed per tenant. ' + 'A negative value means unlimited.')) +] +cfg.CONF.register_opts(lbaas_quota_opts, 'QUOTAS') + class Loadbalancer(extensions.ExtensionDescriptor): diff --git a/neutron/extensions/servicevm.py b/neutron/extensions/servicevm.py new file mode 100755 index 00000000000..2415d75d874 --- /dev/null +++ b/neutron/extensions/servicevm.py @@ -0,0 +1,476 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013, 2014 Intel Corporation. +# Copyright 2013, 2014 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel Corporation. + +import abc + +import six + +from neutron.api import extensions +from neutron.api.v2 import attributes as attr +from neutron.api.v2 import resource_helper +from neutron.common import exceptions +from neutron.openstack.common import log as logging +from neutron.plugins.common import constants +from neutron.services.service_base import ServicePluginBase + + +LOG = logging.getLogger(__name__) + + +class DeviceDriverNotSpecified(exceptions.InvalidInput): + message = _('device driver is not speicfied') + + +class MgmtDriverNotSpecified(exceptions.InvalidInput): + message = _('management driver is not speicfied') + + +class ServiceTypesNotSpecified(exceptions.InvalidInput): + message = _('service types are not speicfied') + + +class DeviceTemplateInUse(exceptions.InUse): + message = _('device template %(device_template_id)s is still in use') + + +class DeviceInUse(exceptions.InUse): + message = _('Device %(device_id)s is still in use') + + +class InvalidDeviceDriver(exceptions.InvalidInput): + message = _('invalid name for device driver %(device_driver)s') + + +class InvalidMgmtDriver(exceptions.InvalidInput): + message = _('invalid name for management driver %(mgmt_driver)s') + + +class InvalidServiceType(exceptions.InvalidInput): + message = _('invalid service type %(service_type)s') + + +class DeviceCreateFailed(exceptions.NeutronException): + message = _('creating device based on %(device_template_id)s failed') + + +class DeviceCreateWaitFailed(exceptions.NeutronException): + message = _('waiting for creation of device %(device_id)s failed') + + +class DeviceDeleteFailed(exceptions.NeutronException): + message = _('deleting device %(device_id)s failed') + + +class DeviceTemplateNotFound(exceptions.NotFound): + message = _('device template %(device_template_id)s could not be found') + + +class SeviceTypeNotFound(exceptions.NotFound): + message = _('service type %(service_type_id)s could not be found') + + +class DeviceNotFound(exceptions.NotFound): + message = _('device %(device_id)s could not be found') + + +class ServiceInstanceNotManagedByUser(exceptions.InUse): + message = _('service instance %(service_instance_id)s is ' + 'managed by other service') + + +class ServiceInstanceInUse(exceptions.InUse): + message = _('service instance %(service_instance_id)s is still in use') + +class deplomentpolicyExists(exceptions.InUse): + message = _('deployment policy is configured for the service type on tenant') + +class Serviceinuse(exceptions.InUse): + message = _('service instance %(service_type)s is in use on tenant') + +class AttributeNotSpecified(exceptions.InvalidInput): + message = _('Attribute %(attr)s not speicfied') + +class AttributeCountMismatch(exceptions.InvalidInput): + message = _('Create command supports 6 attributes enable_ha, admin_user, admin_password, platform, version, license_category') +class ServiceInstanceNotFound(exceptions.NotFound): + message = _('service instance %(service_instance_id)s could not be found') + + +def _validate_service_type_list(data, valid_values=None): + if not isinstance(data, list): + msg = _("invalid data format for service list: '%s'") % data + LOG.debug(msg) + return msg + if not data: + msg = _("empty list is not allowed for service list. '%s'") % data + LOG.debug(msg) + return msg + key_specs = { + 'service_type': { + 'type:string': None, + } + } + for service in data: + msg = attr._validate_dict(service, key_specs) + if msg: + LOG.debug(msg) + return msg + + +def _validate_service_context_list(data, valid_values=None): + if not isinstance(data, list): + msg = _("invalid data format for service context list: '%s'") % data + LOG.debug(msg) + return msg + + key_specs = { + 'network_id': {'type:uuid': None}, + 'subnet_id': {'type:uuid': None}, + 'port_id': {'type:uuid': None}, + 'router_id': {'type:uuid': None}, + 'role': {'type:string': None}, + 'index': {'type:non_negative': None, + 'convert_to': attr.convert_to_int}, + } + for sc_entry in data: + msg = attr._validate_dict_or_empty(sc_entry, key_specs=key_specs) + if msg: + LOG.debug(msg) + return msg + + +attr.validators['type:service_type_list'] = _validate_service_type_list +attr.validators['type:service_context_list'] = _validate_service_context_list + + +RESOURCE_ATTRIBUTE_MAP = { + + 'device_templates': { + 'id': { + 'allow_post': False, + 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True, + }, + 'tenant_id': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True, + }, + 'name': { + 'allow_post': True, + 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, + 'default': '', + }, + 'description': { + 'allow_post': True, + 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, + 'default': '', + }, + 'service_types': { + 'allow_post': True, + 'allow_put': False, + 'convert_to': attr.convert_to_list, + 'validate': {'type:service_type_list': None}, + 'is_visible': True, + 'default': attr.ATTR_NOT_SPECIFIED, + }, + 'device_driver': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:string': None}, + 'is_visible': True, + 'default': 'noop', + }, + 'mgmt_driver': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:string': None}, + 'is_visible': True, + 'default': 'noop', + }, + 'attributes': { + 'allow_post': True, + 'allow_put': True, + 'convert_to': attr.convert_none_to_empty_dict, + 'validate': {'type:dict_or_nodata': None}, + 'is_visible': True, + 'default': None, + }, + 'scope': { + 'allow_post': True, + 'allow_put': True, + 'is_visible': True, + 'default': None, + }, + }, + 'devices': { + 'id': { + 'allow_post': False, + 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True + }, + 'tenant_id': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True + }, + 'template_id': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + }, + 'instance_id': { + 'allow_post': False, + 'allow_put': False, + 'validate': {'type:string': None}, + 'is_visible': True, + }, + 'mgmt_address': { + 'allow_post': False, + 'allow_put': False, + 'validate': {'type:string': None}, + 'is_visible': True, + }, + 'kwargs': { + 'allow_post': True, + 'allow_put': True, + 'validate': {'type:dict_or_none': None}, + 'is_visible': True, + 'default': {}, + }, + 'service_contexts': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:service_context_list': None}, + 'is_visible': True, + }, + 'services': { + 'allow_post': False, + 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + }, + 'status': { + 'allow_post': False, + 'allow_put': False, + 'is_visible': True, + }, + }, + + 'service_instances': { + 'id': { + 'allow_post': False, + 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True + }, + 'tenant_id': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True + }, + 'name': { + 'allow_post': True, + 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, + }, + 'service_type_id': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + }, + 'service_table_id': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:string': None}, + 'is_visible': True, + }, + 'mgmt_driver': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:string': None}, + 'is_visible': True, + }, + 'mgmt_address': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:string': None}, + 'is_visible': True, + }, + 'service_contexts': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:service_context_list': None}, + 'is_visible': True, + }, + 'devices': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:uuid_list': None}, + 'convert_to': attr.convert_to_list, + 'is_visible': True, + }, + 'status': { + 'allow_post': False, + 'allow_put': False, + 'is_visible': True, + }, + 'kwargs': { + 'allow_post': True, + 'allow_put': True, + 'validate': {'type:dict_or_none': None}, + 'is_visible': True, + 'default': {}, + }, + }, +} + + +class Servicevm(extensions.ExtensionDescriptor): + @classmethod + def get_name(cls): + return 'Service VM' + + @classmethod + def get_alias(cls): + return 'servicevm' + + @classmethod + def get_description(cls): + return "Extension for ServiceVM service" + + @classmethod + def get_namespace(cls): + return 'http://wiki.openstack.org/Neutron/ServiceVM' + + @classmethod + def get_updated(cls): + return "2013-11-19T10:00:00-00:00" + + @classmethod + def get_resources(cls): + special_mappings = {} + plural_mappings = resource_helper.build_plural_mappings( + special_mappings, RESOURCE_ATTRIBUTE_MAP) + plural_mappings['devices'] = 'device' + plural_mappings['device_templates'] = 'device_template' + plural_mappings['service_types'] = 'service_type' + plural_mappings['service_contexts'] = 'service_context' + plural_mappings['services'] = 'service' + attr.PLURALS.update(plural_mappings) + return resource_helper.build_resource_info( + plural_mappings, RESOURCE_ATTRIBUTE_MAP, constants.SERVICEVM, + register_quota=True, translate_name=True) + + @classmethod + def get_plugin_interface(cls): + return ServiceVMPluginBase + + def update_attributes_map(self, attributes): + super(Servicevm, self).update_attributes_map( + attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP) + + def get_extended_resources(self, version): + if version == "2.0": + return RESOURCE_ATTRIBUTE_MAP + return {} + + +@six.add_metaclass(abc.ABCMeta) +class ServiceVMPluginBase(ServicePluginBase): + + def get_plugin_name(self): + return constants.SERVICEVM + + def get_plugin_type(self): + return constants.SERVICEVM + + def get_plugin_description(self): + return 'Service VM plugin' + + @abc.abstractmethod + def create_device_template(self, context, device_template): + pass + + @abc.abstractmethod + def delete_device_template(self, context, device_template_id): + pass + + @abc.abstractmethod + def get_device_template(self, context, device_template_id, fields=None): + pass + + @abc.abstractmethod + def get_device_templates(self, context, filters=None, fields=None): + pass + + @abc.abstractmethod + def get_devices(self, context, filters=None, fields=None): + pass + + @abc.abstractmethod + def get_device(self, context, device_id, fields=None): + pass + + @abc.abstractmethod + def create_device(self, context, device): + pass + + @abc.abstractmethod + def update_device( + self, context, device_id, device): + pass + + @abc.abstractmethod + def delete_device(self, context, device_id): + pass + + @abc.abstractmethod + def get_service_instances(self, context, filters=None, fields=None): + pass + + @abc.abstractmethod + def get_service_instance(self, context, service_instance_id, fields=None): + pass + + @abc.abstractmethod + def update_service_instance(self, context, service_instance_id, + service_instance): + pass diff --git a/neutron/plugins/common/constants.py b/neutron/plugins/common/constants.py index e3ed1ab70ec..018654e8319 100644 --- a/neutron/plugins/common/constants.py +++ b/neutron/plugins/common/constants.py @@ -23,7 +23,7 @@ VPN = "VPN" METERING = "METERING" L3_ROUTER_NAT = "L3_ROUTER_NAT" - +SERVICEVM = "SERVICEVM" #maps extension alias to service type EXT_TO_SERVICE_MAPPING = { @@ -32,12 +32,14 @@ 'fwaas': FIREWALL, 'vpnaas': VPN, 'metering': METERING, - 'router': L3_ROUTER_NAT + 'router': L3_ROUTER_NAT, + 'servicevm': SERVICEVM, + 'lbaas-ssl': LOADBALANCER, } # TODO(salvatore-orlando): Move these (or derive them) from conf file ALLOWED_SERVICES = [CORE, DUMMY, LOADBALANCER, FIREWALL, VPN, METERING, - L3_ROUTER_NAT] + L3_ROUTER_NAT, SERVICEVM] COMMON_PREFIXES = { CORE: "", @@ -47,6 +49,7 @@ VPN: "/vpn", METERING: "/metering", L3_ROUTER_NAT: "", + SERVICEVM: "/servicevm", } # Service operation status constants @@ -58,6 +61,12 @@ INACTIVE = "INACTIVE" ERROR = "ERROR" +ACTIVE_PENDING_STATUSES = ( + ACTIVE, + PENDING_CREATE, + PENDING_UPDATE +) + # FWaaS firewall rule action FWAAS_ALLOW = "allow" FWAAS_DENY = "deny" @@ -66,3 +75,12 @@ TCP = "tcp" UDP = "udp" ICMP = "icmp" + +# Network Type constants +TYPE_FLAT = 'flat' +TYPE_GRE = 'gre' +TYPE_LOCAL = 'local' +TYPE_VXLAN = 'vxlan' +TYPE_VLAN = 'vlan' +TYPE_NONE = 'none' +GLOBAL_DEVICE_TEMPLATE_TID = 'global' diff --git a/neutron/services/loadbalancer/drivers/abstract_ssl_extension_driver.py b/neutron/services/loadbalancer/drivers/abstract_ssl_extension_driver.py new file mode 100644 index 00000000000..631a86bbea0 --- /dev/null +++ b/neutron/services/loadbalancer/drivers/abstract_ssl_extension_driver.py @@ -0,0 +1,70 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 Radware LTD. +# +# 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. +# +# @author: Evgeny Fedoruk, Radware + +import abc + +import six + + +#@six.add_metaclass(abc.ABCMeta) +class LBaaSAbstractSSLDriver(object): + """Abstract lbaas ssl driver that expose ~same API as lbaas ssl extension. + + SSL entities update actions will be habdled by the driver + The entities are the dicts that are returned to the tenant. + Create, Delete and Get are not part of the API - it will be handled + by the dbmixin. + """ + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def update_ssl_policy(self, context, ssl_policy, vip): + pass + + @abc.abstractmethod + def update_ssl_certificate(self, context, ssl_certificate, vip): + pass + + @abc.abstractmethod + def update_ssl_trusted_certificate(self, context, + ssl_trusted_certificate, vip): + pass + + @abc.abstractmethod + def create_vip_ssl_association(self, context, + ssl_policy, ssl_certificates, + ssl_trusted_certificates, vip): + """Driver should: + Remove all PENDING_DELETE association to ssl policy, certificates + and trusted certificates. + use plugin._remove_pending_delete_vip_ssl() + + Update status of all PENDING_CREATE associations to ssl policy, + certificates and trusted certificates + use plugin.update_status() + """ + pass + + @abc.abstractmethod + def delete_vip_ssl_association(self, context, vip): + """Driver should: + Remove all PENDING_DELETE association to ssl policy, certificates + and trusted certificates. + use plugin._remove_pending_delete_vip_ssl + """ + pass diff --git a/neutron/services/loadbalancer/plugin.py b/neutron/services/loadbalancer/plugin.py index ea7eff3aac6..d707a35f29f 100644 --- a/neutron/services/loadbalancer/plugin.py +++ b/neutron/services/loadbalancer/plugin.py @@ -19,11 +19,16 @@ from neutron.common import exceptions as n_exc from neutron import context from neutron.db import api as qdbapi +from neutron.db.loadbalancer import lbaas_ssl_db as ssldb from neutron.db.loadbalancer import loadbalancer_db as ldb from neutron.db import servicetype_db as st_db +from neutron.extensions import loadbalancer +from neutron.openstack.common import excutils from neutron.openstack.common import log as logging from neutron.plugins.common import constants from neutron.services.loadbalancer import agent_scheduler +from neutron.services.loadbalancer.drivers import ( + abstract_ssl_extension_driver as abs_ssl_driver) from neutron.services import provider_configuration as pconf from neutron.services import service_base @@ -31,7 +36,8 @@ class LoadBalancerPlugin(ldb.LoadBalancerPluginDb, - agent_scheduler.LbaasAgentSchedulerDbMixin): + agent_scheduler.LbaasAgentSchedulerDbMixin, + ssldb.LBaasSSLDbMixin): """Implementation of the Neutron Loadbalancer Service Plugin. This class manages the workflow of LBaaS request/response. @@ -40,7 +46,8 @@ class LoadBalancerPlugin(ldb.LoadBalancerPluginDb, """ supported_extension_aliases = ["lbaas", "lbaas_agent_scheduler", - "service-type"] + "service-type", + "lbaas-ssl"] # lbaas agent notifiers to handle agent update operations; # can be updated by plugin drivers while loading; @@ -80,7 +87,7 @@ def _check_orphan_pool_associations(self, context, provider_names): msg = _("Delete associated loadbalancer pools before " "removing providers %s") % list(lost_providers) LOG.exception(msg) - raise SystemExit(msg) + raise SystemExit(1) def _get_driver_for_provider(self, provider): if provider in self.drivers: @@ -97,6 +104,27 @@ def _get_driver_for_pool(self, context, pool_id): raise n_exc.Invalid(_("Error retrieving provider for pool %s") % pool_id) + def _get_driver_for_vip_ssl(self, context, vip_id): + vip = self.get_vip(context, vip_id) + pool = self.get_pool(context, vip['pool_id']) + if pool['provider']: + try: + driver = self.drivers[pool['provider']] + if not issubclass( + driver.__class__, + abs_ssl_driver.LBaaSAbstractSSLDriver + ): + raise n_exc.ExtensionNotSupportedByProvider( + extension_name='lbaas-ssl', + provider_name=pool['provider']) + return driver + except KeyError: + raise n_exc.Invalid(_("Error retrieving provider for " + "vip's %s SSL configuration"), vip_id) + else: + raise n_exc.Invalid(_("Error retrieving provider for vip %s"), + vip_id) + def get_plugin_type(self): return constants.LOADBALANCER @@ -123,12 +151,115 @@ def _delete_db_vip(self, context, id): super(LoadBalancerPlugin, self).delete_vip(context, id) def delete_vip(self, context, id): - self.update_status(context, ldb.Vip, + + #mmadhavs + #disallow VIP deletion if ssl_policy is associated + #self.update_status(context, ldb.Vip, + # id, constants.PENDING_DELETE) + with context.session.begin(subtransactions=True): + self.update_status(context, ldb.Vip, id, constants.PENDING_DELETE) + self._ensure_vip_delete_conditions(context, id) + #mmadhavs v = self.get_vip(context, id) driver = self._get_driver_for_pool(context, v['pool_id']) driver.delete_vip(context, v) + def update_ssl_policy(self, context, id, ssl_policy): + new_policy = super(LoadBalancerPlugin, self).update_ssl_policy( + context, id, ssl_policy) + + with context.session.begin(subtransactions=True): + qry = context.session.query( + ldb.Vip + ).filter_by( + ssl_policy_id=new_policy['id']) + + for vip in qry: + driver = self._get_driver_for_vip_ssl(context, vip['id']) + driver.update_ssl_policy(context, new_policy, vip['id']) + return new_policy + + def update_ssl_certificate(self, context, id, ssl_certificate): + new_cert = super(LoadBalancerPlugin, self).update_ssl_certificate( + context, id, ssl_certificate) + with context.session.begin(subtransactions=True): + qry = context.session.query( + ssldb.VipSSLCertificateAssociation + ).filter_by(ssl_certificate_id=new_cert['id']) + for assoc in qry: + driver = self._get_driver_for_vip_ssl(context, assoc['vip_id']) + driver.update_ssl_certificate(context, new_cert, + assoc['vip_id']) + return new_cert + + def update_ssl_trusted_certificate(self, context, id, + ssl_trusted_certificate): + new_trusted_cert = super(LoadBalancerPlugin, + self).update_ssl_trusted_certificate( + context, id, ssl_trusted_certificate) + + with context.session.begin(subtransactions=True): + qry = context.session.query( + ssldb.VipSSLTrustedCertificateAssociation + ).filter_by( + ssl_trusted_certificate_id=new_trusted_cert['id']).join( + ldb.Vip) + + for assoc in qry: + driver = self._get_driver_for_vip_ssl(context, assoc['vip_id']) + driver.update_ssl_trusted_certificate(context, + new_trusted_cert, + assoc['vip_id']) + + return new_trusted_cert + + def create_vip_ssl_association(self, context, + ssl_association, vip_id): + res = super(LoadBalancerPlugin, self).create_vip_ssl_association( + context, ssl_association, vip_id) + + if ssl_association['ssl_association']['ssl_policy']: + policy_id = ssl_association[ + 'ssl_association']['ssl_policy']['id'] + else: + policy_id = None + + cert_ids_keys = dict((cert['id'], cert['private_key']) + for cert in ssl_association[ + 'ssl_association']['ssl_certificates']) + trusted_cert_ids = [ + cert['id'] for cert in ssl_association[ + 'ssl_association']['ssl_trusted_certificates']] + + ssl_policy = self.get_ssl_policy(context, policy_id) + ssl_certificates = self.get_ssl_certificates( + context, filters={'id': cert_ids_keys.keys()}) + ssl_trusted_certificates = self.get_ssl_trusted_certificates( + context, filters={'id': trusted_cert_ids} + ) + + # Append the private keys to certificates + for cert in ssl_certificates: + cert.update({'private_key': cert_ids_keys[cert['id']]}) + + vip = self.get_vip(context, vip_id) + driver = self._get_driver_for_vip_ssl(context, vip_id) + driver.create_vip_ssl_association(context, + ssl_policy, ssl_certificates, + ssl_trusted_certificates, vip) + + return res + + def delete_vip_ssl_association(self, context, id, vip_id): + vip = self.get_vip(context, vip_id) + driver = self._get_driver_for_vip_ssl(context, vip_id) + driver.delete_vip_ssl_association(context, vip) + + res = super(LoadBalancerPlugin, self).delete_vip_ssl_association( + context, id, vip_id) + return res + def _get_provider_name(self, context, pool): if ('provider' in pool and pool['provider'] != attrs.ATTR_NOT_SPECIFIED): @@ -153,7 +284,15 @@ def create_pool(self, context, pool): #because provider was not known to db plugin at pool creation p['provider'] = provider_name driver = self.drivers[provider_name] - driver.create_pool(context, p) + try: + driver.create_pool(context, p) + except loadbalancer.NoEligibleBackend: + # that should catch cases when backend of any kind + # is not available (agent, appliance, etc) + self.update_status(context, ldb.Pool, + p['id'], constants.ERROR, + "No eligible backend") + raise loadbalancer.NoEligibleBackend(pool_id=p['id']) return p def update_pool(self, context, id, pool): @@ -168,13 +307,28 @@ def update_pool(self, context, id, pool): def _delete_db_pool(self, context, id): # proxy the call until plugin inherits from DBPlugin # rely on uuid uniqueness: - with context.session.begin(subtransactions=True): - self.service_type_manager.del_resource_associations(context, [id]) - super(LoadBalancerPlugin, self).delete_pool(context, id) + try: + with context.session.begin(subtransactions=True): + self.service_type_manager.del_resource_associations( + context, [id]) + super(LoadBalancerPlugin, self).delete_pool(context, id) + except Exception: + # that should not happen + # if it's still a case - something goes wrong + # log the error and mark the pool as ERROR + LOG.error(_('Failed to delete pool %s, putting it in ERROR state'), + id) + with excutils.save_and_reraise_exception(): + self.update_status(context, ldb.Pool, + id, constants.ERROR) def delete_pool(self, context, id): - self.update_status(context, ldb.Pool, - id, constants.PENDING_DELETE) + # check for delete conditions and update the status + # within a transaction to avoid a race + with context.session.begin(subtransactions=True): + self.update_status(context, ldb.Pool, + id, constants.PENDING_DELETE) + self._ensure_pool_delete_conditions(context, id) p = self.get_pool(context, id) driver = self._get_driver_for_provider(p['provider']) driver.delete_pool(context, p) diff --git a/neutron/vm/__init__.py b/neutron/vm/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/vm/constants.py b/neutron/vm/constants.py new file mode 100644 index 00000000000..62ce9137a79 --- /dev/null +++ b/neutron/vm/constants.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013, 2014 Intel Corporation. +# Copyright 2013, 2014 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel Corporation. + +# sevice type +SVC_TYPE_ROUTER = 'router' +SVC_TYPE_LOADBALANCER = 'loadbalancer' + +# attribute key for service to spin up device +## for nova driver. novaclient library uses those +ATTR_KEY_IMAGE = 'image' +ATTR_KEY_FLAVOR = 'flavor' + + +# Role of service context +ROLE_NONE = 'None' +ROLE_MGMT = 'mgmt' +ROLE_TWOLEG_INGRESS = 'two-leg-ingress' +ROLE_TWOLEG_EGRESS = 'two-leg-egress' diff --git a/neutron/vm/drivers/__init__.py b/neutron/vm/drivers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/vm/drivers/abstract_driver.py b/neutron/vm/drivers/abstract_driver.py new file mode 100644 index 00000000000..ecb06c9f9d1 --- /dev/null +++ b/neutron/vm/drivers/abstract_driver.py @@ -0,0 +1,69 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013, 2014 Intel Corporation. +# Copyright 2013, 2014 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel Corporation. + +import abc + +import six + +from neutron.api import extensions + + +@six.add_metaclass(abc.ABCMeta) +class DeviceAbstractDriver(extensions.PluginInterface): + + @abc.abstractmethod + def get_type(self): + """Return one of predefined type of the hosting device drivers.""" + pass + + @abc.abstractmethod + def get_name(self): + """Return a symbolic name for the service VM plugin.""" + pass + + @abc.abstractmethod + def get_description(self): + pass + + @abc.abstractmethod + def create(self, plugin, context, device): + """Create device and return its id.""" + + @abc.abstractmethod + def create_wait(self, plugin, context, device_id): + """wait for device creation to complete.""" + + @abc.abstractmethod + def update(self, plugin, context, device): + pass + + @abc.abstractmethod + def update_wait(self, plugin, context, device_id): + pass + + @abc.abstractmethod + def delete(self, plugin, context, device_id): + pass + + @abc.abstractmethod + def delete_wait(self, plugin, context, device_id): + pass diff --git a/neutron/vm/hosting_device_scheduler.py b/neutron/vm/hosting_device_scheduler.py new file mode 100644 index 00000000000..33a6a5ff141 --- /dev/null +++ b/neutron/vm/hosting_device_scheduler.py @@ -0,0 +1,130 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013, 2014 Intel Corporation. +# Copyright 2013, 2014 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel Corporation. + +import random + +import sqlalchemy as sa + +from neutron.db.vm import vm_db +from neutron.openstack.common import log as logging +from neutron.plugins.common import constants + + +LOG = logging.getLogger(__name__) + + +class ChanceScheduler(object): + + """Select a Device that can serve a service in a random way.""" + + def schedule(self, plugin, context, + service_type, service_instance_id, name, service_context): + """ + :param service_context: list of DeviceServiceContext + without service_instance_id + [{'network_id': network_id, + 'subnet_id': subnet_id, + 'port_id': port_id, + 'router_id': router_id, + 'role': role, + 'index': index}, + ... ] + They can be missing or None = don't care + """ + with context.session.begin(subtransactions=True): + # Race. prevent from inserting ServiceDeviceBinding + + # select hosting device that is capable of service_type, but + # not yet used for it. + # i.e. + # device.service_type in + # [st.service_types for st in + # device.template.service_types] + # and + # device.sevice_type not in + # [ls.service_type for ls in device.services] + query = ( + context.session.query(vm_db.Device). + filter(vm_db.Device.status == constants.ACTIVE). + filter( + sa.exists(). + where(sa.and_( + vm_db.Device.template_id == vm_db.DeviceTemplate.id, + vm_db.DeviceTemplate.id == + vm_db.ServiceType.template_id, + vm_db.ServiceType.service_type == service_type))). + filter( + ~sa.exists(). + where(sa.and_( + vm_db.Device.id == + vm_db.ServiceDeviceBinding.device_id, + vm_db.ServiceDeviceBinding.service_instance_id == + vm_db.ServiceInstance.id, + vm_db.ServiceInstance.service_type_id == + vm_db.ServiceType.id, + vm_db.ServiceType.service_type == service_type)))) + + for sc_entry in service_context: + network_id = sc_entry.get('network_id') + subnet_id = sc_entry.get('subnet_id') + port_id = sc_entry.get('port_id') + router_id = sc_entry.get('router_id') + role = sc_entry.get('role') + index = sc_entry.get('index') + + expr = [ + vm_db.Device.id == vm_db.DeviceServiceContext.device_id] + if network_id is not None: + expr.append( + vm_db.DeviceServiceContext.network_id == network_id) + if subnet_id is not None: + expr.append( + vm_db.DeviceServiceContext.subnet_id == subnet_id) + if port_id is not None: + expr.append(vm_db.DeviceServiceContext.port_id == port_id) + if router_id is not None: + expr.append( + vm_db.DeviceServiceContext.router_id == router_id) + if role is not None: + expr.append(vm_db.DeviceServiceContext.role == role) + if index is not None: + expr.append(vm_db.DeviceServiceContext.index == index) + query = query.filter(sa.exists().where(sa.and_(*expr))) + + candidates = query.with_lockmode("update").all() + if not candidates: + LOG.debug(_('no hosting device supporing %s'), service_type) + return + device = random.choice(candidates) + + service_type_id = [s.id for s in device.template.service_types + if s.service_type == service_type][0] + + service_instance_param = { + 'name': name, + 'service_table_id': service_instance_id, + 'service_type': service_type, + 'service_type_id': service_type_id, + } + service_instance_dict = plugin._create_service_instance( + context, device.id, service_instance_param) + return (plugin._make_device_dict(device), service_instance_dict) diff --git a/neutron/vm/mgmt_drivers/__init__.py b/neutron/vm/mgmt_drivers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/vm/mgmt_drivers/abstract_driver.py b/neutron/vm/mgmt_drivers/abstract_driver.py new file mode 100644 index 00000000000..26b8b81f3b9 --- /dev/null +++ b/neutron/vm/mgmt_drivers/abstract_driver.py @@ -0,0 +1,101 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013, 2014 Intel Corporation. +# Copyright 2013, 2014 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel Corporation. + +import abc + +import six + +from neutron.api import extensions +from neutron.openstack.common import jsonutils +from neutron.vm import constants + + +@six.add_metaclass(abc.ABCMeta) +class DeviceMGMTAbstractDriver(extensions.PluginInterface): + + @abc.abstractmethod + def get_type(self): + """Return one of predefined type of the hosting device drivers.""" + pass + + @abc.abstractmethod + def get_name(self): + """Return a symbolic name for the service VM plugin.""" + pass + + @abc.abstractmethod + def get_description(self): + pass + + @abc.abstractmethod + def mgmt_address(self, plugin, context, device): + pass + + @abc.abstractmethod + def mgmt_call(self, plugin, context, device, kwargs): + pass + + def mgmt_service_driver(self, plugin, context, device, service_instance): + # use same mgmt driver to communicate with service + return self.get_name() + + @abc.abstractmethod + def mgmt_service_address(self, plugin, context, device, service_instance): + pass + + @abc.abstractmethod + def mgmt_service_call(self, plugin, context, device, + service_instance, kwargs): + pass + + +class DeviceMGMTByNetwork(DeviceMGMTAbstractDriver): + def mgmt_address(self, plugin, context, device): + mgmt_entries = [sc_entry for sc_entry in device.service_context + if (sc_entry.role == constants.ROLE_MGMT and + sc_entry.port_id)] + if not mgmt_entries: + return + port = plugin._core_plugin.get_port(context, mgmt_entries[0].port_id) + if not port: + return + mgmt_address = port['fixed_ips'][0] # subnet_id and ip_address + mgmt_address['network_id'] = port['network_id'] + mgmt_address['port_id'] = port['id'] + mgmt_address['mac_address'] = port['mac_address'] + return jsonutils.dumps(mgmt_address) + + def mgmt_service_address(self, plugin, context, device, service_instance): + mgmt_entries = [sc_entry for sc_entry + in service_instance.service_context + if (sc_entry.role == constants.ROLE_MGMT and + sc_entry.port_id)] + if not mgmt_entries: + return + port = plugin._core_plugin.get_port(context, mgmt_entries[0].port_id) + if not port: + return + mgmt_address = port['fixed_ips'][0] # subnet_id and ip_address + mgmt_address['network_id'] = port['network_id'] + mgmt_address['port_id'] = port['id'] + mgmt_address['mac_address'] = port['mac_address'] + return jsonutils.dumps(mgmt_address) diff --git a/neutron/vm/mgmt_drivers/constants.py b/neutron/vm/mgmt_drivers/constants.py new file mode 100644 index 00000000000..6418cf13560 --- /dev/null +++ b/neutron/vm/mgmt_drivers/constants.py @@ -0,0 +1,33 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013, 2014 Intel Corporation. +# Copyright 2013, 2014 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel Corporation. + +# key +KEY_ACTION = 'action' +KEY_KWARGS = 'kwargs' + +# ACTION type +ACTION_CREATE = 'create' +ACTION_UPDATE = 'update' +ACTION_DELETE = 'delete' +ACTION_CREATE_SERVICE = 'create_service' +ACTION_UPDATE_SERVICE = 'update_service' +ACTION_DELETE_SERVICE = 'delete_service' diff --git a/neutron/vm/plugin.py b/neutron/vm/plugin.py new file mode 100644 index 00000000000..38656ea83f8 --- /dev/null +++ b/neutron/vm/plugin.py @@ -0,0 +1,646 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013, 2014 Intel Corporation. +# Copyright 2013, 2014 Isaku Yamahata +# +# 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. +# +# @author: Isaku Yamahata, Intel Corporation. + +import eventlet +from oslo.config import cfg +import webob.exc +import pdb +from neutron.api.v2 import attributes +from neutron.common import driver_manager +from neutron.db.vm import vm_db +from neutron.extensions import servicevm +from neutron.openstack.common import excutils +from neutron.openstack.common import log as logging +from neutron.plugins.common import constants +from neutron.vm.mgmt_drivers import constants as mgmt_constants +from neutron.plugins.common import constants +from neutron import manager +from neutron.db import l3_db +from neutronclient.v2_0 import client +LOG = logging.getLogger(__name__) + + +class ServiceVMMgmtMixin(object): + OPTS = [ + cfg.MultiStrOpt( + 'mgmt_driver', default=[], + help=_('MGMT driver to communicate with ' + 'Hosting Device/logical service ' + 'instance servicevm plugin will use')), + ] + cfg.CONF.register_opts(OPTS, 'servicevm') + + def __init__(self): + super(ServiceVMMgmtMixin, self).__init__() + self._mgmt_manager = driver_manager.DriverManager( + 'neutron.servicevm.mgmt.drivers', cfg.CONF.servicevm.mgmt_driver) + + def mgmt_address(self, context, device_dict): + return self._mgmt_manager.invoke( + self._mgmt_device_driver(device_dict), 'mgmt_address', plugin=self, + context=context, device=device_dict) + + def mgmt_call(self, context, device_dict, kwargs): + return self._mgmt_manager.invoke( + self._mgmt_device_driver(device_dict), 'mgmt_call', plugin=self, + context=context, device=device_dict, kwargs=kwargs) + + def mgmt_service_driver(self, context, device_dict, service_instance_dict): + return self._mgmt_manager.invoke( + self._mgmt_device_driver(device_dict), 'mgmt_service_driver', + plugin=self, context=context, device=device_dict, + service_instance=service_instance_dict) + + def mgmt_service_address(self, context, device_dict, + service_instance_dict): + return self._mgmt_manager.invoke( + self._mgmt_service_driver(service_instance_dict), + 'mgmt_service_address', plugin=self, context=context, + device=device_dict, service_instance=service_instance_dict) + + def mgmt_service_call(self, context, device_dict, service_instance_dict, + kwargs): + return self._mgmt_manager.invoke( + self._mgmt_service_driver(service_instance_dict), + 'mgmt_service_call', plugin=self, context=context, + device=device_dict, service_instance=service_instance_dict, + kwargs=kwargs) + + +class ServiceVMPlugin(vm_db.ServiceResourcePluginDb, ServiceVMMgmtMixin): + """ServiceVMPlugin which supports ServiceVM framework + """ + OPTS = [ + cfg.MultiStrOpt( + 'device_driver', default=[], + help=_('Hosting device drivers servicevm plugin will use')), + ] + cfg.CONF.register_opts(OPTS, 'servicevm') + supported_extension_aliases = ['servicevm'] + + def __init__(self): + super(ServiceVMPlugin, self).__init__() + self._pool = eventlet.GreenPool() + self._device_manager = driver_manager.DriverManager( + 'neutron.servicevm.device.drivers', + cfg.CONF.servicevm.device_driver) + + def spawn_n(self, function, *args, **kwargs): + self._pool.spawn_n(function, *args, **kwargs) + + ########################################################################### + # hosting device template + + def create_device_template(self, context, device_template): + if not context.is_admin: + msg='Unauthorized, only admin user can access' + raise webob.exc.HTTPBadRequest(msg) + template = device_template['device_template'] + LOG.debug(_('template %s'), template) + device_driver = template.get('device_driver') + if not attributes.is_attr_set(device_driver): + LOG.debug(_('hosting device driver must be specified')) + raise servicevm.DeviceDriverNotSpecified() + if device_driver not in self._device_manager: + LOG.debug(_('unknown hosting device driver ' + '%(device_driver)s in %(drivers)s'), + {'device_driver': device_driver, + 'drivers': cfg.CONF.servicevm.device_driver}) + #raise servicevm.InvalidDeviceDriver(device_driver=device_driver) + + mgmt_driver = template.get('mgmt_driver') + if not attributes.is_attr_set(mgmt_driver): + LOG.debug(_('mgmt driver must be specified')) + #raise servicevm.MgmtDriverNotSpecified() + if mgmt_driver not in self._mgmt_manager: + LOG.debug(_('unknown mgmt driver %(mgmt_driver)s in %(drivers)s'), + {'mgmt_driver': mgmt_driver, + 'drivers': cfg.CONF.servicevm.mgmt_driver}) + #raise servicevm.InvalidMgmtDriver(mgmt_driver=mgmt_driver) + tenantId = template.get('tenant_id') + if not attributes.is_attr_set(tenantId): + tenantId=context.__dict__['tenant'] + + service_types = template.get('service_types') + scope = template.get('scope') + if not attributes.is_attr_set(service_types): + LOG.debug(_('service type must be specified')) + raise servicevm.ServiceTypesNotSpecified() + for service_type in service_types: + # TODO(yamahata): + # framework doesn't know what services are valid for now. + # so doesn't check it here yet. + if scope=='global': + isExist = self.check_global_policy_for_service(context,service_type['service_type']) + if isExist: + user_msg = 'Global Deployment Policy for the service type \'%s\' exists' %(service_type['service_type']) + raise webob.exc.HTTPBadRequest(user_msg) + if self.isdevicetemplate_inTenant(context,service_type['service_type'],tenantId): + LOG.debug(_('Device Template for service type exists in tenant')) + raise servicevm.deplomentpolicyExists() + if service_type['service_type'] == 'l3router': + if self.isrouter_inTenant(context,tenantId): + LOG.debug(_('service type exist in tenant')) + raise servicevm.Serviceinuse(service_type=service_type['service_type']) + if service_type['service_type'] == 'lbaas': + if self.isvip_inTenant(context,tenantId): + LOG.debug(_('service type exist in tenant')) + raise servicevm.Serviceinuse(service_type=service_type['service_type']) + attribute_dict = template.get('attributes') + mandatory_attributes = ['admin_user','admin_password','platform'] + all_attributes = ['enable_ha','admin_user','admin_password','platform','version','throughput_level','feature_level','availability_zone','availability_zone_primary','availability_zone_secondary','license_category'] + msg = attributes._verify_dict_keys(mandatory_attributes,attribute_dict,False) + if msg: + LOG.debug(msg) + user_msg = 'Madatory attributes missing!! Mandatory attributes are %s but given %s' %(mandatory_attributes,attribute_dict.keys()) + raise webob.exc.HTTPBadRequest(user_msg) + msg=self.validateAttr(attribute_dict,all_attributes) + if msg: + LOG.debug(msg) + raise webob.exc.HTTPBadRequest(msg) + + return super(ServiceVMPlugin, self).create_device_template( + context, device_template) + + ########################################################################### + # hosting device + def delete_device_template(self, context, device_template_id): + if not context.is_admin: + msg='Unauthorized, only admin user can access' + raise webob.exc.HTTPBadRequest(msg) + device_template=self.get_device_template(context, device_template_id) + tenant_id=device_template['tenant_id'] + service_type=device_template['service_types'][0] + if service_type['service_type'] == 'l3router': + if self.isrouter_inTenant(context,tenant_id): + LOG.debug(_('service type exist in tenant')) + raise servicevm.Serviceinuse(service_type=service_type['service_type']) + if service_type['service_type'] == 'lbaas': + if self.isvip_inTenant(context,tenant_id): + LOG.debug(_('service type exist in tenant')) + raise servicevm.Serviceinuse(service_type=service_type['service_type']) + return super(ServiceVMPlugin, self).delete_device_template( + context, device_template_id) + + def get_device_template(self, context, device_template_id, fields=None): + if not context.is_admin: + msg='Unauthorized, only admin user can access' + raise webob.exc.HTTPBadRequest(msg) + return super(ServiceVMPlugin, self).get_device_template(context, device_template_id, fields=None) + + def get_device_templates(self, context, filters=None, fields=None): + if not context.is_admin: + msg='Unauthorized, only admin user can access' + raise webob.exc.HTTPBadRequest(msg) + return super(ServiceVMPlugin, self).get_device_templates(context, filters, fields=None) + + + def _create_device_wait(self, context, device_dict): + driver_name = self._device_driver_name(device_dict) + device_id = device_dict['id'] + instance_id = self._instance_id(device_dict) + + try: + self._device_manager.invoke( + driver_name, 'create_wait', plugin=self, + context=context, device_id=instance_id) + except servicevm.DeviceCreateWaitFailed: + instance_id = None + del device_dict['instance_id'] + + if instance_id is None: + mgmt_address = None + else: + mgmt_address = self.mgmt_address(context, device_dict) + + self._create_device_post( + context, device_id, instance_id, mgmt_address, + device_dict['service_context']) + if instance_id is None: + return + + kwargs = { + mgmt_constants.KEY_ACTION: mgmt_constants.ACTION_CREATE, + mgmt_constants.KEY_KWARGS: {'device': device_dict}, + } + new_status = constants.ACTIVE + try: + self.mgmt_call(context, device_dict, kwargs) + except Exception: + new_status = constants.ERROR + self._create_device_status(context, device_id, new_status) + + def _create_device(self, context, device): + device_dict = self._create_device_pre(context, device) + device_id = device_dict['id'] + driver_name = self._device_driver_name(device_dict) + LOG.debug(_('device_dict %s'), device_dict) + instance_id = self._device_manager.invoke( + driver_name, 'create', plugin=self, + context=context, device=device_dict) + + if instance_id is None: + self._create_device_post(context, device_id, None, None) + return + + device_dict['instance_id'] = instance_id + return device_dict + + def create_device(self, context, device): + device_dict = self._create_device(context, device) + self.spawn_n(self._create_device_wait, context, device_dict) + return device_dict + + # not for wsgi, but for service to creaste hosting device + def create_device_sync(self, context, template_id, kwargs, + service_context): + assert template_id is not None + device = { + 'device': { + 'template_id': template_id, + 'kwargs': kwargs, + 'service_context': service_context, + }, + } + device_dict = self._create_device(context, device) + if 'instance_id' not in device_dict: + raise servicevm.DeviceCreateFailed(device_template_id=template_id) + + self._create_device_wait(context, device_dict) + if 'instance_id' not in device_dict: + raise servicevm.DeviceCreateFailed(device_template_id=template_id) + + return device_dict + + def _update_device_wait(self, context, device_dict): + driver_name = self._device_driver_name(device_dict) + instance_id = self._instance_id(device_dict) + kwargs = { + mgmt_constants.KEY_ACTION: mgmt_constants.ACTION_UPDATE, + mgmt_constants.KEY_KWARGS: {'device': device_dict}, + } + new_status = constants.ACTIVE + try: + self._device_manager.invoke( + driver_name, 'update_wait', plugin=self, + context=context, device_id=instance_id) + self.mgmt_call(context, device_dict, kwargs) + except Exception: + new_status = constants.ERROR + self._update_device_post(context, device_dict['id'], device_dict, + new_status) + + def update_device(self, context, device_id, device): + device_dict = self._update_device_pre(context, device_id, device) + driver_name = self._device_driver_name(device_dict) + instance_id = self._instance_id(device_dict) + + try: + self._device_manager.invoke(driver_name, 'update', plugin=self, + context=context, device_id=instance_id) + except Exception: + with excutils.save_and_reraise_exception(): + self._update_device_post( + context, device_id, device, constants.ERROR) + + self.spawn_n(self._update_device_wait, context, device_dict) + return device_dict + + def _delete_device_wait(self, context, device_dict): + driver_name = self._device_driver_name(device_dict) + instance_id = self._instance_id(device_dict) + + e = None + try: + self._device_manager.invoke( + driver_name, 'delete_wait', plugin=self, + context=context, device_id=instance_id) + except Exception as e_: + e = e_ + device_id = device_dict['id'] + self._delete_device_post(context, device_id, e) + + def delete_device(self, context, device_id): + device_dict = self._delete_device_pre(context, device_id) + driver_name = self._device_driver_name(device_dict) + instance_id = self._instance_id(device_dict) + kwargs = { + mgmt_constants.KEY_ACTION: mgmt_constants.ACTION_DELETE, + mgmt_constants.KEY_KWARGS: {'device': device_dict}, + } + try: + self.mgmt_call(context, device_dict, kwargs) + self._device_manager.invoke(driver_name, 'delete', plugin=self, + context=context, device_id=instance_id) + except Exception as e: + # TODO(yamahata): when the devaice is already deleted. mask + # the error, and delete row in db + # Other case mark error + with excutils.save_and_reraise_exception(): + self._delete_device_post(context, device_id, e) + + self._delete_device_post(context, device_id, None) + self.spawn_n(self._delete_device_wait, context, device_dict) + + ########################################################################### + # logical service instance + # + def _create_service_instance_mgmt( + self, context, device_dict, service_instance_dict): + kwargs = { + mgmt_constants.KEY_ACTION: mgmt_constants.ACTION_CREATE_SERVICE, + mgmt_constants.KEY_KWARGS: { + 'device': device_dict, + 'service_instance': service_instance_dict, + }, + } + self.mgmt_call(context, device_dict, kwargs) + + mgmt_driver = self.mgmt_service_driver( + context, device_dict, service_instance_dict) + service_instance_dict['mgmt_driver'] = mgmt_driver + mgmt_address = self.mgmt_service_address( + context, device_dict, service_instance_dict) + service_instance_dict['mgmt_address'] = mgmt_address + LOG.debug(_('service_instance_dict ' + '%(service_instance_dict)s ' + 'mgmt_driver %(mgmt_driver)s ' + 'mgmt_address %(mgmt_address)s'), + {'service_instance_dict': + service_instance_dict, + 'mgmt_driver': mgmt_driver, 'mgmt_address': mgmt_address}) + self._update_service_instance_mgmt( + context, service_instance_dict['id'], mgmt_driver, mgmt_address) + + self.mgmt_service_call( + context, device_dict, service_instance_dict, kwargs) + + def _create_service_instance_by_type( + self, context, device_dict, + name, service_type, service_table_id): + LOG.debug(_('device_dict %(device_dict)s ' + 'service_type %(service_type)s'), + {'device_dict': device_dict, + 'service_type': service_type}) + service_type_id = [ + s['id'] for s in + device_dict['device_template']['service_types'] + if s['service_type'].upper() == service_type.upper()][0] + + service_instance_param = { + 'name': name, + 'service_table_id': service_table_id, + 'service_type': service_type, + 'service_type_id': service_type_id, + } + service_instance_dict = self._create_service_instance( + context, device_dict['id'], service_instance_param) + + self._create_service_instance_mgmt( + context, device_dict, service_instance_dict) + return service_instance_dict + + # for service drivers. e.g. hosting_driver of loadbalancer + def create_service_instance_by_type(self, context, device_dict, + name, service_type, service_table_id): + self._update_device_pre(context, device_dict['id'], device_dict) + new_status = constants.ACTIVE + try: + return self._create_service_instance_by_type( + context, device_dict, name, service_type, + service_table_id) + except Exception: + LOG.exception(_('create_service_instance_by_type')) + new_status = constants.ERROR + finally: + self._update_device_post(context, device_dict['id'], device_dict, + new_status) + + def _create_service_instance_wait(self, context, device_id, + service_instance_dict): + device_dict = self.get_device(context, device_id) + + new_status = constants.ACTIVE + try: + self._create_service_instance_mgmt( + context, device_dict, service_instance_dict) + except Exception: + LOG.exception(_('_create_service_instance_mgmt')) + new_status = constants.ERROR + self._update_service_instance_post( + context, service_instance_dict['id'], new_status) + + # for service drivers. e.g. hosting_driver of loadbalancer + def _create_service_instance(self, context, device_id, + service_instance_param): + service_instance_dict = super( + ServiceVMPlugin, self)._create_service_instance( + context, device_id, service_instance_param) + self.spawn_n(self._create_service_instance_wait, context, + device_id, service_instance_dict) + return service_instance_dict + + def create_service_instance(self, context, service_instance): + raise webob.exc.HTTPMethodNotAllowed() + + def _update_service_instance_wait(self, context, service_instance_dict, + mgmt_kwargs, callback, errorback): + devices = service_instance_dict['devices'] + assert len(devices) == 1 + device_dict = self.get_device(context, devices[0]) + kwargs = { + mgmt_constants.KEY_ACTION: mgmt_constants.ACTION_UPDATE_SERVICE, + mgmt_constants.KEY_KWARGS: mgmt_kwargs, + } + try: + self.mgmt_service_call(context, device_dict, + service_instance_dict, kwargs) + self.mgmt_call(context, device_dict, kwargs) + except Exception: + LOG.exception(_('mgmt call failed %s'), kwargs) + self._update_service_instance_post( + context, service_instance_dict['id'], constants.ERROR) + if errorback: + errorback() + else: + self._update_service_instance_post( + context, service_instance_dict['id'], constants.ACTIVE) + if callback: + callback() + + # for service drivers. e.g. hosting_driver of loadbalancer + def _update_service_instance(self, context, service_instance_id, + mgmt_kwargs, callback, errorback): + service_instance_dict = self._update_service_instance_pre( + context, service_instance_id, {}) + self.spawn_n(self._update_service_instance_wait, context, + service_instance_dict, mgmt_kwargs, callback, errorback) + + # for service drivers. e.g. hosting_driver of loadbalancer + def _update_service_table_instance( + self, context, service_table_id, mgmt_kwargs, callback, errorback): + + _device_dict, service_instance_dict = self.get_by_service_table_id( + context, service_table_id) + self.spawn_n(self._update_service_instance_wait, context, + service_instance_dict, mgmt_kwargs, callback, errorback) + + def update_service_instance(self, context, service_instance_id, + service_instance): + mgmt_kwargs = service_instance['service_instance'].get('kwarg', {}) + service_instance_dict = self._update_service_instance_pre( + context, service_instance_id, service_instance) + + self.spawn_n(self._update_service_instance_wait, context, + service_instance_dict, mgmt_kwargs, None, None) + return service_instance_dict + + def _delete_service_instance_wait(self, context, device, service_instance, + mgmt_kwargs, callback, errorback): + service_instance_id = service_instance['id'] + kwargs = { + mgmt_constants.KEY_ACTION: mgmt_constants.ACTION_DELETE_SERVICE, + mgmt_constants.KEY_KWARGS: mgmt_kwargs, + 'device': device, + 'service_instance': service_instance, + } + try: + self.mgmt_service_call(context, device, service_instance, kwargs) + self.mgmt_call(context, device, kwargs) + except Exception: + LOG.exception(_('mgmt call failed %s'), kwargs) + self._update_service_instance_post(context, service_instance_id, + constants.ERROR) + if errorback: + errorback() + else: + self._delete_service_instance_post(context, service_instance_id) + if callback: + callback() + + # for service drivers. e.g. hosting_driver of loadbalancer + def _delete_service_table_instance( + self, context, service_table_instance_id, + mgmt_kwargs, callback, errorback): + device, service_instance = self._device_plugin.get_by_service_table_id( + context, service_table_instance_id) + self._delete_service_instance_pre(context, service_instance['id']) + self._pool_spawn_n( + self._delete_service_instance_wait, context, device, + service_instance, mgmt_kwargs, callback, errorback) + + def delete_service_instance(self, context, service_instance_id): + raise webob.exc.HTTPMethodNotAllowed() + def isrouter_inTenant(self,context,tenant_id): + l3plugin = manager.NeutronManager.get_service_plugins().get(constants.L3_ROUTER_NAT) + lbaasplugin= manager.NeutronManager.get_service_plugins()[ + constants.LOADBALANCER] + routers=l3plugin.get_routers( context) + for router in routers: + if router['tenant_id']==tenant_id: + return True + return False + + def isvip_inTenant(self,context,tenant_id): + lbaasplugin= manager.NeutronManager.get_service_plugins()[ + constants.LOADBALANCER] + vips=lbaasplugin.get_vips(context) + for vip in vips: + if vip['tenant_id']==tenant_id: + return True + return False + def isdevicetemplate_inTenant(self,context,service_type,tenant_id): + device_templates = self.get_device_templates(context,None) + for servicetypes in device_templates: + if servicetypes['tenant_id'] == tenant_id: + service = servicetypes['service_types'] + service_name =service[0] + if service_name['service_type']==service_type: + return True + return False + + def check_global_policy_for_service(self,context,service_type): + nc=self.get_neutron_client() + templates = nc.list_device_templates() + device_templates = templates['device_templates'] + for servicetypes in device_templates: + if servicetypes['tenant_id'] == constants.GLOBAL_DEVICE_TEMPLATE_TID: + service = servicetypes['service_types'] + service_name =service[0] + if service_name['service_type']==service_type: + return True + + def validateAttr(self,attribute_dict,supported_attributes): + for attr,value in attribute_dict.items(): + if attr in supported_attributes: + if attr=='enable_ha': + supported_values = ['true','false'] + msg=attributes._validate_values(value,supported_values) + if msg: + msg = 'Unsupported value is given for the attribute \'enable_ha\' supported values are %s' % (supported_values) + elif attr=='admin_user': + msg=self.isempty(attr,value) + elif attr=='admin_password': + msg=self.isempty(attr,value) + elif attr == 'platform': + supported_platforms = ['Csr1000v','Netscaler1000V','l3agent'] + msg=attributes._validate_values(value,supported_platforms) + if msg: + msg = 'Unsupported value is given for the attribute \'platform\' supported platforms are %s' % (supported_platforms) + elif attr == 'availability_zone': + if 'enable_ha' in attribute_dict and attribute_dict['enable_ha']=='false': + LOG.debug('Availability zone set for Non HA') + else: + msg =' Attribute \'availibilty_zone\' is applicable only for Non-HA . Set attribute enable_ha = false' + elif attr == 'availability_zone_primary' or attr == 'availability_zone_secondary': + if 'enable_ha' in attribute_dict and attribute_dict['enable_ha']=='true': + LOG.debug('Availability zone set for HA') + else: + msg ='Attribute \'%s\' is applicable only for HA . Set attribute enable_ha = true' % (attr) + else: + msg=None + else: + msg = 'Unknown attribute \'%s\' supported attributes are %s'%(attr,supported_attributes) + if msg: + return msg + return msg + def isempty(self,attr,value): + if value.strip()=='': + msg = 'Attribute %s should not be empty' %(attr) + else: + msg = None + return msg + def get_neutron_client(self): + try: + auth_host = cfg.CONF.keystone_authtoken.auth_host + auth_port = cfg.CONF.keystone_authtoken.auth_port + admin_tenant_name = cfg.CONF.keystone_authtoken.admin_tenant_name + admin_user = cfg.CONF.keystone_authtoken.admin_user + admin_password = cfg.CONF.keystone_authtoken.admin_password + nc = client.Client(username=admin_user, + password=admin_password, + tenant_name=admin_tenant_name, + auth_url='http://'+auth_host+':'+str(auth_port)+'/v2.0') + return nc + except Exception: + raise +