# # 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 heat.common.i18n import _ from heat.engine import attributes from heat.engine import resource from heat.engine.resources.aws.cfn import wait_condition_handle as aws_wch from heat.engine.resources import wait_condition as wc_base from heat.engine import support class HeatWaitConditionHandle(wc_base.BaseWaitConditionHandle): support_status = support.SupportStatus(version='2014.2') METADATA_KEYS = ( DATA, REASON, STATUS, UNIQUE_ID ) = ( 'data', 'reason', 'status', 'id' ) ATTRIBUTES = ( TOKEN, ENDPOINT, CURL_CLI, ) = ( 'token', 'endpoint', 'curl_cli', ) attributes_schema = { TOKEN: attributes.Schema( _('Token for stack-user which can be used for signalling handle'), cache_mode=attributes.Schema.CACHE_NONE ), ENDPOINT: attributes.Schema( _('Endpoint/url which can be used for signalling handle'), cache_mode=attributes.Schema.CACHE_NONE ), CURL_CLI: attributes.Schema( _('Convenience attribute, provides curl CLI command ' 'prefix, which can be used for signalling handle completion or ' 'failure. You can signal success by adding ' '--data-binary \'{"status": "SUCCESS"}\' ' ', or signal failure by adding ' '--data-binary \'{"status": "FAILURE"}\''), cache_mode=attributes.Schema.CACHE_NONE ), } def handle_create(self): password = uuid.uuid4().hex self.data_set('password', password, True) self._create_user() self.resource_id_set(self._get_user_id()) # FIXME(shardy): The assumption here is that token expiry > timeout # but we probably need a check here to fail fast if that's not true # Also need to implement an update property, such that the handle # can be replaced on update which will replace the token token = self._user_token() self.data_set('token', token, True) self.data_set('endpoint', '%s/signal' % self._get_resource_endpoint()) def _get_resource_endpoint(self): # Get the endpoint from stack.clients then replace the context # project_id with the path to the resource (which includes the # context project_id), then replace the context project with # the one needed for signalling from the stack_user_project heat_client_plugin = self.stack.clients.client_plugin('heat') endpoint = heat_client_plugin.get_heat_url() rsrc_ep = endpoint.replace(self.context.tenant_id, self.identifier().url_path()) return rsrc_ep.replace(self.context.tenant_id, self.stack.stack_user_project_id) def handle_delete(self): self._delete_user() @property def password(self): return self.data().get('password') def _resolve_attribute(self, key): if self.resource_id: if key == self.TOKEN: return self.data().get('token') elif key == self.ENDPOINT: return self.data().get('endpoint') elif key == self.CURL_CLI: # Construct curl command for template-author convenience return ("curl -i -X POST " "-H 'X-Auth-Token: %(token)s' " "-H 'Content-Type: application/json' " "-H 'Accept: application/json' " "%(endpoint)s" % dict(token=self.data().get('token'), endpoint=self.data().get('endpoint'))) def handle_signal(self, details=None): ''' Validate and update the resource metadata. metadata is not mandatory, but if passed it must use the following format: { "status" : "Status (must be SUCCESS or FAILURE)", "data" : "Arbitrary data", "reason" : "Reason string" } Optionally "id" may also be specified, but if missing the index of the signal received will be used. ''' rsrc_metadata = self.metadata_get(refresh=True) signal_num = len(rsrc_metadata) + 1 reason = 'Signal %s received' % signal_num # Tolerate missing values, default to success metadata = details or {} metadata.setdefault(self.REASON, reason) metadata.setdefault(self.DATA, None) metadata.setdefault(self.UNIQUE_ID, signal_num) metadata.setdefault(self.STATUS, self.STATUS_SUCCESS) return super(HeatWaitConditionHandle, self).handle_signal(metadata) class UpdateWaitConditionHandle(aws_wch.WaitConditionHandle): ''' This works identically to a regular WaitConditionHandle, except that on update it clears all signals received and changes the handle. Using this handle means that you must setup the signal senders to send their signals again any time the update handle changes. This allows us to roll out new configurations and be confident that they are rolled out once UPDATE COMPLETE is reached. ''' support_status = support.SupportStatus(version='2014.1') def update(self, after, before=None, prev_resource=None): raise resource.UpdateReplace(self.name) def resource_mapping(): return { 'OS::Heat::WaitConditionHandle': HeatWaitConditionHandle, 'OS::Heat::UpdateWaitConditionHandle': UpdateWaitConditionHandle, }