def login(self, host, user=None, password=None, session_key=None): """ Establishes a session with the provided iLO sub-system using the provided credentials Example: | DL ILO API Login | 192.168.20.10 | Administrator | iwf01help | :param str host: IP or FQDN of the iLO Sub-system :param str user: Name of the user known in the iLO realm :param str password: Password of the provided user :param session_key: Authorization key for supporting Single Sign ON :return: None """ self.ilo_host = host self.ilo_user = user self.ilo_pass = password self.ilo_sso = session_key try: self.client = RedfishClient(host=host, user=user, password=password, session_key=session_key) except: # no-qa logger.debug("Unable to reach {}".format(host)) raise AssertionError("Failed to establish communication")
def _set_power_state(self, state): """ Applies the requested power state on the system. :param str state: Supported values are On, ForceOff, ForceRestart, PushPowerButton :return: :bool True on success else False """ uri = self.client.get_resource_uris('ComputerSystem.')[0] # For Rack server, there would ideally be only one item. if self.client.type_path.defs.isgen9: r = self.client.post(uri['@odata.id'], {'Action': 'Reset', 'ResetType': state}) else: pr = self.client.get(uri['@odata.id']) if pr.status == 200: p = pr.dict['Actions']['#ComputerSystem.Reset']['target'] else: _ = 'Unable to gather resource path to perform {}'.format(state) logger.debug(_) return False r = self.client.post(p, {'Action': 'ComputerSystem.Reset', 'ResetType': state}) e = self.client.get_message(r) if e.get('error_code') == 'Success': return True elif e.get('error_code') == 'InvalidOperationForSystemState': # System was in the requested state hence passing it logger.info(e.get('error_info')) return True return False
def _validate_and_set_host(self, host): """ Verifies and adds the target information to the body :param dict host: Contains :return bool: True if the payload is updated with host information """ attrs = ['ipv4', 'user', 'password'] if not host.get('os'): log.debug('Missing required attribute: os') return False if not set(attrs).issubset(host.keys()): m = 'Found attributes: {}'.format(host.keys()) m += '\nRequired attributes: {}'.format(attrs) log.info(m) return False if host.get('datacenter'): if host.get('manager') is None: log.info('Missing required attribute: manager') return False self.body['host'] = deepcopy(host) log.debug('Host information found {}'.format(host)) return True
def get_resource_uris(self, types): """ Returns the system resource links matching the request type :param str types: Type to be searched :return list: List of system resources """ data = list() for i in self.SYS_RESOURCE['resources']: found = False if i.get('@odata.type', None) and types.lower() in \ i.get('@odata.type').lower(): for e in self.SYS_RESOURCE['resources']: if (i['@odata.type'] + '/settings/').lower() == \ (e['@odata.id']).lower(): found = True break if not found: data.append(i) if not data: logger.debug("{} resource or feature not supported".format(types)) raise AssertionError("Not implemented feature") return data
def wait_till_finished_post(self, timeout=20, poll=10): """ Waits till the system has completed Post Discovery stage and is booting the OS or searching for boot loader. Useful in cases where the manager is applying settings and rebooting Example: | ${r}= | DL ILO API Return After POST Complete | <min> | <sec> :param int timeout: Timeout value in minutes :param int poll: Polling interval in seconds :return: True when POST is completed within time frame else False """ if self.get_power_state_server() == 'Off': self.set_power_state_on() timeout = datetime.now() + timedelta(minutes=timeout) while timeout > datetime.now(): logger.debug("Sleeping for {} seconds before poll".format(poll)) sleep(poll) s = self.get_post_state_server() if s == 'InPostDiscoveryComplete' or s == 'FinishedPost': # System manager will request for a server reboot after this # stage when there pending settings to be applied so sleep and # if state is the same break sleep(3) rs = self.get_post_state_server() if rs == 'InPostDiscoveryComplete' or rs == 'FinishedPost': return True logger.debug("Timeout: System has not completed POST") return False
def _get_base_registry(self): """ Returns the list of message registries :return: dict """ resp = self.get(uri='/redfish/v1/Registries/') msg = dict() for e in resp.dict['Members']: if not [x for x in ['/Base/', '/iLO/'] if x in e['@odata.id']]: continue else: reg = self.get(uri=e['@odata.id']) for l in reg.dict['Location']: loc = l['Uri']['extref'] if 'extref' in l['Uri'] else l['Uri'] r = self.get(uri=loc) if r.status == 200: msg[r.dict['RegistryPrefix']] = r.dict['Messages'] else: m = "{} not found at {}".format(r.dict['RegistryPrefix'], loc) logger.debug(m) return msg
def upload_folder_from_local(self, local_path, remote_path): # check if local_path is a folder if not os.path.isdir(local_path): return False, { 'except_obj': IOError( "Specified :local_path '%s' not exist or not a folder!" % local_path), 'line': sys._getframe().f_lineno } self._open_sftp() # firstly create remote_path is not exist self.create_folder(remote_path) for dirpath, dirnames, filenames in os.walk(local_path): if dirnames: # create folder on remote for dirname in dirnames: folder_to_create = os.path.join(remote_path, dirname).replace( os.path.sep, '/') # create if not exist try: self.sftp.listdir(folder_to_create) except IOError as ex: if ex.errno == errno.ENOENT: logger.debug("Creating folder '%s' on remote" % folder_to_create) self.sftp.mkdir(folder_to_create) else: exc_type, exc_obj, exc_tb = sys.exc_info() return False, { 'except_obj': ex, 'line': exc_tb.tb_lineno } if filenames: # do single upload for filename in filenames: local_file_path = os.path.join(dirpath, filename).replace( os.path.sep, '/') remote_file_path = os.path.join( remote_path, local_file_path.replace(os.path.sep, '/').replace( local_path.replace(os.path.sep, '/'), '').lstrip('/')).replace(os.path.sep, '/') logger.debug("Uploading file on remote: '%s' => '%s'" % (local_file_path, remote_file_path)) self.sftp.put(local_file_path, remote_file_path) return True, None
def ilo_wrap(self, *args, **kwargs): """ Method that verifies the connection is authenticated :param self: The object :param args: Linear list of arguments :param kwargs: List of key value pairs :return: Results of the specified method. """ if self.client is None: logger.debug("DEBUG: ILO communication not established.") raise AssertionError("iLO login is required") return func(self, *args, **kwargs)
def _get_resource_directory(self): """ Returns the list of system resources :return: Dictionary """ resp = self.get(uri='/redfish/v1/resourcedirectory/') if resp.status == 200: return {'resources': resp.dict['Instances']} else: logger.debug("Unable to find resource directory.") raise AssertionError("Resource directory is missing.")
def get_firmware_bundle(fw_path, fw_name=''): if os.path.exists(fw_path): spp_folder = fw_path else: return False spp_list = [each for each in os.listdir(spp_folder) if each.startswith(fw_name)] if not spp_list: logger.debug("Please upload a SPP to folder '%s' before test" % spp_folder) return False spp_name = sorted(spp_list, reverse=True)[0] absolute_path = spp_folder + '\\' + spp_name if not os.path.isfile(absolute_path): return False return absolute_path
def get_power_state_server(self): """ Retrieves the power status of the system Example: | ${pwr}= | DL ILO API Get Power State | :return str: On | Off | Unknown | Reset """ uri = self.client.get_resource_uris('ComputerSystem.')[0] r = self.client.get(uri['@odata.id']) if r.status != 200: logger.debug("Unable to retrieve the power state.") return 'Unknown' return r.dict['PowerState']
def _remote_cmd(self, cmd, block=True): """ Executes the given command on the remote system and returns the outpu :param cmd: Command to be executed on the remote system :type: str :return str: Output """ r = remote_cmd(host_ip=self.host['ipv4'], username=self.host['user'], password=self.host['password'], command=cmd, block=block) if r.get('status').lower() == "failed": log.debug('Command: {}\nOutput: {}'.format(cmd, r['error'])) return r.get('error') if r.get('error') else False if r.get('status').lower() == "success": log.debug('Command: {}\nOutput: {}'.format(cmd, r['output'])) return r['output'] if r['output'] else True
def _add_network_profile(self, net_profile): """ In conjunction with ports, creates the configuration dictionary :param dict net_profile: Network profile information :return bool: True when the configuration is added to self.body """ net_types = list() if 'redhat' in self.body['host']['os'].lower() or \ 'red hat' in self.body['host']['os'] or \ 'rhel' in self.body['host']['os']: net_types = ['ethernet', 'bond', 'team'] elif 'vmware' in self.body['host']['os'].lower(): net_types = ['vss', 'vds'] if not net_profile.get('type') or \ not net_profile['type'].lower() in net_types: log.info('Unknown network config: {}'.format(net_profile['type'])) log.info('Supported network configurations: {}'.format(net_types)) return False net_uri = self._get_network_uri(net_profile.get('ov_network')) if net_uri is None: return False if net_uri not in self.ports: log.info('Network not part of server profile connections {}') return False self.net_profile = deepcopy(net_profile) if 'redhat' in self.body['host']['os'].lower() or \ 'red hat' in self.body['host']['os'] or \ 'rhel' in self.body['host']['os']: return self._add_rhel_config(net_profile['ov_network'], net_uri) elif 'vmware' in self.body['host']['os'].lower(): return self._add_vmware_config(net_uri) log.debug('Unknown network profile type {}'.format( net_profile['type'])) return False
def get_post_state_server(self): """ Retrieves the Post state of the system Example: | ${state}= | DL ILO API Get Post State :return str: Unknown | Reset | PowerOff | InPost | FinishedPost | \ InPostDiscoveryComplete """ uri = self.client.get_resource_uris('ComputerSystem.')[0] r = self.client.get(uri['@odata.id']) if r.status != 200: logger.debug("Unable to retrieve the Post State of the server.") return 'Unknown' if self.client.type_path.defs.isgen9: return r.dict['Oem']['Hp']['PostState'] else: return r.dict['Oem']['Hpe']['PostState']
def _get_network_uri(self, name): """ Retrieve the networkUri from OneView based on the argument :param str name: Network name :return str: Network's URI corresponding to OV """ p = '?&filter="\'name\'==\'{}\'"'.format(name.lower()) r = self.fusion_api_get_ethernet_networks(param=p) if r.get('count') == 0: r = self.fusion_api_get_network_set(param=p) if r.get('count') == 0: log.info('{} not found in OneView'.format(name)) return None if r.get('count') > 1: log.info('Found more than one match for {}'.format(name)) return None log.debug('Retrieved the network uri for {}'.format(name)) return r.get('members')[0]['uri']
def call_cmd(self, cmd, realout=False): out = DataObj() out.add_property('stdout', None) out.add_property('stderr', None) out.add_property('code', 1) stdin, stdout, stderr = self.ssh.exec_command(cmd) if realout is False: out.add_property('stdout', stdout.read()) out.add_property('stderr', stdout.read()) else: outputs = [] while not stdout.channel.exit_status_ready(): # Only print data if there is data to read in the channel if stdout.channel.recv_ready(): rl, wl, xl = select.select([stdout.channel], [], [], 0.0) if len(rl) > 0: # Print data from stdout data = stdout.channel.recv(1024) outputs.append(data) # logger.debug(data, also_console=True, newline=False, time_prefix=False) logger.debug(data) else: data = stdout.channel.recv(1024) outputs.append(data) # logger._debug(data, also_console=True, newline=False, time_prefix=False) logger.debug(data) out.add_property('stdout', ''.join(outputs)) out.add_property('stderr', stderr.read()) # logger._debug('', time_prefix=False) logger.debug('') out.code = stdout.channel.recv_exit_status() return out
def reset_ilo(self): """ Performs a reset of the iLO sub-system Example: | ${r}= | DL ILO API Reset iLO Manager | :return: :bool True on success else False """ uris = self.client.get_resource_uris('Manager.') # For rack server, there would only be one item however there are # instances where DL's holds multiple managers. One scenario is after # running SUT outside of OneView. So iterate through the managers for uri in uris: if self.client.type_path.defs.isgen9: r = self.client.post(uri['@odata.id'], {'Action': 'Reset'}) else: pr = self.client.get(uri['@odata.id']) if pr.status == 200: p = pr.dict['Actions']['#Manager.Reset']['target'] else: _ = "Unable to perform reset at {}".format(uri['@odata.id']) logger.debug(_) continue r = self.client.post(p, {'Action': 'Manager.Reset'}) e = self.client.get_message(r) if e.get('error_code') == 'ResetInProgress': logger.debug('Sleeping for 90 seconds before verification') # Waking up to early results in-complete Resource entries which # cause feature not implemented errors. sleep(90) self.login(self.ilo_host, self.ilo_user, self.ilo_pass, self.ilo_sso) return True return False
def _network_config_generator(self, sp_url, host_info, nets): """ Keyword to generate the required payload for configuring the target system networks Usage ${body}= Create Payload For OS Networks Configuration | ... <ov_server_profile_url> | ... <host_info> | ... <network_profiles> | [Returns] A dictionary which would be an input to 'Configure OS Networks' :param str sp_url: Target system's OneView server profile URL :param dict host_info: Target system's credentials and type :param list nets: List of network profiles :return: """ sp = self.fusion_api_get_server_profiles(uri=sp_url) if sp['status_code'] != 200: log.info("Invalid OneView Server Profile URL: {}".format(sp_url)) return self.body if int(sp['type'].split('V')[-1]) < 8: self._set_ports(sp.get('connections', list())) else: self._set_ports(sp.get('connectionSettings').get('connections')) if not self._validate_and_set_host(host_info): log.info("Invalid target server information: {}".format(host_info)) return self.body for net in nets: if self._add_network_profile(net): log.debug('{} network configuration created.'.format(net)) return self.body if len(self.body['config']) else dict()
def open_ssh(self, host, username, password): # if call this method again when connection is keeping. # suppose we want to disconnect current SSH connection then connect to another host if self.connected is False: try: self.ssh = paramiko.SSHClient() self.sftp = None self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) self.ssh.connect(host, username=username, password=password, look_for_keys=False) self.connected = True logger.debug("Connected to %s" % host) except Exception as e: self.connected = False logger.debug("Connected to %s failed with the exception %s" % (host, e.message)) # close current connect & connect to another host else: self.close_ssh() self.open_ssh(host, username, password)
def get_message(self, resp): """ Returns the error message code and description :param resp: RestResponse object :return dict: Error code & Description """ if not self.MSG_REGISTRY: logger.debug("No message registry found.") return dict() try: r = resp.dict # The response would be of the below format # {"error":{"@Message.ExtendedInfo": \ # [{"MessageId":"iLO.0.10.ResetInProgress"}] n = r['error']['@Message.ExtendedInfo'][0]['MessageId'].split('.') except: logger.debug("No extended error information.") return dict() for err in self.MSG_REGISTRY: if err != n[0]: continue for e in self.MSG_REGISTRY[err]: if e == n[3]: code = n[3] desc = self.MSG_REGISTRY[err][e]['Description'] m = "iLO Error Code:\t {}".format(code) m += "\nDescription:\t{}".format(desc) logger.debug(m) return {'error_code': code, 'error_info': desc} # Unexpected code path logger.debug("There exists an error code with no description") return {'error_code': n[3], 'error_info': None}
def _get_network_addr(self, uri, vmware=False): """ Returns a dictionary that holds config['networks'] entry :param str uri: OneView network information :param bool vmware: If the requestor is vmware :return dict: config['networks'] dictionary """ n = self.fusion_api_get_ethernet_networks(uri=uri) if n is None: log.debug('No network information for {}'.format(uri)) return None b = dict({'dhcp': True, 'vlan': n.get('vlanId')}) if vmware: b.update({'name': n['name'], 'type': n.get('purpose')}) # DHCP is set till the method is able to allocate an IP address if n.get('subnetUri', None) is None: log.debug('No associated network for {}'.format(uri)) return b s = self.fusion_api_get_ipv4_subnet(uri=n.get('subnetUri')) if s is None: log.debug('No subnet information for {}'.format(n['subnetUri'])) return b a = self.fusion_api_allocate_ipv4_subnet({'count': 1}, s['uri']) if a.get('status_code') != 200: log.debug('No available IPv4 address.') return b b['dhcp'] = False b.update({'ipv4': a['idList'][0], 'netmask': s['subnetmask']}) return b
def _add_rhel_config(self, name, uri): """ Creates the network configuration for RHEL based systems :param str name: Name of the network name :param str uri: OneView's networkUri :return bool: True when network configuration is appended to body """ port_count = len(self.ports[uri]) if self.net_profile['type'].lower() == 'ethernet' and port_count != 1: _ = 'Invalid number of ports {}'.format(name) log.info(_) return False if (self.net_profile['type'].lower() == 'bond' or self.net_profile['type'].lower() == 'team') and \ port_count != 2: _ = 'Invalid number of port for {}'.format(name) log.info(_) return False cfg = { 'name': self.net_profile.get('name', name), 'type': self.net_profile['type'], 'ports': self.ports[uri] } if self.net_profile['type'].lower() == 'bond': cfg.update({'bond_opts': self.net_profile['bond_opts']}) elif self.net_profile['type'].lower() == 'team': cfg.update({ 'team_opts': self.net_profile['team_opts'], 'team_port_cfg': self.net_profile['team_port_cfg'] }) nets = list() if 'ethernet-network' in uri: n = self._get_network_addr(uri) if n is not None: # VLAN is configured on the interconnect if n.get('vlan'): # Removing VLAN as it is a single network and Oneview # configures the same on the interconnect downlink port del n['vlan'] nets.append(n) elif 'network-set' in uri: r = self.fusion_api_get_network_set(uri=uri) if r.get('nativeNetworkUri'): n = self._get_network_addr(r['nativeNetworkUri']) if n is not None: if n.get('vlan'): # Removing VLAN as it is native to network set and # configured by Oneview on the interconnect downlink del n['vlan'] nets.append(n) for u in r.get('networkUris', []): if u == r.get('nativeNetworkUri'): log.debug('Ignoring this network as it is native') continue n = self._get_network_addr(u) if n is not None: nets.append(n) else: log.info('Unknown OneView network connection: {}'.format(uri)) log.info('Supported types: ethernet, network-set') return False if nets: cfg['networks'] = deepcopy(nets) self.body['config'].append(cfg) log.debug('Network config: {}'.format(cfg)) return True log.debug('No network information retrieved... skipping') return False
def _add_vmware_config(self, uri): """ Creates the network configuration for VMware based systems :param str uri: OneView's networkUri :return bool: True when network configuration is appended to body """ if self.net_profile.get('name') is None: log.info('Virtual Switch name is required') return False if self.net_profile.get('type').lower() == 'vds': attrs = ['manager', 'datacenter', 'cluster'] if not set(attrs).issubset(self.body['host'].keys()): m = 'Missing required attributes.' m += "Additional required attributes are\t{}".format(attrs) log.info(m) return False cfg = dict({ 'name': self.net_profile['name'], 'type': self.net_profile['type'], 'ports': self.ports[uri] }) nets = list() if 'ethernet-network' in uri: n = self._get_network_addr(uri, True) if n is None: return False if self.net_profile.get('networks') and \ self.net_profile['networks'].get(n['vlan']): if 'policy' in self.net_profile['networks'][n['vlan']]: n['policy'] = \ self.net_profile['network'][n['vlan']]['policy'] if 'vmk' in self.net_profile['network'][n['vlan']]: n['vmk'] = self.net_profile['network'][n['vlan']]['vmk'] else: # No vmkernel port would be created n['vmk'] = False else: n['vmk'] = False # VLAN is configured on the downlink port del n['vlan'] nets.append(n) elif 'network-set' in uri: ns = self.fusion_api_get_network_set(uri=uri) if ns.get('nativeNetworkUri'): n = self._get_network_addr(ns['nativeNetworkUri'], True) if n is not None: if self.net_profile.get('networks') and \ self.net_profile['networks'].get(n['vlan']): if 'policy' in self.net_profile['networks'][n['vlan']]: n['policy'] = \ self.net_profile['networks'][n['vlan']]['policy'] if 'vmk' in self.net_profile['networks'][n['vlan']]: n['vmk'] = \ self.net_profile['networks'][n['vlan']]['vmk'] else: n['vmk'] = False else: n['vmk'] = False del n['vlan'] nets.append(n) for u in ns.get('networkUris', []): if u == ns.get('nativeNetworkUri'): log.debug('Ignoring this network as it is a native one.') continue n = self._get_network_addr(u, True) if n is None: continue if self.net_profile.get('networks') and \ self.net_profile['networks'].get(n['vlan']): if 'policy' in self.net_profile['networks'][n['vlan']]: n['policy'] = \ self.net_profile['networks'][n['vlan']]['policy'] if 'vmk' in self.net_profile['networks'][n['vlan']]: n['vmk'] = \ self.net_profile['networks'][n['vlan']]['vmk'] else: n['vmk'] = False else: n['vmk'] = False nets.append(n) cfg['active'] = self.net_profile.get('active', len(cfg['ports'])) else: log.info('Unsupported network connection {}'.format(uri)) log.info('Supported types: vss, vds') if nets: cfg['networks'] = nets self.body['config'].append(cfg) log.debug('Network config: {}'.format(cfg)) return True log.debug('No network information was retrieved... skipping') return False