Source code for heat.engine.resources.server_base

#
#    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 uuid

from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils

from heat.common import exception
from heat.common import password_gen
from heat.engine.clients import progress
from heat.engine.resources import stack_user

cfg.CONF.import_opt('max_server_name_length', 'heat.common.config')

LOG = logging.getLogger(__name__)


[docs]class BaseServer(stack_user.StackUser): """Base Server resource.""" physical_resource_name_limit = cfg.CONF.max_server_name_length entity = 'servers' def __init__(self, name, json_snippet, stack): super(BaseServer, self).__init__(name, json_snippet, stack) self.default_collectors = [] def _server_name(self): name = self.properties[self.NAME] if name: return name return self.physical_resource_name() def _container_and_object_name(self, props): deployment_swift_data = props.get( self.DEPLOYMENT_SWIFT_DATA, self.properties[self.DEPLOYMENT_SWIFT_DATA]) container_name = deployment_swift_data[self.CONTAINER] if container_name is None: container_name = self.physical_resource_name() object_name = deployment_swift_data[self.OBJECT] if object_name is None: object_name = self.data().get('metadata_object_name') if object_name is None: object_name = str(uuid.uuid4()) return container_name, object_name def _populate_deployments_metadata(self, meta, props): meta['deployments'] = meta.get('deployments', []) meta['os-collect-config'] = meta.get('os-collect-config', {}) occ = meta['os-collect-config'] collectors = list(self.default_collectors) occ['collectors'] = collectors region_name = (self.context.region_name or cfg.CONF.region_name_for_services) # set existing values to None to override any boot-time config occ_keys = ('heat', 'zaqar', 'cfn', 'request') for occ_key in occ_keys: if occ_key not in occ: continue existing = occ[occ_key] for k in existing: existing[k] = None queue_id = self.data().get('metadata_queue_id') if self.transport_poll_server_heat(props): occ.update({'heat': { 'user_id': self._get_user_id(), 'password': self.password, 'auth_url': self.keystone().server_keystone_endpoint_url( fallback_endpoint=self.context.auth_url), 'project_id': self.stack.stack_user_project_id, 'stack_id': self.stack.identifier().stack_path(), 'resource_name': self.name, 'region_name': region_name}}) collectors.append('heat') elif self.transport_zaqar_message(props): queue_id = queue_id or self.physical_resource_name() occ.update({'zaqar': { 'user_id': self._get_user_id(), 'password': self.password, 'auth_url': self.keystone().server_keystone_endpoint_url( fallback_endpoint=self.context.auth_url), 'project_id': self.stack.stack_user_project_id, 'queue_id': queue_id, 'region_name': region_name}}) collectors.append('zaqar') elif self.transport_poll_server_cfn(props): heat_client_plugin = self.stack.clients.client_plugin('heat') config_url = heat_client_plugin.get_cfn_metadata_server_url() occ.update({'cfn': { 'metadata_url': config_url, 'access_key_id': self.access_key, 'secret_access_key': self.secret_key, 'stack_name': self.stack.name, 'path': '%s.Metadata' % self.name}}) collectors.append('cfn') elif self.transport_poll_temp_url(props): container_name, object_name = self._container_and_object_name( props) self.client('swift').put_container(container_name) url = self.client_plugin('swift').get_temp_url( container_name, object_name, method='GET') put_url = self.client_plugin('swift').get_temp_url( container_name, object_name) self.data_set('metadata_put_url', put_url) self.data_set('metadata_object_name', object_name) collectors.append('request') occ.update({'request': {'metadata_url': url}}) collectors.append('local') self.metadata_set(meta) # push replacement polling config to any existing push-based sources if queue_id: zaqar_plugin = self.client_plugin('zaqar') zaqar = zaqar_plugin.create_for_tenant( self.stack.stack_user_project_id, self._user_token()) queue = zaqar.queue(queue_id) queue.post({'body': meta, 'ttl': zaqar_plugin.DEFAULT_TTL}) self.data_set('metadata_queue_id', queue_id) object_name = self.data().get('metadata_object_name') if object_name: container_name, object_name = self._container_and_object_name( props) self.client('swift').put_object( container_name, object_name, jsonutils.dumps(meta)) def _create_transport_credentials(self, props): if self.transport_poll_server_cfn(props): self._create_user() self._create_keypair() elif (self.transport_poll_server_heat(props) or self.transport_zaqar_message(props)): if self.password is None: self.password = password_gen.generate_openstack_password() self._create_user() self._register_access_key() @property def access_key(self): return self.data().get('access_key') @property def secret_key(self): return self.data().get('secret_key') @property def password(self): return self.data().get('password') @password.setter def password(self, password): if password is None: self.data_delete('password') else: self.data_set('password', password, True)
[docs] def transport_poll_server_cfn(self, props): return props[ self.SOFTWARE_CONFIG_TRANSPORT] == self.POLL_SERVER_CFN
[docs] def transport_poll_server_heat(self, props): return props[ self.SOFTWARE_CONFIG_TRANSPORT] == self.POLL_SERVER_HEAT
[docs] def transport_poll_temp_url(self, props): return props[ self.SOFTWARE_CONFIG_TRANSPORT] == self.POLL_TEMP_URL
[docs] def transport_zaqar_message(self, props): return props[ self.SOFTWARE_CONFIG_TRANSPORT] == self.ZAQAR_MESSAGE
[docs] def check_create_complete(self, server_id): return True
def _resolve_attribute(self, name): if self.resource_id is None: return if name == self.NAME_ATTR: return self._server_name() if name == self.OS_COLLECT_CONFIG: return self.metadata_get().get('os-collect-config', {})
[docs] def handle_update(self, json_snippet, tmpl_diff, prop_diff): if tmpl_diff.metadata_changed(): # If SOFTWARE_CONFIG user_data_format is enabled we require # the "deployments" and "os-collect-config" keys for Deployment # polling. We can attempt to merge the occ data, but any # metadata update containing deployments will be discarded. new_md = json_snippet.metadata() if self.user_data_software_config(): metadata = self.metadata_get(True) or {} new_occ_md = new_md.get('os-collect-config', {}) occ_md = metadata.get('os-collect-config', {}) occ_md.update(new_occ_md) new_md['os-collect-config'] = occ_md deployment_md = metadata.get('deployments', []) new_md['deployments'] = deployment_md self.metadata_set(new_md) updaters = [] if self.SOFTWARE_CONFIG_TRANSPORT in prop_diff: self._update_software_config_transport(prop_diff) # NOTE(pas-ha) optimization is possible (starting first task # right away), but we'd rather not, as this method already might # have called several APIs return updaters
def _update_software_config_transport(self, prop_diff): if not self.user_data_software_config(): return try: self._delete_queue() self._delete_temp_url() except Exception: pass metadata = self.metadata_get(True) or {} self._create_transport_credentials(prop_diff) self._populate_deployments_metadata(metadata, prop_diff)
[docs] def metadata_update(self, new_metadata=None): """Refresh the metadata if new_metadata is None.""" if new_metadata is None: # Re-resolve the template metadata and merge it with the # current resource metadata. This is necessary because the # attributes referenced in the template metadata may change # and the resource itself adds keys to the metadata which # are not specified in the template (e.g the deployments data) meta = self.metadata_get(refresh=True) or {} tmpl_meta = self.t.metadata() meta.update(tmpl_meta) self.metadata_set(meta)
@staticmethod def _check_maximum(count, maximum, msg): """Check a count against a maximum. Unless maximum is -1 which indicates that there is no limit. """ if maximum != -1 and count > maximum: raise exception.StackValidationFailed(message=msg) def _delete_temp_url(self): object_name = self.data().get('metadata_object_name') if not object_name: return with self.client_plugin('swift').ignore_not_found: container = self.properties[self.DEPLOYMENT_SWIFT_DATA].get( 'container') container = container or self.physical_resource_name() swift = self.client('swift') swift.delete_object(container, object_name) headers = swift.head_container(container) if int(headers['x-container-object-count']) == 0: swift.delete_container(container) self.data_delete('metadata_object_name') self.data_delete('metadata_put_url') def _delete_queue(self): queue_id = self.data().get('metadata_queue_id') if not queue_id: return client_plugin = self.client_plugin('zaqar') zaqar = client_plugin.create_for_tenant( self.stack.stack_user_project_id, self._user_token()) with client_plugin.ignore_not_found: zaqar.queue(queue_id).delete() self.data_delete('metadata_queue_id')
[docs] def handle_snapshot_delete(self, state): if state[1] != self.FAILED and self.resource_id: image_id = self.client().servers.create_image( self.resource_id, self.physical_resource_name()) return progress.ServerDeleteProgress( self.resource_id, image_id, False) return self._delete()
[docs] def handle_delete(self): return self._delete()
[docs] def check_delete_complete(self, prg): if not prg: return True
def _show_resource(self): rsrc_dict = super(BaseServer, self)._show_resource() rsrc_dict.setdefault( self.OS_COLLECT_CONFIG, self.metadata_get().get('os-collect-config', {})) return rsrc_dict