def _validate_statistics_methods(self, method, **kwargs): jsonschema.validate(kwargs, self.statistics_schema) global_params = ('unhandled_requests', 'response_time', 'cpu_throttling', 'memory_throttling', 'communication_failures') if kwargs['scope'] == 'policy' and 'policy_id' not in kwargs: raise exception.MissingParameterValue(_('Missing "policy_id"')) if kwargs.get('parameter_name') not in global_params: if 'domain_id' not in kwargs: raise exception.MissingParameterValue(_('Missing "domain_id"')) if method == 'reset_nm_statistics': if 'parameter_name' in kwargs: if kwargs['parameter_name'] not in global_params: raise exception.InvalidParameterValue( _('Invalid parameter name for resetting statistic, ' 'individual reset is possible only for: %s') % ', '.join(global_params)) elif method == 'get_nm_statistics': if 'parameter_name' not in kwargs: raise exception.MissingParameterValue( _('Parameter name is mandatory for getting statistics')) # valid parameters depend on scope if (kwargs['parameter_name'] not in nm_commands.STATISTICS[kwargs['scope']]): raise exception.InvalidParameterValue( _('Invalid parameter name %(param)% for scope ' '%(scope)s') % {'param': kwargs['parameter_name'], 'scope': kwargs['scope']})
def _validate_policy_methods(self, method, **kwargs): if method in ('get_nm_policy', 'remove_nm_policy', 'get_nm_policy_suspend', 'remove_nm_policy_suspend'): jsonschema.validate(kwargs, self.main_ids_schema) elif method == 'control_nm_policy': jsonschema.validate(kwargs, self.control_schema) if kwargs['scope'] != 'global' and 'domain_id' not in kwargs: raise exception.MissingParameterValue(_('Missing "domain_id"')) if kwargs['scope'] == 'policy' and 'policy_id' not in kwargs: raise exception.MissingParameterValue(_('Missing "policy_id"')) elif method == 'set_nm_policy': jsonschema.validate(kwargs, self.policy_schema) if kwargs['policy_trigger'] == 'boot': if not isinstance(kwargs['target_limit'], dict): raise exception.InvalidParameterValue(_('Invalid boot ' 'policy')) elif 'correction_time' not in kwargs: raise exception.MissingParameterValue( _('Missing "correction_time" for no-boot policy')) elif method == 'set_nm_policy_suspend': jsonschema.validate(kwargs, self.suspend_schema) elif method == 'get_nm_capabilities': jsonschema.validate(kwargs, self.get_cap_schema)
def _parse_driver_info(node): info = node.driver_info or {} missing_info = [key for key in REQUIRED_PROPERTIES if not info.get(key)] if missing_info: raise ironic_exception.MissingParameterValue( _("Missing the following iBoot credentials in node's" " driver_info: %s.") % missing_info) address = info['iboot_address'] username = info['iboot_username'] password = info['iboot_password'] relay_id = info.get('iboot_relay_id', 1) try: relay_id = int(relay_id) except ValueError: raise ironic_exception.InvalidParameterValue( _("iBoot PDU relay id must be an integer.")) port = info.get('iboot_port', 9100) port = utils.validate_network_port(port, 'iboot_port') return { 'address': address, 'username': username, 'password': password, 'port': port, 'relay_id': relay_id, 'uuid': node.uuid, }
def _validate_policy_methods(self, method, **kwargs): if method in ('get_nm_policy', 'remove_nm_policy', 'get_nm_policy_suspend', 'remove_nm_policy_suspend'): jsonschema.validate(kwargs, self.main_ids_schema) elif method == 'control_nm_policy': jsonschema.validate(kwargs, self.control_schema) if kwargs['scope'] != 'global' and 'domain_id' not in kwargs: raise exception.MissingParameterValue(_('Missing "domain_id"')) if kwargs['scope'] == 'policy' and 'policy_id' not in kwargs: raise exception.MissingParameterValue(_('Missing "policy_id"')) elif method == 'set_nm_policy': jsonschema.validate(kwargs, self.policy_schema) if kwargs['policy_trigger'] == 'boot': if not isinstance(kwargs['target_limit'], dict): raise exception.InvalidParameterValue( _('Invalid boot ' 'policy')) elif 'correction_time' not in kwargs: raise exception.MissingParameterValue( _('Missing "correction_time" for no-boot policy')) elif method == 'set_nm_policy_suspend': jsonschema.validate(kwargs, self.suspend_schema) elif method == 'get_nm_capabilities': jsonschema.validate(kwargs, self.get_cap_schema)
def _get_nm_address(task): """Get Intel Node Manager target channel and address. :param task: a TaskManager instance. :raises: IPMIFailure if Intel Node Manager is not detected on a node or if an error happens during detection. :returns: a tuple with IPMI channel and address of Intel Node Manager. """ node = task.node driver_internal_info = node.driver_internal_info def _save_to_node(channel, address): driver_internal_info['intel_nm_channel'] = channel driver_internal_info['intel_nm_address'] = address node.driver_internal_info = driver_internal_info node.save() channel = driver_internal_info.get('intel_nm_channel') address = driver_internal_info.get('intel_nm_address') if channel and address: return channel, address if channel is False and address is False: raise exception.IPMIFailure( _('Driver data indicates that Intel ' 'Node Manager detection failed.')) LOG.info(_LI('Start detection of Intel Node Manager on node %s'), node.uuid) sdr_filename = os.path.join(CONF.tempdir, node.uuid + '.sdr') res = None try: ipmitool.dump_sdr(task, sdr_filename) res = nm_commands.parse_slave_and_channel(sdr_filename) finally: ironic_utils.unlink_without_raise(sdr_filename) if res is None: _save_to_node(False, False) raise exception.IPMIFailure(_('Intel Node Manager is not detected.')) address, channel = res LOG.debug( 'Intel Node Manager sensors present in SDR on node %(node)s, ' 'channel %(channel)s address %(address)s.', { 'node': node.uuid, 'channel': channel, 'address': address }) # SDR can contain wrong info, try simple command node.driver_info['ipmi_bridging'] = 'single' node.driver_info['ipmi_target_channel'] = channel node.driver_info['ipmi_target_address'] = address try: ipmitool.send_raw(task, _command_to_string(nm_commands.get_version(None))) _save_to_node(channel, address) return channel, address except exception.IPMIFailure: _save_to_node(False, False) raise exception.IPMIFailure( _('Intel Node Manager sensors record ' 'present in SDR but Node Manager is not ' 'responding.'))
def _validate_statistics_methods(self, method, **kwargs): jsonschema.validate(kwargs, self.statistics_schema) global_params = ('unhandled_requests', 'response_time', 'cpu_throttling', 'memory_throttling', 'communication_failures') if kwargs['scope'] == 'policy' and 'policy_id' not in kwargs: raise exception.MissingParameterValue(_('Missing "policy_id"')) if kwargs.get('parameter_name') not in global_params: if 'domain_id' not in kwargs: raise exception.MissingParameterValue(_('Missing "domain_id"')) if method == 'reset_nm_statistics': if 'parameter_name' in kwargs: if kwargs['parameter_name'] not in global_params: raise exception.InvalidParameterValue( _('Invalid parameter name for resetting statistic, ' 'individual reset is possible only for: %s') % ', '.join(global_params)) elif method == 'get_nm_statistics': if 'parameter_name' not in kwargs: raise exception.MissingParameterValue( _('Parameter name is mandatory for getting statistics')) # valid parameters depend on scope if (kwargs['parameter_name'] not in nm_commands.STATISTICS[kwargs['scope']]): raise exception.InvalidParameterValue( _('Invalid parameter name %(param)% for scope ' '%(scope)s') % { 'param': kwargs['parameter_name'], 'scope': kwargs['scope'] })
def _ipmi_timestamp_to_isotime(timestamp): """Convert IPMI timestamp to iso8601.""" if timestamp == _UNSPECIFIED_TIMESTAMP: raise exception.InvalidIPMITimestamp(_('IPMI timestamp is invalid or ' 'unspecified')) if timestamp <= _INIT_TIMESTAMP_MAX: raise exception.InvalidIPMITimestamp(_('IPMI initialization is not ' 'completed, relative time is ' '%d second') % timestamp) return datetime.datetime.utcfromtimestamp(timestamp).isoformat()
def _get_nm_address(task): """Get Intel Node Manager target channel and address. :param task: a TaskManager instance. :raises: IPMIFailure if Intel Node Manager is not detected on a node or if an error happens during detection. :returns: a tuple with IPMI channel and address of Intel Node Manager. """ node = task.node driver_internal_info = node.driver_internal_info def _save_to_node(channel, address): driver_internal_info['intel_nm_channel'] = channel driver_internal_info['intel_nm_address'] = address node.driver_internal_info = driver_internal_info node.save() channel = driver_internal_info.get('intel_nm_channel') address = driver_internal_info.get('intel_nm_address') if channel and address: return channel, address if channel is False and address is False: raise exception.IPMIFailure(_('Driver data indicates that Intel ' 'Node Manager detection failed.')) LOG.info(_LI('Start detection of Intel Node Manager on node %s'), node.uuid) sdr_filename = os.path.join(CONF.tempdir, node.uuid + '.sdr') res = None try: ipmitool.dump_sdr(task, sdr_filename) res = nm_commands.parse_slave_and_channel(sdr_filename) finally: ironic_utils.unlink_without_raise(sdr_filename) if res is None: _save_to_node(False, False) raise exception.IPMIFailure(_('Intel Node Manager is not detected.')) address, channel = res LOG.debug('Intel Node Manager sensors present in SDR on node %(node)s, ' 'channel %(channel)s address %(address)s.', {'node': node.uuid, 'channel': channel, 'address': address}) # SDR can contain wrong info, try simple command node.driver_info['ipmi_bridging'] = 'single' node.driver_info['ipmi_target_channel'] = channel node.driver_info['ipmi_target_address'] = address try: ipmitool.send_raw(task, _command_to_string(nm_commands.get_version(None))) _save_to_node(channel, address) return channel, address except exception.IPMIFailure: _save_to_node(False, False) raise exception.IPMIFailure(_('Intel Node Manager sensors record ' 'present in SDR but Node Manager is not ' 'responding.'))
def wrapper(raw_data): msg = _('Data from Intel Node Manager %s') try: return func(raw_data) except (IndexError, struct.error): raise ironic_exception.IPMIFailure(msg % _('has wrong length.')) except KeyError: raise ironic_exception.IPMIFailure(msg % _('is corrupted.')) except ValueError: raise ironic_exception.IPMIFailure(msg % _('cannot be converted.'))
def wrapper(raw_data): msg = _('Data from Intel Node Manager %s') try: return func(raw_data) except (IndexError, struct.error): raise exception.IPMIFailure(msg % _('has wrong length.')) except KeyError: raise exception.IPMIFailure(msg % _('is corrupted.')) except ValueError: raise exception.IPMIFailure(msg % _('cannot be converted.'))
def set_power_state(self, task, pstate, timeout=None): """Turn the power on or off. :param task: a TaskManager instance containing the node to act on. :param pstate: The desired power state, one of ironic.common.states POWER_ON, POWER_OFF. :param timeout: timeout (in seconds). Unsupported by this interface. :raises: InvalidParameterValue if iboot parameters are invalid or if an invalid power state was specified. :raises: MissingParameterValue if required iboot parameters are missing. :raises: PowerStateFailure if the power couldn't be set to pstate. """ # TODO(rloo): Support timeouts! if timeout is not None: LOG.warning( "The 'iboot' Power Interface's 'set_power_state' method " "doesn't support the 'timeout' parameter. Ignoring " "timeout=%(timeout)s", {'timeout': timeout}) driver_info = _parse_driver_info(task.node) if pstate == states.POWER_ON: _switch(driver_info, True) elif pstate == states.POWER_OFF: _switch(driver_info, False) else: raise ironic_exception.InvalidParameterValue( _("set_power_state called with invalid " "power state %s.") % pstate) _check_power_state(driver_info, pstate)
def set_boot_device(self, task, device, persistent=False): """Set the boot device for the task's node. Set the boot device to use on next boot of the node. :param task: a task from TaskManager. :param device: the boot device :param persistent: Boolean value. True if the boot device will persist to all future boots, False if not. Default: False. :raises: InvalidParameterValue if an invalid boot device is specified. """ node = task.node if device not in amt_common.BOOT_DEVICES_MAPPING: raise ironic_exception.InvalidParameterValue( _("set_boot_device called with invalid device " "%(device)s for node %(node_id)s.") % { 'device': device, 'node_id': node.uuid }) # AMT/vPro doesn't support set boot_device persistent, so we have to # save amt_boot_device/amt_boot_persistent in driver_internal_info. driver_internal_info = node.driver_internal_info driver_internal_info['amt_boot_device'] = device driver_internal_info['amt_boot_persistent'] = persistent node.driver_internal_info = driver_internal_info node.save()
def set_boot_device(self, task, device, persistent=False): """Set the boot device for the task's node. Set the boot device to use on next boot of the node. :param task: a task from TaskManager. :param device: the boot device :param persistent: Boolean value. True if the boot device will persist to all future boots, False if not. Default: False. :raises: InvalidParameterValue if an invalid boot device is specified. """ node = task.node if device not in amt_common.BOOT_DEVICES_MAPPING: raise ironic_exception.InvalidParameterValue( _("set_boot_device called with invalid device " "%(device)s for node %(node_id)s." ) % {'device': device, 'node_id': node.uuid}) # AMT/vPro doesn't support set boot_device persistent, so we have to # save amt_boot_device/amt_boot_persistent in driver_internal_info. driver_internal_info = node.driver_internal_info driver_internal_info['amt_boot_device'] = device driver_internal_info['amt_boot_persistent'] = persistent node.driver_internal_info = driver_internal_info node.save()
def set_power_state(self, task, pstate): """Wakes the task's node on power on. Powering off is not supported. Wakes the task's node on. Wake-On-Lan does not support powering the task's node off so, just log it. :param task: a TaskManager instance containing the node to act on. :param pstate: The desired power state, one of ironic.common.states POWER_ON, POWER_OFF. :raises: InvalidParameterValue if parameters are invalid. :raises: MissingParameterValue if required parameters are missing. :raises: WOLOperationError if an error occur when sending the magic packets """ node = task.node params = _parse_parameters(task) if pstate == states.POWER_ON: _send_magic_packets(task, params['host'], params['port']) elif pstate == states.POWER_OFF: LOG.info('Power off called for node %s. Wake-On-Lan does not ' 'support this operation. Manual intervention ' 'required to perform this action.', node.uuid) else: raise ironic_exception.InvalidParameterValue(_( "set_power_state called for Node %(node)s with invalid " "power state %(pstate)s.") % {'node': node.uuid, 'pstate': pstate})
def set_power_state(self, task, pstate): """Wakes the task's node on power on. Powering off is not supported. Wakes the task's node on. Wake-On-Lan does not support powering the task's node off so, just log it. :param task: a TaskManager instance containing the node to act on. :param pstate: The desired power state, one of ironic.common.states POWER_ON, POWER_OFF. :raises: InvalidParameterValue if parameters are invalid. :raises: MissingParameterValue if required parameters are missing. :raises: WOLOperationError if an error occur when sending the magic packets """ node = task.node params = _parse_parameters(task) if pstate == states.POWER_ON: _send_magic_packets(task, params['host'], params['port']) elif pstate == states.POWER_OFF: LOG.info( _LI('Power off called for node %s. Wake-On-Lan does not ' 'support this operation. Manual intervention ' 'required to perform this action.'), node.uuid) else: raise ironic_exception.InvalidParameterValue( _("set_power_state called for Node %(node)s with invalid " "power state %(pstate)s.") % { 'node': node.uuid, 'pstate': pstate })
def validate(self, task, method, http_method, **kwargs): """Validates the vendor method's parameters. This method validates whether the supplied data contains the required information for the driver. :param task: a TaskManager instance. :param method: name of vendor method. :param http_method: HTTP method. :param kwargs: data passed to vendor's method. :raises: InvalidParameterValue if supplied data is not valid. :raises: MissingParameterValue if parameters missing in supplied data. """ try: if method in ('get_nm_policy', 'remove_nm_policy', 'get_nm_policy_suspend', 'remove_nm_policy_suspend'): jsonschema.validate(kwargs, self.main_ids_schema) elif method == 'control_nm_policy': jsonschema.validate(kwargs, self.control_schema) no_domain = _('Missing "domain_id"') no_policy = _('Missing "policy_id"') if kwargs['scope'] == 'domain' and not kwargs.get('domain_id'): raise exception.MissingParameterValue(no_domain) if kwargs['scope'] == 'policy': if not kwargs.get('domain_id'): raise exception.MissingParameterValue(no_domain) if not kwargs.get('policy_id'): raise exception.MissingParameterValue(no_policy) elif method == 'set_nm_policy': jsonschema.validate(kwargs, self.policy_schema) if kwargs['policy_trigger'] == 'boot': if not isinstance(kwargs['target_limit'], dict): raise exception.InvalidParameterValue(_('Invalid boot ' 'policy')) elif method == 'set_nm_policy_suspend': jsonschema.validate(kwargs, self.suspend_schema) elif method == 'get_nm_capabilities': jsonschema.validate(kwargs, self.get_cap_schema) except json_schema_exc.ValidationError as e: raise exception.InvalidParameterValue(_('Input data validation ' 'error: %s') % e)
def _set_and_wait(task, target_state): """Helper function for DynamicLoopingCall. This method changes the power state and polls AMT until the desired power state is reached. :param task: a TaskManager instance contains the target node. :param target_state: desired power state. :returns: one of ironic.common.states. :raises: PowerStateFailure if cannot set the node to target_state. :raises: AMTFailure. :raises: AMTConnectFailure :raises: InvalidParameterValue """ node = task.node driver = task.driver if target_state not in (states.POWER_ON, states.POWER_OFF): raise ironic_exception.InvalidParameterValue(_( 'Unsupported target_state: %s') % target_state) elif target_state == states.POWER_ON: boot_device = node.driver_internal_info.get('amt_boot_device') if boot_device and boot_device != amt_common.DEFAULT_BOOT_DEVICE: driver.management.ensure_next_boot_device(node, boot_device) def _wait(status): status['power'] = _power_status(node) if status['power'] == target_state: raise loopingcall.LoopingCallDone() if status['iter'] >= CONF.amt_driver.max_attempts: status['power'] = states.ERROR LOG.warning(_LW("AMT failed to set power state %(state)s after " "%(tries)s retries on node %(node_id)s."), {'state': target_state, 'tries': status['iter'], 'node_id': node.uuid}) raise loopingcall.LoopingCallDone() try: _set_power_state(node, target_state) except Exception: # Log failures but keep trying LOG.warning(_LW("AMT set power state %(state)s for node %(node)s " "- Attempt %(attempt)s times of %(max_attempt)s " "failed."), {'state': target_state, 'node': node.uuid, 'attempt': status['iter'] + 1, 'max_attempt': CONF.amt_driver.max_attempts}) status['iter'] += 1 status = {'power': None, 'iter': 0} timer = loopingcall.FixedIntervalLoopingCall(_wait, status) timer.start(interval=CONF.amt_driver.action_wait).wait() if status['power'] != target_state: raise ironic_exception.PowerStateFailure(pstate=target_state) return status['power']
def __init__(self): if not importutils.try_import('pywsman'): raise ironic_exception.DriverLoadError( driver=self.__class__.__name__, reason=_("Unable to import pywsman library")) self.power = amt_power.AMTPower() self.boot = pxe.PXEBoot() self.deploy = agent.AgentDeploy() self.management = amt_management.AMTManagement()
def validate_network_port(port, port_name="Port"): """Validates the given port. :param port: TCP/UDP port. :param port_name: Name of the port. :returns: An integer port number. :raises: InvalidParameterValue, if the port is invalid. """ try: port = int(port) except ValueError: raise ironic_exception.InvalidParameterValue(_( '%(port_name)s "%(port)s" is not a valid integer.') % {'port_name': port_name, 'port': port}) if port < 1 or port > 65535: raise ironic_exception.InvalidParameterValue(_( '%(port_name)s "%(port)s" is out of range. Valid port ' 'numbers must be between 1 and 65535.') % {'port_name': port_name, 'port': port}) return port
def _parse_parameters(task): driver_info = task.node.driver_info host = driver_info.get('wol_host', '255.255.255.255') port = driver_info.get('wol_port', 9) port = utils.validate_network_port(port, 'wol_port') if len(task.ports) < 1: raise ironic_exception.MissingParameterValue(_( 'Wake-On-Lan needs at least one port resource to be ' 'registered in the node')) return {'host': host, 'port': port}
def _parse_parameters(task): driver_info = task.node.driver_info host = driver_info.get('wol_host', '255.255.255.255') port = driver_info.get('wol_port', 9) port = utils.validate_network_port(port, 'wol_port') if len(task.ports) < 1: raise ironic_exception.MissingParameterValue( _('Wake-On-Lan needs at least one port resource to be ' 'registered in the node')) return {'host': host, 'port': port}
def validate_network_port(port, port_name="Port"): """Validates the given port. :param port: TCP/UDP port. :param port_name: Name of the port. :returns: An integer port number. :raises: InvalidParameterValue, if the port is invalid. """ try: port = int(port) except ValueError: raise ironic_exception.InvalidParameterValue( _('%(port_name)s "%(port)s" is not a valid integer.') % { 'port_name': port_name, 'port': port }) if port < 1 or port > 65535: raise ironic_exception.InvalidParameterValue( _('%(port_name)s "%(port)s" is out of range. Valid port ' 'numbers must be between 1 and 65535.') % { 'port_name': port_name, 'port': port }) return port
def parse_driver_info(node): """Parses and creates AMT driver info :param node: an Ironic node object. :returns: AMT driver info. :raises: MissingParameterValue if any required parameters are missing. :raises: InvalidParameterValue if any parameters have invalid values. """ info = node.driver_info or {} d_info = {} missing_info = [] for param in REQUIRED_PROPERTIES: value = info.get(param) if value: if isinstance(value, six.text_type): value = value.encode() d_info[param[4:]] = value else: missing_info.append(param) if missing_info: raise ironic_exception.MissingParameterValue(_( "AMT driver requires the following to be set in " "node's driver_info: %s.") % missing_info) d_info['uuid'] = node.uuid param = 'amt_protocol' protocol = info.get(param, CONF.amt_driver.get(param[4:])) if protocol not in AMT_PROTOCOL_PORT_MAP: raise ironic_exception.InvalidParameterValue( _("Invalid protocol %s.") % protocol) d_info[param[4:]] = protocol return d_info
def validate(self, task, method, http_method, **kwargs): """Validates the vendor method's parameters. This method validates whether the supplied data contains the required information for the driver. :param task: a TaskManager instance. :param method: name of vendor method. :param http_method: HTTP method. :param kwargs: data passed to vendor's method. :raises: InvalidParameterValue if supplied data is not valid. :raises: MissingParameterValue if parameters missing in supplied data. """ try: if 'statistics' in method: self._validate_statistics_methods(method, **kwargs) else: self._validate_policy_methods(method, **kwargs) except json_schema_exc.ValidationError as e: raise exception.InvalidParameterValue(_('Input data validation ' 'error: %s') % e)
def _send_magic_packets(task, dest_host, dest_port): """Create and send magic packets. Creates and sends a magic packet for each MAC address registered in the Node. :param task: a TaskManager instance containing the node to act on. :param dest_host: The broadcast to this IP address. :param dest_port: The destination port. :raises: WOLOperationError if an error occur when connecting to the host or sending the magic packets """ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) with contextlib.closing(s) as sock: for port in task.ports: address = port.address.replace(':', '') # TODO(lucasagomes): Implement sending the magic packets with # SecureON password feature. If your NIC is capable of, you can # set the password of your SecureON using the ethtool utility. data = 'FFFFFFFFFFFF' + (address * 16) packet = bytearray.fromhex(data) try: sock.sendto(packet, (dest_host, dest_port)) except socket.error as e: msg = (_("Failed to send Wake-On-Lan magic packets to " "node %(node)s port %(port)s. Error: %(error)s") % { 'node': task.node.uuid, 'port': port.address, 'error': e }) LOG.exception(msg) raise exception.WOLOperationError(msg) # let's not flood the network with broadcast packets time.sleep(0.5)
def set_power_state(self, task, pstate, timeout=None): """Wakes the task's node on power on. Powering off is not supported. Wakes the task's node on. Wake-On-Lan does not support powering the task's node off so, just log it. :param task: a TaskManager instance containing the node to act on. :param pstate: The desired power state, one of ironic.common.states POWER_ON, POWER_OFF. :param timeout: timeout (in seconds). Unsupported by this interface. :raises: InvalidParameterValue if parameters are invalid. :raises: MissingParameterValue if required parameters are missing. :raises: WOLOperationError if an error occur when sending the magic packets """ # TODO(rloo): Support timeouts! if timeout is not None: LOG.warning( "The 'wol' Power Interface's 'set_power_state' method " "doesn't support the 'timeout' parameter. Ignoring " "timeout=%(timeout)s", {'timeout': timeout}) node = task.node params = _parse_parameters(task) if pstate == states.POWER_ON: _send_magic_packets(task, params['host'], params['port']) elif pstate == states.POWER_OFF: LOG.info('Power off called for node %s. Wake-On-Lan does not ' 'support this operation. Manual intervention ' 'required to perform this action.', node.uuid) else: raise ironic_exception.InvalidParameterValue(_( "set_power_state called for Node %(node)s with invalid " "power state %(pstate)s.") % {'node': node.uuid, 'pstate': pstate})
def _send_magic_packets(task, dest_host, dest_port): """Create and send magic packets. Creates and sends a magic packet for each MAC address registered in the Node. :param task: a TaskManager instance containing the node to act on. :param dest_host: The broadcast to this IP address. :param dest_port: The destination port. :raises: WOLOperationError if an error occur when connecting to the host or sending the magic packets """ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) with contextlib.closing(s) as sock: for port in task.ports: address = port.address.replace(':', '') # TODO(lucasagomes): Implement sending the magic packets with # SecureON password feature. If your NIC is capable of, you can # set the password of your SecureON using the ethtool utility. data = 'FFFFFFFFFFFF' + (address * 16) packet = bytearray.fromhex(data) try: sock.sendto(packet, (dest_host, dest_port)) except socket.error as e: msg = (_("Failed to send Wake-On-Lan magic packets to " "node %(node)s port %(port)s. Error: %(error)s") % {'node': task.node.uuid, 'port': port.address, 'error': e}) LOG.exception(msg) raise exception.WOLOperationError(msg) # let's not flood the network with broadcast packets time.sleep(0.5)
def set_power_state(self, task, pstate): """Turn the power on or off. :param task: a TaskManager instance containing the node to act on. :param pstate: The desired power state, one of ironic.common.states POWER_ON, POWER_OFF. :raises: InvalidParameterValue if iboot parameters are invalid or if an invalid power state was specified. :raises: MissingParameterValue if required iboot parameters are missing. :raises: PowerStateFailure if the power couldn't be set to pstate. """ driver_info = _parse_driver_info(task.node) if pstate == states.POWER_ON: _switch(driver_info, True) elif pstate == states.POWER_OFF: _switch(driver_info, False) else: raise ironic_exception.InvalidParameterValue( _("set_power_state called with invalid " "power state %s.") % pstate) _check_power_state(driver_info, pstate)
def _set_and_wait(task, target_state): """Helper function for DynamicLoopingCall. This method changes the power state and polls AMT until the desired power state is reached. :param task: a TaskManager instance contains the target node. :param target_state: desired power state. :returns: one of ironic.common.states. :raises: PowerStateFailure if cannot set the node to target_state. :raises: AMTFailure. :raises: AMTConnectFailure :raises: InvalidParameterValue """ node = task.node driver = task.driver if target_state not in (states.POWER_ON, states.POWER_OFF): raise ironic_exception.InvalidParameterValue( _('Unsupported target_state: %s') % target_state) elif target_state == states.POWER_ON: boot_device = node.driver_internal_info.get('amt_boot_device') if boot_device and boot_device != amt_common.DEFAULT_BOOT_DEVICE: driver.management.ensure_next_boot_device(node, boot_device) def _wait(status): status['power'] = _power_status(node) if status['power'] == target_state: raise loopingcall.LoopingCallDone() if status['iter'] >= CONF.amt_driver.max_attempts: status['power'] = states.ERROR LOG.warning( "AMT failed to set power state %(state)s after " "%(tries)s retries on node %(node_id)s.", { 'state': target_state, 'tries': status['iter'], 'node_id': node.uuid }) raise loopingcall.LoopingCallDone() try: _set_power_state(node, target_state) except Exception: # Log failures but keep trying LOG.warning( "AMT set power state %(state)s for node %(node)s " "- Attempt %(attempt)s times of %(max_attempt)s " "failed.", { 'state': target_state, 'node': node.uuid, 'attempt': status['iter'] + 1, 'max_attempt': CONF.amt_driver.max_attempts }) status['iter'] += 1 status = {'power': None, 'iter': 0} timer = loopingcall.FixedIntervalLoopingCall(_wait, status) timer.start(interval=CONF.amt_driver.action_wait).wait() if status['power'] != target_state: raise ironic_exception.PowerStateFailure(pstate=target_state) return status['power']
from oslo_log import log as logging from oslo_service import loopingcall from oslo_utils import excutils from oslo_utils import importutils from ironic_staging_drivers.amt import common as amt_common from ironic_staging_drivers.amt import resource_uris from ironic_staging_drivers.common import exception from ironic_staging_drivers.common.i18n import _ pywsman = importutils.try_import('pywsman') opts = [ cfg.IntOpt('max_attempts', default=3, help=_('Maximum number of times to attempt an AMT operation, ' 'before failing')), cfg.IntOpt('action_wait', default=10, help=_('Amount of time (in seconds) to wait, before retrying ' 'an AMT operation')) ] CONF = cfg.CONF CONF.register_opts(opts, group='amt_driver') LOG = logging.getLogger(__name__) AMT_POWER_MAP = { states.POWER_ON: '2', states.POWER_OFF: '8', }
from oslo_service import loopingcall from oslo_utils import importutils import six from ironic_staging_drivers.common.i18n import _ from ironic_staging_drivers.common import utils iboot = importutils.try_import('iboot') LOG = logging.getLogger(__name__) opts = [ cfg.IntOpt('max_retry', default=3, min=0, help=_('Maximum retries for iBoot operations')), cfg.IntOpt('retry_interval', default=1, min=0, help=_('Time (in seconds) between retry attempts for iBoot ' 'operations')), cfg.IntOpt('reboot_delay', default=5, min=0, help=_('Time (in seconds) to sleep between when rebooting ' '(powering off and on again).')) ] CONF = cfg.CONF opt_group = cfg.OptGroup(name='iboot', title='Options for the iBoot power driver')
class AMTConnectFailure(exception.IronicException): _msg_fmt = _("Failed to connect to AMT service. This could be caused " "by the wrong amt_address or bad network environment.")
class AMTFailure(exception.IronicException): _msg_fmt = _("AMT call failed: %(cmd)s.")
class LibvirtError(exception.IronicException): message = _("Libvirt call failed: %(err)s.")
from oslo_config import cfg from oslo_log import log as logging from oslo_utils import importutils import six from ironic_staging_drivers.common import exception from ironic_staging_drivers.common.i18n import _ pywsman = importutils.try_import('pywsman') _SOAP_ENVELOPE = 'http://www.w3.org/2003/05/soap-envelope' LOG = logging.getLogger(__name__) REQUIRED_PROPERTIES = { 'amt_address': _('IP address or host name of the node. Required.'), 'amt_password': _('Password. Required.'), 'amt_username': _('Username to log into AMT system. Required.'), } OPTIONAL_PROPERTIES = { 'amt_protocol': _('Protocol used for AMT endpoint. one of http, https; ' 'default is "http". Optional.'), } COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy() COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES) opts = [ cfg.StrOpt('protocol', default='http', choices=['http', 'https'], help=_('Protocol used for AMT endpoint, '
from ironic.common import states from ironic.conductor import task_manager from ironic.drivers import base from oslo_log import log from ironic_staging_drivers.common import exception from ironic_staging_drivers.common.i18n import _ from ironic_staging_drivers.common.i18n import _LI from ironic_staging_drivers.common import utils LOG = log.getLogger(__name__) REQUIRED_PROPERTIES = {} OPTIONAL_PROPERTIES = { 'wol_host': _('Broadcast IP address; defaults to ' '255.255.255.255. Optional.'), 'wol_port': _("Destination port; defaults to 9. Optional."), } COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy() COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES) def _send_magic_packets(task, dest_host, dest_port): """Create and send magic packets. Creates and sends a magic packet for each MAC address registered in the Node. :param task: a TaskManager instance containing the node to act on. :param dest_host: The broadcast to this IP address.
from ironic.common import exception as ironic_exception from ironic.common import states from ironic.conductor import task_manager from ironic.drivers import base from oslo_log import log from ironic_staging_drivers.common import exception from ironic_staging_drivers.common.i18n import _ from ironic_staging_drivers.common import utils LOG = log.getLogger(__name__) REQUIRED_PROPERTIES = {} OPTIONAL_PROPERTIES = { 'wol_host': _('Broadcast IP address; defaults to ' '255.255.255.255. Optional.'), 'wol_port': _("Destination port; defaults to 9. Optional."), } COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy() COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES) def _send_magic_packets(task, dest_host, dest_port): """Create and send magic packets. Creates and sends a magic packet for each MAC address registered in the Node. :param task: a TaskManager instance containing the node to act on. :param dest_host: The broadcast to this IP address. :param dest_port: The destination port.
from oslo_utils import importutils from ironic_staging_drivers.amt import common as amt_common from ironic_staging_drivers.amt import resource_uris from ironic_staging_drivers.common import exception from ironic_staging_drivers.common.i18n import _ from ironic_staging_drivers.common.i18n import _LE from ironic_staging_drivers.common.i18n import _LI from ironic_staging_drivers.common.i18n import _LW pywsman = importutils.try_import('pywsman') opts = [ cfg.IntOpt('max_attempts', default=3, help=_('Maximum number of times to attempt an AMT operation, ' 'before failing')), cfg.IntOpt('action_wait', default=10, help=_('Amount of time (in seconds) to wait, before retrying ' 'an AMT operation')) ] CONF = cfg.CONF CONF.register_opts(opts, group='amt_driver') LOG = logging.getLogger(__name__) AMT_POWER_MAP = { states.POWER_ON: '2', states.POWER_OFF: '8', }