trove/trove/cluster/service.py

201 lines
8.6 KiB
Python

# Copyright 2014 eBay Software Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config.cfg import NoSuchOptError
from oslo_log import log as logging
from trove.cluster import models
from trove.cluster import views
from trove.common import apischema
from trove.common import cfg
from trove.common import exception
from trove.common.i18n import _
from trove.common import notification
from trove.common.notification import StartNotification
from trove.common import pagination
from trove.common import utils
from trove.common import wsgi
from trove.datastore import models as datastore_models
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class ClusterController(wsgi.Controller):
"""Controller for cluster functionality."""
schemas = apischema.cluster.copy()
@classmethod
def get_action_schema(cls, body, action_schema):
action_type = list(body.keys())[0]
return action_schema.get(action_type, {})
@classmethod
def get_schema(cls, action, body):
action_schema = super(ClusterController, cls).get_schema(action, body)
if action == 'action':
action_schema = cls.get_action_schema(body, action_schema)
return action_schema
def action(self, req, body, tenant_id, id):
LOG.debug(("Committing Action Against Cluster for "
"Tenant '%(tenant_id)s'\n"
"req : '%(req)s'\n\nid : '%(id)s'\n\n") %
{"req": req, "id": id, "tenant_id": tenant_id})
if not body:
raise exception.BadRequest(_("Invalid request body."))
if len(body) != 1:
raise exception.BadRequest(_("Action request should have exactly"
" one action specified in body"))
context = req.environ[wsgi.CONTEXT_KEY]
cluster = models.Cluster.load(context, id)
cluster.action(context, req, *next(iter(body.items())))
view = views.load_view(cluster, req=req, load_servers=False)
wsgi_result = wsgi.Result(view.data(), 202)
return wsgi_result
def show(self, req, tenant_id, id):
"""Return a single cluster."""
LOG.debug(("Showing a Cluster for Tenant '%(tenant_id)s'\n"
"req : '%(req)s'\n\nid : '%(id)s'\n\n") %
{"req": req, "id": id, "tenant_id": tenant_id})
context = req.environ[wsgi.CONTEXT_KEY]
cluster = models.Cluster.load(context, id)
return wsgi.Result(views.load_view(cluster, req=req).data(), 200)
def show_instance(self, req, tenant_id, cluster_id, instance_id):
"""Return a single instance belonging to a cluster."""
LOG.debug(("Showing an Instance in a Cluster for Tenant "
"'%(tenant_id)s'\n"
"req : '%(req)s'\n\n"
"cluster_id : '%(cluster_id)s'\n\n"
"instance_id : '%(instance_id)s;\n\n") %
{"req": req, "tenant_id": tenant_id,
"cluster_id": cluster_id,
"instance_id": instance_id})
context = req.environ[wsgi.CONTEXT_KEY]
cluster = models.Cluster.load(context, cluster_id)
instance = models.Cluster.load_instance(context, cluster.id,
instance_id)
return wsgi.Result(views.ClusterInstanceDetailView(
instance, req=req).data(), 200)
def delete(self, req, tenant_id, id):
"""Delete a cluster."""
LOG.debug(("Deleting a Cluster for Tenant '%(tenant_id)s'\n"
"req : '%(req)s'\n\nid : '%(id)s'\n\n") %
{"req": req, "id": id, "tenant_id": tenant_id})
context = req.environ[wsgi.CONTEXT_KEY]
cluster = models.Cluster.load(context, id)
context.notification = notification.DBaaSClusterDelete(context,
request=req)
with StartNotification(context, cluster_id=id):
cluster.delete()
return wsgi.Result(None, 202)
def index(self, req, tenant_id):
"""Return a list of clusters."""
LOG.debug(("Showing a list of clusters for Tenant '%(tenant_id)s'\n"
"req : '%(req)s'\n\n") % {"req": req,
"tenant_id": tenant_id})
context = req.environ[wsgi.CONTEXT_KEY]
if not context.is_admin and context.tenant != tenant_id:
raise exception.TroveOperationAuthError(tenant_id=context.tenant)
# load all clusters and instances for the tenant
clusters, marker = models.Cluster.load_all(context, tenant_id)
view = views.ClustersView(clusters, req=req)
paged = pagination.SimplePaginatedDataView(req.url, 'clusters', view,
marker)
return wsgi.Result(paged.data(), 200)
def create(self, req, body, tenant_id):
LOG.debug(("Creating a Cluster for Tenant '%(tenant_id)s'\n"
"req : '%(req)s'\n\nbody : '%(body)s'\n\n") %
{"tenant_id": tenant_id, "req": req, "body": body})
context = req.environ[wsgi.CONTEXT_KEY]
name = body['cluster']['name']
datastore_args = body['cluster'].get('datastore', {})
datastore, datastore_version = (
datastore_models.get_datastore_version(**datastore_args))
# TODO(saurabhs): add extended_properties to apischema
extended_properties = body['cluster'].get('extended_properties', {})
try:
clusters_enabled = (CONF.get(datastore_version.manager)
.get('cluster_support'))
except NoSuchOptError:
clusters_enabled = False
if not clusters_enabled:
raise exception.ClusterDatastoreNotSupported(
datastore=datastore.name,
datastore_version=datastore_version.name)
nodes = body['cluster']['instances']
instances = []
for node in nodes:
flavor_id = utils.get_id_from_href(node['flavorRef'])
volume_size = volume_type = nics = availability_zone = None
modules = None
if 'volume' in node:
volume_size = int(node['volume']['size'])
volume_type = node['volume'].get('type')
if 'nics' in node:
nics = node['nics']
if 'availability_zone' in node:
availability_zone = node['availability_zone']
if 'modules' in node:
modules = node['modules']
instances.append({"flavor_id": flavor_id,
"volume_size": volume_size,
"volume_type": volume_type,
"nics": nics,
"availability_zone": availability_zone,
'region_name': node.get('region_name'),
"modules": modules})
locality = body['cluster'].get('locality')
if locality:
locality_domain = ['affinity', 'anti-affinity']
locality_domain_msg = ("Invalid locality '%s'. "
"Must be one of ['%s']" %
(locality,
"', '".join(locality_domain)))
if locality not in locality_domain:
raise exception.BadRequest(msg=locality_domain_msg)
context.notification = notification.DBaaSClusterCreate(context,
request=req)
with StartNotification(context, name=name, datastore=datastore.name,
datastore_version=datastore_version.name):
cluster = models.Cluster.create(context, name, datastore,
datastore_version, instances,
extended_properties,
locality)
cluster.locality = locality
view = views.load_view(cluster, req=req, load_servers=False)
return wsgi.Result(view.data(), 200)