def _get_k2entry_for_lpar_tag(k2response, lpar_key, lpar_id): """ This method returns the K2Entry in K2Response.entries that corresponds to the lpar id passed in. :param k2response: VirtualIOServer K2Response :param lpar_key: The key to search for within the lpar :param lpar_id: The lpar_id passed in :returns K2Entry: K2Entry in the K2Response that corresponds to the VIOS passed in. Returns None if not found. """ # Be sure they passed in an id if not lpar_id: ras.trace(LOG, __name__, ras.TRACE_INFO, _("No lpar_id passed in")) return None # Be sure the response they gave us has entries to loop over if not k2response or not k2response.feed or not k2response.feed.entries: ras.trace(LOG, __name__, ras.TRACE_INFO, _("Empty K2 response given")) return None # Loop through all VIOSes in the K2 response for entry in k2response.feed.entries: # Get the id and name of this VIOS in the response k2_lpar_id = entry.element.findtext(lpar_key) # See if it matches what we're looking for if str(lpar_id) == k2_lpar_id: return entry # If we got here, we didn't find a match. This really shouldn't # happen, but better to be paranoid... ras.trace(LOG, __name__, ras.TRACE_ERROR, _("Failed to find VIOS in K2 response")) return None
def log_k2ex_and_get_msg(ex, prefix, topology): """ LOG K2 exception and extracted message. Return NLS message """ LOG.exception(ex) detail = {} k2msg = _("None") if isinstance(ex, K2Error) and ex.k2response: detail['Request_headers'] = ex.k2response.reqheaders detail['Response_headers'] = ex.k2response.headers detail['Response_body'] = ex.k2response.body detail['Response_status'] = ex.k2response.status if hasattr(ex.k2response, 'k2err'): m = ex.k2response.k2err.find('./Message') if m is not None: k2msg = m.text msg = _("%(prefix)s ***K2 Operator Error***: %(ex_msg)s [K2 Error body " "Message: %(k2msg)s]") %\ dict(prefix=prefix, ex_msg=ex, k2msg=k2msg) LOG.error(msg) if detail: LOG.error(_("Error details: %s") % detail) if topology is not None: if 'error' in topology: topology['error'].append(msg) else: topology['error'] = [msg] return msg
def get_k2entry_for_vios(k2response, vios): """ This method returns the K2Entry in K2Response.entries that corresponds to the vios passed in. :param k2response: VirtualIOServer K2Response :param vios: VioServer DOM object :returns K2Entry: K2Entry in the K2Response that corresponds to the VIOS passed in. Returns None if not found. """ # Be sure they passed in a vios if not vios: ras.trace(LOG, __name__, ras.TRACE_INFO, _("No VIOS passed in")) return None # Be sure the response they gave us has entries to loop over if not k2response or not k2response.feed or not k2response.feed.entries: ras.trace(LOG, __name__, ras.TRACE_INFO, _("Empty K2 response given")) return None # Loop through all VIOSes in the K2 response for entry in k2response.feed.entries: # Get the id and name of this VIOS in the response lpar_id = int(entry.element.findtext("PartitionID")) lpar_name = entry.element.findtext("PartitionName") # See if it matches what we're looking for if lpar_id == vios.lpar_id and lpar_name == vios.lpar_name: return entry # If we got here, we didn't find a match. This really shouldn't # happen, but better to be paranoid... ras.trace(LOG, __name__, ras.TRACE_ERROR, _("Failed to find VIOS in K2 response")) return None
def get_vios_info(vios_entry): """ Get the specific data for the VIOS from the K2 VIOS entry. """ # Initialize the empty dictionary. vios_info = {} if not vios_entry: LOG.warn(_("No Virtual I/O Servers returned in HMC response.")) return vios_info vios_info['k2element'] = vios_entry # track for later logging # Get the Partition Name Element partitionNameElement = \ vios_entry.element.find('./PartitionName') # If the Partition Name element exists if partitionNameElement is not None: # Save the partition name into the vios info object. vios_info['name'] = partitionNameElement.gettext() else: vios_info['name'] = None vios_info['uuid'] = uuid = vios_entry.properties['id'] LOG.debug("Processing VIOS partition '%s' with uuid: %s" % (vios_info['name'], uuid)) #Get the Partition State Element partitionStateElement = \ vios_entry.element.find('./PartitionState') if partitionStateElement is not None: #Save the partition State into the vios info object. vios_info['state'] = partitionStateElement.gettext() #Get the RMC State Element RMCStateElement = \ vios_entry.element.find('./ResourceMonitoringControlState') if RMCStateElement is not None: #Save the RMC State into the vios info object. vios_info['rmc_state'] = RMCStateElement.gettext() # Get the Partition ID Element. partitionIDElement = vios_entry.element.find('./PartitionID') # If the partition ID Element exists if partitionIDElement is not None: # Save the partition id to the vios info object. vios_info['lpar_id'] = partitionIDElement.gettext() # For each VIOS, also call the helper method to extract # the FC Port info from call already done. LOG.debug("Getting FC ports for vios '%s'." % uuid) vios_info['fcports'] = parse_fcports(vios_entry, vios_info) else: LOG.warn(_("HMC Problem: No PartitionID element for VIOS '%s'.") % uuid) return vios_info
def execute(self): """ Overrides parent method. Restarts networking on the host endpoint to pick up configuration changes. Checks and starts neutron-openvswitch-agent if it is not running already on host. """ LOG.info(_("Running 'systemctl restart network.service' on host.")) self.commandex.send_network_restart() LOG.info(_("Running 'systemctl restart " "neutron-openvswitch-agent.service' on host.")) self.commandex.restart_neutron_agent()
def execute(self): """ Overrides parent method. Will throw an error if the ping to the Qpid server fails. If it succeeds this method does nothing. """ LOG.info(_("Attempting to ping the Qpid Server")) if self.rollback: LOG.error(_("Rollback due to API parameter.")) raise exception.QpidPingFailure(timeout="0") adapter = self.dom_obj_converter.find_management_adapter() if adapter is None: LOG.error(_("Failed to ping the Qpid Server.")) raise exception.QpidPingFailure(timeout="1")
def _gen_ovs_port_name(self, desired_name, host_obj): """ Typically an OVSPort is named the same as the component it contains, however there be cases where this name is already taken. This function handles generating an OVSPort name if needed. :param desired_name: The desired name for the OVSPort, typically this is the name of the component contained by the OVSPort. :param host_obj: A host KVM DOM object to search for existing ports. :returns: A string that can be used as an OVSPort name. """ if not desired_name: desired_name = 'pvc-ovs-port' # Check to see if the desired name can be used if not self._does_ovs_port_exist(desired_name, host_obj): return desired_name # Increment a number after the desired name until a spot is found for i in range(0, 100000): name = desired_name + '-' + str(i) if not self._does_ovs_port_exist(name, host_obj): return name # Something is wrong, thousands of OVSPorts with this name exist. LOG.error(_('Failed to generate OVSPort name from desired_name %s') % desired_name) return ''
def get_scg_by_id(context, scg_id): """ Look up storage connectivity group by id. :param context: A context object that is used to authorize any DB access. :param scg_id: The 'id' of the storage connectivity group to lookup. :returns: The SCG object looked up. Otherwise an error is raised. """ # DB facing logic comes next. # Now construct and use a transaction. scg_factory = storage_dom.StorageConnectivityGroupFactory.get_factory() txn = scg_factory.get_resource_transaction() with txn.begin(subtransactions=True): scg = None try: scg = scg_factory.find_scg_by_id(context, scg_id, transaction=txn) except Exception as e: LOG.exception(e) LOG.error(_("Exception trying to find a storage connectivity group" " using resource id = %s") % scg_id) if scg is None: msg = stgex.IBMPowerVCSCGNotFound.msg_fmt % locals() ex = stgex.IBMPowerVCSCGNotFound(msg) LOG.exception(ex) raise ex scg_dict = scg.to_dict(context=context, transaction=txn) # Since to_dict() is called within the transaction, # the hydrated dictionary will be cached on the scg object # and future references to it will have the host_list # nested dictionary data. LOG.debug("Looked up SCG: %s" % scg_dict) return scg
def get_k2element_for_sea(k2vios, sea): """ This method returns the K2Element that corresponds to the SEA on the specific VIOS passed in. :param k2vios: K2Entry for the VIOS we're searching :param sea: SharedEthernetAdapter DOM object :returns K2Element: K2Element in the K2Response that corresponds to the SEA passed in. Returns None if not found. """ if not k2vios or not sea: ras.trace(LOG, __name__, ras.TRACE_INFO, _("No VIOS or SEA passed in for SEA Gather")) return None # Now get all the SharedEthernetAdapters seas = k2vios.element.findall("./SharedEthernetAdapters/" "SharedEthernetAdapter") # Be sure we found some SEAs in the K2Response if seas: # Loop through all trunk adapters found and grab adapter attributes for s in seas: # Device names match? if sea.name == s.findtext("DeviceName"): # Score! return s # TODO: Remove once everyone's at 1340A HMC if sea.name == s.findtext("InterfaceName"): return s # If we got here, didn't find it. return None
def get_compute_nodes_from_DB(context, msg_dict=None): """ This returns a list of compute nodes after querying the Nova DB. :param context: A context object that is used to authorize the DB access. :returns: A list of compute nodes that are in service """ context = context.elevated() # What is the purpose of elevation? compute_nodes = nova_db.compute_node_get_all(context) return_nodes = [] for compute in compute_nodes: service = compute['service'] if not service: msg = _("No service entry for compute ID %s.") % compute['id'] LOG.warn(msg) if msg_dict: msg_dict['messages'].append(msg) continue return_nodes.append(service["host"]) # host_to_send = {'db_id': compute['id'], # 'host_name': service['host'], # 'hyp_hostname': compute['hypervisor_hostname']} LOG.debug("db_hosts: %s" % return_nodes) return return_nodes
def http_error_if_ivm(): host_types = get_supported_host_types() mgr_type = CONF.powervm_mgr_type.lower() if mgr_type == 'ivm' and 'kvm' not in host_types: msg = _("Storage connectivity group based requests are not " "supported for PowerVC Express installations (IVM mode).") raise exc.HTTPBadRequest(explanation=six.text_type(msg))
def _update_port_remove_pass(self, current_ovs, desired_ovs): """ Determines the micro ops to run for the update port flow, where we are removing ports from a given vSwitch. :param current_ovs: The current systems Open vSwitch DOM (single vswitch) :param desired_ovs: The Open vSwitch DOM for a single vSwitch that the user passed in, which represents what the desired final state will be. :return: The micro ops that should be run to delete the desired ports on the vSwitch. """ # Sort the OVS ports into lists based on desired action ports_to_delete = self._get_ports_to_delete(current_ovs, desired_ovs) micro_op_list = [] # Remove any ports from the vswitch that are no longer desired. for port in ports_to_delete: LOG.info(_('Remove port operation for Virtual Switch %(vswitch)s ' 'and port %(port)s.' % {'vswitch': current_ovs.name, 'port': port.name})) op = ovs_port_remove.OvsPortRemove(current_ovs.name, port.name) micro_op_list.append(op) return micro_op_list
def _find_managed_system(oper, host_dict): """ Finds the ManagedSystem entry for this specific host that the HMC is managing. :param oper: The HMC operator for interacting with the HMC. :param host_dict: has the machine, model, and serial info for managed system to look up :return: The K2 object for the managed system """ # Get the ManagedSystem entry we want by Machine type, model, and serial # from the K2 API. We use the 'search' suffix to achieve this. k2resp = None try: suffix = '(MachineType==%s&&Model==%s&&SerialNumber==%s)' % \ (host_dict['machine'], host_dict['model'], host_dict['serial']) k2resp = k2_read(oper, 'ManagedSystem', suffixType='search', suffixParm=suffix, timeout=K2_MNG_SYS_READ_SEC, xag=XAG_DEFAULT) except Exception as ex: LOG.exception(ex) if k2resp is None: LOG.error(_("Managed System K2 response was none or failed.")) return None xpath = './MachineTypeModelAndSerialNumber/SerialNumber' entries = k2resp.feed.findentries(xpath, host_dict['serial']) if entries is None: LOG.warn(_("Managed System HMC response did not have any entries " "for host '%s'.") % host_dict) return None # Confirm same model and type machine_xpath = './MachineTypeModelAndSerialNumber/MachineType' model_xpath = './MachineTypeModelAndSerialNumber/Model' for entry in entries: entry_machine = entry.element.findtext(machine_xpath) entry_model = entry.element.findtext(model_xpath) if (entry_machine == host_dict['machine'] and entry_model == host_dict['model']): host_dict['uuid'] = entry.properties['id'] return entry LOG.warn(_("Managed System HMC response did not have an 'entry' " "element for host '%s'.") % host_dict) return None
def undo(self): """ Overrides parent method. Undoing a restart isn't really possible. """ LOG.info(_("Running undo 'service restart network.service' on host.")) self.commandex.send_network_restart()
def _update_port_update_pass(self, current_ovs, desired_ovs): """ Determines the micro ops to run for the update port flow, where we are modifying (but not adding/removing) ports to a given vSwitch. :param current_ovs: The current systems Open vSwitch DOM (single vswitch) :param desired_ovs: The Open vSwitch DOM for a single vSwitch that the user passed in, which represents what the desired final state will be. :return: The micro ops that should be run to update the desired ports on the vSwitch. """ micro_op_list = [] prev_voted_ports = [] # Determine which ports have changes... for desired_port in desired_ovs.ovs_port_list: current_port = self._find_ovs_port_by_voting( desired_port.name, desired_port.port_list, current_ovs.ovs_port_list, prev_voted_ports) if current_port: prev_voted_ports.append(current_port) # The desired port already exists, check for changes if desired_port != current_port: pt_names = [] for port in desired_port.port_list: # if we are trying to add a linux bridge, # we need to remove ports from bridge to # add directly to vswitch is_bridge = isinstance(port, dom_kvm.LinuxBridge) if is_bridge: op = move_bridge_ports.\ MoveBridgePorts(port.name) micro_op_list.append(op) # need to add micro op to move ip address # from bridge to the vswitch op = move_ip_address.MoveIpAddress( port.name, desired_ovs.name) micro_op_list.append(op) for bridge_port in port.port_list: pt_names.append(bridge_port.name) else: pt_names.append(port.name) op = ovs_port_update.OvsPortUpdate(current_ovs.name, current_port.name, desired_port.name, pt_names) micro_op_list.append(op) LOG.info(_('Update port operation for Virtual Switch ' '%(vswitch)s and port %(port)s.' % {'vswitch': current_ovs.name, 'port': current_port.name})) return micro_op_list
def validate(self, current_dom): """ No validation required. :param current_dom: State of the system prior to running :returns updated_dom: State of the system post running :returns warning_list: List of warnings that were found during validation """ LOG.info(_("Validating if a network restart can be performed")) return current_dom, [warning.OVSPortModificationVMActionWarning()]
def validate(self, current_dom): """ No validation required. :param current_dom: State of the system prior to running :returns updated_dom: State of the system post running :returns warning_list: List of warnings that were found during validation """ LOG.info(_("Validating if a ping to Qpid server can be " "performed")) return current_dom, []
def update_host_ovs(self, context): """update host ovs data on current host""" LOG.info(_('Updating Open vSwitch host data...')) LOG.debug("Current DOM: %s" % self.current_dom.to_dict()) LOG.debug("Requested DOM: %s" % self.desired_dom.to_dict()) builder = mob.MicroOperationBuilder(context, self.current_dom, self.desired_dom, self.rollback) mo_list = builder.get_micro_ops_for_update() # run validation return self._run_micro_op_list(mo_list)
def cleanup_ovs_tap_ports(): """ This method will be periodically invoked to cleans up orphaned OVS tap ports. This prevents Neutron from filling up the logs (and eventually the disk) with warnings about the orphaned OVS ports. """ LOG.debug('Entry: OVS TAP port cleanup task') # Create command runner commandex = commandlet.CommandExecutor # Get a list of all OVS ports on the integration bridge args = ['list-ports', CONF.integration_bridge] result = commandex.run_vsctl(args, check_error=True) port_names = [] if result: port_names = result.strip().split('\n') # Dump all OVS data with JSON formatting and OpenStack OVS agent data args = ['--format=json', '--', '--columns=name,external_ids,ofport', 'list', 'Interface'] result = commandex.run_vsctl(args, check_error=True) if not result: return # Parse JSON data into one row per OVS port for row in jsonutils.loads(result)['data']: # Verify we're looking at a port on the integration bridge name = row[0] if name not in port_names: continue # Check the status of the port ofport = row[2] try: int_ofport = int(ofport) except (ValueError, TypeError): # The status is not an integer, just continue continue else: # A status of 0 or negative means there is a problem if int_ofport <= 0: LOG.info(_('Deleting openvswitch port: %s'), row) args = ['--', '--if-exists', 'del-port', CONF.integration_bridge, name] commandex.run_vsctl(args, check_error=True) LOG.debug('Exit: OVS TAP port cleanup task')
def is_rmc_state_allowed(vios_ref, rmc_state): """ Use this helper method to return True if the RMC state of a VIOS is one of the allowed states that will not prevent a VIOS from being flagged as storage-ready for SCGs. Not hanging this method off the storage VIOS DOM yet as it can be used with live K2 data in addition to DB values. Current impl accepts ['active', 'busy'] A busy state may mean that a VIOS is not ready for storage connectivity, but that will be determined at attach time rather than when an SCG is evaluated through public APIs. """ if rmc_state == 'busy': LOG.info(_("The Virtual I/O Server '%(vios)s' has an RMC state of " "'busy', which may impact its connectivity capability " "for storage operations.") % dict(vios=vios_ref)) return (rmc_state in ['active', 'busy'])
def _is_cec_running(self): """ Will return if the CEC is up. """ k2_mg_sys = self._find_k2_managed_system() state_elem = k2_mg_sys.element.find('State') if state_elem is None: LOG.info(_("State is not found. Assuming CEC is powered off.")) return False # It *should* always be a K2 element, and if so, get the actual state # off of it if isinstance(state_elem, k2.K2Element): state_elem = state_elem.gettext() state = (state_elem.lower() in ('operating', 'standby')) LOG.debug("State of the system is %s" % state_elem) return state
def get_fcport_by_id(context, transaction, fc_id): """ Look up fc port by id. :param context: A context object that is used to authorize any DB access. :param fc_id: The 'udid' of the fc port to lookup. :returns: The fc port object looked up. Otherwise an error is raised. """ # Regular DB facing logic comes next. # Now construct and use a transaction. LOG.debug("Look for FC port with id: %s" % fc_id) fcport_factory = storage_dom.FcPortFactory.get_factory() fcport = fcport_factory.find_fcport_by_id(context, fc_id, transaction=transaction) if fcport is None: LOG.error(_("Did not find an FC port with id: %s" % fc_id)) msg = stgex.IBMPowerVCFCPortNotFound.msg_fmt % locals() ex = stgex.IBMPowerVCFCPortNotFound(msg) LOG.exception(ex) raise ex return fcport
def cluster_registration_verification(oper): """Method to verify the HMC level is good for cluster reg. support""" if not CONF.ibmpowervm_ssp_hmc_version_check: return True else: try: #Get the ManagementConsole information from the K2 API. mc_info = oper.read('ManagementConsole') #Parse the feed for the HMC version info ver_info = mc_info.feed.entries[0].element.find('VersionInfo') #Get the maintenance number from the version info maintenance = ver_info.findtext('Maintenance') #Get the minor number from the version info minor = ver_info.findtext('Minor') #Get the version number from the version info version = ver_info.findtext('Version') #Combine to get the HMC release version release = version + maintenance + minor LOG.info(_("Management Console release is '%s'.") % release) return int(release) >= 810 except Exception as exc: LOG.exception(exc) LOG.error("There was an error getting the HMC release version") return True
def build_seas_from_K2_response( k2response, vios, k2_net_bridges, vswitch_map, k2_oper, dom_factory=model.No_DB_DOM_Factory() ): """ This method takes in a VirtualIOServer K2Response and builds the K2 SharedEthernetAdapters into SharedEthernetAdapter DOM objects. Each SEA's VEAs (trunk adapters in K2-speak) will also be built and all adapters (both SEAs and VEAs) will be added to the passed in VioServer object. :param k2response: VirtualIOServer K2Response :param vios: VioServer DOM object to add VEAs to :param k2_net_bridges: The NetworkBridge K2Response :param vswitch_map: The map of vswitches on the CEC :param k2_oper: The k2 operator :param dom_factory: Factory used to create the DOM objects, optional. :returns sea_list: List of SharedEthernetAdapter DOM objects """ sea_list = [] entry = get_k2entry_for_vios(k2response, vios) # If VIOS not found, return empty list if not entry: ras.trace(LOG, __name__, ras.TRACE_ERROR, ras.vif_get_msg("error", "VIOS_UNKNOWN")) return sea_list # Now get all the SharedEthernetAdapters seas, ctrl_chan_map, sea_pvid_map = _find_seas_from_bridge(entry, k2_net_bridges) # Be sure we found some SEAs in the K2Response if seas: # Loop through all trunk adapters found and grab adapter attributes for sea in seas: # Device name name = sea.findtext("DeviceName") # TODO: Remove once everyone's at 1340A HMC if name is None: name = sea.findtext("InterfaceName") # If we got no interface name, we can't proceed. if not name: raise excp.IBMPowerVMInvalidHostConfig(attr="DeviceName") # Find all VEAs and add them to this SEA # First, we need the SEA's pvid so we can identify the # primary VEA sea_pvid = sea_pvid_map.get(name) primary_vea = None slot = 0 control_channel = None additional_veas = [] vswitch_name = None # Now get all VEAs under this SEA for vea in build_veas_from_K2_sea_k2_entry(sea, vios, vswitch_map, k2_oper): # Grab the vswitch name. We may need this for the control # channel lookup below. vswitch_name = vea.vswitch_name if vea.pvid == sea_pvid: # Found the primary VEA primary_vea = vea slot = vea.slot else: # Must just be an additional VEA additional_veas.append(vea) # If this SEA has a control channel, find the right VEA for it. ctrl_chan_ifname = sea.findtext("ControlChannelInterfaceName") if ctrl_chan_ifname: # Look in the map for the <devicename,vswitchid> key that # matches this SEA's control channel info. find_key = ctrl_chan_ifname + "," + find_vswitch_id_for_name(vswitch_name, vswitch_map) ctrl_chan_id = int(ctrl_chan_map.get(find_key, 0)) # We know that topo_hmc._populate_adapters_into_vios() # already built the VIOS DOM object with ALL VEAs in it... # so spin through and find the CNAs to find our control # channel. for vea in vios.get_all_virtual_ethernet_adapters(): # If this is a CNA (non-trunk adapter) and the pvid # matches the control channel id and it's on the same # vswitch as this SEA if not vea.is_trunk and vea.pvid == ctrl_chan_id and vea.vswitch_name == vswitch_name: # We've got our match! control_channel = vea # Break out of the loop break # If we get here, we expect to have found the control # channel. Log if we don't. if control_channel is None: ras.trace( LOG, __name__, ras.TRACE_WARNING, _( "Didn't find control channel for device " "%(dev)s, vswitch %(vswitch)s, and id " "%(c_id)d)" % {"dev": ctrl_chan_ifname, "vswitch": vswitch_name, "c_id": ctrl_chan_id} ), ) # Build and add the DOM object to the return list. This # should raise an exception if there is something wrong with # the configuration of this SEA. sea_list.append( dom_factory.create_sea( name=name, vio_server=vios, slot=slot, state="Available", primary_vea=primary_vea, control_channel=control_channel, additional_veas=additional_veas, ) ) ras.trace(LOG, __name__, ras.TRACE_DEBUG, "Built SEA %s on VIOS %s" % (name, vios.name)) # Return what we found (which could be nothing, but that's bad) ras.trace(LOG, __name__, ras.TRACE_DEBUG, "Found %d SEAs" % len(sea_list)) return sea_list
def _build_veas_from_K2_response(k2_entry, vios, vswitch_map, k2_oper, dom_factory=model.No_DB_DOM_Factory()): """ This method takes in a VirtualIOServer K2Response and builds the TrunkAdapters into VirtualEthernetAdapter DOM objects, which are added to the passed in VioServer DOM object. :param k2_entry: K2 entry to searched for TrunkAdapters and ClientNetworkAdapters :param vios: VioServer DOM object that owns the adapters in k2_entry :param vswitch_map: The map of vswitches on the CEC :param k2_oper: The k2 operator :param dom_factory: Factory used to create the DOM objects, optional. :returns veas: List of VirtualEthernetAdapter DOM objects built from the passed in TrunkAdapters list """ vea_list = [] # If VIOS not found, return empty list if not k2_entry: ras.trace(LOG, __name__, ras.TRACE_ERROR, ras.vif_get_msg("error", "VIOS_UNKNOWN")) return vea_list # First, get all the VirtualEthernetAdapters (TrunkAdapters) net_adapters = k2_entry.findall("./TrunkAdapters/TrunkAdapter") if not net_adapters: net_adapters = [] # A VIOS can have 'Client Network Adapters' - which are adapters with # a single VLAN that are not yet part of a SEA. We need to take these # into account as they are also VEAs, just with a special situation. cnas = k2_entry.findall("./ClientNetworkAdapters/link") if cnas: uri = cnas[0].get("href").rsplit("/", 1)[0] if uri: # Get all CNAs in one K2 call k2_cna_resp = k2_oper.readbyhref(href=uri + "?group=None", timeout=hmc.K2_READ_SEC) # If we found some if k2_cna_resp and k2_cna_resp.feed and k2_cna_resp.feed.entries: # Add each to the adapters list for entry in k2_cna_resp.feed.entries: net_adapters.append(entry.element) # Loop through all trunk adapters found and grab adapter attributes for adapter in net_adapters: # Device name if "ClientNetworkAdapter" == adapter.tag: name = "CNA_" + adapter.findtext("LocationCode") else: name = adapter.findtext("DeviceName") # TODO: Remove once everyone's at 1340A HMC if name is None: name = adapter.findtext("InterfaceName") if not name: ras.trace(LOG, __name__, ras.TRACE_WARNING, "Missing name") # Slot number slot = adapter.findtext("VirtualSlotNumber") if slot: slot = int(slot) else: # Set to an invalid value so constructor blows up slot = 0 ras.trace(LOG, __name__, ras.TRACE_WARNING, "Missing slot") # Port VLAN ID pvid = adapter.findtext("PortVLANID") if pvid: pvid = int(pvid) else: # Set to an invalid value so constructor blows up pvid = 0 ras.trace(LOG, __name__, ras.TRACE_WARNING, "Missing pvid") # This is a TrunkAdapter..so by definition is_trunk is true. if "ClientNetworkAdapter" == adapter.tag: is_trunk = False trunk_priority = 1 else: # Trunk adapter case. is_trunk = True trunk_priority = adapter.findtext("TrunkPriority") if trunk_priority: trunk_priority = int(trunk_priority) else: # Set to an invalid value so constructor blows up trunk_priority = 0 ras.trace(LOG, __name__, ras.TRACE_WARNING, "Missing trunk_priority") # State (we'll have to map from K2 terminology to ours) state = adapter.findtext("VariedOn") if state and state == "true": state = "Available" else: if not state: ras.trace(LOG, __name__, ras.TRACE_WARNING, "Missing state") state = "Defined" # 8021Q enabled ieee_eth = adapter.findtext("TaggedVLANSupported") if ieee_eth and ieee_eth == "true": ieee_eth = True else: if not ieee_eth: ras.trace(LOG, __name__, ras.TRACE_WARNING, _("Missing ieee_eth")) ieee_eth = False # Addl vlans addl_vlan_ids = adapter.findtext("TaggedVLANIDs") if addl_vlan_ids: # VirtualEthernetAdapter requires list of ints addl_vlan_ids = map(int, addl_vlan_ids.split(" ")) else: # VirtualEthernetAdapter requires empty list, not None addl_vlan_ids = [] # vswitch name vswitch_id = adapter.findtext("VirtualSwitchID") vswitch_name = find_vswitch_name_for_id(vswitch_id, vswitch_map) if vswitch_name and vswitch_name != "": vswitch_name = vswitch_name else: # Use a default value ras.trace(LOG, __name__, ras.TRACE_DEBUG, "Using default virtual switch name. " "Found %s." % vswitch_name) vswitch_name = "ETHERNET0" # Build and add the DOM object to the return list. This # should raise an exception if there is something wrong with # the configuration of this VEA. try: vea = dom_factory.create_vea( name=name, vio_server=vios, slot=slot, pvid=pvid, is_trunk=is_trunk, trunk_pri=trunk_priority, state=state, ieee_eth=ieee_eth, vswitch_name=vswitch_name, addl_vlan_ids=addl_vlan_ids, ) vea_list.append(vea) except Exception as e: # Took an exception creating the VEA. Log the failing VEA info # and proceed ras.trace(LOG, __name__, ras.TRACE_WARNING, _("Failed to create VEA DOM. (%s)" % e)) ras.trace( LOG, __name__, ras.TRACE_WARNING, _( "Invalid VEA attributes - tag: %(tag)s, name: " "%(name)s, slot: %(slot)d, pvid: %(pvid)d, is_trunk: " "%(is_trunk)s, trunk_priority: %(trunk_priority)d, " "state: %(state)s, ieee_eth: %(ieee_eth)s, " "addl_vlan_ids: %(addl_vlan_ids)s" % { "tag": adapter.tag, "name": name, "slot": slot, "pvid": pvid, "is_trunk": is_trunk, "trunk_priority": trunk_priority, "state": state, "ieee_eth": ieee_eth, "addl_vlan_ids": addl_vlan_ids, } ), ) # Return what we found (which could be nothing, but that's bad) ras.trace(LOG, __name__, ras.TRACE_DEBUG, "Found %d VEAs" % len(vea_list)) return vea_list
def get_micro_ops_for_update(self): """ Determines the list of micro ops need to transform the current DOM into the desired DOM for an HTTP PUT operation, or an "update". This will not create or delete vswitches, but it will update the configuration of a vswitch by adding, removing or changing vswitch ports. :returns micro_op_list: A list of MicroOperation objects. """ LOG.info(_('Network micro operations builder for network is ' 'starting.')) # Update ports for each vswitch. Each pass runs against all vSwitches. # The first pass is all remove operations. Followed by update (which # may add or remove). Finally it runs the add pass. micro_op_list = [] # do preliminary validation of each ovs in desired dom for ovs in self.desired_dom.ovs_list: LOG.info(_('Adding beginning Virtual Switch validate to operation ' 'list for Virtual Switch %s.' % ovs.name)) op = beginning_vswitch_validator.\ BeginningVswitchValidator(ovs.name, self.desired_dom) micro_op_list.append(op) micro_op_list.extend(self._update_pass(self.current_dom, self.desired_dom, self._update_port_remove_pass)) micro_op_list.extend(self._update_pass(self.current_dom, self.desired_dom, self._update_port_update_pass)) micro_op_list.extend(self._update_pass(self.current_dom, self.desired_dom, self._update_port_add_pass)) # Add any needed micro ops to the end of the op list if len(micro_op_list) > 0: # If ports were changed, a service network restart is needed # as well as end user warnings restart = False ovs_list = [] for op in micro_op_list: if(isinstance(op, ovs_port_add.OvsPortAdd) or isinstance(op, ovs_port_remove.OvsPortRemove) or isinstance(op, ovs_port_update.OvsPortUpdate)): ovs_list.append(op.ovs_name) restart = True if restart: LOG.info(_('Adding service network restart to operation ' 'list.')) op = service_network_restart.ServiceNetworkRestart() micro_op_list.append(op) op = management_host_ping.PingQpid(self.context, self.rollback) micro_op_list.append(op) # Add a validator for general OVSPort change warnings for ovs_name in set(ovs_list): op = ovs_port_warn_add_remove.OvsPortWarnAddRemove(ovs_name) micro_op_list.append(op) # Validate that each vswitch has a valid config for ovs in self.desired_dom.ovs_list: LOG.info(_('Adding end Virtual Switch validate to operation ' 'list for Virtual Switch %s.' % ovs.name)) op = end_vswitch_validator.EndVswitchValidator(ovs.name) micro_op_list.append(op) return micro_op_list
def _update_port_add_pass(self, current_ovs, desired_ovs): """ Determines the micro ops to run for the update port flow, where we are adding ports to a given vSwitch. :param current_ovs: The current systems Open vSwitch DOM (single vswitch) :param desired_ovs: The Open vSwitch DOM for a single vSwitch that the user passed in, which represents what the desired final state will be. :return: The micro ops that should be run to add the desired ports to the vSwitch. """ micro_op_list = [] prev_voted_ports = [] # Find ports to create ports_to_create = {} for desired_port in desired_ovs.ovs_port_list: ovs_port = self._find_ovs_port_by_voting(desired_port.name, desired_port.port_list, current_ovs.ovs_port_list, prev_voted_ports) if not ovs_port and desired_port and desired_port.name: cmp_list = [] for port in desired_port.port_list: # if we are trying to add a linux bridge, # we need to remove ports from bridge to # add directly to vswitch is_bridge = isinstance(port, dom_kvm.LinuxBridge) if is_bridge: op = move_bridge_ports.MoveBridgePorts(port.name) micro_op_list.append(op) # need to add micro op to move ip address # from bridge to the vswitch op = move_ip_address.MoveIpAddress(port.name, desired_ovs.name) micro_op_list.append(op) # Since the request came in to add all the bridge ports # to this port, rather than break them out into unique # OVS ports (like one may expect) we add them into # the bonded port. # # This is safer so as not to create loops. for bridge_port in port.port_list: cmp_list.append(bridge_port.name) else: cmp_list.append(port.name) if len(cmp_list) > 0: ports_to_create[desired_port.name] = cmp_list elif ovs_port: prev_voted_ports.append(ovs_port) # Add new ports to the vswitch. for port in ports_to_create: LOG.info(_('Add port operation for Virtual Switch %(vswitch)s ' 'and port %(port)s.' % {'vswitch': current_ovs.name, 'port': port})) cmp_names = [] for comp in ports_to_create[port]: cmp_names.append(comp) op = ovs_port_add.OvsPortAdd(current_ovs.name, port, cmp_names) micro_op_list.append(op) return micro_op_list
def _run_micro_op_list(self, mo_list): """ Run validation and, if force_flag is true or no warnings, run execute. Any errors encountered during execute will result in an undo call, in reverse order, on all previously executed micro ops. :param mo_list: A list of all micro ops needed to run the desired operation. This list should be generated by the micro ops builder. :returns: A dictionary containing all warnings and errors encountered during the running of the micro ops. """ current_dom = copy.deepcopy(self.current_dom) warning_list = [] return_dict = {} ifcfgs = '' ovsvsctl_show = {} # always run validation, even if force flag is # set to True to make sure there are no errors # encountered. ops_ran_list = [] for micro_op in mo_list: try: ops_ran_list.append(micro_op.__class__.__name__) LOG.debug("running micro op %s with DOM %s" % (micro_op.__class__, current_dom)) current_dom, curr_warning_list = \ micro_op.validate(current_dom) warning_list.extend(curr_warning_list) for warning in curr_warning_list: LOG.warn(_('Warning "%(warn_name)s" occurred during ' 'validation of operation %(oper)s: %(warn)s') % {'warn_name': warning.name, 'oper': micro_op.__class__.__name__, 'warn': warning}) except Exception as exc: LOG.exception(exc) LOG.error(_("List of operations run: %s" % ops_ran_list)) return_dict[agent.ERRORS_KEY] = [{'message': '%s' % exc}] break # if the force flag is set we can ignore warnings but # we cannot avoid errors, so check to be sure there # were no errors ops_ran_list = [] if((self.force_flag or len(warning_list) == 0) and agent.ERRORS_KEY not in return_dict): # in case of error, last_index is used to determine # where to start undo from last_index = -1 # execute micro op list for i in range(0, len(mo_list)): try: ops_ran_list.append(mo_list[i].__class__.__name__) mo_list[i].execute() except Exception as exc: LOG.exception(exc) LOG.error(_("List of operations run: %s" % ops_ran_list)) return_dict[agent.ERRORS_KEY] = [{'message': "%s" % exc}] last_index = i break # do we need to undo because of error? if last_index != -1: LOG.error(_("Error during operation execution, undoing " "operations...")) # Get the current state of the ifcfg files and ovs to log later try: ifcfgs = commandlet.CommandExecutor.\ get_all_ifcfg_files_for_logging() ovsvsctl_show = \ commandlet.CommandExecutor.send_vsctl_command() except Exception as e: LOG.exception(e) undo_list = [] # yes, undo needed; undo in reverse order reversed_list = self._reorder_ops_for_undo(mo_list, last_index) for op in reversed_list: try: op.undo() undo_list.append(op.__class__.__name__) except Exception as exc: # if we hit an error during undo, we will # add the error to the error list and continue # to attempt to undo the remaining micro ops LOG.exception(exc) return_dict[agent.ERRORS_KEY].append( {'message': '%s' % exc}) LOG.error(_("Undone operations: %s" % undo_list)) # we are not doing an execution, so return the warnings else: # add warnings to return list if len(warning_list) > 0: return_dict[agent.WARNINGS_KEY] = [] for warning in warning_list: return_dict[agent.WARNINGS_KEY].append( {'message': '%s' % warning}) # Errors/warnings occurred. Log initial dom, request dom, the dom as # it was when the error occurred, operations run, ifcfg files, and # ovs-vsctl show output if return_dict is not {}: debug_info_list = [] debug_info_list.append(_('Initial Object Model is:')) debug_info_list.append(json.dumps(self.current_dom.to_dict(), sort_keys=True, indent=4)) debug_info_list.append(_('Requested Object Model is:')) debug_info_list.append(json.dumps(self.desired_dom.to_dict(), sort_keys=True, indent=4)) debug_info_list.append(_('Current Object Model is:')) debug_info_list.append(json.dumps(current_dom.to_dict(), sort_keys=True, indent=4)) debug_info_list.append(_("List of operations returned by builder: " "%s" % [mo_list[i].__class__.__name__ for i in range(0, len(mo_list))])) if ifcfgs: # These are only logged on errors, not on warnings debug_info_list.append(_("Contents of ifcfg files: %s" % ifcfgs)) debug_info_list.append(_("ovs-vsctl show: ")) debug_info_list.append(json.dumps(ovsvsctl_show, sort_keys=True, indent=4)) if agent.ERRORS_KEY in return_dict: for message in debug_info_list: LOG.error(message) else: for message in debug_info_list: LOG.warn(message) return return_dict
def _populate_adapters_into_vios(self, vios, dom_factory=model.No_DB_DOM_Factory()): """ This method will populate the given VIOS with all the adapters, both SEA and VEA, found on that VIOS. :param vios: VioServer to fetch adapters from :param dom_factory: Factory used to create the DOM objects, optional. :returns boolean: True if at least one SEA was found, False otherwise. """ # Now populate the VIOS information # If the VIOS RMC state is down, we can't use adapters on it. curr_state = vios.rmc_state.lower() if curr_state != 'active': # send notification to UI if RMC is in a busy state # and notification hasn't been sent during the # interval specified. if curr_state == 'busy' and CONF.send_rmc_busy_message: curr_time = time.time() send_interval = 3600 # 60 minutes if((curr_time - IBMPowerVMNetworkTopoHMC.last_rmc_busy_notification) > send_interval): IBMPowerVMNetworkTopoHMC.last_rmc_busy_notification = \ curr_time payload = {} payload['msg'] = _('The RMC for %(vio)s on %(host)s has ' 'a status code of busy. One reason ' 'for this can be the incorrect sizing ' 'of the VIOS. Refer to the IBM PowerVM ' 'Best Practices red book to ensure ' 'that the VIOS has the appropriate set ' 'of resources to service the ' 'requests.') \ % {'vio': vios.name, 'host': CONF.host_display_name} notifier = rpc.get_notifier(service='compute', host=CONF.host) notifier.warn(ctx.get_admin_context(), 'compute.instance.log', payload) ras.trace(LOG, __name__, ras.TRACE_WARNING, _('RMC state is not active on VIOS lpar %s') % vios.lpar_name) return False # If we found NO VEAs, then we can't proceed. An SEA backed by # no VEA is useless. if not utils.build_veas_from_K2_vios_response(self.k2_vios_resp, vios, self.vswitch_map, self.operator, dom_factory): ras.trace(LOG, __name__, ras.TRACE_WARNING, ras.vif_get_msg('info', 'VIOS_NOVEA')) return False # If we find at least one SEA, we're successful. NOTE: It may # seem like we're building duplicate VEAs into the VIOS with the # call below since we already built them with the call above. # This is ok because the VioServer object will filter out # duplicates. It's necessary to do the first build above because # that's the only way we'll find orphaned VEAs. if utils.build_seas_from_K2_response(self.k2_vios_resp, vios, self.k2_net_bridge_resp, self.vswitch_map, self.operator, dom_factory): ras.trace(LOG, __name__, ras.TRACE_DEBUG, ras.vif_get_msg('info', 'FOUND_SEAS') % {'num_seas': len(vios.get_shared_ethernet_adapters()), 'vios_name': vios.name}) return True else: # Found no SEAs... this means the VIOS is not usable for deploy. ras.trace(LOG, __name__, ras.TRACE_WARNING, ras.vif_get_msg('error', 'VIOS_NOSEA')) return False
def _get_all_vios(self, dom_factory=model.No_DB_DOM_Factory()): """ This method will return all VIOSes available on the current host (as identified in the operator). :param dom_factory: Factory used to create the DOM objects, optional. :returns vioses: A list of VioServer objects """ # Get the system we are managing managed_system_uuid = self._find_managed_system() # Clear out the data self.vio_server_map = {} self._repopulate_vswitch_data() # Move on to VIOS info. Start with an empty list vios_list = [] # Get all the VIOSes on the HMC. The response will contain all the # info we need for adapters, so save it off so we don't have to # make unnecessary K2 calls later on. ras.function_tracepoint(LOG, __name__, ras.TRACE_DEBUG, 'Getting VIOS') self.k2_vios_resp = self.operator.read(rootType='ManagedSystem', rootId=managed_system_uuid, childType='VirtualIOServer', timeout=hmc.K2_RMC_READ_SEC, xag=[k2const.VIOS_NET_EXT_PROP], age=self.read_age) ras.function_tracepoint(LOG, __name__, ras.TRACE_DEBUG, 'Got VIOS') # Get the network bridges ras.function_tracepoint(LOG, __name__, ras.TRACE_DEBUG, 'Getting Network Bridges') self.k2_net_bridge_resp = self.operator.read( rootType='ManagedSystem', rootId=managed_system_uuid, childType='NetworkBridge', timeout=hmc.K2_RMC_READ_SEC, age=self.read_age) ras.function_tracepoint(LOG, __name__, ras.TRACE_DEBUG, 'Got Network Bridges') # If we find no VIOS, just return if not self.k2_vios_resp.feed or not self.k2_vios_resp.feed.entries: ras.trace(LOG, __name__, ras.TRACE_DEBUG, ras.vif_get_msg('error', 'VIOS_NONE') % self.host_name) return vios_list # Loop through all VIOSes found for entry in self.k2_vios_resp.feed.entries: # Grab the necessary attributes lpar_id = int(entry.element.findtext('PartitionID')) lpar_name = entry.element.findtext('PartitionName') state = entry.element.findtext('PartitionState') rmc_state = entry.element.findtext('ResourceMonitoring' 'ControlState') # If RMC is down, we want to log why. Could be helpful down the # road... if rmc_state is None or rmc_state.lower() != 'active': LOG.warn(_('K2 RMC state for lpar %(lpid)d (%(lname)s): ' '%(state)s') % {'lpid': lpar_id, 'lname': lpar_name, 'state': rmc_state}) data = self.k2_vios_resp.body LOG.warn(_('K2Response: %(resp)s') % {'resp': data}) # Add the data to the map self.vio_server_map[lpar_id] = entry.properties['link'] # Create a VIOS DOM object vios = dom_factory.create_vios(lpar_name=lpar_name, lpar_id=lpar_id, host_name=self.host_name, state=state, rmc_state=rmc_state) vios_list.append(vios) ras.trace(LOG, __name__, ras.TRACE_DEBUG, ras.vif_get_msg('info', 'FOUND_VIOS') % {'lpar_id': lpar_id, 'lpar_name': lpar_name}) return vios_list