poppy/poppy/distributed_task/taskflow/task/common.py

293 lines
11 KiB
Python

# Copyright (c) 2014 Rackspace, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import requests
from oslo_config import cfg
from oslo_context import context as context_utils
from oslo_log import log
from taskflow import task
from poppy.distributed_task.utils import memoized_controllers
from poppy.model.helpers import provider_details
from poppy.transport.pecan.models.request import (
provider_details as req_provider_details
)
LOG = log.getLogger(__name__)
conf = cfg.CONF
conf(project='poppy', prog='poppy', args=[])
DEFAULT_OPTIONS = [
cfg.StrOpt('datacenter', default='',
help='The datacenter in which poppy is deployed')
]
DEFAULT_GROUP = 'DEFAULT'
LOG_DELIVERY_OPTIONS = [
cfg.StrOpt('identity_url', default='',
help='OpenStack Identity URL'),
cfg.StrOpt('container_name', default='.CDN_ACCESS_LOGS',
help='Swift container to put logs'),
cfg.ListOpt('preferred_dcs', default=['DC1', 'DC2'],
help='Preferred DCs to create container'),
]
LOG_DELIVERY_GROUP = 'log_delivery'
def create_log_delivery_container(project_id, auth_token):
# log delivery enabled, create log delivery container for the user
conf.register_opts(LOG_DELIVERY_OPTIONS, group=LOG_DELIVERY_GROUP)
conf.register_opts(DEFAULT_OPTIONS, group=DEFAULT_GROUP)
identity_url = conf['log_delivery']['identity_url']
container_name = conf['log_delivery']['container_name']
preferred_dcs = conf['log_delivery']['preferred_dcs']
payload = {
"auth": {
"tenantName": project_id,
"token": {
"id": auth_token
}
}
}
headers = {'Content-Type': 'application/json'}
try:
response = requests.post(identity_url,
data=json.dumps(payload),
headers=headers)
LOG.info("Keystone request for {0}"
"finished "
"in {1} seconds".format(project_id,
response.elapsed.total_seconds()))
catalog = response.json()
services = catalog['access']['serviceCatalog']
except KeyError:
LOG.info("Could not authenticate "
"with keystone : {0}".format(response.text))
LOG.info("Skipping container {0} creation".format(container_name))
return []
swifturl_public = None
swifturl_internal = None
current_dc = None
for service in services:
if service['type'] == 'object-store':
endpoints = service['endpoints']
for endpoint in endpoints:
if endpoint['region'] in preferred_dcs:
current_dc = endpoint['region']
swifturl_public = endpoint['publicURL']
swifturl_internal = endpoint['internalURL']
break
if swifturl_public and swifturl_internal:
public_container_url = '{0}/{1}'.format(swifturl_public,
container_name)
internal_container_url = '{0}/{1}'.format(swifturl_internal,
container_name)
headers = {
'Content-Type': 'application/json',
'X-Auth-Token': auth_token
}
if conf['DEFAULT']['datacenter'].upper() == current_dc.upper():
container_url = internal_container_url
LOG.info("Choosing internalURL : {0}".format(container_url))
else:
container_url = public_container_url
LOG.info("Choosing publicURL : {0}".format(container_url))
LOG.info('Starting to '
'create container {0}'.format(container_url))
response = requests.put(container_url,
headers=headers)
LOG.info("Swift "
"request for {0} finished "
"in {1} seconds".format(project_id,
response.elapsed.total_seconds()))
LOG.info("Swift request's "
"response headers : {0}".format(str(response.headers)))
if response.ok:
LOG.info('Created container {0}'.format(container_url))
container_urls = {
'publicURL': public_container_url,
'internalURL': internal_container_url
}
log_responders = [container_urls]
return log_responders
else:
LOG.info('Error creating '
'container {0}'.format(container_url))
return []
else:
return []
class ContextUpdateTask(task.Task):
def execute(self, context_dict):
context = context_utils.RequestContext.from_dict(context_dict)
context.update_store()
class UpdateProviderDetailTask(task.Task):
def execute(self, provider_details_dict, project_id, service_id):
provider_details_dict = dict([
(k, provider_details.ProviderDetail.init_from_dict(detail))
for k, detail
in provider_details_dict.items()])
service_controller, self.storage_controller = \
memoized_controllers.task_controllers('poppy', 'storage')
service_obj = self.storage_controller.get_service(
project_id,
service_id
)
service_obj.provider_details = provider_details_dict
enabled = lambda provider: any([True if 'log_delivery'
in access_url else False
for access_url
in provider.access_urls])
if not all(map(enabled, provider_details_dict.values())):
service_obj.log_delivery.enabled = False
LOG.info("Service to be updated to {0} "
"for project_id: {1} "
"and service_id: {2}".format(service_obj.to_dict(),
project_id,
service_id))
self.storage_controller.update_service(
project_id,
service_id,
service_obj
)
LOG.info('Update service detail task complete...')
def revert(self, *args, **kwargs):
try:
if getattr(self, 'storage_controller') \
and self.storage_controller._driver.session:
self.storage_controller._driver.close_connection()
LOG.info('Cassandra session being shutdown')
except AttributeError:
LOG.info('Cassandra session already shutdown')
class UpdateProviderDetailIfNotEmptyTask(task.Task):
default_provides = "provider_details_updated"
def execute(self, changed_provider_details_dict, project_id, service_id):
if changed_provider_details_dict != {}:
provider_details_dict = dict([
(k, provider_details.ProviderDetail.init_from_dict(detail))
for k, detail
in changed_provider_details_dict.items()])
service_controller, self.storage_controller = \
memoized_controllers.task_controllers('poppy', 'storage')
self.storage_controller.update_provider_details(
project_id,
service_id,
provider_details_dict)
LOG.info("Provider Details to be updated to {0} "
"for project_id: {1} "
"and service_id: {2}".format(changed_provider_details_dict,
project_id,
service_id))
LOG.info('Updating service detail task '
'complete for Changed Provider Details : '
'{0}'.format(changed_provider_details_dict))
def revert(self, *args, **kwargs):
try:
if getattr(self, 'storage_controller') \
and self.storage_controller._driver.session:
self.storage_controller._driver.close_connection()
LOG.info('Cassandra session being shutdown')
except AttributeError:
LOG.info('Cassandra session already shutdown')
class UpdateProviderDetailErrorTask(task.Task):
default_provides = "changed_provider_details_dict"
def execute(self, responders, service_id, provider_details, hard):
provider_details = json.loads(provider_details)
for provider in provider_details:
# NOTE(tonytan4ever): if the purge_url is None, it means to purge
# all content, else only purge a specific purge url
provider_details[provider] = (
req_provider_details.load_from_json(
provider_details[provider]))
# Find any failed attempt of purging, and stores it in provider
# detail for future debugging purpose
changed_provider_details_dict = {}
for responder in responders:
# this is the item of responder, if there's "error"
# key in it, it means the purging for this provider failed.
# in that case we will need to update the provider detail
# info for the service from poppy storage.
provider_name = list(responder.items())[0][0]
if 'error' in responder[provider_name]:
LOG.info('Purging content from {0} '
'failed'.format(provider_name))
LOG.info('Updating provider detail '
'status of {0} for {1}'.format(provider_name,
service_id))
# stores the error info for debugging purposes.
changed_provider_details_dict[provider_name] = (
provider_details[provider_name]
)
changed_provider_details_dict[provider_name].error_info = (
responder[provider_name].get('error_detail')
)
changed_provider_details_dict[provider_name].error_message = (
responder[provider_name].get('error')
)
if not json.loads(hard):
changed_provider_details_dict[provider_name].status = \
'failed'
else:
changed_provider_details_dict[provider_name] = (
provider_details[provider_name]
)
changed_provider_details_dict[provider_name].status = (
'deployed')
changed_provider_details_dict[provider_name].error_info = \
None
changed_provider_details_dict[provider_name].error_message = \
None
# serialize changed_provider_details_dict
for provider_name in changed_provider_details_dict:
changed_provider_details_dict[provider_name] = (
changed_provider_details_dict[provider_name].to_dict())
return changed_provider_details_dict