Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package fence-agents for openSUSE:Factory checked in at 2025-01-29 16:18:25 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/fence-agents (Old) and /work/SRC/openSUSE:Factory/.fence-agents.new.2316 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "fence-agents" Wed Jan 29 16:18:25 2025 rev:82 rq:1241168 version:4.16.0+git.1738070632.02141bfa Changes: -------- --- /work/SRC/openSUSE:Factory/fence-agents/fence-agents.changes 2025-01-27 21:33:22.740918329 +0100 +++ /work/SRC/openSUSE:Factory/.fence-agents.new.2316/fence-agents.changes 2025-01-29 16:19:04.349102829 +0100 @@ -1,0 +2,6 @@ +Wed Jan 29 09:16:09 UTC 2025 - vark...@suse.com + +- Update to version 4.16.0+git.1738070632.02141bfa: + * fence_azure_arm: use azure-identity instead of msrestazure, which has been deprecated (#603) + +------------------------------------------------------------------- Old: ---- fence-agents-4.16.0+git.1735891853.256a570a.tar.xz New: ---- fence-agents-4.16.0+git.1738070632.02141bfa.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ fence-agents.spec ++++++ --- /var/tmp/diff_new_pack.X1NyYa/_old 2025-01-29 16:19:04.977128872 +0100 +++ /var/tmp/diff_new_pack.X1NyYa/_new 2025-01-29 16:19:04.977128872 +0100 @@ -16,10 +16,10 @@ # -%define agent_list aliyun alom apc apc_snmp aws bladecenter brocade cisco_mds cisco_ucs drac5 dummy eaton_snmp eaton_ssh emerson eps evacuate gce hds_cb hpblade ibmblade ibmz ibm_powervs ibm_vpc ifmib ilo ilo_moonshot ilo_mp ilo_ssh intelmodular ipdu ipmilan ironic kdump lpar mpath netio powerman pve raritan rcd_serial redfish rsa rsb sanbox2 sbd scsi vbox virsh vmware vmware_rest wti zvm +%define agent_list aliyun alom apc apc_snmp aws azure_arm bladecenter brocade cisco_mds cisco_ucs drac5 dummy eaton_snmp eaton_ssh emerson eps evacuate gce hds_cb hpblade ibmblade ibmz ibm_powervs ibm_vpc ifmib ilo ilo_moonshot ilo_mp ilo_ssh intelmodular ipdu ipmilan ironic kdump lpar mpath netio powerman pve raritan rcd_serial redfish rsa rsb sanbox2 sbd scsi vbox virsh vmware vmware_rest wti zvm Name: fence-agents Summary: Set of unified programs capable of host isolation ("fencing") -Version: 4.16.0+git.1735891853.256a570a +Version: 4.16.0+git.1738070632.02141bfa Release: 0 License: GPL-2.0-or-later AND LGPL-2.0-or-later Group: Productivity/Clustering/HA @@ -93,6 +93,7 @@ %global allfenceagents %(cat <<EOF %{allfenceagents} \\ fence-agents-aws \\ +fence-agents-azure-arm \\ fence-agents-gce \\ fence-agents-ironic @@ -379,33 +380,33 @@ %{_sbindir}/fence_aws %{_mandir}/man8/fence_aws.8* -#%package azure-arm -#License: GPL-2.0-or-later AND LGPL-2.0-or-later -#Summary: Fence agent for Azure Resource Manager -#Requires: fence-agents-common = %{version}-%{release} -#%if 0%{?rhel} && 0%{?rhel} < 9 -#Requires: python3-azure-sdk -#%else -#Requires: python311-azure-core -#Requires: python311-azure-identity -#Requires: python311-azure-mgmt-compute -#Requires: python311-azure-mgmt-network -#Requires: python311-pexpect -#Requires: python311-pycurl -#%endif -#BuildArch: noarch -#Conflicts: %{name} < %{version}-%{release} -# -#%description azure-arm -#Fence agent for Azure Resource Manager instances. -# -#%files azure-arm -#%{_sbindir}/fence_azure_arm -#%{_datadir}/fence/azure_fence.py* -#%if 0%{?fedora} || 0%{?centos} || 0%{?rhel} -#%{_datadir}/fence/__pycache__/azure_fence.* -#%endif -#%{_mandir}/man8/fence_azure_arm.8* +%package azure-arm +License: GPL-2.0-or-later AND LGPL-2.0-or-later +Summary: Fence agent for Azure Resource Manager +Requires: fence-agents-common = %{version}-%{release} +%if 0%{?rhel} && 0%{?rhel} < 9 +Requires: python3-azure-sdk +%else +Requires: python311-azure-core +Requires: python311-azure-identity +Requires: python311-azure-mgmt-compute +Requires: python311-azure-mgmt-network +Requires: python311-pexpect +Requires: python311-pycurl +%endif +BuildArch: noarch +Conflicts: %{name} < %{version}-%{release} + +%description azure-arm +Fence agent for Azure Resource Manager instances. + +%files azure-arm +%{_sbindir}/fence_azure_arm +%{_datadir}/fence/azure_fence.py* +%if 0%{?fedora} || 0%{?centos} || 0%{?rhel} +%{_datadir}/fence/__pycache__/azure_fence.* +%endif +%{_mandir}/man8/fence_azure_arm.8* %package bladecenter License: GPL-2.0-or-later AND LGPL-2.0-or-later ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.X1NyYa/_old 2025-01-29 16:19:05.017130531 +0100 +++ /var/tmp/diff_new_pack.X1NyYa/_new 2025-01-29 16:19:05.021130697 +0100 @@ -3,6 +3,6 @@ <param name="url">git://github.com/ClusterLabs/fence-agents.git</param> <param name="changesrevision">8d746be92f191aa289f13a3703031c122a5e6cf3</param></service><service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/fence-agents</param> - <param name="changesrevision">256a570a0ba9ec14a9efbd1b6570ac3b299da98b</param></service></servicedata> + <param name="changesrevision">02141bfa032cb81dd40a0b0966d7c570a3bc25fc</param></service></servicedata> (No newline at EOF) ++++++ fence-agents-4.16.0+git.1735891853.256a570a.tar.xz -> fence-agents-4.16.0+git.1738070632.02141bfa.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.16.0+git.1735891853.256a570a/agents/azure_arm/fence_azure_arm.py new/fence-agents-4.16.0+git.1738070632.02141bfa/agents/azure_arm/fence_azure_arm.py --- old/fence-agents-4.16.0+git.1735891853.256a570a/agents/azure_arm/fence_azure_arm.py 2025-01-03 09:10:53.000000000 +0100 +++ new/fence-agents-4.16.0+git.1738070632.02141bfa/agents/azure_arm/fence_azure_arm.py 2025-01-28 14:23:52.000000000 +0100 @@ -15,7 +15,7 @@ if clients: compute_client = clients[0] rgName = options["--resourceGroup"] - vms = compute_client.virtual_machines.list(rgName) + vms = azure_fence.get_vm_list(compute_client,rgName) try: for vm in vms: result[vm.name] = ("", None) @@ -31,7 +31,7 @@ rgName = options["--resourceGroup"] try: - vms = compute_client.virtual_machines.list(rgName) + vms = azure_fence.get_vm_list(compute_client,rgName) except Exception as e: fail_usage("Failed: %s" % e) @@ -72,7 +72,7 @@ powerState = "unknown" try: - vmStatus = compute_client.virtual_machines.get(rgName, vmName, expand="instanceView") + vmStatus = azure_fence.get_vm_resource(compute_client, rgName, vmName) except Exception as e: fail_usage("Failed: %s" % e) @@ -115,23 +115,10 @@ if (options["--action"]=="off"): logging.info("Poweroff " + vmName + " in resource group " + rgName) - try: - # try new API version first - compute_client.virtual_machines.begin_power_off(rgName, vmName, skip_shutdown=True) - except AttributeError: - # use older API verson if it fails - logging.debug("Poweroff " + vmName + " did not work via 'virtual_machines.begin_power_off. Trying virtual_machines.power_off'.") - compute_client.virtual_machines.power_off(rgName, vmName, skip_shutdown=True) + azure_fence.do_vm_power_off(compute_client, rgName, vmName, True) elif (options["--action"]=="on"): logging.info("Starting " + vmName + " in resource group " + rgName) - try: - # try new API version first - compute_client.virtual_machines.begin_start(rgName, vmName) - except AttributeError: - # use older API verson if it fails - logging.debug("Starting " + vmName + " did not work via 'virtual_machines.begin_start. Trying virtual_machines.start'.") - compute_client.virtual_machines.start(rgName, vmName) - + azure_fence.do_vm_start(compute_client, rgName, vmName) def define_new_opts(): all_opt["resourceGroup"] = { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.16.0+git.1735891853.256a570a/lib/azure_fence.py.py new/fence-agents-4.16.0+git.1738070632.02141bfa/lib/azure_fence.py.py --- old/fence-agents-4.16.0+git.1735891853.256a570a/lib/azure_fence.py.py 2025-01-03 09:10:53.000000000 +0100 +++ new/fence-agents-4.16.0+git.1738070632.02141bfa/lib/azure_fence.py.py 2025-01-28 14:23:52.000000000 +0100 @@ -14,8 +14,9 @@ IP_TYPE_DYNAMIC = "Dynamic" MAX_RETRY = 10 RETRY_WAIT = 5 -COMPUTE_CLIENT_API_VERSION = "2021-11-01" NETWORK_MGMT_CLIENT_API_VERSION = "2021-05-01" +AZURE_RHEL8_COMPUTE_VERSION = "27.2.0" +AZURE_COMPUTE_VERSION_5 = "5.0.0" class AzureSubResource: Type = None @@ -88,6 +89,59 @@ return resource +def azure_dep_versions(v): + return tuple(map(int, (v.split(".")))) + +# Do azure API call to list all virtual machines in a resource group +def get_vm_list(compute_client,rgName): + return compute_client.virtual_machines.list(rgName) + +# Do azue API call to shutdown a virtual machine +def do_vm_power_off(compute_client,rgName,vmName, skipShutdown): + try: + # Version is not available in azure-mgmt-compute version 14.0.0 until 27.2.0 + from azure.mgmt.compute import __version__ + except ImportError: + __version__ = "0.0.0" + + # use different implementation call based on used version + if (azure_dep_versions(__version__) == azure_dep_versions(AZURE_COMPUTE_VERSION_5)): + logging.debug("{do_vm_power_off} azure.mgtm.compute version is to old to use 'begin_power_off' use 'power_off' function") + compute_client.virtual_machines.power_off(rgName, vmName, skip_shutdown=skipShutdown) + return + + compute_client.virtual_machines.begin_power_off(rgName, vmName, skip_shutdown=skipShutdown) + +# Do azure API call to start a virtual machine +def do_vm_start(compute_client,rgName,vmName): + try: + # Version is not available in azure-mgmt-compute version 14.0.0 until 27.2.0 + from azure.mgmt.compute import __version__ + except ImportError: + __version__ = "0.0.0" + + # use different implementation call based on used version + if (azure_dep_versions(__version__) == azure_dep_versions(AZURE_COMPUTE_VERSION_5)): + logging.debug("{do_vm_start} azure.mgtm.compute version is to old to use 'begin_start' use 'start' function") + compute_client.virtual_machines.start(rgName, vmName) + return + + compute_client.virtual_machines.begin_start(rgName, vmName) + +def get_vm_resource(compute_client, rgName, vmName): + try: + # Version is not available in azure-mgmt-compute version 14.0.0 until 27.2.0 + from azure.mgmt.compute import __version__ + except ImportError: + __version__ = "0.0.0" + + # use different implementation call based on used version + if (azure_dep_versions(__version__) <= azure_dep_versions(AZURE_RHEL8_COMPUTE_VERSION)): + return compute_client.virtual_machines.get(rgName, vmName, "instanceView") + + return compute_client.virtual_machines.get(resource_group_name=rgName, vm_name=vmName,expand="instanceView") + + def get_fence_subnet_for_config(ipConfig, network_client): subnetResource = get_azure_resource(ipConfig.subnet.id) logging.debug("{get_fence_subnet_for_config} testing virtual network %s in resource group %s for a fence subnet" %(subnetResource.ResourceName, subnetResource.ResourceGroupName)) @@ -154,7 +208,7 @@ result = FENCE_STATE_ON try: - vm = compute_client.virtual_machines.get(rgName, vmName, "instanceView") + vm = get_vm_resource(compute_client, rgName, vmName) allNICOK = True for nicRef in vm.network_profile.network_interfaces: @@ -181,7 +235,7 @@ import msrestazure.azure_exceptions logging.info("{set_network_state} Setting state %s for %s in resource group %s" % (operation, vmName, rgName)) - vm = compute_client.virtual_machines.get(rgName, vmName, "instanceView") + vm = get_vm_resource(compute_client,rgName, vmName) operations = [] for nicRef in vm.network_profile.network_interfaces: @@ -270,10 +324,72 @@ return config +# Function to fetch endpoints from metadata endpoint for azure_stack +def get_cloud_from_arm_metadata_endpoint(arm_endpoint): + try: + import requests + session = requests.Session() + metadata_endpoint = arm_endpoint + "/metadata/endpoints?api-version=2015-01-01" + response = session.get(metadata_endpoint) + if response.status_code == 200: + metadata = response.json() + return { + "resource_manager": arm_endpoint, + "credential_scopes": [metadata.get("graphEndpoint") + "/.default"], + "authority_hosts": metadata['authentication'].get('loginEndpoint').replace("https://","") + } + else: + fail_usage("Failed to get cloud from metadata endpoint: %s - %s" % arm_endpoint, e) + except Exception as e: + fail_usage("Failed to get cloud from metadata endpoint: %s - %s" % arm_endpoint, e) + +def get_azure_arm_endpoints(cloudName, authority): + cloudEnvironment = { + "authority_hosts": authority + } + + if cloudName == "AZURE_CHINA_CLOUD": + cloudEnvironment["resource_manager"] = "https://management.chinacloudapi.cn/" + cloudEnvironment["credential_scopes"] = ["https://management.chinacloudapi.cn/.default"] + return cloudEnvironment + + if cloudName == "AZURE_US_GOV_CLOUD": + cloudEnvironment["resource_manager"] = "https://management.usgovcloudapi.net/" + cloudEnvironment["credential_scopes"] = ["https://management.core.usgovcloudapi.net/.default"] + return cloudEnvironment + + if cloudName == "AZURE_PUBLIC_CLOUD": + cloudEnvironment["resource_manager"] = "https://management.azure.com/" + cloudEnvironment["credential_scopes"] = ["https://management.azure.com/.default"] + return cloudEnvironment + + def get_azure_cloud_environment(config): - cloud_environment = None - if config.Cloud: + if (config.Cloud is None): + config.Cloud = "public" + + try: + from azure.identity import AzureAuthorityHosts + + azureCloudName = "AZURE_PUBLIC_CLOUD" + authorityHosts = AzureAuthorityHosts.AZURE_PUBLIC_CLOUD if (config.Cloud.lower() == "china"): + azureCloudName = "AZURE_CHINA_CLOUD" + authorityHosts = AzureAuthorityHosts.AZURE_CHINA + elif (config.Cloud.lower() == "usgov"): + azureCloudName = "AZURE_US_GOV_CLOUD" + authorityHosts = AzureAuthorityHosts.AZURE_GOVERNMENT + elif (config.Cloud.lower() == "stack"): + # use custom function to call the azuer stack metadata endpoint to get required configuration. + return get_cloud_from_arm_metadata_endpoint(config.MetadataEndpoint) + + return get_azure_arm_endpoints(azureCloudName, authorityHosts) + + except ImportError: + if (config.Cloud.lower() == "public"): + from msrestazure.azure_cloud import AZURE_PUBLIC_CLOUD + cloud_environment = AZURE_PUBLIC_CLOUD + elif (config.Cloud.lower() == "china"): from msrestazure.azure_cloud import AZURE_CHINA_CLOUD cloud_environment = AZURE_CHINA_CLOUD elif (config.Cloud.lower() == "germany"): @@ -286,61 +402,44 @@ from msrestazure.azure_cloud import get_cloud_from_metadata_endpoint cloud_environment = get_cloud_from_metadata_endpoint(config.MetadataEndpoint) - return cloud_environment + authority_hosts = cloud_environment.endpoints.active_directory.replace("http://","") + return { + "resource_manager": cloud_environment.endpoints.resource_manager, + "credential_scopes": [cloud_environment.endpoints.active_directory_resource_id + "/.default"], + "authority_hosts": authority_hosts, + "cloud_environment": cloud_environment, + } def get_azure_credentials(config): credentials = None cloud_environment = get_azure_cloud_environment(config) - if config.UseMSI and cloud_environment: + if config.UseMSI: try: from azure.identity import ManagedIdentityCredential - credentials = ManagedIdentityCredential(cloud_environment=cloud_environment) + credentials = ManagedIdentityCredential(identity_config={"resource_id": cloud_environment["resource_manager"]}) except ImportError: from msrestazure.azure_active_directory import MSIAuthentication - credentials = MSIAuthentication(cloud_environment=cloud_environment) - elif config.UseMSI: - try: - from azure.identity import ManagedIdentityCredential - credentials = ManagedIdentityCredential() - except ImportError: - from msrestazure.azure_active_directory import MSIAuthentication - credentials = MSIAuthentication() - elif cloud_environment: - try: - # try to use new libraries ClientSecretCredential (azure.identity, based on azure.core) - from azure.identity import ClientSecretCredential - credentials = ClientSecretCredential( - client_id = config.ApplicationId, - client_secret = config.ApplicationKey, - tenant_id = config.Tenantid, - cloud_environment=cloud_environment - ) - except ImportError: - # use old libraries ServicePrincipalCredentials (azure.common) if new one is not available - from azure.common.credentials import ServicePrincipalCredentials - credentials = ServicePrincipalCredentials( - client_id = config.ApplicationId, - secret = config.ApplicationKey, - tenant = config.Tenantid, - cloud_environment=cloud_environment - ) - else: - try: - # try to use new libraries ClientSecretCredential (azure.identity, based on azure.core) - from azure.identity import ClientSecretCredential - credentials = ClientSecretCredential( - client_id = config.ApplicationId, - client_secret = config.ApplicationKey, - tenant_id = config.Tenantid - ) - except ImportError: - # use old libraries ServicePrincipalCredentials (azure.common) if new one is not available - from azure.common.credentials import ServicePrincipalCredentials - credentials = ServicePrincipalCredentials( - client_id = config.ApplicationId, - secret = config.ApplicationKey, - tenant = config.Tenantid - ) + credentials = MSIAuthentication(cloud_environment=cloud_environment["cloud_environment"]) + return + + try: + # try to use new libraries ClientSecretCredential (azure.identity, based on azure.core) + from azure.identity import ClientSecretCredential + credentials = ClientSecretCredential( + client_id = config.ApplicationId, + client_secret = config.ApplicationKey, + tenant_id = config.Tenantid, + authority=cloud_environment["authority_hosts"] + ) + except ImportError: + # use old libraries ServicePrincipalCredentials (azure.common) if new one is not available + from azure.common.credentials import ServicePrincipalCredentials + credentials = ServicePrincipalCredentials( + client_id = config.ApplicationId, + secret = config.ApplicationKey, + tenant = config.Tenantid, + cloud_environment=cloud_environment["cloud_environment"] + ) return credentials @@ -350,39 +449,40 @@ cloud_environment = get_azure_cloud_environment(config) credentials = get_azure_credentials(config) - if cloud_environment: - if (config.Cloud.lower() == "stack") and not config.MetadataEndpoint: - fail_usage("metadata-endpoint not specified") + # Try to read the default used api version from the installed package. + try: + compute_api_version = ComputeManagementClient.LATEST_PROFILE.get_profile_dict()["azure.mgmt.compute.ComputeManagementClient"]["virtual_machines"] + except Exception as e: + compute_api_version = ComputeManagementClient.DEFAULT_API_VERSION + logging.debug("{get_azure_compute_client} Failed to get the latest profile: %s using the default api version %s" % (e, compute_api_version)) - try: - from azure.profiles import KnownProfiles - if (config.Cloud.lower() == "stack"): - client_profile = KnownProfiles.v2020_09_01_hybrid - credential_scope = cloud_environment.endpoints.active_directory_resource_id + "/.default" - else: - client_profile = KnownProfiles.default - credential_scope = cloud_environment.endpoints.resource_manager + "/.default" - compute_client = ComputeManagementClient( - credentials, - config.SubscriptionId, - base_url=cloud_environment.endpoints.resource_manager, - profile=client_profile, - credential_scopes=[credential_scope], - api_version=COMPUTE_CLIENT_API_VERSION - ) - except TypeError: - compute_client = ComputeManagementClient( - credentials, - config.SubscriptionId, - base_url=cloud_environment.endpoints.resource_manager, - api_version=COMPUTE_CLIENT_API_VERSION - ) - else: + logging.debug("{get_azure_compute_client} use virtual_machine api version: %s" %(compute_api_version)) + + if (config.Cloud.lower() == "stack") and not config.MetadataEndpoint: + fail_usage("metadata-endpoint not specified") + + try: + from azure.profiles import KnownProfiles + if (config.Cloud.lower() == "stack"): + client_profile = KnownProfiles.v2020_09_01_hybrid + else: + client_profile = KnownProfiles.default compute_client = ComputeManagementClient( credentials, config.SubscriptionId, - api_version=COMPUTE_CLIENT_API_VERSION + base_url=cloud_environment["resource_manager"], + profile=client_profile, + credential_scopes=cloud_environment["credential_scopes"], + api_version=compute_api_version ) + except TypeError: + compute_client = ComputeManagementClient( + credentials, + config.SubscriptionId, + base_url=cloud_environment["resource_manager"], + api_version=compute_api_version + ) + return compute_client def get_azure_network_client(config): @@ -391,37 +491,31 @@ cloud_environment = get_azure_cloud_environment(config) credentials = get_azure_credentials(config) - if cloud_environment: - if (config.Cloud.lower() == "stack") and not config.MetadataEndpoint: - fail_usage("metadata-endpoint not specified") + if (config.Cloud.lower() == "stack") and not config.MetadataEndpoint: + fail_usage("metadata-endpoint not specified") - try: - from azure.profiles import KnownProfiles - if (config.Cloud.lower() == "stack"): - client_profile = KnownProfiles.v2020_09_01_hybrid - credential_scope = cloud_environment.endpoints.active_directory_resource_id + "/.default" - else: - client_profile = KnownProfiles.default - credential_scope = cloud_environment.endpoints.resource_manager + "/.default" - network_client = NetworkManagementClient( - credentials, - config.SubscriptionId, - base_url=cloud_environment.endpoints.resource_manager, - profile=client_profile, - credential_scopes=[credential_scope], - api_version=NETWORK_MGMT_CLIENT_API_VERSION - ) - except TypeError: - network_client = NetworkManagementClient( - credentials, - config.SubscriptionId, - base_url=cloud_environment.endpoints.resource_manager, - api_version=NETWORK_MGMT_CLIENT_API_VERSION - ) + + from azure.profiles import KnownProfiles + + if (config.Cloud.lower() == "stack"): + client_profile = KnownProfiles.v2020_09_01_hybrid else: + client_profile = KnownProfiles.default + + try: + network_client = NetworkManagementClient( + credentials, + config.SubscriptionId, + base_url=cloud_environment["resource_manager"], + profile=client_profile, + credential_scopes=cloud_environment["credential_scopes"], + api_version=NETWORK_MGMT_CLIENT_API_VERSION + ) + except TypeError: network_client = NetworkManagementClient( credentials, config.SubscriptionId, + base_url=cloud_environment["resource_manager"], api_version=NETWORK_MGMT_CLIENT_API_VERSION ) return network_client