kingbird/kingbird/api/controllers/v1/sync_manager.py

269 lines
12 KiB
Python

# Copyright (c) 2017 Ericsson AB.
# 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_log import log as logging
from oslo_utils import uuidutils
import collections
import pecan
from pecan import expose
from pecan import request
from kingbird.api.controllers import restcomm
from kingbird.common import consts
from kingbird.common.endpoint_cache import EndpointCache
from kingbird.common import exceptions
from kingbird.common.i18n import _
from kingbird.db.sqlalchemy import api as db_api
from kingbird.drivers.openstack.glance_v2 import GlanceClient
from kingbird.drivers.openstack.nova_v2 import NovaClient
from kingbird.rpc import client as rpc_client
LOG = logging.getLogger(__name__)
class ResourceSyncController(object):
VERSION_ALIASES = {
'Newton': '1.0',
}
def __init__(self, *args, **kwargs):
super(ResourceSyncController, self).__init__(*args, **kwargs)
self.rpc_client = rpc_client.EngineClient()
# to do the version compatibility for future purpose
def _determine_version_cap(self, target):
version_cap = 1.0
return version_cap
@expose(generic=True, template='json')
def index(self):
# Route the request to specific methods with parameters
pass
def _entries_to_database(self, context, target_regions, source_region,
resources, resource_type, result):
"""Manage the entries to database"""
# Insert into the child table
job_ids = list()
for region in target_regions:
for resource in resources:
job_id = uuidutils.generate_uuid()
job_ids.append(job_id)
try:
db_api.resource_sync_create(context, result,
region, source_region,
resource, resource_type,
job_id)
except exceptions.JobNotFound:
pecan.abort(404, _('Job not found'))
return job_ids
@index.when(method='GET', template='json')
def get(self, project, action=None):
"""Get details about Sync Job.
:param project: It's UUID of the project.
:param action: Optional. If provided, it can be
'active' to get the list of active jobs.
'job-id' to get the details of a job.
"""
context = restcomm.extract_context_from_environ()
if not uuidutils.is_uuid_like(project) or project != context.project:
pecan.abort(400, _('Invalid request URL'))
result = collections.defaultdict(dict)
if not action or action == 'active':
result['job_set'] = db_api.sync_job_list(context, action)
elif uuidutils.is_uuid_like(action):
try:
result['job_set'] = db_api.resource_sync_list(
context, action)
except exceptions.JobNotFound:
pecan.abort(404, _('Job not found'))
else:
pecan.abort(400, _('Invalid request URL'))
return result
@index.when(method='POST', template='json')
def post(self, project):
"""Sync resources present in one region to another region.
:param project: It's UUID of the project.
"""
context = restcomm.extract_context_from_environ()
if not uuidutils.is_uuid_like(project) or project != context.project:
pecan.abort(400, _('Invalid request URL'))
request_data = eval(request.body)
if not request_data:
pecan.abort(400, _('Body required'))
request_data = request_data.get('resource_set')
if not request_data:
pecan.abort(400, _('resource_set required'))
parent_job_id = uuidutils.generate_uuid()
try:
result = db_api.sync_job_create(context, parent_job_id)
except exceptions.InternalError:
pecan.abort(500, _('Internal Server Error.'))
if 'Sync' in request_data.keys():
for iteration in range(len(request_data['Sync'])):
payload = request_data['Sync'][iteration]
response = self._get_post_data(payload,
context, result)
else:
response = self._get_post_data(request_data,
context, result)
return response
def _get_post_data(self, payload, context, result):
resource_type = payload.get('resource_type')
target_regions = payload.get('target')
force = eval(str(payload.get('force', False)))
source_regions = list()
if not target_regions or not isinstance(target_regions, list):
pecan.abort(400, _('Target regions required'))
source_region = payload.get('source')
if not source_region:
pecan.abort(400, _('Source region required'))
if isinstance(source_region, list):
source_regions = source_region
if isinstance(source_region, str):
source_regions.append(source_region)
source_resources = payload.get('resources')
if not source_resources:
pecan.abort(400, _('Source resources required'))
session = EndpointCache().get_session_from_token(
context.auth_token, context.project)
if resource_type == consts.KEYPAIR:
for source in source_regions:
# Create Source Region object
source_nova_client = NovaClient(source, session)
# Check for keypairs in Source Region
for source_keypair in source_resources:
source_keypair = source_nova_client.\
get_keypairs(source_keypair)
if not source_keypair:
db_api._delete_failure_sync_job(context, result.job_id)
pecan.abort(404)
jobs = self._entries_to_database(context, target_regions,
source, source_resources,
resource_type, result)
return self._keypair_sync(force, context, result, jobs)
elif resource_type == consts.IMAGE:
for source in source_regions:
# Create Source Region glance_object
glance_driver = GlanceClient(source, context)
# Check for images in Source Region
for image in source_resources:
source_image = glance_driver.check_image(image)
if image != source_image:
db_api._delete_failure_sync_job(context, result.job_id)
pecan.abort(404)
jobs = self._entries_to_database(context, target_regions,
source, source_resources,
resource_type, result)
return self._image_sync(force, context, result, jobs)
elif resource_type == consts.FLAVOR:
if not context.is_admin:
db_api._delete_failure_sync_job(context, result.job_id)
pecan.abort(403, _('Admin required'))
for source in source_regions:
# Create Source Region object
source_nova_client = NovaClient(source, session)
for flavor in source_resources:
source_flavor = source_nova_client.get_flavor(flavor)
if not source_flavor:
db_api._delete_failure_sync_job(context, result.job_id)
pecan.abort(404)
jobs = self._entries_to_database(context, target_regions,
source, source_resources,
resource_type, result)
return self._flavor_sync(force, context, result, jobs)
else:
pecan.abort(400, _('Bad resource_type'))
@index.when(method='delete', template='json')
def delete(self, project, job_id):
"""Delete the database entries of a given job_id.
:param project: It's UUID of the project.
:param job_id: ID of the job for which the database entries
have to be deleted.
"""
context = restcomm.extract_context_from_environ()
if not uuidutils.is_uuid_like(project) or project != context.project:
pecan.abort(400, _('Invalid request URL'))
if uuidutils.is_uuid_like(job_id):
try:
status = db_api.sync_job_status(context, job_id)
except exceptions.JobNotFound:
pecan.abort(404, _('Job not found'))
if status == consts.JOB_PROGRESS:
pecan.abort(406, _('action not supported'
' while sync is in progress'))
try:
db_api.sync_job_delete(context, job_id)
except exceptions.JobNotFound:
pecan.abort(404, _('Job not found'))
return 'job %s deleted from the database.' % job_id
else:
pecan.abort(400, _('Bad request'))
def _keypair_sync(self, force, context, result, jobs):
"""Make an rpc call to kb-engine.
:param force: Mention force as "True" to sync the same resource
again otherwise False
:param context: context of the request.
:param result: Result object to return an output.
:param jobs: List of resource_sync_id's for job_id.
"""
self.rpc_client.keypair_sync_for_user(context, result.job_id,
force, jobs)
return {'job_status': {'id': result.job_id,
'status': result.sync_status,
'created_at': result.created_at}}
def _image_sync(self, force, context, result, jobs):
"""Make an rpc call to engine.
:param force: Mention force as "True" to sync the same resource
again otherwise False
:param context: context of the request.
:param result: Result object to return an output.
:param jobs: List of resource_sync_id's for job_id.
"""
self.rpc_client.image_sync(context, result.job_id, force, jobs)
return {'job_status': {'id': result.job_id,
'status': result.sync_status,
'created_at': result.created_at}}
def _flavor_sync(self, force, context, result, jobs):
"""Make an rpc call to engine.
:param force: Mention force as "True" to sync the same resource
again otherwise False
:param context: context of the request.
:param result: Result object to return an output.
:param jobs: List of resource_sync_id's for job_id.
"""
self.rpc_client.flavor_sync(context, result.job_id, force, jobs)
return {'job_status': {'id': result.job_id,
'status': result.sync_status,
'created_at': result.created_at}}