diff --git a/drivers/linstor-manager b/drivers/linstor-manager index f0404b809..8c04367d2 100755 --- a/drivers/linstor-manager +++ b/drivers/linstor-manager @@ -1183,6 +1183,77 @@ def set_node_preferred_interface(session, args): raise XenAPIPlugin.Failure('-1', [str(e)]) return str(True) +def list_network_path(session, args): + group_name = args["groupName"] + + linstor = LinstorVolumeManager( + get_controller_uri(), + group_name, + logger=util.SMlog + ) + try: + return str(linstor.list_node_path()) + except Exception as e: + raise XenAPIPlugin.Failure('-1', [str(e)]) + +def get_network_path(session, args): + group_name = args["groupName"] + node1 = args["node1"] + node2 = args["node2"] + + linstor = LinstorVolumeManager( + get_controller_uri(), + group_name, + logger=util.SMlog + ) + try: + return str(linstor.get_node_path(node1, node2)) + except Exception as e: + raise XenAPIPlugin.Failure('-1', [str(e)]) + +def set_network_path(session, args): + group_name = args["groupName"] + hostname1 = args["node1"] + hostname2 = args["node2"] + network_name = args["network"] + + linstor = LinstorVolumeManager( + get_controller_uri(), + group_name, + logger=util.SMlog + ) + ret_list = [] + try: + list_resp = linstor.set_node_path(hostname1, hostname2, network_name) + for resp in list_resp: + if(resp.is_error()): + raise XenAPIPlugin.Failure('-1', [str(resp)]) + ret_list.append(str(resp)) + + except Exception as e: + raise XenAPIPlugin.Failure('-1', [str(e)]) + return str(ret_list) + +def destroy_network_path(session, args): + group_name = args["groupName"] + hostname1 = args["node1"] + hostname2 = args["node2"] + + linstor = LinstorVolumeManager( + get_controller_uri(), + group_name, + logger=util.SMlog + ) + ret_list = [] + try: + list_resp = linstor.destroy_node_path(hostname1, hostname2) + for resp in list_resp: + if(resp.is_error()): + raise XenAPIPlugin.Failure('-1', [str(resp)]) + ret_list.append(str(resp)) + except Exception as e: + raise XenAPIPlugin.Failure('-1', [str(e)]) + return str(ret_list) if __name__ == '__main__': XenAPIPlugin.dispatch({ @@ -1238,5 +1309,10 @@ if __name__ == '__main__': 'modifyNodeInterface': modify_node_interface, 'listNodeInterfaces': list_node_interfaces, 'getNodePreferredInterface': get_node_preferred_interface, - 'setNodePreferredInterface': set_node_preferred_interface + 'setNodePreferredInterface': set_node_preferred_interface, + + 'listNetworkPath': list_network_path, + 'getNetworkPath': get_network_path, + 'setNetworkPath': set_network_path, + 'destroyNetworkPath': destroy_network_path }) diff --git a/drivers/linstorvhdutil.py b/drivers/linstorvhdutil.py index 7f8efa12d..64148d636 100644 --- a/drivers/linstorvhdutil.py +++ b/drivers/linstorvhdutil.py @@ -33,18 +33,21 @@ def call_remote_method(session, host_ref, method, device_path, args): + host_rec = session.xenapi.host.get_record(host_ref) + host_uuid = host_rec['uuid'] + try: response = session.xenapi.host.call_plugin( host_ref, MANAGER_PLUGIN, method, args ) except Exception as e: - util.SMlog('call-plugin ({} with {}) exception: {}'.format( - method, args, e + util.SMlog('call-plugin on {} ({} with {}) exception: {}'.format( + host_uuid, method, args, e )) raise util.SMException(str(e)) - util.SMlog('call-plugin ({} with {}) returned: {}'.format( - method, args, response + util.SMlog('call-plugin on {} ({} with {}) returned: {}'.format( + host_uuid, method, args, response )) return response @@ -75,6 +78,17 @@ class ErofsLinstorCallException(LinstorCallException): class NoPathLinstorCallException(LinstorCallException): pass +def log_successful_call(target_host, device_path, vdi_uuid, remote_method, response): + util.SMlog( + 'Successful access on {} for device {} ({}): `{}` => {}'.format(target_host, device_path, vdi_uuid, remote_method, str(response)), + priority=util.LOG_DEBUG + ) + +def log_failed_call(target_host, next_target, device_path, vdi_uuid, remote_method, e): + util.SMlog( + 'Failed to call method on {} for device {} ({}): {}. Trying accessing on {}... (cause: {})'.format(target_host, device_path, vdi_uuid, remote_method, next_target, e), + priority=util.LOG_DEBUG + ) def linstorhostcall(local_method, remote_method): def decorated(response_parser): @@ -86,33 +100,6 @@ def wrapper(*args, **kwargs): self._linstor.get_volume_name(vdi_uuid) ) - # A. Try a call using directly the DRBD device to avoid - # remote request. - - # Try to read locally if the device is not in use or if the device - # is up to date and not diskless. - (node_names, in_use_by) = \ - self._linstor.find_up_to_date_diskful_nodes(vdi_uuid) - - local_e = None - try: - if not in_use_by or socket.gethostname() in node_names: - return self._call_local_method(local_method, device_path, *args[2:], **kwargs) - except ErofsLinstorCallException as e: - local_e = e.cmd_err - except Exception as e: - local_e = e - - util.SMlog( - 'unable to execute `{}` locally, retry using a readable host... (cause: {})'.format( - remote_method, local_e if local_e else 'local diskless + in use or not up to date' - ) - ) - - if in_use_by: - node_names = {in_use_by} - - # B. Execute the plugin on master or slave. remote_args = { 'devicePath': device_path, 'groupName': self._linstor.group_name @@ -121,14 +108,45 @@ def wrapper(*args, **kwargs): remote_args = {str(key): str(value) for key, value in remote_args.iteritems()} try: - def remote_call(): - host_ref = self._get_readonly_host(vdi_uuid, device_path, node_names) - return call_remote_method(self._session, host_ref, remote_method, device_path, remote_args) - response = util.retry(remote_call, 5, 2) - except Exception as remote_e: - self._raise_openers_exception(device_path, local_e or remote_e) + host_ref_attached = util.get_hosts_attached_on(self._session, [vdi_uuid])[0] + if host_ref_attached: + response = call_remote_method( + self._session, host_ref_attached, remote_method, device_path, remote_args + ) + log_successful_call('attached node', device_path, vdi_uuid, remote_method, response) + return response_parser(self, vdi_uuid, response) + except Exception as e: + log_failed_call('attached node', 'master', device_path, vdi_uuid, remote_method, e) + + try: + master_ref = self._session.xenapi.pool.get_all_records().values()[0]['master'] + response = call_remote_method(self._session, master_ref, remote_method, device_path, remote_args) + log_successful_call('master', device_path, vdi_uuid, remote_method, response) + return response_parser(self, vdi_uuid, response) + except Exception as e: + log_failed_call('master', 'primary', device_path, vdi_uuid, remote_method, e) + + + nodes, primary_hostname = self._linstor.find_up_to_date_diskful_nodes(vdi_uuid) + if primary_hostname: + try: + host_ref = self._get_readonly_host(vdi_uuid, device_path, {primary_hostname}) + response = call_remote_method(self._session, host_ref, remote_method, device_path, remote_args) + log_successful_call('primary', device_path, vdi_uuid, remote_method, response) + return response_parser(self, vdi_uuid, response) + except Exception as remote_e: + self._raise_openers_exception(device_path, remote_e) + else: + log_failed_call('primary', 'another node', device_path, vdi_uuid, remote_method, e) + + try: + host = self._get_readonly_host(vdi_uuid, device_path, nodes) + response = call_remote_method(self._session, host, remote_method, device_path, remote_args) + log_successful_call('another node', device_path, vdi_uuid, remote_method, response) + return response_parser(self, vdi_uuid, response) + except Exception as remote_e: + self._raise_openers_exception(device_path, remote_e) - return response_parser(self, vdi_uuid, response) return wrapper return decorated diff --git a/drivers/linstorvolumemanager.py b/drivers/linstorvolumemanager.py index 8bfd1c1b4..d8436b624 100755 --- a/drivers/linstorvolumemanager.py +++ b/drivers/linstorvolumemanager.py @@ -46,6 +46,8 @@ PLUGIN = 'linstor-manager' +PATH_NAME = "xostor" + # ============================================================================== def get_local_volume_openers(resource_name, volume): @@ -1438,6 +1440,24 @@ def find_up_to_date_diskful_nodes(self, volume_uuid): return (node_names, in_use_by) + def get_primary(self, volume_uuid): + """ + Find the node that opened a volume, i.e. the primary. + :rtype: str + """ + volume_name = self.get_volume_name(volume_uuid) + + resource_states = filter( + lambda resource_state: resource_state.name == volume_name, + self._get_resource_cache().resource_states + ) + + for resource_state in resource_states: + if resource_state.in_use: + return resource_state.node_name + + return None + def invalidate_resource_cache(self): """ If resources are impacted by external commands like vhdutil, @@ -1602,6 +1622,50 @@ def set_node_preferred_interface(self, node_name, name): 'Failed to set preferred node interface on `{}`: {}'.format(node_name, error_str) ) + def list_node_path(self): + result = self._linstor.node_conn_list() + errors = self._filter_errors(result) + if errors: + error_str = self._get_error_str(errors) + raise LinstorVolumeManagerError( + 'Failed to list node connection: {}'.format(error_str) + ) + return result + + def get_node_path(self, hostname1, hostname2): + result = self._linstor.node_conn_list_specific_pair(node_a=hostname1, node_b=hostname2) + errors = self._filter_errors(result) + if errors: + error_str = self._get_error_str(errors) + raise LinstorVolumeManagerError( + 'Failed to get node connection for `{}` <-> `{}`: {}'.format(hostname1, hostname2, error_str) + ) + return result + + def set_node_path(self, hostname1, hostname2, network_name): + property_name1 = "Paths/{netname}/{node_name}".format(netname=PATH_NAME, node_name=hostname1) + property_name2 = "Paths/{netname}/{node_name}".format(netname=PATH_NAME, node_name=hostname2) + result = self._linstor.node_conn_modify(hostname1, hostname2, property_dict={property_name1: network_name, property_name2: network_name}, delete_props=None) + errors = self._filter_errors(result) + if errors: + error_str = self._get_error_str(errors) + raise LinstorVolumeManagerError( + 'Failed to set node connection `{}` <-> `{}`: {}'.format(hostname1, hostname2, error_str) + ) + return result + + def destroy_node_path(self, hostname1, hostname2): + property_name1 = "Paths/{netname}/{node_name}".format(netname=PATH_NAME, node_name=hostname1) + property_name2 = "Paths/{netname}/{node_name}".format(netname=PATH_NAME, node_name=hostname2) + result = self._linstor.node_conn_modify(hostname1, hostname2, property_dict=None, delete_props=[property_name1, property_name2]) + errors = self._filter_errors(result) + if errors: + error_str = self._get_error_str(errors) + raise LinstorVolumeManagerError( + 'Failed to destroy node connection `{}` <-> `{}`: {}'.format(hostname1, hostname2, error_str) + ) + return result + def get_nodes_info(self): """ Get all nodes + statuses, used or not by the pool.