646c08396e
Fixes SoftwareConfigService to use Resource object in place resource db api. Implements blueprint versioned-objects Change-Id: Idd9b1215275d899bbf8a7c1a603af9c35dda3008
262 lines
10 KiB
Python
262 lines
10 KiB
Python
#
|
|
# 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_serialization import jsonutils
|
|
from oslo_utils import timeutils
|
|
import requests
|
|
import six
|
|
from six.moves.urllib import parse as urlparse
|
|
|
|
from heat.common.i18n import _
|
|
from heat.common.i18n import _LI
|
|
from heat.engine import api
|
|
from heat.objects import resource as resource_object
|
|
from heat.objects import software_config as software_config_object
|
|
from heat.objects import software_deployment as software_deployment_object
|
|
from heat.openstack.common import service
|
|
from heat.rpc import api as rpc_api
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class SoftwareConfigService(service.Service):
|
|
|
|
def show_software_config(self, cnxt, config_id):
|
|
sc = software_config_object.SoftwareConfig.get_by_id(cnxt, config_id)
|
|
return api.format_software_config(sc)
|
|
|
|
def create_software_config(self, cnxt, group, name, config,
|
|
inputs, outputs, options):
|
|
|
|
sc = software_config_object.SoftwareConfig.create(cnxt, {
|
|
'group': group,
|
|
'name': name,
|
|
'config': {
|
|
'inputs': inputs,
|
|
'outputs': outputs,
|
|
'options': options,
|
|
'config': config
|
|
},
|
|
'tenant': cnxt.tenant_id})
|
|
return api.format_software_config(sc)
|
|
|
|
def delete_software_config(self, cnxt, config_id):
|
|
software_config_object.SoftwareConfig.delete(cnxt, config_id)
|
|
|
|
def list_software_deployments(self, cnxt, server_id):
|
|
all_sd = software_deployment_object.SoftwareDeployment.get_all(
|
|
cnxt, server_id)
|
|
result = [api.format_software_deployment(sd) for sd in all_sd]
|
|
return result
|
|
|
|
def metadata_software_deployments(self, cnxt, server_id):
|
|
if not server_id:
|
|
raise ValueError(_('server_id must be specified'))
|
|
all_sd = software_deployment_object.SoftwareDeployment.get_all(
|
|
cnxt, server_id)
|
|
# sort the configs by config name, to give the list of metadata a
|
|
# deterministic and controllable order.
|
|
all_sd_s = sorted(all_sd, key=lambda sd: sd.config.name)
|
|
result = [api.format_software_config(sd.config) for sd in all_sd_s]
|
|
return result
|
|
|
|
def _push_metadata_software_deployments(self, cnxt, server_id):
|
|
rs = (resource_object.Resource.
|
|
get_by_physical_resource_id(cnxt, server_id))
|
|
if not rs:
|
|
return
|
|
deployments = self.metadata_software_deployments(cnxt, server_id)
|
|
md = rs.rsrc_metadata or {}
|
|
md['deployments'] = deployments
|
|
rs.update_and_save({'rsrc_metadata': md})
|
|
|
|
metadata_put_url = None
|
|
for rd in rs.data:
|
|
if rd.key == 'metadata_put_url':
|
|
metadata_put_url = rd.value
|
|
break
|
|
if metadata_put_url:
|
|
json_md = jsonutils.dumps(md)
|
|
requests.put(metadata_put_url, json_md)
|
|
|
|
def _refresh_software_deployment(self, cnxt, sd, deploy_signal_id):
|
|
container, object_name = urlparse.urlparse(
|
|
deploy_signal_id).path.split('/')[-2:]
|
|
swift_plugin = cnxt.clients.client_plugin('swift')
|
|
swift = swift_plugin.client()
|
|
|
|
try:
|
|
headers = swift.head_object(container, object_name)
|
|
except Exception as ex:
|
|
# ignore not-found, in case swift is not consistent yet
|
|
if swift_plugin.is_not_found(ex):
|
|
LOG.info(_LI('Signal object not found: %(c)s %(o)s') % {
|
|
'c': container, 'o': object_name})
|
|
return sd
|
|
raise ex
|
|
|
|
lm = headers.get('last-modified')
|
|
|
|
last_modified = swift_plugin.parse_last_modified(lm)
|
|
prev_last_modified = sd.updated_at
|
|
|
|
if prev_last_modified:
|
|
# assume stored as utc, convert to offset-naive datetime
|
|
prev_last_modified = prev_last_modified.replace(tzinfo=None)
|
|
|
|
if prev_last_modified and (last_modified <= prev_last_modified):
|
|
return sd
|
|
|
|
try:
|
|
(headers, obj) = swift.get_object(container, object_name)
|
|
except Exception as ex:
|
|
# ignore not-found, in case swift is not consistent yet
|
|
if swift_plugin.is_not_found(ex):
|
|
LOG.info(_LI(
|
|
'Signal object not found: %(c)s %(o)s') % {
|
|
'c': container, 'o': object_name})
|
|
return sd
|
|
raise ex
|
|
if obj:
|
|
self.signal_software_deployment(
|
|
cnxt, sd.id, jsonutils.loads(obj),
|
|
timeutils.strtime(last_modified))
|
|
|
|
return software_deployment_object.SoftwareDeployment.get_by_id(
|
|
cnxt, sd.id)
|
|
|
|
def show_software_deployment(self, cnxt, deployment_id):
|
|
sd = software_deployment_object.SoftwareDeployment.get_by_id(
|
|
cnxt, deployment_id)
|
|
if sd.status == rpc_api.SOFTWARE_DEPLOYMENT_IN_PROGRESS:
|
|
c = sd.config.config
|
|
input_values = dict((i['name'], i['value']) for i in c['inputs'])
|
|
transport = input_values.get('deploy_signal_transport')
|
|
if transport == 'TEMP_URL_SIGNAL':
|
|
sd = self._refresh_software_deployment(
|
|
cnxt, sd, input_values.get('deploy_signal_id'))
|
|
return api.format_software_deployment(sd)
|
|
|
|
def create_software_deployment(self, cnxt, server_id, config_id,
|
|
input_values, action, status,
|
|
status_reason, stack_user_project_id):
|
|
|
|
sd = software_deployment_object.SoftwareDeployment.create(cnxt, {
|
|
'config_id': config_id,
|
|
'server_id': server_id,
|
|
'input_values': input_values,
|
|
'tenant': cnxt.tenant_id,
|
|
'stack_user_project_id': stack_user_project_id,
|
|
'action': action,
|
|
'status': status,
|
|
'status_reason': status_reason})
|
|
self._push_metadata_software_deployments(cnxt, server_id)
|
|
return api.format_software_deployment(sd)
|
|
|
|
def signal_software_deployment(self, cnxt, deployment_id, details,
|
|
updated_at):
|
|
|
|
if not deployment_id:
|
|
raise ValueError(_('deployment_id must be specified'))
|
|
|
|
sd = software_deployment_object.SoftwareDeployment.get_by_id(
|
|
cnxt, deployment_id)
|
|
status = sd.status
|
|
|
|
if not status == rpc_api.SOFTWARE_DEPLOYMENT_IN_PROGRESS:
|
|
# output values are only expected when in an IN_PROGRESS state
|
|
return
|
|
|
|
details = details or {}
|
|
|
|
output_status_code = rpc_api.SOFTWARE_DEPLOYMENT_OUTPUT_STATUS_CODE
|
|
ov = sd.output_values or {}
|
|
status = None
|
|
status_reasons = {}
|
|
status_code = details.get(output_status_code)
|
|
if status_code and str(status_code) != '0':
|
|
status = rpc_api.SOFTWARE_DEPLOYMENT_FAILED
|
|
status_reasons[output_status_code] = _(
|
|
'Deployment exited with non-zero status code: %s'
|
|
) % details.get(output_status_code)
|
|
event_reason = 'deployment failed (%s)' % status_code
|
|
else:
|
|
event_reason = 'deployment succeeded'
|
|
|
|
for output in sd.config.config['outputs'] or []:
|
|
out_key = output['name']
|
|
if out_key in details:
|
|
ov[out_key] = details[out_key]
|
|
if output.get('error_output', False):
|
|
status = rpc_api.SOFTWARE_DEPLOYMENT_FAILED
|
|
status_reasons[out_key] = details[out_key]
|
|
event_reason = 'deployment failed'
|
|
|
|
for out_key in rpc_api.SOFTWARE_DEPLOYMENT_OUTPUTS:
|
|
ov[out_key] = details.get(out_key)
|
|
|
|
if status == rpc_api.SOFTWARE_DEPLOYMENT_FAILED:
|
|
# build a status reason out of all of the values of outputs
|
|
# flagged as error_output
|
|
status_reasons = [' : '.join((k, six.text_type(status_reasons[k])))
|
|
for k in status_reasons]
|
|
status_reason = ', '.join(status_reasons)
|
|
else:
|
|
status = rpc_api.SOFTWARE_DEPLOYMENT_COMPLETE
|
|
status_reason = _('Outputs received')
|
|
|
|
self.update_software_deployment(
|
|
cnxt, deployment_id=deployment_id,
|
|
output_values=ov, status=status, status_reason=status_reason,
|
|
config_id=None, input_values=None, action=None,
|
|
updated_at=updated_at)
|
|
# Return a string describing the outcome of handling the signal data
|
|
return event_reason
|
|
|
|
def update_software_deployment(self, cnxt, deployment_id, config_id,
|
|
input_values, output_values, action,
|
|
status, status_reason, updated_at):
|
|
update_data = {}
|
|
if config_id:
|
|
update_data['config_id'] = config_id
|
|
if input_values:
|
|
update_data['input_values'] = input_values
|
|
if output_values:
|
|
update_data['output_values'] = output_values
|
|
if action:
|
|
update_data['action'] = action
|
|
if status:
|
|
update_data['status'] = status
|
|
if status_reason:
|
|
update_data['status_reason'] = status_reason
|
|
if updated_at:
|
|
update_data['updated_at'] = timeutils.normalize_time(
|
|
timeutils.parse_isotime(updated_at))
|
|
else:
|
|
update_data['updated_at'] = timeutils.utcnow()
|
|
|
|
sd = software_deployment_object.SoftwareDeployment.update_by_id(
|
|
cnxt, deployment_id, update_data)
|
|
|
|
# only push metadata if this update resulted in the config_id
|
|
# changing, since metadata is just a list of configs
|
|
if config_id:
|
|
self._push_metadata_software_deployments(cnxt, sd.server_id)
|
|
|
|
return api.format_software_deployment(sd)
|
|
|
|
def delete_software_deployment(self, cnxt, deployment_id):
|
|
software_deployment_object.SoftwareDeployment.delete(
|
|
cnxt, deployment_id)
|