382 lines
16 KiB
Diff
382 lines
16 KiB
Diff
--- a/lib/azure_fence.py.py 2025-01-28 11:38:59.877243912 +0100
|
|
+++ b/lib/azure_fence.py.py 2025-01-28 09:54:15.746703761 +0100
|
|
@@ -14,6 +14,9 @@
|
|
IP_TYPE_DYNAMIC = "Dynamic"
|
|
MAX_RETRY = 10
|
|
RETRY_WAIT = 5
|
|
+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
|
|
@@ -49,7 +52,7 @@
|
|
return None
|
|
|
|
def get_azure_resource(id):
|
|
- match = re.match('(/subscriptions/([^/]*)/resourceGroups/([^/]*))(/providers/([^/]*/[^/]*)/([^/]*))?((/([^/]*)/([^/]*))*)', id)
|
|
+ match = re.match(r'(/subscriptions/([^/]*)/resourceGroups/([^/]*))(/providers/([^/]*/[^/]*)/([^/]*))?((/([^/]*)/([^/]*))*)', id)
|
|
if not match:
|
|
fail_usage("{get_azure_resource} cannot parse resource id %s" % id)
|
|
|
|
@@ -86,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))
|
|
@@ -152,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:
|
|
@@ -179,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:
|
|
@@ -268,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"):
|
|
@@ -284,31 +402,43 @@
|
|
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:
|
|
- from msrestazure.azure_active_directory import MSIAuthentication
|
|
- credentials = MSIAuthentication(cloud_environment=cloud_environment)
|
|
- elif config.UseMSI:
|
|
- from msrestazure.azure_active_directory import MSIAuthentication
|
|
- credentials = MSIAuthentication()
|
|
- elif cloud_environment:
|
|
- from azure.common.credentials import ServicePrincipalCredentials
|
|
- credentials = ServicePrincipalCredentials(
|
|
+ if config.UseMSI:
|
|
+ try:
|
|
+ from azure.identity import ManagedIdentityCredential
|
|
+ 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["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,
|
|
- secret = config.ApplicationKey,
|
|
- tenant = config.Tenantid,
|
|
- cloud_environment=cloud_environment
|
|
+ client_secret = config.ApplicationKey,
|
|
+ tenant_id = config.Tenantid,
|
|
+ authority=cloud_environment["authority_hosts"]
|
|
)
|
|
- else:
|
|
+ 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
|
|
+ tenant = config.Tenantid,
|
|
+ cloud_environment=cloud_environment["cloud_environment"]
|
|
)
|
|
|
|
return credentials
|
|
@@ -317,40 +447,75 @@
|
|
from azure.mgmt.compute import ComputeManagementClient
|
|
|
|
cloud_environment = get_azure_cloud_environment(config)
|
|
- if cloud_environment and config.Cloud.lower() == "stack" and not config.MetadataEndpoint:
|
|
- fail_usage("metadata-endpoint not specified")
|
|
credentials = get_azure_credentials(config)
|
|
|
|
- if cloud_environment:
|
|
+ # 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))
|
|
+
|
|
+ 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,
|
|
- base_url=cloud_environment.endpoints.resource_manager
|
|
+ base_url=cloud_environment["resource_manager"],
|
|
+ profile=client_profile,
|
|
+ credential_scopes=cloud_environment["credential_scopes"],
|
|
+ api_version=compute_api_version
|
|
)
|
|
- else:
|
|
+ except TypeError:
|
|
compute_client = ComputeManagementClient(
|
|
credentials,
|
|
- config.SubscriptionId
|
|
+ config.SubscriptionId,
|
|
+ base_url=cloud_environment["resource_manager"],
|
|
+ api_version=compute_api_version
|
|
)
|
|
+
|
|
return compute_client
|
|
|
|
def get_azure_network_client(config):
|
|
from azure.mgmt.network import NetworkManagementClient
|
|
|
|
cloud_environment = get_azure_cloud_environment(config)
|
|
- if cloud_environment and config.Cloud.lower() == "stack" and not config.MetadataEndpoint:
|
|
- fail_usage("metadata-endpoint not specified")
|
|
credentials = get_azure_credentials(config)
|
|
|
|
- if cloud_environment:
|
|
+ if (config.Cloud.lower() == "stack") and not config.MetadataEndpoint:
|
|
+ fail_usage("metadata-endpoint not specified")
|
|
+
|
|
+
|
|
+ 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.endpoints.resource_manager
|
|
+ base_url=cloud_environment["resource_manager"],
|
|
+ profile=client_profile,
|
|
+ credential_scopes=cloud_environment["credential_scopes"],
|
|
+ api_version=NETWORK_MGMT_CLIENT_API_VERSION
|
|
)
|
|
- else:
|
|
+ except TypeError:
|
|
network_client = NetworkManagementClient(
|
|
credentials,
|
|
- config.SubscriptionId
|
|
+ config.SubscriptionId,
|
|
+ base_url=cloud_environment["resource_manager"],
|
|
+ api_version=NETWORK_MGMT_CLIENT_API_VERSION
|
|
)
|
|
return network_client
|
|
--- a/agents/azure_arm/fence_azure_arm.py 2025-01-28 11:38:59.880243982 +0100
|
|
+++ b/agents/azure_arm/fence_azure_arm.py 2025-01-28 11:43:16.290189381 +0100
|
|
@@ -7,7 +7,6 @@
|
|
sys.path.append("@FENCEAGENTSLIBDIR@")
|
|
from fencing import *
|
|
from fencing import fail_usage, run_command, run_delay
|
|
-
|
|
sys.path.insert(0, '/usr/lib/fence-agents/bundled/azure')
|
|
import azure_fence
|
|
|
|
@@ -17,7 +16,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)
|
|
@@ -33,7 +32,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)
|
|
|
|
@@ -74,7 +73,7 @@
|
|
|
|
powerState = "unknown"
|
|
try:
|
|
- vmStatus = compute_client.virtual_machines.get(rgName, vmName, "instanceView")
|
|
+ vmStatus = azure_fence.get_vm_resource(compute_client, rgName, vmName)
|
|
except Exception as e:
|
|
fail_usage("Failed: %s" % e)
|
|
|
|
@@ -117,11 +116,10 @@
|
|
|
|
if (options["--action"]=="off"):
|
|
logging.info("Poweroff " + vmName + " in resource group " + rgName)
|
|
- 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)
|
|
- compute_client.virtual_machines.start(rgName, vmName)
|
|
-
|
|
+ azure_fence.do_vm_start(compute_client, rgName, vmName)
|
|
|
|
def define_new_opts():
|
|
all_opt["resourceGroup"] = {
|
|
@@ -241,7 +239,7 @@
|
|
except ImportError:
|
|
fail_usage("Azure Resource Manager Python SDK not found or not accessible")
|
|
except Exception as e:
|
|
- fail_usage("Failed: %s" % re.sub("^, ", "", str(e)))
|
|
+ fail_usage("Failed: %s" % re.sub(r"^, ", r"", str(e)))
|
|
|
|
if "--network-fencing" in options:
|
|
# use off-action to quickly return off once network is fenced instead of
|