def startup_sanity_check(): if (not cfg.CONF.stack_user_domain_id and not cfg.CONF.stack_user_domain_name): # FIXME(shardy): Legacy fallback for folks using old heat.conf # files which lack domain configuration LOG.warning( _LW('stack_user_domain_id or stack_user_domain_name not ' 'set in heat.conf falling back to using default')) else: domain_admin_user = cfg.CONF.stack_domain_admin domain_admin_password = cfg.CONF.stack_domain_admin_password if not (domain_admin_user and domain_admin_password): raise exception.Error( _('heat.conf misconfigured, cannot ' 'specify "stack_user_domain_id" or ' '"stack_user_domain_name" without ' '"stack_domain_admin" and ' '"stack_domain_admin_password"')) auth_key_len = len(cfg.CONF.auth_encryption_key) if auth_key_len in (16, 24): LOG.warning( _LW('Please update auth_encryption_key to be 32 characters.')) elif auth_key_len != 32: raise exception.Error( _('heat.conf misconfigured, auth_encryption_key ' 'must be 32 characters'))
def handle_resume(self): """Resume an instance. Note we do not wait for the ACTIVE state, this is polled for by check_resume_complete in a similar way to the create logic so we can take advantage of coroutines. """ if self.resource_id is None: raise exception.Error( _('Cannot resume %s, resource_id not set') % self.name) try: server = self.client().servers.get(self.resource_id) except Exception as e: if self.client_plugin().is_not_found(e): raise exception.NotFound( _('Failed to find instance %s') % self.resource_id) else: raise else: # if the instance has been resumed successful, # no need to resume again if self.client_plugin().get_status(server) != 'ACTIVE': LOG.debug("resuming instance %s" % self.resource_id) server.resume() return server.id
def create_stack_user(self, username, password=''): """Create a user defined as part of a stack. The user is defined either via template or created internally by a resource. This user will be added to the heat_stack_user_role as defined in the config. Returns the keystone ID of the resulting user. """ # FIXME(shardy): There's duplicated logic between here and # create_stack_domain user, but this function is expected to # be removed after the transition of all resources to domain # users has been completed stack_user_role = self.client.roles.list( name=cfg.CONF.heat_stack_user_role) if len(stack_user_role) == 1: role_id = stack_user_role[0].id # Create the user user = self.client.users.create( name=self._get_username(username), password=password, default_project=self.context.tenant_id) # Add user to heat_stack_user_role LOG.debug("Adding user %(user)s to role %(role)s" % { 'user': user.id, 'role': role_id}) self.client.roles.grant(role=role_id, user=user.id, project=self.context.tenant_id) else: LOG.error(_LE("Failed to add user %(user)s to role %(role)s, " "check role exists!"), { 'user': username, 'role': cfg.CONF.heat_stack_user_role}) raise exception.Error(_("Can't find role %s") % cfg.CONF.heat_stack_user_role) return user.id
def reload_loadbalancers(group, load_balancers, exclude=None): """Notify the LoadBalancer to reload its config. This must be done after activation (instance in ACTIVE state), otherwise the instances' IP addresses may not be available. """ exclude = exclude or [] id_list = grouputils.get_member_refids(group, exclude=exclude) for name, lb in six.iteritems(load_balancers): props = copy.copy(lb.properties.data) if 'Instances' in lb.properties_schema: props['Instances'] = id_list elif 'members' in lb.properties_schema: props['members'] = id_list else: raise exception.Error( _("Unsupported resource '%s' in LoadBalancerNames") % name) lb_defn = rsrc_defn.ResourceDefinition( lb.name, lb.type(), properties=props, metadata=lb.t.metadata(), deletion_policy=lb.t.deletion_policy()) scheduler.TaskRunner(lb.update, lb_defn)()
def handle_resume(self): stack = self.nested() if stack is None: raise exception.Error( _('Cannot resume %s, stack not created') % self.name) stack_identity = identifier.HeatIdentifier( self.context.tenant_id, self.physical_resource_name(), self.resource_id) self.rpc_client().stack_resume(self.context, dict(stack_identity))
def _check_complete(self): sd = self.rpc_client().check_software_deployment( self.context, self.resource_id, self.stack.time_remaining()) status = sd[rpc_api.SOFTWARE_DEPLOYMENT_STATUS] if status == SoftwareDeployment.COMPLETE: return True elif status == SoftwareDeployment.FAILED: status_reason = sd[rpc_api.SOFTWARE_DEPLOYMENT_STATUS_REASON] message = _("Deployment to server failed: %s") % status_reason LOG.info(message) raise exception.Error(message)
def get_external_network_id(client): ext_filter = {'router:external': True} ext_nets = client.list_networks(**ext_filter)['networks'] if len(ext_nets) != 1: # TODO(sbaker) if there is more than one external network # add a heat configuration variable to set the ID of # the default one raise exception.Error( _('Expected 1 external network, found %d') % len(ext_nets)) external_network_id = ext_nets[0]['id'] return external_network_id
def detach_volume(self, server_id, attach_id): # detach the volume using volume_attachment try: self.client().volumes.delete_server_volume(server_id, attach_id) except Exception as ex: if not (self.is_not_found(ex) or self.is_bad_request(ex)): raise exception.Error( _("Could not detach attachment %(att)s " "from server %(srv)s.") % { 'srv': server_id, 'att': attach_id })
def check_rebuild(self, server_id): """Verify that a rebuilding server is rebuilt. Raise error if it ends up in an ERROR state. """ server = self.fetch_server(server_id) if server is None or server.status == 'REBUILD': return False if server.status == 'ERROR': raise exception.Error( _("Rebuilding server failed, status '%s'") % server.status) else: return True
def router_for_vpc(client, network_id): # first get the neutron net net = VPC.network_for_vpc(client, network_id) # then find a router with the same name routers = client.list_routers(name=net['name'])['routers'] if len(routers) == 0: # There may be no router if the net was created manually # instead of in another stack. return None if len(routers) > 1: raise exception.Error( _('Multiple routers found with name %s') % net['name']) return routers[0]
def _service_swift_signal(self): swift_client = self.client('swift') try: container = swift_client.get_container(self.stack.id) except Exception as exc: self.client_plugin('swift').ignore_not_found(exc) LOG.debug("Swift container %s was not found" % self.stack.id) return index = container[1] if not index: # Swift objects were deleted by user LOG.debug("Swift objects in container %s were not found" % self.stack.id) return # Remove objects that are for other resources, given that # multiple swift signals in the same stack share a container object_name = self.physical_resource_name() filtered = [obj for obj in index if object_name in obj['name']] # Fetch objects from Swift and filter results signal_names = [] for obj in filtered: try: signal = swift_client.get_object(self.stack.id, obj['name']) except Exception as exc: self.client_plugin('swift').ignore_not_found(exc) continue body = signal[1] if body == swift.IN_PROGRESS: # Ignore the initial object continue signal_names.append(obj['name']) if body == "": self.signal(details={}) continue try: self.signal(details=jsonutils.loads(body)) except ValueError: raise exception.Error(_("Failed to parse JSON data: %s") % body) # remove the signals that were consumed for signal_name in signal_names: if signal_name != object_name: swift_client.delete_object(self.stack.id, signal_name) if object_name in signal_names: swift_client.delete_object(self.stack.id, object_name)
def attach_volume(self, server_id, volume_id, device): try: va = self.client().volumes.create_server_volume( server_id=server_id, volume_id=volume_id, device=device) except Exception as ex: if self.is_client_exception(ex): raise exception.Error( _("Failed to attach volume %(vol)s to server %(srv)s " "- %(err)s") % { 'vol': volume_id, 'srv': server_id, 'err': ex }) else: raise return va.id
def check_resize(self, server_id, flavor): """Verify that a resizing server is properly resized. If that's the case, confirm the resize, if not raise an error. """ server = self.fetch_server(server_id) # resize operation is asynchronous so the server resize may not start # when checking server status (the server may stay ACTIVE instead # of RESIZE). if not server or server.status in ('RESIZE', 'ACTIVE'): return False if server.status == 'VERIFY_RESIZE': return True else: raise exception.Error( _("Resizing to '%(flavor)s' failed, status '%(status)s'") % dict(flavor=flavor, status=server.status))
def handle_create(self): router_id = self.properties.get(self.ROUTER_ID) routes = self.client().show_router( router_id).get('router').get('routes') if not routes: routes = [] new_route = {'destination': self.properties[self.DESTINATION], 'nexthop': self.properties[self.NEXTHOP]} if new_route in routes: msg = _('Route duplicates an existing route.') raise exception.Error(msg) routes.append(new_route) self.client().update_router(router_id, {'router': {'routes': routes}}) new_route['router_id'] = router_id self.resource_id_set( '%(router_id)s:%(destination)s:%(nexthop)s' % new_route)
def _create(self): con = self.context volume_api_version = self.get_volume_api_version() if cfg.CONF.FusionSphere.pubcloud: service_type = self.EVS client_version = '2' elif volume_api_version == 1: service_type = self.VOLUME client_version = '1' elif volume_api_version == 2: service_type = self.VOLUME_V2 client_version = '2' else: raise exception.Error(_('No volume service available.')) LOG.info(_LI('Creating Cinder client with volume API version %d.'), volume_api_version) endpoint_type = self._get_client_option(CLIENT_NAME, 'endpoint_type') args = { 'service_type': service_type, 'auth_url': con.auth_url or '', 'project_id': con.tenant_id, 'username': None, 'api_key': None, 'endpoint_type': endpoint_type, 'http_log_debug': self._get_client_option(CLIENT_NAME, 'http_log_debug'), 'cacert': self._get_client_option(CLIENT_NAME, 'ca_file'), 'insecure': self._get_client_option(CLIENT_NAME, 'insecure'), 'timeout': self._get_client_option(CLIENT_NAME, 'timeout') } client = cc.Client(client_version, **args) management_url = self.url_for(service_type=service_type, endpoint_type=endpoint_type) client.client.auth_token = self.auth_token client.client.management_url = management_url if cfg.CONF.FusionSphere.pubcloud: client.volume_api_version = 2 else: client.volume_api_version = volume_api_version return client
def _delete_volume(self): """Call the volume delete API. Returns False if further checking of volume status is required, True otherwise. """ try: cinder = self.client() vol = cinder.volumes.get(self.resource_id) if vol.status == 'in-use': raise exception.Error(_('Volume in use')) # if the volume is already in deleting status, # just wait for the deletion to complete if vol.status != 'deleting': cinder.volumes.delete(self.resource_id) return False except Exception as ex: self.client_plugin().ignore_not_found(ex) return True
def stack_domain_user_token(self, user_id, project_id, password): """Get a token for a stack domain user.""" if not self.stack_domain: # Note, no legacy fallback path as we don't want to deploy # tokens for non stack-domain users inside instances msg = _('Cannot get stack domain user token, no stack domain id ' 'configured, please fix your heat.conf') raise exception.Error(msg) # Create a keystoneclient session, then request a token with no # catalog (the token is expected to be used inside an instance # where a specific endpoint will be specified, and user-data # space is limited..) auth = kc_auth_v3.Password(auth_url=self.v3_endpoint, user_id=user_id, password=password, project_id=project_id, include_catalog=False) return auth.get_token(self.session)
def create_stack_domain_user(self, username, project_id, password=None): """Create a domain user defined as part of a stack. The user is defined either via template or created internally by a resource. This user will be added to the heat_stack_user_role as defined in the config, and created in the specified project (which is expected to be in the stack_domain). Returns the keystone ID of the resulting user. """ if not self.stack_domain: # FIXME(shardy): Legacy fallback for folks using old heat.conf # files which lack domain configuration return self.create_stack_user(username=username, password=password) # We add the new user to a special keystone role # This role is designed to allow easier differentiation of the # heat-generated "stack users" which will generally have credentials # deployed on an instance (hence are implicitly untrusted) stack_user_role = self.domain_admin_client.roles.list( name=cfg.CONF.heat_stack_user_role) if len(stack_user_role) == 1: role_id = stack_user_role[0].id # Create user user = self.domain_admin_client.users.create( name=self._get_username(username), password=password, default_project=project_id, domain=self.stack_domain_id) # Add to stack user role LOG.debug("Adding user %(user)s to role %(role)s" % { 'user': user.id, 'role': role_id}) self.domain_admin_client.roles.grant(role=role_id, user=user.id, project=project_id) else: LOG.error(_LE("Failed to add user %(user)s to role %(role)s, " "check role exists!"), {'user': username, 'role': cfg.CONF.heat_stack_user_role}) raise exception.Error(_("Can't find role %s") % cfg.CONF.heat_stack_user_role) return user.id
def check_create_complete(self, create_data): if timeutils.is_older_than(*create_data): raise SwiftSignalTimeout(self) statuses = self.get_status() if not statuses: return False for status in statuses: if status == self.STATUS_FAILURE: failure = SwiftSignalFailure(self) LOG.info(_LI('%(name)s Failed (%(failure)s)'), { 'name': str(self), 'failure': str(failure) }) raise failure elif status != self.STATUS_SUCCESS: raise exception.Error(_("Unknown status: %s") % status) if len(statuses) >= self.properties[self.COUNT]: LOG.info(_LI("%s Succeeded"), str(self)) return True return False
def _create_keypair(self): # Subclasses may optionally call this in handle_create to create # an ec2 keypair associated with the user, the resulting keys are # stored in resource_data if self.data().get('credential_id'): return # a keypair was created already user_id = self._get_user_id() kp = self.keystone().create_stack_domain_user_keypair( user_id=user_id, project_id=self.stack.stack_user_project_id) if not kp: raise exception.Error( _("Error creating ec2 keypair for user %s") % user_id) else: try: credential_id = kp.id except AttributeError: # keystone v2 keypairs do not have an id attribute. Use the # access key instead. credential_id = kp.access self.data_set('credential_id', credential_id, redact=True) self.data_set('access_key', kp.access, redact=True) self.data_set('secret_key', kp.secret, redact=True) return kp
def get_image_id_by_name(self, image_identifier): """Return the ID for the specified image name. :param image_identifier: image name :returns: the id of the requested :image_identifier: :raises: exception.EntityNotFound, exception.PhysicalResourceNameAmbiguity """ try: filters = {'name': image_identifier} image_list = self.client().images.find(**filters) except sahara_base.APIException as ex: raise exception.Error( _("Error retrieving image list from sahara: " "%s") % six.text_type(ex)) num_matches = len(image_list) if num_matches == 0: raise exception.EntityNotFound(entity='Image', name=image_identifier) elif num_matches > 1: raise exception.PhysicalResourceNameAmbiguity( name=image_identifier) else: return image_list[0].id
def get_signals(self): try: container = self.client().get_container(self.stack.id) except Exception as exc: self.client_plugin().ignore_not_found(exc) LOG.debug("Swift container %s was not found" % self.stack.id) return [] index = container[1] if not index: LOG.debug("Swift objects in container %s were not found" % self.stack.id) return [] # Remove objects in that are for other handle resources, since # multiple SwiftSignalHandle resources in the same stack share # a container filtered = [obj for obj in index if self.obj_name in obj['name']] # Fetch objects from Swift and filter results obj_bodies = [] for obj in filtered: try: signal = self.client().get_object(self.stack.id, obj['name']) except Exception as exc: self.client_plugin().ignore_not_found(exc) continue body = signal[1] if body == swift.IN_PROGRESS: # Ignore the initial object continue if body == "": obj_bodies.append({}) continue try: obj_bodies.append(jsonutils.loads(body)) except ValueError: raise exception.Error( _("Failed to parse JSON data: %s") % body) # Set default values on each signal signals = [] signal_num = 1 for signal in obj_bodies: # Remove previous signals with the same ID sig_id = self.UNIQUE_ID ids = [s.get(sig_id) for s in signals if sig_id in s] if ids and sig_id in signal and ids.count(signal[sig_id]) > 0: [ signals.remove(s) for s in signals if s.get(sig_id) == signal[sig_id] ] # Make sure all fields are set, since all are optional signal.setdefault(self.DATA, None) unique_id = signal.setdefault(sig_id, signal_num) reason = 'Signal %s received' % unique_id signal.setdefault(self.REASON, reason) signal.setdefault(self.STATUS, self.STATUS_SUCCESS) signals.append(signal) signal_num += 1 return signals
def handle_check(self): server = self.client().servers.get(self.resource_id) if not self.client_plugin()._check_active(server, 'Instance'): raise exception.Error( _("Instance is not ACTIVE (was: %s)") % server.status.strip())
def handle_suspend(self): if self.resource_id is None: raise exception.Error( _('Cannot suspend %s, resource not found') % self.name) self.heat().actions.suspend(stack_id=self.resource_id)