def _get_orphan_pairs(self): """ Uses the DOM topology to get a list of all VLANs/VSwitches which are orphaned. :return: List of VSwitch/VLAN ID pairs. The list will look like: [ 'ETHERNET0:12', 'ETHERNET1:43'] """ # Read the VIOS adapters from the database session = db_session.get_session() orphan_pairs = [] with session.begin(): vios_list = db_api.vio_server_find_all(context.get_admin_context(), CONF.host, session) host = dom_model.Host(CONF.host, vios_list) for vios in vios_list: orphan_veas = vios.get_orphan_virtual_ethernet_adapters() for vea in orphan_veas: pair_string = '%(vs)s:%(vlan)d' % {'vs': vea.vswitch_name, 'vlan': vea.pvid} if not host.is_vlan_vswitch_on_sea(vea.pvid, vea.vswitch_name): orphan_pairs.append(pair_string) for addl_vlan in vea.addl_vlan_ids: pair_string = ('%(vs)s:%(vlan)d' % {'vs': vea.vswitch_name, 'vlan': addl_vlan}) if not host.is_vlan_vswitch_on_sea(addl_vlan, vea.vswitch_name): orphan_pairs.append(pair_string) return orphan_pairs
def get_host_dom(context, host_id, session=None): """ Will query the system for a given host and will return its corresponding DOM object. :param context: The context for the query :param host_id: The host identifier :param session: Optional session to keep the objects within a single transaction """ vio_servers = dom_api.vio_server_find_all(context, host_id, session) return dom_model.Host(host_id, vio_servers)
def _get_host_dom(self, context, sea): """ Maintains a cache of the host dom information. Is cleared each time a filter method is called. Keeps this information in cache so that multiple lookups to the same host are not done. :param context: The request to perform DB actions against :param sea: The shared ethernet adapter to look up the host dom for """ host_name = sea.vio_server.get_host_name() host_dom_cache = self.host_dom_map.get(host_name, None) if host_dom_cache is not None: return host_dom_cache vio_servers = dom_api.vio_server_find_all(context, host_name) host_dom = dom_model.Host(host_name, vio_servers) self.host_dom_map[host_name] = host_dom return host_dom
def _log_before_and_after(context, host_data, db_session): """ Logs the DOM info before and after the inventory job to help debug. :param context: The database context. :param host_data: A dictionary of data that represents the latest inventory information on the server. The data should be in the network DOM format. :param db_session: The database session. Should be started and finalized outside this class. """ try: # Read the new VIOS data, and the old VIOS data from the database new_vioses = [] old_vioses = [] server_dom = dom.parse_to_host(host_data, dom.No_DB_DOM_Factory()) for vios in server_dom.vio_servers: new_vioses.append(pprint.pformat(vios.to_dictionary())) for vios in db.vio_server_find_all(context, server_dom.host_name, db_session): old_vioses.append(pprint.pformat(vios.to_dictionary())) # Log the VIOS data. Use LOG_NO_NAME to improve readability. LOG_NO_NAME.error(_('New VIOS data:')) LOG_NO_NAME.error(_('host_name:')) LOG_NO_NAME.error(server_dom.host_name) for vios_string in new_vioses: for line in vios_string.split('\n'): LOG_NO_NAME.error(line) LOG_NO_NAME.error('Old VIOS data:') for vios_string in old_vioses: for line in vios_string.split('\n'): LOG_NO_NAME.error(line) except Exception: # This method is only invoked to log data during error flows. If an # error occurs during the error flow, that's not much we can do. pass
def _reconcile_host(context, host_data, dom_factory=dom.DOM_Factory(), db_session=None): """ Performs the actual reconciliation at the host level :param context: The database context. :param host_data: A dictionary of data that represents the latest inventory information on the server. The data should be in the network DOM format. :param dom_factory: Optional factory used to create the DOM objects. Not required to be set. :param db_session: The database session. Should be started and finalized outside this class. """ if not db_session: db_session = session.get_session() # Parse the inventory data into a DOM object. Use the no_db DOM factory # as we want to parse into non-DB backed elements to start... non_db_fact = dom.No_DB_DOM_Factory() server_dom = dom.parse_to_host(host_data, non_db_fact) msg = (ras.vif_get_msg('info', 'RECONCILE_HOST_START') % {'host': server_dom.host_name}) ras.trace(LOG, __name__, ras.TRACE_DEBUG, msg) # Get the inventory data from the database. db_vio_servers = db.vio_server_find_all(context, server_dom.host_name, db_session) # If there are no VIO Servers, the system may be turned off. It is very # unlikely that they are actually removed (all of them at least). # Therefore, we flip the SEAs in each VioServer to a state of unavailable # and they do not show up in the UI...but are not deleted. if len(server_dom.vio_servers) == 0: LOG.info(_("Flipping host %s to unavailable due to lack of VioServers" % server_dom.host_name)) _make_system_unavailable(db_vio_servers, context, db_session) return # The first step is to find VIO Servers do add/remove/modify. Those are # the three passes that need to be made. # # We start with the idea that all of the data base items should be removed. # From there, we parse down which are still on the system (therefore need # to be modified) and then the new adds. db_vios_to_del = dom.shallow_copy_as_ordinary_list(db_vio_servers) srv_vios_to_add = [] srv_vios_to_modify = [] for vio_server in server_dom.vio_servers: db_vios = _find_vios(db_vio_servers, vio_server.lpar_id) if db_vios: srv_vios_to_modify.append(vio_server) db_vios_to_del.remove(db_vios) else: srv_vios_to_add.append(vio_server) # Now that we know what to modify/create/delete...loop through each and # execute the commands to reconcile db_host_dom = dom.Host(server_dom.host_name, db_vio_servers) # Save off the network associations first so we can recreate any that # need to be later on. net_assns = _build_net_assn_dict( db.network_association_find_all(context, db_host_dom.host_name, db_session)) for db_vios in db_vios_to_del: _remove_vios(db_vios, db_host_dom, context, db_session) for server_vios in srv_vios_to_modify: _reconcile_vios(_find_vios(db_vio_servers, server_vios.lpar_id), server_vios, context, db_session, dom_factory) for server_vios in srv_vios_to_add: _add_vios(server_vios, db_host_dom, context, db_session, dom_factory) msg = (ras.vif_get_msg('info', 'RECONCILE_HOST_END') % {'host': server_dom.host_name}) ras.trace(LOG, __name__, ras.TRACE_DEBUG, msg) # Cleanup NetworkAssociations in case any VIOSes went away or came back. _cleanup_network_associations(db_host_dom, net_assns, context, db_session)
def _get_viable_hosts(self, context, instance_uuid, hosts_from_db, network_uuids, source_host=None): """ Find the viable hosts that match the network requirements. :param context: A context object used to authorize the DB access. :param instance_uuid: The instance UUID that will be used to get the network requirements of an instance. :param hosts_from_db: A list of compute nodes :param network_uuids: An list of requested networks for the deployment. :param source_host: When this API is invoked for LPM, the host that is being migrated from. This is needed to check vswitch config on the source host. :returns: A dictionary of hosts with the hostid and the hostname """ candidate_hosts = copy.copy(hosts_from_db) # Determine which vswitches are needed for each network on source_host required_vswitches = {} if source_host: netasc_list = dom_api.network_association_find_all(context, source_host) for netasc in netasc_list: for net_uuid in network_uuids: if netasc.neutron_net_id == net_uuid and\ netasc.sea is not None: required_vswitches[net_uuid] = \ netasc.sea.get_primary_vea().vswitch_name dom_factory = dom_model.DOM_Factory() # Check if each host contains a valid network for host in hosts_from_db: # Keep track of the networks that remaining to be processed. networks_to_process = copy.copy(network_uuids) if not networks_to_process: continue # Get the DOM so that we can verify the state of the chains. host_dom = dom_model.Host(host, dom_api.vio_server_find_all(context, host)) # Walk through each of the network associations that exist for # the host and see if it was set to be used. A 'use this host' # indication is if the network associations SEA is not None netasc_list = dom_api.network_association_find_all(context, host) for net_assn in netasc_list: if net_assn.neutron_net_id in network_uuids: # No longer a network to process networks_to_process.remove(net_assn.neutron_net_id) # Check the SEA chain. At least one SEA in the chain # should be available. If there are no SEAs, then remove # it as a candidate at_least_one_available = False if net_assn.sea: s_nm = net_assn.sea.name s_lp = net_assn.sea.vio_server.lpar_id sea_chain = host_dom.find_sea_chain_for_sea_info(s_nm, s_lp) for sea in sea_chain: if sea and sea.is_available(): at_least_one_available = True break # Check the vswitches. The same vswitch name must be used # on the source and target host. vswitch_matches = True if net_assn.neutron_net_id in required_vswitches and\ net_assn.sea is not None: if(net_assn.sea.get_primary_vea().vswitch_name != required_vswitches[net_assn.neutron_net_id]): vswitch_matches = False # Check the conditions. If there isn't one available, # yet the host is still here...remove that host as a # candidate. if((not vswitch_matches or not at_least_one_available) and host in candidate_hosts): candidate_hosts.remove(host) # Skip the next, computationally expensive, step if there are no # more networks to process. if len(networks_to_process) == 0: continue # There may be some networks that haven't been processed yet. # This is typically for when a host was added after the network # was created. In this scenario, we should call down into # the adapter mapping task and find the default. The default # may be do not use. # # We don't do this by default because it's far more expensive # than a database operation, and we want the standard flow to be # as fast as possible. temp_context = context.elevated() vios_list = dom_api.vio_server_find_all(temp_context, host) host_dom = dom_factory.create_host(host, vios_list) for net_id in networks_to_process: net_info = self.get(temp_context, net_id) if not net_info or\ net_info.get('provider:segmentation_id') is None: continue vlanid = int(net_info.get('provider:segmentation_id')) sea = self.build_default_network_assn(temp_context, host_dom, vlanid, net_id) # If the SEA that came back from this is None, we need to # remove it as a candidate. We can be confident at this # point though that the network association was created, # thus speeding up our flow through next time. if sea is None or not sea.is_available(): candidate_hosts.remove(host) return candidate_hosts
def _get_specific_host_seas(self, context, host, vswitch=None, vlan=None, net_id=None, session=None, ports=None): """ This method will return the SEA candidates for a given host, and only that host. The format of the response will be: { "host_name": "host2", "adapters": [ { "default": true, "sea_name": "ent5", "vswitch": "ETHERNET0", "lpar_id": 1, "ha_lpar_id": null, "ha_mode": "disabled", "pvid": 1, "state": "Available", "ha_state": null, "lpar_name": "15-34B9Z", "ha_lpar_name": null, "ha_sea": null } ] } :param context: The context for the request. :param host: The host name (as a string) :param vswitch: The vswitch that should be used to help identify the default adapter. If set to None all of the vSwitches will be utilized. :param vlan: The vlan that should be used to help identify the default adapter. If set to None (the default value), a VLAN ID of 1 will be used. :param net_id: The network UUID of a neutron Network. This is optional :param ports: An optional list of ports for the specified network. """ # Build basic data to determine targets vio_servers = dom_api.vio_server_find_all(context, host, session) host_dom = dom_model.Host(host, vio_servers) vswitches = self._get_vswitches(host_dom.find_all_primary_seas()) # If the network id was set, then we should always use that networks # vlan id instead of the passed in value. vlan = self._determine_vlan(vlan, net_id, host, context) # We need to determine if this network has any VMs on the host. If so, # then we can't be set to Do not Use. We also can't allow them to # change vSwitches. allow_do_not_use_option = False if net_id: # We only allow the do not use option if the VM count for this # network is 0. Otherwise a VM is using it, and we can't flip # to do not use until all the VMs are done. vm_list = dom_api.instances_find(context, host, net_id, ports) allow_do_not_use_option = (len(vm_list) == 0) # As noted above...if the network association has VMs, then we # can't let the user change the vSwitch that the networks are on. # Therefore, set the specific_vswitch so that the candidate_sea # loop won't take any other vSwitches into account. if len(vm_list) > 0: net_assn = dom_api.network_association_find(context, host_dom.host_name, net_id, session) # If there is a network association, just override the vSwitch # list with the network associations vSwitch. This limits it # to a single vSwitch search scope. if net_assn and net_assn.sea: vswitches = [net_assn.sea.primary_vea.vswitch_name] else: # If there was not a network id, then we assume that this is a # new network and therefore do not use should always be returned. allow_do_not_use_option = True # Variable to store all candidate SEAs candidate_seas = [] # Walk through the vswitches on this host and determine the valid # candidates (broken into pools defined by the vswitches). for vswitch_name in vswitches: # If the user passed in a vswitch, and we don't match, continue if vswitch is not None and vswitch != vswitch_name: continue # Extend the candidates candidate_seas.extend(self._get_candidate_seas_for_vswitch( host_dom, vswitch_name, vlan)) # Now we need to find the default adapter...may be None, which # indicates that it is a do not use. default_sea = self._find_default_adapter(host_dom, candidate_seas, net_id, vlan, context, session) # If the default sea is not selected, and there's only one vswitch # we need to determine a default adapter for this VLAN and create # that relationship. if(default_sea is None and not allow_do_not_use_option and net_id): vswitch_with_vlan = [] for vswitch in vswitches: sea = host_dom.find_sea_for_vlan_vswitch(vlan, vswitch) if sea: vswitch_with_vlan.append(sea) # We would like to set this as the default since in this # present call to host-seas - we need to report a default if len(vswitch_with_vlan) == 1: default_sea = vswitch_with_vlan[0] dom_api.network_association_put_sea(context, host, net_id, vswitch_with_vlan[0], session) # Now, build the adapter list to return adapter_list = [] if allow_do_not_use_option or len(vswitches) > 1: adapter_list.append(self._format_sea_to_dict_response (host_dom, None, default_sea is None)) if default_sea and default_sea not in candidate_seas: candidate_seas.append(default_sea) for sea in candidate_seas: adapter_list.append(self._format_sea_to_dict_response (host_dom, sea, default_sea == sea)) msg = (ras.vif_get_msg('info', 'HOST_SEAS_RETURN') % {'vlan': vlan, 'host_name': host, 'list': adapter_list}) ras.function_tracepoint( LOG, __name__, ras.TRACE_INFO, msg) return { 'host_name': host_dom.host_name, 'adapters': adapter_list }