def upgrade(migrate_engine): meta = MetaData(migrate_engine) instance_types = Table('instance_types', meta, autoload=True) keypairs = Table('key_pairs', meta, autoload=True) aggregates = Table('aggregates', meta, autoload=True) instance_groups = Table('instance_groups', meta, autoload=True) base_msg = _('Migration cannot continue until all these have ' 'been migrated to the api database. Please run ' '`nova-manage db online_migrations\' on Newton ' 'code before continuing.') count = select([func.count()]).select_from(instance_types).where( instance_types.c.deleted == 0).scalar() if count: msg = (base_msg + _('There are still %(count)i unmigrated flavors. ') % { 'count': count }) raise exception.ValidationError(detail=msg) count = select([ func.count() ]).select_from(keypairs).where(keypairs.c.deleted == 0).scalar() if count: msg = (base_msg + _('There are still %(count)i unmigrated keypairs. ') % { 'count': count }) raise exception.ValidationError(detail=msg) count = select([ func.count() ]).select_from(aggregates).where(aggregates.c.deleted == 0).scalar() if count: msg = (base_msg + _('There are still %(count)i unmigrated aggregates. ') % { 'count': count }) raise exception.ValidationError(detail=msg) count = select([func.count()]).select_from(instance_groups).where( instance_groups.c.deleted == 0).scalar() if count: msg = (base_msg + _('There are still %(count)i unmigrated instance groups. ') % { 'count': count }) raise exception.ValidationError(detail=msg)
def scan_for_null_records(table, col_name, check_fkeys): """Queries the table looking for NULL instances of the given column. :param col_name: The name of the column to look for in the table. :param check_fkeys: If True, check the table for foreign keys back to the instances table and if not found, return. :raises: exception.ValidationError: If any records are found. """ if col_name in table.columns: # NOTE(mriedem): filter out tables that don't have a foreign key back # to the instances table since they could have stale data even if # instances.uuid wasn't NULL. if check_fkeys: fkey_found = False fkeys = table.c[col_name].foreign_keys or [] for fkey in fkeys: if fkey.column.table.name == 'instances': fkey_found = True if not fkey_found: return records = len(list( table.select().where(table.c[col_name] == null()).execute() )) if records: msg = _("There are %(records)d records in the " "'%(table_name)s' table where the uuid or " "instance_uuid column is NULL. These must be " "manually cleaned up before the migration will pass. " "Consider running the " "'nova-manage db null_instance_uuid_scan' command.") % ( {'records': records, 'table_name': table.name}) raise exception.ValidationError(detail=msg)
def validate(name: str, value: str): """Validate a given extra spec. :param name: Extra spec name. :param value: Extra spec value. :raises: exception.ValidationError if validation fails. """ # attempt a basic lookup for extra specs without embedded parameters if name in VALIDATORS: VALIDATORS[name].validate(name, value) return # if that failed, fallback to a linear search through the registry for validator in VALIDATORS.values(): if re.fullmatch(validator.name_regex, name): validator.validate(name, value) return # check if there's a namespace; if not, we've done all we can do if ':' not in name: # no namespace return # if there is, check if it's one we recognize for namespace in NAMESPACES: if re.fullmatch(namespace, name.split(':', 1)[0]): break else: return raise exception.ValidationError( f"Validation failed; extra spec '{name}' does not appear to be a " f"valid extra spec.")
def _validate_int(self, value): try: value = int(value) except ValueError: raise exception.ValidationError( f"Validation failed; '{value}' is not a valid integer value.") if 'max' in self.value and self.value['max'] < value: raise exception.ValidationError( f"Validation failed; '{value}' is greater than the max value " f"of '{self.value['max']}'.") if 'min' in self.value and self.value['min'] > value: raise exception.ValidationError( f"Validation failed; '{value}' is less than the min value " f"of '{self.value['min']}'.")
def _validate_bool(self, value): try: strutils.bool_from_string(value, strict=True) except ValueError: raise exception.ValidationError( f"Validation failed; '{value}' is not a valid boolean-like " f"value.")
def wrapper(*args, **kwargs): # The request object is always the second argument. # However numerous unittests pass in the request object # via kwargs instead so we handle that as well. # TODO(cyeoh): cleanup unittests so we don't have to # to do this if 'req' in kwargs: req = kwargs['req'] else: req = args[1] # NOTE(Kevin_Zheng): The webob package throws UnicodeError when # param cannot be decoded. Catch this and raise HTTP 400. try: query_dict = req.GET.dict_of_lists() except UnicodeDecodeError: msg = _('Query string is not UTF-8 encoded') raise exception.ValidationError(msg) if _schema_validation_helper(query_params_schema, query_dict, min_version, max_version, args, kwargs, is_body=False): # NOTE(alex_xu): The additional query parameters were stripped # out when `additionalProperties=True`. This is for backward # compatible with v2.1 API and legacy v2 API. But it makes the # system more safe for no more unexpected parameters pass down # to the system. In the future, we may block all of those # additional parameters by Microversion. _strip_additional_query_parameters(query_params_schema, req) return func(*args, **kwargs)
def validate(self, *args, **kwargs): try: self.validator.validate(*args, **kwargs) except jsonschema.ValidationError as ex: # NOTE: For whole OpenStack message consistency, this error # message has been written as the similar format of WSME. if len(ex.path) > 0: detail = _("Invalid input for field/attribute %(path)s." " Value: %(value)s. %(message)s") % { 'path': ex.path.pop(), 'value': ex.instance, 'message': ex.message } else: detail = ex.message raise exception.ValidationError(detail=detail) except TypeError as ex: # NOTE: If passing non string value to patternProperties parameter, # TypeError happens. Here is for catching the TypeError. detail = six.text_type(ex) raise exception.ValidationError(detail=detail)
def validate(self, *args, **kwargs): try: self.validator.validate(*args, **kwargs) except jsonschema.ValidationError as ex: if isinstance(ex.cause, exception.InvalidName): detail = ex.cause.format_message() elif len(ex.path) > 0: if self.is_body: # NOTE: For whole OpenStack message consistency, this error # message has been written as the similar format of # WSME. detail = _("Invalid input for field/attribute %(path)s. " "Value: %(value)s. %(message)s") % { 'path': ex.path.pop(), 'value': ex.instance, 'message': ex.message } else: # NOTE: Use 'ex.path.popleft()' instead of 'ex.path.pop()', # due to the structure of query parameters is a dict # with key as name and value is list. So the first # item in the 'ex.path' is the key, and second item # is the index of list in the value. We need the # key as the parameter name in the error message. # So pop the first value out of 'ex.path'. detail = _("Invalid input for query parameters %(path)s. " "Value: %(value)s. %(message)s") % { 'path': ex.path.popleft(), 'value': ex.instance, 'message': ex.message } else: detail = ex.message raise exception.ValidationError(detail=detail) except TypeError as ex: # NOTE: If passing non string value to patternProperties parameter, # TypeError happens. Here is for catching the TypeError. detail = six.text_type(ex) raise exception.ValidationError(detail=detail)
def upgrade(migrate_engine): meta = MetaData() meta.bind = migrate_engine flavors = Table('flavors', meta, autoload=True) count = select([func.count()]).select_from(flavors).scalar() if count == 0: # NOTE(danms): We need to be careful here if this is a new # installation, which can't possibly have any mappings. Check # to see if any flavors are defined to determine if we are # upgrading an existing system. If not, then don't obsess over # the lack of mappings return cell_mappings = Table('cell_mappings', meta, autoload=True) count = select([func.count()]).select_from(cell_mappings).scalar() # Two mappings are required at a minimum, cell0 and your first cell if count < 2: msg = _('Cell mappings are not created, but required for Ocata. ' 'Please run nova-manage cell_v2 simple_cell_setup before ' 'continuing.') raise exception.ValidationError(detail=msg) count = select([func.count()]).select_from(cell_mappings).where( cell_mappings.c.uuid == objects.CellMapping.CELL0_UUID).scalar() if count != 1: msg = _('A mapping for Cell0 was not found, but is required for ' 'Ocata. Please run nova-manage cell_v2 simple_cell_setup ' 'before continuing.') raise exception.ValidationError(detail=msg) host_mappings = Table('host_mappings', meta, autoload=True) count = select([func.count()]).select_from(host_mappings).scalar() if count == 0: msg = _('No host mappings were found, but are required for Ocata. ' 'Please run nova-manage cell_v2 simple_cell_setup before ' 'continuing.') raise exception.ValidationError(detail=msg)
def validate(self, name, value): name_match = re.fullmatch(self.name_regex, name) if not name_match: # NOTE(stephenfin): This is mainly here for testing purposes raise exception.ValidationError( f"Validation failed; expected a name of format '{self.name}' " f"but got '{name}'.") if self.value['type'] == int: self._validate_int(value) elif self.value['type'] == bool: self._validate_bool(value) else: # str self._validate_str(value)
def upgrade(migrate_engine): meta = MetaData(migrate_engine) services = Table('services', meta, autoload=True) # Count non-deleted services where uuid is null. count = select([func.count()]).select_from(services).where(and_( services.c.deleted == 0, services.c.uuid == null())).execute().scalar() if count > 0: msg = _('There are still %(count)i unmigrated records in ' 'the services table. Migration cannot continue ' 'until all records have been migrated. Run the ' '"nova-manage db online_data_migrations" routine.') % { 'count': count} raise exception.ValidationError(detail=msg)
def verify_origin_proto(self, console_type, origin_proto): if console_type == 'novnc': expected_proto = \ urlparse.urlparse(CONF.novncproxy_base_url).scheme elif console_type == 'spice-html5': expected_proto = \ urlparse.urlparse(CONF.spice.html5proxy_base_url).scheme elif console_type == 'serial': expected_proto = \ urlparse.urlparse(CONF.serial_console.base_url).scheme else: detail = _("Invalid Console Type for WebSocketProxy: '%s'") % \ console_type raise exception.ValidationError(detail=detail) return origin_proto == expected_proto
def upgrade(migrate_engine): meta = MetaData(migrate_engine) compute_nodes = Table('compute_nodes', meta, autoload=True) aggregates = Table('aggregates', meta, autoload=True) for table in (compute_nodes, aggregates): count = select([func.count()]).select_from(table).where( table.c.uuid == None).execute().scalar() # NOQA if count > 0: msg = WARNING_MSG % { 'count': count, 'table': table.name, } raise exception.ValidationError(detail=msg) pci_devices = Table('pci_devices', meta, autoload=True) count = select([func.count()]).select_from(pci_devices).where( pci_devices.c.parent_addr == None).execute().scalar() # NOQA if count > 0: msg = WARNING_MSG % { 'count': count, 'table': pci_devices.name, } raise exception.ValidationError(detail=msg)
def verify_origin_proto(self, connection_info, origin_proto): access_url = connection_info.get('access_url') if not access_url: detail = _("No access_url in connection_info. " "Cannot validate protocol") raise exception.ValidationError(detail=detail) expected_protos = [urlparse.urlparse(access_url).scheme] # NOTE: For serial consoles the expected protocol could be ws or # wss which correspond to http and https respectively in terms of # security. if 'ws' in expected_protos: expected_protos.append('http') if 'wss' in expected_protos: expected_protos.append('https') return origin_proto in expected_protos
def validate(self, *args, **kwargs): try: self.validator.validate(*args, **kwargs) except jsonschema.ValidationError as ex: # NOTE: For whole OpenStack message consistency, this error # message has been written as the similar format of WSME. if len(ex.path) > 0: detail = _("Invalid input for field/attribute %(path)s." " Value: %(value)s. %(message)s") % { 'path': ex.path.pop(), 'value': ex.instance, 'message': ex.message } else: detail = ex.message raise exception.ValidationError(detail=detail)
def upgrade(migrate_engine): meta = MetaData(migrate_engine) instances = Table('instances', meta, autoload=True) sysmeta = Table('instance_system_metadata', meta, autoload=True) count = select([func.count()]).select_from(sysmeta).where( and_(instances.c.uuid == sysmeta.c.instance_uuid, sysmeta.c.key == 'instance_type_id', sysmeta.c.deleted != sysmeta.c.id, instances.c.deleted != instances.c.id)).execute().scalar() if count > 0: msg = _('There are still %(count)i unmigrated flavor records. ' 'Migration cannot continue until all instance flavor ' 'records have been migrated to the new format. Please run ' '`nova-manage db migrate_flavor_data\' first.') % { 'count': count } raise exception.ValidationError(detail=msg)
def _flavor_bool(self, val, key): """Will validate and return the boolean for a given value. :param val: The value to parse into a boolean. :param key: The flavor key. :return: The boolean value for the attribute. If is not well formed will raise an ValidationError. """ trues = ['true', 't', 'yes', 'y'] falses = ['false', 'f', 'no', 'n'] if val.lower() in trues: return True elif val.lower() in falses: return False else: msg = (_('Flavor attribute %(attr)s must be either True or ' 'False. Current value %(val)s is not allowed.') % {'attr': key, 'val': val}) raise exception.ValidationError(msg)
def verify_origin_proto(self, console_type, origin_proto): if console_type == 'novnc': expected_protos = \ [urlparse.urlparse(CONF.novncproxy_base_url).scheme] elif console_type == 'spice-html5': expected_protos = \ [urlparse.urlparse(CONF.spice.html5proxy_base_url).scheme] elif console_type == 'serial': expected_protos = \ [urlparse.urlparse(CONF.serial_console.base_url).scheme] # NOTE: For serial consoles the expected protocol could be ws or # wss which correspond to http and https respectively in terms of # security. if 'ws' in expected_protos: expected_protos.append('http') if 'wss' in expected_protos: expected_protos.append('https') else: detail = _("Invalid Console Type for WebSocketProxy: '%s'") % \ console_type raise exception.ValidationError(detail=detail) return origin_proto in expected_protos
def new_websocket_client(self): """Called after a new WebSocket connection has been established.""" # Reopen the eventlet hub to make sure we don't share an epoll # fd with parent and/or siblings, which would be bad from eventlet import hubs hubs.use_hub() # The nova expected behavior is to have token # passed to the method GET of the request parse = urlparse.urlparse(self.path) if parse.scheme not in ('http', 'https'): # From a bug in urlparse in Python < 2.7.4 we cannot support # special schemes (cf: http://bugs.python.org/issue9374) if sys.version_info < (2, 7, 4): raise exception.NovaException( _("We do not support scheme '%s' under Python < 2.7.4, " "please use http or https") % parse.scheme) query = parse.query token = urlparse.parse_qs(query).get("token", [""]).pop() if not token: # NoVNC uses it's own convention that forward token # from the request to a cookie header, we should check # also for this behavior hcookie = self.headers.getheader('cookie') if hcookie: cookie = Cookie.SimpleCookie() cookie.load(hcookie) if 'token' in cookie: token = cookie['token'].value ctxt = context.get_admin_context() rpcapi = consoleauth_rpcapi.ConsoleAuthAPI() connect_info = rpcapi.check_token(ctxt, token=token) if not connect_info: raise exception.InvalidToken(token=token) # Verify Origin expected_origin_hostname = self.headers.getheader('Host') if ':' in expected_origin_hostname: e = expected_origin_hostname expected_origin_hostname = e.split(':')[0] origin_url = self.headers.getheader('Origin') # missing origin header indicates non-browser client which is OK if origin_url is not None: origin = urlparse.urlparse(origin_url) origin_hostname = origin.hostname origin_scheme = origin.scheme if origin_hostname == '' or origin_scheme == '': detail = _("Origin header not valid.") raise exception.ValidationError(detail=detail) if expected_origin_hostname != origin_hostname: detail = _("Origin header does not match this host.") raise exception.ValidationError(detail=detail) if not self.verify_origin_proto(connect_info, origin_scheme): detail = _("Origin header protocol does not match this host.") raise exception.ValidationError(detail=detail) self.msg(_('connect info: %s') % str(connect_info)) host = connect_info['host'] port = int(connect_info['port']) # Connect to the target self.msg(_("connecting to: %(host)s:%(port)s") % {'host': host, 'port': port}) tsock = self.socket(host, port, connect=True) # Handshake as necessary if connect_info.get('internal_access_path'): tsock.send("CONNECT %s HTTP/1.1\r\n\r\n" % connect_info['internal_access_path']) while True: data = tsock.recv(4096, socket.MSG_PEEK) if data.find("\r\n\r\n") != -1: if data.split("\r\n")[0].find("200") == -1: raise exception.InvalidConnectionInfo() tsock.recv(len(data)) break # Start proxying try: self.do_proxy(tsock) except Exception: if tsock: tsock.shutdown(socket.SHUT_RDWR) tsock.close() self.vmsg(_("%(host)s:%(port)s: Target closed") % {'host': host, 'port': port}) raise
def new_websocket_client(self): """Called after a new WebSocket connection has been established.""" # Reopen the eventlet hub to make sure we don't share an epoll # fd with parent and/or siblings, which would be bad from eventlet import hubs hubs.use_hub() # The nova expected behavior is to have token # passed to the method GET of the request query = urlparse.urlparse(self.path).query token = urlparse.parse_qs(query).get("token", [""]).pop() if not token: # NoVNC uses it's own convention that forward token # from the request to a cookie header, we should check # also for this behavior hcookie = self.headers.getheader('cookie') if hcookie: cookie = Cookie.SimpleCookie() cookie.load(hcookie) if 'token' in cookie: token = cookie['token'].value ctxt = context.get_admin_context() rpcapi = consoleauth_rpcapi.ConsoleAuthAPI() connect_info = rpcapi.check_token(ctxt, token=token) if not connect_info: raise Exception(_("Invalid Token")) # Verify Origin expected_origin_hostname = self.headers.getheader('Host') if ':' in expected_origin_hostname: e = expected_origin_hostname expected_origin_hostname = e.split(':')[0] origin_url = self.headers.getheader('Origin') # missing origin header indicates non-browser client which is OK if origin_url is not None: origin = urlparse.urlparse(origin_url) origin_hostname = origin.hostname origin_scheme = origin.scheme if origin_hostname == '' or origin_scheme == '': detail = _("Origin header not valid.") raise exception.ValidationError(detail=detail) if expected_origin_hostname != origin_hostname: detail = _("Origin header does not match this host.") raise exception.ValidationError(detail=detail) if not self.verify_origin_proto(connect_info['console_type'], origin.scheme): detail = _("Origin header protocol does not match this host.") raise exception.ValidationError(detail=detail) self.msg(_('connect info: %s'), str(connect_info)) host = connect_info['host'] port = int(connect_info['port']) # Connect to the target self.msg( _("connecting to: %(host)s:%(port)s") % { 'host': host, 'port': port }) tsock = self.socket(host, port, connect=True) # Handshake as necessary if connect_info.get('internal_access_path'): tsock.send("CONNECT %s HTTP/1.1\r\n\r\n" % connect_info['internal_access_path']) while True: data = tsock.recv(4096, socket.MSG_PEEK) if data.find("\r\n\r\n") != -1: if not data.split("\r\n")[0].find("200"): raise Exception(_("Invalid Connection Info")) tsock.recv(len(data)) break instance_id = connect_info.get('instance_uuid', 'None') # Start proxying try: operationlog.info( "VNC: host:%s, port:%s, is connecting to vm %s, at %s" % (host, port, instance_id, timeutils.utcnow()), extra={"type": "operate"}) self.do_proxy(tsock) except Exception: if tsock: tsock.shutdown(socket.SHUT_RDWR) tsock.close() operationlog.info( "VNC: host:%s, port:%s, lost connection with vm %s, at %s" % (host, port, instance_id, timeutils.utcnow()), extra={"type": "operate"}) self.vmsg( _("%(host)s:%(port)s: Target closed") % { 'host': host, 'port': port }) LOG.audit("%s:%s: Target closed" % (host, port)) raise
def spawn(self, context, instance, image_meta, injected_files, admin_password, network_info=None, block_device_info=None): # The compute manager is meant to know the node uuid, so missing uuid # is a significant issue. It may mean we've been passed the wrong data. node_uuid = instance.get('node') if not node_uuid: raise exception.NovaException( _("Ironic node uuid not supplied to " "driver for instance %s.") % instance['uuid']) icli = client_wrapper.IronicClientWrapper() node = icli.call("node.get", node_uuid) flavor = flavor_obj.Flavor.get_by_id(context, instance['instance_type_id']) self._add_driver_fields(node, instance, image_meta, flavor) #validate we ready to do the deploy validate_chk = icli.call("node.validate", node_uuid) if not validate_chk.deploy or not validate_chk.power: # something is wrong. undo we we have done self._cleanup_deploy(node, instance, network_info) raise exception.ValidationError( _("Ironic node: %(id)s failed to validate." " (deploy: %(deploy)s, power: %(power)s)") % { 'id': node.uuid, 'deploy': validate_chk.deploy, 'power': validate_chk.power }) # prepare for the deploy try: self._plug_vifs(node, instance, network_info) self._start_firewall(instance, network_info) except Exception: with excutils.save_and_reraise_exception(): LOG.error( _("Error preparing deploy for instance %(instance)s " "on baremetal node %(node)s.") % { 'instance': instance['uuid'], 'node': node_uuid }) self._cleanup_deploy(node, instance, network_info) # trigger the node deploy try: icli.call("node.set_provision_state", node_uuid, ironic_states.ACTIVE) except ( exception.NovaException, # Retry failed ironic_exception.HTTPInternalServerError, # Validations ironic_exception.HTTPBadRequest) as e: # Maintenance msg = (_("Failed to request Ironic to provision instance " "%(inst)s: %(reason)s") % { 'inst': instance['uuid'], 'reason': str(e) }) LOG.error(msg) self._cleanup_deploy(node, instance, network_info) raise exception.InstanceDeployFailure(msg) # wait for the node to be marked as ACTIVE in Ironic def _wait_for_active(): try: node = icli.call("node.get_by_instance_uuid", instance['uuid']) except ironic_exception.HTTPNotFound: raise exception.InstanceNotFound(instance_id=instance['uuid']) if node.provision_state == ironic_states.ACTIVE: # job is done raise loopingcall.LoopingCallDone() if node.target_provision_state == ironic_states.DELETED: # ironic is trying to delete it now raise exception.InstanceNotFound(instance_id=instance['uuid']) if node.provision_state == ironic_states.NOSTATE: # ironic already deleted it raise exception.InstanceNotFound(instance_id=instance['uuid']) if node.provision_state == ironic_states.DEPLOYFAIL: # ironic failed to deploy msg = (_("Failed to provision instance %(inst)s: %(reason)s") % { 'inst': instance['uuid'], 'reason': node.last_error }) LOG.error(msg) raise exception.InstanceDeployFailure(msg) timer = loopingcall.FixedIntervalLoopingCall(_wait_for_active) # TODO(lucasagomes): Make the time configurable timer.start(interval=10).wait()
def new_websocket_client(self): """Called after a new WebSocket connection has been established.""" # Reopen the eventlet hub to make sure we don't share an epoll # fd with parent and/or siblings, which would be bad from eventlet import hubs hubs.use_hub() # The nova expected behavior is to have token # passed to the method GET of the request parse = urlparse.urlparse(self.path) if parse.scheme not in ('http', 'https'): # From a bug in urlparse in Python < 2.7.4 we cannot support # special schemes (cf: http://bugs.python.org/issue9374) if sys.version_info < (2, 7, 4): raise exception.NovaException( _("We do not support scheme '%s' under Python < 2.7.4, " "please use http or https") % parse.scheme) query = parse.query token = urlparse.parse_qs(query).get("token", [""]).pop() if not token: # NoVNC uses it's own convention that forward token # from the request to a cookie header, we should check # also for this behavior hcookie = self.headers.get('cookie') if hcookie: cookie = Cookie.SimpleCookie() for hcookie_part in hcookie.split(';'): hcookie_part = hcookie_part.lstrip() try: cookie.load(hcookie_part) except Cookie.CookieError: # NOTE(stgleb): Do not print out cookie content # for security reasons. LOG.warning('Found malformed cookie') else: if 'token' in cookie: token = cookie['token'].value ctxt = context.get_admin_context() connect_info = self._get_connect_info(ctxt, token) # Verify Origin expected_origin_hostname = self.headers.get('Host') if ':' in expected_origin_hostname: e = expected_origin_hostname if '[' in e and ']' in e: expected_origin_hostname = e.split(']')[0][1:] else: expected_origin_hostname = e.split(':')[0] expected_origin_hostnames = CONF.console.allowed_origins expected_origin_hostnames.append(expected_origin_hostname) origin_url = self.headers.get('Origin') # missing origin header indicates non-browser client which is OK if origin_url is not None: origin = urlparse.urlparse(origin_url) origin_hostname = origin.hostname origin_scheme = origin.scheme # If the console connection was forwarded by a proxy (example: # haproxy), the original protocol could be contained in the # X-Forwarded-Proto header instead of the Origin header. Prefer the # forwarded protocol if it is present. forwarded_proto = self.headers.get('X-Forwarded-Proto') if forwarded_proto is not None: origin_scheme = forwarded_proto if origin_hostname == '' or origin_scheme == '': detail = _("Origin header not valid.") raise exception.ValidationError(detail=detail) if origin_hostname not in expected_origin_hostnames: detail = _("Origin header does not match this host.") raise exception.ValidationError(detail=detail) if not self.verify_origin_proto(connect_info, origin_scheme): detail = _("Origin header protocol does not match this host.") raise exception.ValidationError(detail=detail) self.msg(_('connect info: %s'), str(connect_info)) host = connect_info.host port = connect_info.port # Connect to the target self.msg( _("connecting to: %(host)s:%(port)s") % { 'host': host, 'port': port }) tsock = self.socket(host, port, connect=True) # Handshake as necessary if 'internal_access_path' in connect_info: path = connect_info.internal_access_path if path: tsock.send( encodeutils.safe_encode('CONNECT %s HTTP/1.1\r\n\r\n' % path)) end_token = "\r\n\r\n" while True: data = tsock.recv(4096, socket.MSG_PEEK) token_loc = data.find(end_token) if token_loc != -1: if data.split("\r\n")[0].find("200") == -1: raise exception.InvalidConnectionInfo() # remove the response from recv buffer tsock.recv(token_loc + len(end_token)) break if self.server.security_proxy is not None: tenant_sock = TenantSock(self) try: tsock = self.server.security_proxy.connect(tenant_sock, tsock) except exception.SecurityProxyNegotiationFailed: LOG.exception("Unable to perform security proxying, shutting " "down connection") tenant_sock.close() tsock.shutdown(socket.SHUT_RDWR) tsock.close() raise tenant_sock.finish_up() # Start proxying try: self.do_proxy(tsock) except Exception: if tsock: tsock.shutdown(socket.SHUT_RDWR) tsock.close() self.vmsg( _("%(host)s:%(port)s: " "Websocket client or target closed") % { 'host': host, 'port': port }) raise
def spawn(self, context, instance, image_meta, injected_files, admin_password, network_info=None, block_device_info=None): # The compute manager is meant to know the node uuid, so missing uuid # is a significant issue. It may mean we've been passed the wrong data. node_uuid = instance.get('node') if not node_uuid: raise exception.NovaException( _("Ironic node uuid not supplied to " "driver for instance %s.") % instance['uuid']) icli = client_wrapper.IronicClientWrapper() node = icli.call("node.get", node_uuid) flavor = flavor_obj.Flavor.get_by_id(context, instance['instance_type_id']) self._add_driver_fields(node, instance, image_meta, flavor) # NOTE(Shrews): The default ephemeral device needs to be set for # services (like cloud-init) that depend on it being returned by the # metadata server. Addresses bug https://launchpad.net/bugs/1324286. if flavor['ephemeral_gb']: instance.default_ephemeral_device = '/dev/sda1' instance.save() #validate we ready to do the deploy validate_chk = icli.call("node.validate", node_uuid) if not validate_chk.deploy or not validate_chk.power: # something is wrong. undo we we have done self._cleanup_deploy(node, instance, network_info) raise exception.ValidationError( _("Ironic node: %(id)s failed to validate." " (deploy: %(deploy)s, power: %(power)s)") % { 'id': node.uuid, 'deploy': validate_chk.deploy, 'power': validate_chk.power }) # prepare for the deploy try: self._plug_vifs(node, instance, network_info) self._start_firewall(instance, network_info) except Exception: with excutils.save_and_reraise_exception(): LOG.error( _("Error preparing deploy for instance %(instance)s " "on baremetal node %(node)s.") % { 'instance': instance['uuid'], 'node': node_uuid }) self._cleanup_deploy(node, instance, network_info) # trigger the node deploy try: icli.call("node.set_provision_state", node_uuid, ironic_states.ACTIVE) except ( exception.NovaException, # Retry failed ironic_exception.InternalServerError, # Validations ironic_exception.BadRequest) as e: # Maintenance msg = (_("Failed to request Ironic to provision instance " "%(inst)s: %(reason)s") % { 'inst': instance['uuid'], 'reason': str(e) }) LOG.error(msg) self._cleanup_deploy(node, instance, network_info) raise exception.InstanceDeployFailure(msg) timer = loopingcall.FixedIntervalLoopingCall(self._wait_for_active, icli, instance) timer.start(interval=CONF.ironic.api_retry_interval).wait()
def spawn(self, context, instance, image_meta, injected_files, admin_password, network_info=None, block_device_info=None): """Deploy an instance. :param context: The security context. :param instance: The instance object. :param image_meta: Image dict returned by nova.image.glance that defines the image from which to boot this instance. :param injected_files: User files to inject into instance. Ignored by this driver. :param admin_password: Administrator password to set in instance. Ignored by this driver. :param network_info: Instance network information. :param block_device_info: Instance block device information. Ignored by this driver. """ # The compute manager is meant to know the node uuid, so missing uuid # is a significant issue. It may mean we've been passed the wrong data. node_uuid = instance.get('node') if not node_uuid: raise ironic.exc.BadRequest( _("Ironic node uuid not supplied to " "driver for instance %s.") % instance['uuid']) icli = client_wrapper.IronicClientWrapper() node = icli.call("node.get", node_uuid) flavor = objects.Flavor.get_by_id(context, instance['instance_type_id']) self._add_driver_fields(node, instance, image_meta, flavor) # NOTE(Shrews): The default ephemeral device needs to be set for # services (like cloud-init) that depend on it being returned by the # metadata server. Addresses bug https://launchpad.net/bugs/1324286. if flavor['ephemeral_gb']: instance.default_ephemeral_device = '/dev/sda1' instance.save() # validate we are ready to do the deploy validate_chk = icli.call("node.validate", node_uuid) if not validate_chk.deploy or not validate_chk.power: # something is wrong. undo what we have done self._cleanup_deploy(context, node, instance, network_info) raise exception.ValidationError( _("Ironic node: %(id)s failed to validate." " (deploy: %(deploy)s, power: %(power)s)") % { 'id': node.uuid, 'deploy': validate_chk.deploy, 'power': validate_chk.power }) # prepare for the deploy try: self._plug_vifs(node, instance, network_info) self._start_firewall(instance, network_info) except Exception: with excutils.save_and_reraise_exception(): LOG.error( _LE("Error preparing deploy for instance " "%(instance)s on baremetal node %(node)s."), { 'instance': instance['uuid'], 'node': node_uuid }) self._cleanup_deploy(context, node, instance, network_info) # trigger the node deploy try: icli.call("node.set_provision_state", node_uuid, ironic_states.ACTIVE) except Exception as e: with excutils.save_and_reraise_exception(): msg = (_LE("Failed to request Ironic to provision instance " "%(inst)s: %(reason)s"), { 'inst': instance['uuid'], 'reason': six.text_type(e) }) LOG.error(msg) self._cleanup_deploy(context, node, instance, network_info) timer = loopingcall.FixedIntervalLoopingCall(self._wait_for_active, icli, instance) try: timer.start(interval=CONF.ironic.api_retry_interval).wait() except Exception: with excutils.save_and_reraise_exception(): LOG.error( _LE("Error deploying instance %(instance)s on " "baremetal node %(node)s."), { 'instance': instance['uuid'], 'node': node_uuid }) self.destroy(context, instance, network_info)
def spawn(self, context, instance, image_meta, injected_files, admin_password, network_info=None, block_device_info=None): # The compute manager is meant to know the node uuid, so missing uuid # is a significant issue. It may mean we've been passed the wrong data. node_uuid = instance.get('node') if not node_uuid: raise exception.NovaException( _("Ironic node uuid not supplied to " "driver for instance %s.") % instance['uuid']) icli = self._get_client() node = icli.node.get(node_uuid) # Associate the node to this instance try: # NOTE(deva): this may raise a NodeAlreadyAssociated exception # which we allow to propagate up to the scheduler, # so it retries on another node. patch = [{ 'op': 'replace', 'path': '/instance_uuid', 'value': instance['uuid'] }] self._retry_if_service_is_unavailable(icli.node.update, node_uuid, patch) except (ironic_exception.HTTPBadRequest, MaximumRetriesReached): msg = _("Unable to set instance UUID for node %s") % node_uuid LOG.error(msg) raise exception.NovaException(msg) # Set image id, and other driver info so we can pass it down to Ironic # use the ironic_driver_fields file to import flavor = flavor_obj.Flavor.get_by_id(context, instance['instance_type_id']) self._add_driver_fields(node, instance, image_meta, flavor) #validate we ready to do the deploy validate_chk = icli.node.validate(node_uuid) if not validate_chk.deploy or not validate_chk.power: # something is wrong. undo we we have done self._cleanup_deploy(node, instance, network_info) raise exception.ValidationError( _("Ironic node: %(id)s failed to validate." " (deploy: %(deploy)s, power: %(power)s)") % { 'id': node.uuid, 'deploy': validate_chk.deploy, 'power': validate_chk.power }) # prepare for the deploy try: self._plug_vifs(node, instance, network_info) self._start_firewall(instance, network_info) except Exception: with excutils.save_and_reraise_exception(): LOG.error( _("Error preparing deploy for instance %(instance)s " "on baremetal node %(node)s.") % { 'instance': instance['uuid'], 'node': node_uuid }) self._cleanup_deploy(node, instance, network_info) # trigger the node deploy try: self._retry_if_service_is_unavailable( icli.node.set_provision_state, node_uuid, 'active') except MaximumRetriesReached: msg = (_("Error triggering the node %s to start the deployment") % node_uuid) LOG.error(msg) self._cleanup_deploy(node, instance, network_info) raise exception.NovaException(msg) except ( ironic_exception.HTTPInternalServerError, # Validations ironic_exception.HTTPBadRequest) as e: # Maintenance msg = (_("Failed to request Ironic to provision instance " "%(inst)s: %(reason)s") % { 'inst': instance['uuid'], 'reason': str(e) }) LOG.error(msg) self._cleanup_deploy(node, instance, network_info) raise exception.InstanceDeployFailure(msg) # wait for the node to be marked as ACTIVE in Ironic def _wait_for_active(): try: node = icli.node.get_by_instance_uuid(instance['uuid']) except ironic_exception.HTTPNotFound: raise exception.InstanceNotFound(instance_id=instance['uuid']) if node.provision_state == ironic_states.ACTIVE: # job is done raise loopingcall.LoopingCallDone() if node.target_provision_state == ironic_states.DELETED: # ironic is trying to delete it now raise exception.InstanceNotFound(instance_id=instance['uuid']) if node.provision_state == ironic_states.NOSTATE: # ironic already deleted it raise exception.InstanceNotFound(instance_id=instance['uuid']) if node.provision_state == ironic_states.DEPLOYFAIL: # ironic failed to deploy msg = (_("Failed to provision instance %(inst)s: %(reason)s") % { 'inst': instance['uuid'], 'reason': node.last_error }) LOG.error(msg) raise exception.InstanceDeployFailure(msg) timer = loopingcall.FixedIntervalLoopingCall(_wait_for_active) # TODO(lucasagomes): Make the time configurable timer.start(interval=10).wait()