diff --git a/SOURCES/bz1565670-fence_azure_arm-network-fencing.patch b/SOURCES/bz1565670-fence_azure_arm-network-fencing.patch new file mode 100644 index 0000000..94dce46 --- /dev/null +++ b/SOURCES/bz1565670-fence_azure_arm-network-fencing.patch @@ -0,0 +1,654 @@ +diff -uNr a/fence/agents/azure_arm/fence_azure_arm.py b/fence/agents/azure_arm/fence_azure_arm.py +--- a/fence/agents/azure_arm/fence_azure_arm.py 2018-03-09 16:14:51.357065582 +0100 ++++ b/fence/agents/azure_arm/fence_azure_arm.py 2018-03-13 13:10:56.786200236 +0100 +@@ -3,19 +3,23 @@ + import sys, re, pexpect + import logging + import atexit ++import xml.etree.ElementTree as ET + sys.path.append("/usr/share/fence") + from fencing import * +-from fencing import fail, fail_usage, EC_TIMED_OUT, run_delay ++from fencing import fail_usage, run_command, run_delay ++import azure_fence + + #BEGIN_VERSION_GENERATION +-RELEASE_VERSION="4.0.25.34-695e-dirty" +-BUILD_DATE="(built Wed Jun 28 08:13:44 UTC 2017)" +-REDHAT_COPYRIGHT="Copyright (C) Red Hat, Inc. 2004-2010 All rights reserved." ++RELEASE_VERSION="Fence agent for Azure Resource Manager" ++REDHAT_COPYRIGHT="" ++BUILD_DATE="" + #END_VERSION_GENERATION + +-def get_nodes_list(compute_client, options): ++def get_nodes_list(clients, options): + result = {} +- if compute_client: ++ ++ if clients: ++ compute_client = clients[0] + rgName = options["--resourceGroup"] + vms = compute_client.virtual_machines.list(rgName) + try: +@@ -26,41 +30,100 @@ + + return result + +-def get_power_status(compute_client, options): ++def check_unfence(clients, options): ++ if clients: ++ compute_client = clients[0] ++ network_client = clients[1] ++ rgName = options["--resourceGroup"] ++ ++ try: ++ vms = compute_client.virtual_machines.list(rgName) ++ except Exception as e: ++ fail_usage("Failed: %s" % e) ++ ++ for vm in vms: ++ vmName = vm.name ++ if azure_fence.get_network_state(compute_client, network_client, rgName, vmName) == "off": ++ logging.info("Found fenced node " + vmName) ++ # dont return "off" based on network-fencing status ++ options.pop("--network-fencing", None) ++ options["--plug"] = vmName ++ if get_power_status(clients, options) == "off": ++ logging.info("Unfencing " + vmName) ++ options["--network-fencing"] = "" ++ options["--action"] = "on" ++ set_power_status(clients, options) ++ options["--action"] = "monitor" ++ ++def get_power_status(clients, options): ++ vmstate = { "running": "on", ++ "deallocated": "off", ++ "stopped": "off" } + logging.info("getting power status for VM " + options["--plug"]) + +- if compute_client: ++ if clients: ++ compute_client = clients[0] + rgName = options["--resourceGroup"] + vmName = options["--plug"] + ++ if "--network-fencing" in options: ++ network_client = clients[1] ++ netState = azure_fence.get_network_state(compute_client, network_client, rgName, vmName) ++ logging.info("Found network state of VM: " + netState) ++ ++ # return off quickly once network is fenced instead of waiting for vm state to change ++ if options["--action"] == "off" and netState == "off": ++ logging.info("Network fenced for " + vmName) ++ return netState ++ + powerState = "unknown" + try: + vmStatus = compute_client.virtual_machines.get(rgName, vmName, "instanceView") + except Exception as e: + fail_usage("Failed: %s" % e) ++ + for status in vmStatus.instance_view.statuses: + if status.code.startswith("PowerState"): +- powerState = status.code ++ powerState = status.code.split("/")[1] + break + +- logging.info("Found power state of VM: " + powerState) +- if powerState == "PowerState/running": ++ vmState = vmstate.get(powerState, "unknown") ++ logging.info("Found power state of VM: %s (%s)" % (vmState, powerState)) ++ ++ if "--network-fencing" in options and netState == "off": ++ return "off" ++ ++ if options["--action"] != "on" and vmState != "off": + return "on" + +- return "off" ++ if vmState == "on": ++ return "on" + +-def set_power_status(compute_client, options): ++ return "off" ++ ++def set_power_status(clients, options): + logging.info("setting power status for VM " + options["--plug"] + " to " + options["--action"]) + +- if compute_client: ++ if clients: ++ compute_client = clients[0] + rgName = options["--resourceGroup"] + vmName = options["--plug"] + ++ if "--network-fencing" in options: ++ network_client = clients[1] ++ ++ if (options["--action"]=="off"): ++ logging.info("Fencing network for " + vmName) ++ azure_fence.set_network_state(compute_client, network_client, rgName, vmName, "block") ++ elif (options["--action"]=="on"): ++ logging.info("Unfencing network for " + vmName) ++ azure_fence.set_network_state(compute_client, network_client, rgName, vmName, "unblock") ++ + if (options["--action"]=="off"): +- logging.info("Deallocating " + vmName + "in resource group " + rgName) ++ logging.info("Deallocating " + vmName + " in resource group " + rgName) + compute_client.virtual_machines.deallocate(rgName, vmName) + elif (options["--action"]=="on"): +- logging.info("Starting " + vmName + "in resource group " + rgName) ++ logging.info("Starting " + vmName + " in resource group " + rgName) + compute_client.virtual_machines.start(rgName, vmName) + + +@@ -69,8 +132,8 @@ + "getopt" : ":", + "longopt" : "resourceGroup", + "help" : "--resourceGroup=[name] Name of the resource group", +- "shortdesc" : "Name of resource group.", +- "required" : "1", ++ "shortdesc" : "Name of resource group. Metadata service is used if the value is not provided.", ++ "required" : "0", + "order" : 2 + } + all_opt["tenantId"] = { +@@ -78,23 +141,56 @@ + "longopt" : "tenantId", + "help" : "--tenantId=[name] Id of the Azure Active Directory tenant", + "shortdesc" : "Id of Azure Active Directory tenant.", +- "required" : "1", ++ "required" : "0", + "order" : 3 + } + all_opt["subscriptionId"] = { + "getopt" : ":", + "longopt" : "subscriptionId", + "help" : "--subscriptionId=[name] Id of the Azure subscription", +- "shortdesc" : "Id of the Azure subscription.", +- "required" : "1", ++ "shortdesc" : "Id of the Azure subscription. Metadata service is used if the value is not provided.", ++ "required" : "0", + "order" : 4 + } ++ all_opt["network-fencing"] = { ++ "getopt" : "", ++ "longopt" : "network-fencing", ++ "help" : "--network-fencing Use network fencing. See NOTE-section of\n\ ++ metadata for required Subnet/Network Security\n\ ++ Group configuration.", ++ "shortdesc" : "Use network fencing. See NOTE-section for configuration.", ++ "required" : "0", ++ "order" : 5 ++ } ++ all_opt["msi"] = { ++ "getopt" : "", ++ "longopt" : "msi", ++ "help" : "--msi Use Managed Service Identity instead of\n\ ++ username and password. If specified,\n\ ++ parameters tenantId, login and passwd are not\n\ ++ allowed.", ++ "shortdesc" : "Determines if Managed Service Identity should be used.", ++ "required" : "0", ++ "order" : 6 ++ } ++ all_opt["cloud"] = { ++ "getopt" : ":", ++ "longopt" : "cloud", ++ "help" : "--cloud=[name] Name of the cloud you want to use. Supported\n\ ++ values are china, germany or usgov. Do not use\n\ ++ this parameter if you want to use public\n\ ++ Azure.", ++ "shortdesc" : "Name of the cloud you want to use.", ++ "required" : "0", ++ "order" : 7 ++ } + + # Main agent method + def main(): + compute_client = None ++ network_client = None + +- device_opt = ["resourceGroup", "login", "passwd", "tenantId", "subscriptionId","port"] ++ device_opt = ["login", "passwd", "port", "resourceGroup", "tenantId", "subscriptionId", "network-fencing", "msi", "cloud"] + + atexit.register(atexit_handler) + +@@ -113,36 +209,44 @@ + \n.P\n\ + For instructions to setup credentials see: https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal\ + \n.P\n\ +-Username and password are application ID and authentication key from \"App registrations\"." ++Username and password are application ID and authentication key from \"App registrations\".\ ++\n.P\n\ ++NOTE: NETWORK FENCING\n.br\n\ ++Network fencing requires an additional Subnet named \"fence-subnet\" for the Virtual Network using a Network Security Group with the following rules:\n.br\n\ +++-----------+-----+-------------------------+------+------+-----+-----+--------+\n.br\n\ ++| DIRECTION | PRI | NAME | PORT | PROT | SRC | DST | ACTION |\n.br\n\ +++-----------+-----+-------------------------+------+------+-----+-----+--------+\n.br\n\ ++| Inbound | 100 | FENCE_DENY_ALL_INBOUND | Any | Any | Any | Any | Deny |\n.br\n\ ++| Outbound | 100 | FENCE_DENY_ALL_OUTBOUND | Any | Any | Any | Any | Deny |\n.br\n\ +++-----------+-----+-------------------------+------+------+-----+-----+--------+\ ++\n.P\n\ ++When using network fencing the reboot-action will cause a quick-return once the network has been fenced (instead of waiting for the off-action to succeed). It will check the status during the monitor-action, and request power-on when the shutdown operation is complete." + docs["vendorurl"] = "http://www.microsoft.com" + show_docs(options, docs) + + run_delay(options) + + try: +- from azure.common.credentials import ServicePrincipalCredentials +- from azure.mgmt.compute import ComputeManagementClient +- +- tenantid = options["--tenantId"] +- servicePrincipal = options["--username"] +- spPassword = options["--password"] +- subscriptionId = options["--subscriptionId"] +- credentials = ServicePrincipalCredentials( +- client_id = servicePrincipal, +- secret = spPassword, +- tenant = tenantid +- ) +- compute_client = ComputeManagementClient( +- credentials, +- subscriptionId +- ) ++ config = azure_fence.get_azure_config(options) ++ compute_client = azure_fence.get_azure_compute_client(config) ++ if "--network-fencing" in options: ++ network_client = azure_fence.get_azure_network_client(config) + 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))) + ++ if "--network-fencing" in options: ++ # use off-action to quickly return off once network is fenced instead of ++ # waiting for vm state to change ++ if options["--action"] == "reboot": ++ options["--action"] = "off" ++ # check for devices to unfence in monitor-action ++ elif options["--action"] == "monitor": ++ check_unfence([compute_client, network_client], options) ++ + # Operate the fencing device +- result = fence_action(compute_client, options, set_power_status, get_power_status, get_nodes_list) ++ result = fence_action([compute_client, network_client], options, set_power_status, get_power_status, get_nodes_list) + sys.exit(result) + + if __name__ == "__main__": +diff -uNr a/fence/agents/lib/azure_fence.py.py b/fence/agents/lib/azure_fence.py.py +--- a/fence/agents/lib/azure_fence.py.py 1970-01-01 01:00:00.000000000 +0100 ++++ b/fence/agents/lib/azure_fence.py.py 2018-03-13 13:08:52.818391349 +0100 +@@ -0,0 +1,353 @@ ++import logging, re, time ++from fencing import fail_usage ++ ++FENCE_SUBNET_NAME = "fence-subnet" ++FENCE_INBOUND_RULE_NAME = "FENCE_DENY_ALL_INBOUND" ++FENCE_INBOUND_RULE_DIRECTION = "Inbound" ++FENCE_OUTBOUND_RULE_NAME = "FENCE_DENY_ALL_OUTBOUND" ++FENCE_OUTBOUND_RULE_DIRECTION = "Outbound" ++FENCE_STATE_OFF = "off" ++FENCE_STATE_ON = "on" ++FENCE_TAG_SUBNET_ID = "FENCE_TAG_SUBNET_ID" ++FENCE_TAG_IP_TYPE = "FENCE_TAG_IP_TYPE" ++FENCE_TAG_IP = "FENCE_TAG_IP" ++IP_TYPE_DYNAMIC = "Dynamic" ++MAX_RETRY = 10 ++RETRY_WAIT = 5 ++ ++#BEGIN_VERSION_GENERATION ++RELEASE_VERSION = "New fence lib agent - test release on steroids" ++REDHAT_COPYRIGHT = "" ++BUILD_DATE = "March, 2008" ++#END_VERSION_GENERATION ++ ++class AzureSubResource: ++ Type = None ++ Name = None ++ ++class AzureResource: ++ Id = None ++ SubscriptionId = None ++ ResourceGroupName = None ++ ResourceName = None ++ SubResources = [] ++ ++class AzureConfiguration: ++ RGName = None ++ VMName = None ++ SubscriptionId = None ++ Cloud = None ++ UseMSI = None ++ Tenantid = None ++ ApplicationId = None ++ ApplicationKey = None ++ Verbose = None ++ ++def get_from_metadata(parameter): ++ import requests ++ try: ++ r = requests.get('http://169.254.169.254/metadata/instance?api-version=2017-08-01', headers = {"Metadata":"true"}) ++ return str(r.json()["compute"][parameter]) ++ except: ++ logging.warning("Not able to use metadata service. Am I running in Azure?") ++ ++ return None ++ ++def get_azure_resource(id): ++ match = re.match('(/subscriptions/([^/]*)/resourceGroups/([^/]*))(/providers/([^/]*/[^/]*)/([^/]*))?((/([^/]*)/([^/]*))*)', id) ++ if not match: ++ fail_usage("{get_azure_resource} cannot parse resource id %s" % id) ++ ++ logging.debug("{get_azure_resource} found %s matches for %s" % (len(match.groups()), id)) ++ iGroup = 0 ++ while iGroup < len(match.groups()): ++ logging.debug("{get_azure_resource} group %s: %s" %(iGroup, match.group(iGroup))) ++ iGroup += 1 ++ ++ resource = AzureResource() ++ resource.Id = id ++ resource.SubscriptionId = match.group(2) ++ resource.SubResources = [] ++ ++ if len(match.groups()) > 3: ++ resource.ResourceGroupName = match.group(3) ++ logging.debug("{get_azure_resource} resource group %s" % resource.ResourceGroupName) ++ ++ if len(match.groups()) > 6: ++ resource.ResourceName = match.group(6) ++ logging.debug("{get_azure_resource} resource name %s" % resource.ResourceName) ++ ++ if len(match.groups()) > 7 and match.group(7): ++ splits = match.group(7).split("/") ++ logging.debug("{get_azure_resource} splitting subtypes '%s' (%s)" % (match.group(7), len(splits))) ++ i = 1 # the string starts with / so the first split is empty ++ while i < len(splits) - 1: ++ logging.debug("{get_azure_resource} creating subresource with type %s and name %s" % (splits[i], splits[i+1])) ++ subRes = AzureSubResource() ++ subRes.Type = splits[i] ++ subRes.Name = splits[i+1] ++ resource.SubResources.append(subRes) ++ i += 2 ++ ++ return resource ++ ++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)) ++ vnet = network_client.virtual_networks.get(subnetResource.ResourceGroupName, subnetResource.ResourceName) ++ return get_subnet(vnet, FENCE_SUBNET_NAME) ++ ++def get_subnet(vnet, subnetName): ++ for avSubnet in vnet.subnets: ++ logging.debug("{get_subnet} searching subnet %s testing subnet %s" % (subnetName, avSubnet.name)) ++ if (avSubnet.name.lower() == subnetName.lower()): ++ logging.debug("{get_subnet} subnet found %s" % avSubnet) ++ return avSubnet ++ ++def test_fence_subnet(fenceSubnet, nic, network_client): ++ logging.info("{test_fence_subnet}") ++ testOk = True ++ if not fenceSubnet: ++ testOk = False ++ logging.info("{test_fence_subnet} No fence subnet found for virtual network of network interface %s" % nic.id) ++ else: ++ if not fenceSubnet.network_security_group: ++ testOk = False ++ logging.info("{test_fence_subnet} Fence subnet %s has not network security group" % fenceSubnet.id) ++ else: ++ nsgResource = get_azure_resource(fenceSubnet.network_security_group.id) ++ logging.info("{test_fence_subnet} Getting network security group %s in resource group %s" % (nsgResource.ResourceName, nsgResource.ResourceGroupName)) ++ nsg = network_client.network_security_groups.get(nsgResource.ResourceGroupName, nsgResource.ResourceName) ++ inboundRule = get_inbound_rule_for_nsg(nsg) ++ outboundRule = get_outbound_rule_for_nsg(nsg) ++ if not outboundRule: ++ testOk = False ++ logging.info("{test_fence_subnet} Network Securiy Group %s of fence subnet %s has no outbound security rule that blocks all traffic" % (nsgResource.ResourceName, fenceSubnet.id)) ++ elif not inboundRule: ++ testOk = False ++ logging.info("{test_fence_subnet} Network Securiy Group %s of fence subnet %s has no inbound security rule that blocks all traffic" % (nsgResource.ResourceName, fenceSubnet.id)) ++ ++ return testOk ++ ++def get_inbound_rule_for_nsg(nsg): ++ return get_rule_for_nsg(nsg, FENCE_INBOUND_RULE_NAME, FENCE_INBOUND_RULE_DIRECTION) ++ ++def get_outbound_rule_for_nsg(nsg): ++ return get_rule_for_nsg(nsg, FENCE_OUTBOUND_RULE_NAME, FENCE_OUTBOUND_RULE_DIRECTION) ++ ++def get_rule_for_nsg(nsg, ruleName, direction): ++ logging.info("{get_rule_for_nsg} Looking for security rule %s with direction %s" % (ruleName, direction)) ++ if not nsg: ++ logging.info("{get_rule_for_nsg} Network security group not set") ++ return None ++ ++ for rule in nsg.security_rules: ++ logging.info("{get_rule_for_nsg} Testing a %s securiy rule %s" % (rule.direction, rule.name)) ++ if (rule.access == "Deny") and (rule.direction == direction) \ ++ and (rule.source_port_range == "*") and (rule.destination_port_range == "*") \ ++ and (rule.protocol == "*") and (rule.destination_address_prefix == "*") \ ++ and (rule.source_address_prefix == "*") and (rule.provisioning_state == "Succeeded") \ ++ and (rule.priority == 100) and (rule.name == ruleName): ++ logging.info("{get_rule_for_nsg} %s rule found" % direction) ++ return rule ++ ++ return None ++ ++def get_network_state(compute_client, network_client, rgName, vmName): ++ result = FENCE_STATE_ON ++ ++ try: ++ vm = compute_client.virtual_machines.get(rgName, vmName, "instanceView") ++ ++ allNICOK = True ++ for nicRef in vm.network_profile.network_interfaces: ++ nicresource = get_azure_resource(nicRef.id) ++ nic = network_client.network_interfaces.get(nicresource.ResourceGroupName, nicresource.ResourceName) ++ for ipConfig in nic.ip_configurations: ++ logging.info("{get_network_state} Testing ip configuration %s" % ipConfig.name) ++ fenceSubnet = get_fence_subnet_for_config(ipConfig, network_client) ++ testOk = test_fence_subnet(fenceSubnet, nic, network_client) ++ if not testOk: ++ allNICOK = False ++ elif fenceSubnet.id.lower() != ipConfig.subnet.id.lower(): ++ logging.info("{get_network_state} IP configuration %s is not in fence subnet (ip subnet: %s, fence subnet: %s)" % (ipConfig.name, ipConfig.subnet.id.lower(), fenceSubnet.id.lower())) ++ allNICOK = False ++ if allNICOK: ++ logging.info("{get_network_state} All IP configurations of all network interfaces are in the fence subnet. Declaring VM as off") ++ result = FENCE_STATE_OFF ++ except Exception as e: ++ fail_usage("{get_network_state} Failed: %s" % e) ++ ++ return result ++ ++def set_network_state(compute_client, network_client, rgName, vmName, operation): ++ 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") ++ ++ operations = [] ++ for nicRef in vm.network_profile.network_interfaces: ++ for attempt in range(0, MAX_RETRY): ++ try: ++ nicresource = get_azure_resource(nicRef.id) ++ nic = network_client.network_interfaces.get(nicresource.ResourceGroupName, nicresource.ResourceName) ++ ++ if not nic.tags and operation == "block": ++ nic.tags = {} ++ ++ logging.info("{set_network_state} Searching for tags required to unfence this virtual machine") ++ for ipConfig in nic.ip_configurations: ++ if operation == "block": ++ fenceSubnet = get_fence_subnet_for_config(ipConfig, network_client) ++ testOk = test_fence_subnet(fenceSubnet, nic, network_client) ++ if testOk: ++ logging.info("{set_network_state} Changing subnet of ip config of nic %s" % nic.id) ++ nic.tags[("%s_%s" % (FENCE_TAG_SUBNET_ID, ipConfig.name))] = ipConfig.subnet.id ++ nic.tags[("%s_%s" % (FENCE_TAG_IP_TYPE, ipConfig.name))] = ipConfig.private_ip_allocation_method ++ nic.tags[("%s_%s" % (FENCE_TAG_IP, ipConfig.name))] = ipConfig.private_ip_address ++ ipConfig.subnet = fenceSubnet ++ ipConfig.private_ip_allocation_method = IP_TYPE_DYNAMIC ++ else: ++ fail_usage("{set_network_state} Network interface id %s does not have a network security group." % nic.id) ++ elif operation == "unblock": ++ if not nic.tags: ++ fail_usage("{set_network_state} IP configuration %s is missing the required resource tags (empty)" % ipConfig.name) ++ ++ subnetId = nic.tags.pop("%s_%s" % (FENCE_TAG_SUBNET_ID, ipConfig.name)) ++ ipType = nic.tags.pop("%s_%s" % (FENCE_TAG_IP_TYPE, ipConfig.name)) ++ ipAddress = nic.tags.pop("%s_%s" % (FENCE_TAG_IP, ipConfig.name)) ++ ++ if (subnetId and ipType and (ipAddress or (ipType.lower() == IP_TYPE_DYNAMIC.lower()))): ++ logging.info("{set_network_state} tags found (subnetId: %s, ipType: %s, ipAddress: %s)" % (subnetId, ipType, ipAddress)) ++ ++ subnetResource = get_azure_resource(subnetId) ++ vnet = network_client.virtual_networks.get(subnetResource.ResourceGroupName, subnetResource.ResourceName) ++ logging.info("{set_network_state} looking for subnet %s" % len(subnetResource.SubResources)) ++ oldSubnet = get_subnet(vnet, subnetResource.SubResources[0].Name) ++ if not oldSubnet: ++ fail_usage("{set_network_state} subnet %s not found" % subnetId) ++ ++ ipConfig.subnet = oldSubnet ++ ipConfig.private_ip_allocation_method = ipType ++ if ipAddress: ++ ipConfig.private_ip_address = ipAddress ++ else: ++ fail_usage("{set_network_state} IP configuration %s is missing the required resource tags(subnetId: %s, ipType: %s, ipAddress: %s)" % (ipConfig.name, subnetId, ipType, ipAddress)) ++ ++ logging.info("{set_network_state} updating nic %s" % (nic.id)) ++ op = network_client.network_interfaces.create_or_update(nicresource.ResourceGroupName, nicresource.ResourceName, nic) ++ operations.append(op) ++ break ++ except msrestazure.azure_exceptions.CloudError as cex: ++ logging.error("{set_network_state} CloudError in attempt %s '%s'" % (attempt, cex)) ++ if cex.error and cex.error.error and cex.error.error.lower() == "PrivateIPAddressIsBeingCleanedUp": ++ logging.error("{set_network_state} PrivateIPAddressIsBeingCleanedUp") ++ time.sleep(RETRY_WAIT) ++ ++ except Exception as ex: ++ logging.error("{set_network_state} Exception of type %s: %s" % (type(ex).__name__, ex)) ++ break ++ ++def get_azure_config(options): ++ config = AzureConfiguration() ++ ++ config.RGName = options.get("--resourceGroup") ++ config.VMName = options.get("--plug") ++ config.SubscriptionId = options.get("--subscriptionId") ++ config.Cloud = options.get("--cloud") ++ config.UseMSI = "--msi" in options ++ config.Tenantid = options.get("--tenantId") ++ config.ApplicationId = options.get("--username") ++ config.ApplicationKey = options.get("--password") ++ config.Verbose = options.get("--verbose") ++ ++ if not config.RGName: ++ logging.info("resourceGroup not provided. Using metadata service") ++ config.RGName = get_from_metadata("resourceGroupName") ++ ++ if not config.SubscriptionId: ++ logging.info("subscriptionId not provided. Using metadata service") ++ config.SubscriptionId = get_from_metadata("subscriptionId") ++ ++ return config ++ ++def get_azure_cloud_environment(config): ++ cloud_environment = None ++ if config.Cloud: ++ if (config.Cloud.lower() == "china"): ++ from msrestazure.azure_cloud import AZURE_CHINA_CLOUD ++ cloud_environment = AZURE_CHINA_CLOUD ++ elif (config.Cloud.lower() == "germany"): ++ from msrestazure.azure_cloud import AZURE_GERMAN_CLOUD ++ cloud_environment = AZURE_GERMAN_CLOUD ++ elif (config.Cloud.lower() == "usgov"): ++ from msrestazure.azure_cloud import AZURE_US_GOV_CLOUD ++ cloud_environment = AZURE_US_GOV_CLOUD ++ ++ return 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( ++ client_id = config.ApplicationId, ++ secret = config.ApplicationKey, ++ tenant = config.Tenantid, ++ cloud_environment=cloud_environment ++ ) ++ else: ++ from azure.common.credentials import ServicePrincipalCredentials ++ credentials = ServicePrincipalCredentials( ++ client_id = config.ApplicationId, ++ secret = config.ApplicationKey, ++ tenant = config.Tenantid ++ ) ++ ++ return credentials ++ ++def get_azure_compute_client(config): ++ from azure.mgmt.compute import ComputeManagementClient ++ ++ cloud_environment = get_azure_cloud_environment(config) ++ credentials = get_azure_credentials(config) ++ ++ if cloud_environment: ++ compute_client = ComputeManagementClient( ++ credentials, ++ config.SubscriptionId, ++ base_url=cloud_environment.endpoints.resource_manager ++ ) ++ else: ++ compute_client = ComputeManagementClient( ++ credentials, ++ config.SubscriptionId ++ ) ++ return compute_client ++ ++def get_azure_network_client(config): ++ from azure.mgmt.network import NetworkManagementClient ++ ++ cloud_environment = get_azure_cloud_environment(config) ++ credentials = get_azure_credentials(config) ++ ++ if cloud_environment: ++ network_client = NetworkManagementClient( ++ credentials, ++ config.SubscriptionId, ++ base_url=cloud_environment.endpoints.resource_manager ++ ) ++ else: ++ network_client = NetworkManagementClient( ++ credentials, ++ config.SubscriptionId ++ ) ++ return network_client +diff -uNr a/fence/agents/lib/Makefile.am b/fence/agents/lib/Makefile.am +--- a/fence/agents/lib/Makefile.am 2014-08-06 09:35:08.000000000 +0200 ++++ b/fence/agents/lib/Makefile.am 2018-03-13 13:06:02.507027744 +0100 +@@ -1,12 +1,12 @@ + MAINTAINERCLEANFILES = Makefile.in + +-TARGET = fencing.py fencing_snmp.py ++TARGET = fencing.py fencing_snmp.py azure_fence.py + + if BUILD_XENAPILIB + TARGET += XenAPI.py + endif + +-SRC = fencing.py.py fencing_snmp.py.py XenAPI.py.py check_used_options.py ++SRC = fencing.py.py fencing_snmp.py.py XenAPI.py.py azure_fence.py.py check_used_options.py + + XSL = fence2man.xsl fence2rng.xsl + diff --git a/SOURCES/bz1565701-fence_compute-fence_evacuate-fix-parameters.patch b/SOURCES/bz1565701-fence_compute-fence_evacuate-fix-parameters.patch new file mode 100644 index 0000000..92efed8 --- /dev/null +++ b/SOURCES/bz1565701-fence_compute-fence_evacuate-fix-parameters.patch @@ -0,0 +1,80 @@ +diff -uNr a/fence/agents/compute/fence_compute.py b/fence/agents/compute/fence_compute.py +--- a/fence/agents/compute/fence_compute.py 2018-03-22 12:34:45.989618362 +0100 ++++ b/fence/agents/compute/fence_compute.py 2018-03-22 12:44:11.679816557 +0100 +@@ -353,7 +353,7 @@ + "default" : "", + "order": 1, + } +- all_opt["user-domain"] = { ++ all_opt["user_domain"] = { + "getopt" : "u:", + "longopt" : "user-domain", + "help" : "-u, --user-domain=[name] Keystone v3 User Domain", +@@ -362,7 +362,7 @@ + "default" : "Default", + "order": 2, + } +- all_opt["project-domain"] = { ++ all_opt["project_domain"] = { + "getopt" : "P:", + "longopt" : "project-domain", + "help" : "-d, --project-domain=[name] Keystone v3 Project Domain", +@@ -398,7 +398,7 @@ + "default" : "False", + "order": 2, + } +- all_opt["compute-domain"] = { ++ all_opt["domain"] = { + "getopt" : "d:", + "longopt" : "domain", + "help" : "-d, --domain=[string] DNS domain in which hosts live, useful when the cluster uses short names and nova uses FQDN", +@@ -451,7 +451,7 @@ + atexit.register(atexit_handler) + + device_opt = ["login", "passwd", "tenant_name", "auth_url", "fabric_fencing", +- "no_login", "no_password", "port", "compute-domain", "project-domain", "user-domain", ++ "no_login", "no_password", "port", "domain", "project_domain", "user_domain", + "no_shared_storage", "endpoint_type", "record_only", "instance_filtering", "insecure", "region_name"] + define_new_opts() + all_opt["shell_timeout"]["default"] = "180" +diff -uNr a/fence/agents/compute/fence_evacuate.py b/fence/agents/compute/fence_evacuate.py +--- a/fence/agents/compute/fence_evacuate.py 2018-03-22 12:34:45.990618353 +0100 ++++ b/fence/agents/compute/fence_evacuate.py 2018-03-22 12:44:21.157735516 +0100 +@@ -287,7 +287,7 @@ + "default" : "", + "order": 1, + } +- all_opt["user-domain"] = { ++ all_opt["user_domain"] = { + "getopt" : "u:", + "longopt" : "user-domain", + "help" : "-u, --user-domain=[name] Keystone v3 User Domain", +@@ -296,7 +296,7 @@ + "default" : "Default", + "order": 2, + } +- all_opt["project-domain"] = { ++ all_opt["project_domain"] = { + "getopt" : "P:", + "longopt" : "project-domain", + "help" : "-d, --project-domain=[name] Keystone v3 Project Domain", +@@ -332,7 +332,7 @@ + "default" : "False", + "order": 2, + } +- all_opt["compute-domain"] = { ++ all_opt["domain"] = { + "getopt" : "d:", + "longopt" : "domain", + "help" : "-d, --domain=[string] DNS domain in which hosts live, useful when the cluster uses short names and nova uses FQDN", +@@ -363,8 +363,8 @@ + atexit.register(atexit_handler) + + device_opt = ["login", "passwd", "tenant_name", "auth_url", +- "no_login", "no_password", "port", "compute-domain", "project-domain", +- "user-domain", "no_shared_storage", "endpoint_type", ++ "no_login", "no_password", "port", "domain", "project_domain", ++ "user_domain", "no_shared_storage", "endpoint_type", + "instance_filtering", "insecure", "region_name"] + define_new_opts() + all_opt["shell_timeout"]["default"] = "180" diff --git a/SPECS/fence-agents.spec b/SPECS/fence-agents.spec index df6ef54..e4f928b 100644 --- a/SPECS/fence-agents.spec +++ b/SPECS/fence-agents.spec @@ -16,7 +16,7 @@ Name: fence-agents Summary: Fence Agents for Red Hat Cluster Version: 4.0.11 -Release: 86%{?alphatag:.%{alphatag}}%{?dist} +Release: 86%{?alphatag:.%{alphatag}}%{?dist}.2 License: GPLv2+ and LGPLv2+ Group: System Environment/Base URL: https://github.com/ClusterLabs/fence-agents @@ -154,6 +154,8 @@ Patch129: bz1465436-fence_ipmilan-fix-default-method-inconsistency.patch Patch130: bz1533170-fence_compute-fence_evacuate-add-support-for-keystone-v3-authentication.patch Patch131: bz1519370-fence_ilo3-default-to-onoff.patch Patch132: bz1451776-2-fence_aws-bundled-python-botocore.patch +Patch133: bz1565670-fence_azure_arm-network-fencing.patch +Patch134: bz1565701-fence_compute-fence_evacuate-fix-parameters.patch %if 0%{?rhel} %global supportedagents amt_ws apc apc_snmp aws azure_arm bladecenter brocade cisco_mds cisco_ucs compute drac5 eaton_snmp emerson eps hpblade ibmblade ifmib ilo ilo_moonshot ilo_mp ilo_ssh intelmodular ipdu ipmilan mpath kdump rhevm rsa rsb sbd scsi vmware_rest vmware_soap wti @@ -317,6 +319,8 @@ BuildRequires: iputils %patch130 -p1 -b .bz1533170 %patch131 -p1 -b .bz1519370 %patch132 -p1 -b .bz1451776-2 +%patch133 -p1 -b .bz1565670 +%patch134 -p1 -b .bz1565701 %build ./autogen.sh @@ -464,6 +468,7 @@ The fence-agents-azure-arm package contains a fence agent for Azure instances. %defattr(-,root,root,-) %{_sbindir}/fence_azure_arm %{_mandir}/man8/fence_azure_arm.8* +%{_datadir}/fence/azure_fence.py %package bladecenter License: GPLv2+ and LGPLv2+ @@ -977,6 +982,16 @@ The fence-agents-zvm package contains a fence agent for z/VM hypervisors %endif %changelog +* Thu Apr 12 2018 Oyvind Albrigtsen - 4.0.11-86.2 +- fence_azure_arm: fix subscriptionId from metadata + Resolves: rhbz#1566632 + +* Wed Apr 11 2018 Oyvind Albrigtsen - 4.0.11-86.1 +- fence_azure_arm: add network-fencing + Resolves: rhbz#1565670 +- fence_compute/fence_evacuate: fix parameters + Resolves: rhbz#1565701 + * Wed Feb 7 2018 Oyvind Albrigtsen - 4.0.11-86 - fence-agents-all: remove fence-agents-aws and fence-agents-azure-arm dependencies