예제 #1
0
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)
예제 #3
0
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.")
예제 #4
0
    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']}'.")
예제 #5
0
 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.")
예제 #6
0
        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)
예제 #7
0
 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)
예제 #8
0
 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)
예제 #9
0
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)
예제 #10
0
    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)
예제 #11
0
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)
예제 #12
0
 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
예제 #13
0
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)
예제 #14
0
    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
예제 #15
0
    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)
예제 #17
0
    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)
예제 #18
0
 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
예제 #19
0
    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
예제 #20
0
    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
예제 #21
0
    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()
예제 #22
0
    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
예제 #23
0
    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()
예제 #24
0
파일: driver.py 프로젝트: bopopescu/nova-13
    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)
예제 #25
0
    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()