|
|
3fe01d |
diff -uNr a/fence/agents/azure_arm/fence_azure_arm.py b/fence/agents/azure_arm/fence_azure_arm.py
|
|
|
3fe01d |
--- a/fence/agents/azure_arm/fence_azure_arm.py 2018-03-09 16:14:51.357065582 +0100
|
|
|
3fe01d |
+++ b/fence/agents/azure_arm/fence_azure_arm.py 2018-03-13 13:10:56.786200236 +0100
|
|
|
3fe01d |
@@ -3,19 +3,23 @@
|
|
|
3fe01d |
import sys, re, pexpect
|
|
|
3fe01d |
import logging
|
|
|
3fe01d |
import atexit
|
|
|
3fe01d |
+import xml.etree.ElementTree as ET
|
|
|
3fe01d |
sys.path.append("/usr/share/fence")
|
|
|
3fe01d |
from fencing import *
|
|
|
3fe01d |
-from fencing import fail, fail_usage, EC_TIMED_OUT, run_delay
|
|
|
3fe01d |
+from fencing import fail_usage, run_command, run_delay
|
|
|
3fe01d |
+import azure_fence
|
|
|
3fe01d |
|
|
|
3fe01d |
#BEGIN_VERSION_GENERATION
|
|
|
3fe01d |
-RELEASE_VERSION="4.0.25.34-695e-dirty"
|
|
|
3fe01d |
-BUILD_DATE="(built Wed Jun 28 08:13:44 UTC 2017)"
|
|
|
3fe01d |
-REDHAT_COPYRIGHT="Copyright (C) Red Hat, Inc. 2004-2010 All rights reserved."
|
|
|
3fe01d |
+RELEASE_VERSION="Fence agent for Azure Resource Manager"
|
|
|
3fe01d |
+REDHAT_COPYRIGHT=""
|
|
|
3fe01d |
+BUILD_DATE=""
|
|
|
3fe01d |
#END_VERSION_GENERATION
|
|
|
3fe01d |
|
|
|
3fe01d |
-def get_nodes_list(compute_client, options):
|
|
|
3fe01d |
+def get_nodes_list(clients, options):
|
|
|
3fe01d |
result = {}
|
|
|
3fe01d |
- if compute_client:
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ if clients:
|
|
|
3fe01d |
+ compute_client = clients[0]
|
|
|
3fe01d |
rgName = options["--resourceGroup"]
|
|
|
3fe01d |
vms = compute_client.virtual_machines.list(rgName)
|
|
|
3fe01d |
try:
|
|
|
3fe01d |
@@ -26,41 +30,100 @@
|
|
|
3fe01d |
|
|
|
3fe01d |
return result
|
|
|
3fe01d |
|
|
|
3fe01d |
-def get_power_status(compute_client, options):
|
|
|
3fe01d |
+def check_unfence(clients, options):
|
|
|
3fe01d |
+ if clients:
|
|
|
3fe01d |
+ compute_client = clients[0]
|
|
|
3fe01d |
+ network_client = clients[1]
|
|
|
3fe01d |
+ rgName = options["--resourceGroup"]
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ try:
|
|
|
3fe01d |
+ vms = compute_client.virtual_machines.list(rgName)
|
|
|
3fe01d |
+ except Exception as e:
|
|
|
3fe01d |
+ fail_usage("Failed: %s" % e)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ for vm in vms:
|
|
|
3fe01d |
+ vmName = vm.name
|
|
|
3fe01d |
+ if azure_fence.get_network_state(compute_client, network_client, rgName, vmName) == "off":
|
|
|
3fe01d |
+ logging.info("Found fenced node " + vmName)
|
|
|
3fe01d |
+ # dont return "off" based on network-fencing status
|
|
|
3fe01d |
+ options.pop("--network-fencing", None)
|
|
|
3fe01d |
+ options["--plug"] = vmName
|
|
|
3fe01d |
+ if get_power_status(clients, options) == "off":
|
|
|
3fe01d |
+ logging.info("Unfencing " + vmName)
|
|
|
3fe01d |
+ options["--network-fencing"] = ""
|
|
|
3fe01d |
+ options["--action"] = "on"
|
|
|
3fe01d |
+ set_power_status(clients, options)
|
|
|
3fe01d |
+ options["--action"] = "monitor"
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def get_power_status(clients, options):
|
|
|
3fe01d |
+ vmstate = { "running": "on",
|
|
|
3fe01d |
+ "deallocated": "off",
|
|
|
3fe01d |
+ "stopped": "off" }
|
|
|
3fe01d |
logging.info("getting power status for VM " + options["--plug"])
|
|
|
3fe01d |
|
|
|
3fe01d |
- if compute_client:
|
|
|
3fe01d |
+ if clients:
|
|
|
3fe01d |
+ compute_client = clients[0]
|
|
|
3fe01d |
rgName = options["--resourceGroup"]
|
|
|
3fe01d |
vmName = options["--plug"]
|
|
|
3fe01d |
|
|
|
3fe01d |
+ if "--network-fencing" in options:
|
|
|
3fe01d |
+ network_client = clients[1]
|
|
|
3fe01d |
+ netState = azure_fence.get_network_state(compute_client, network_client, rgName, vmName)
|
|
|
3fe01d |
+ logging.info("Found network state of VM: " + netState)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ # return off quickly once network is fenced instead of waiting for vm state to change
|
|
|
3fe01d |
+ if options["--action"] == "off" and netState == "off":
|
|
|
3fe01d |
+ logging.info("Network fenced for " + vmName)
|
|
|
3fe01d |
+ return netState
|
|
|
3fe01d |
+
|
|
|
3fe01d |
powerState = "unknown"
|
|
|
3fe01d |
try:
|
|
|
3fe01d |
vmStatus = compute_client.virtual_machines.get(rgName, vmName, "instanceView")
|
|
|
3fe01d |
except Exception as e:
|
|
|
3fe01d |
fail_usage("Failed: %s" % e)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
for status in vmStatus.instance_view.statuses:
|
|
|
3fe01d |
if status.code.startswith("PowerState"):
|
|
|
3fe01d |
- powerState = status.code
|
|
|
3fe01d |
+ powerState = status.code.split("/")[1]
|
|
|
3fe01d |
break
|
|
|
3fe01d |
|
|
|
3fe01d |
- logging.info("Found power state of VM: " + powerState)
|
|
|
3fe01d |
- if powerState == "PowerState/running":
|
|
|
3fe01d |
+ vmState = vmstate.get(powerState, "unknown")
|
|
|
3fe01d |
+ logging.info("Found power state of VM: %s (%s)" % (vmState, powerState))
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ if "--network-fencing" in options and netState == "off":
|
|
|
3fe01d |
+ return "off"
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ if options["--action"] != "on" and vmState != "off":
|
|
|
3fe01d |
return "on"
|
|
|
3fe01d |
|
|
|
3fe01d |
- return "off"
|
|
|
3fe01d |
+ if vmState == "on":
|
|
|
3fe01d |
+ return "on"
|
|
|
3fe01d |
|
|
|
3fe01d |
-def set_power_status(compute_client, options):
|
|
|
3fe01d |
+ return "off"
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def set_power_status(clients, options):
|
|
|
3fe01d |
logging.info("setting power status for VM " + options["--plug"] + " to " + options["--action"])
|
|
|
3fe01d |
|
|
|
3fe01d |
- if compute_client:
|
|
|
3fe01d |
+ if clients:
|
|
|
3fe01d |
+ compute_client = clients[0]
|
|
|
3fe01d |
rgName = options["--resourceGroup"]
|
|
|
3fe01d |
vmName = options["--plug"]
|
|
|
3fe01d |
|
|
|
3fe01d |
+ if "--network-fencing" in options:
|
|
|
3fe01d |
+ network_client = clients[1]
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ if (options["--action"]=="off"):
|
|
|
3fe01d |
+ logging.info("Fencing network for " + vmName)
|
|
|
3fe01d |
+ azure_fence.set_network_state(compute_client, network_client, rgName, vmName, "block")
|
|
|
3fe01d |
+ elif (options["--action"]=="on"):
|
|
|
3fe01d |
+ logging.info("Unfencing network for " + vmName)
|
|
|
3fe01d |
+ azure_fence.set_network_state(compute_client, network_client, rgName, vmName, "unblock")
|
|
|
3fe01d |
+
|
|
|
3fe01d |
if (options["--action"]=="off"):
|
|
|
3fe01d |
- logging.info("Deallocating " + vmName + "in resource group " + rgName)
|
|
|
3fe01d |
+ logging.info("Deallocating " + vmName + " in resource group " + rgName)
|
|
|
3fe01d |
compute_client.virtual_machines.deallocate(rgName, vmName)
|
|
|
3fe01d |
elif (options["--action"]=="on"):
|
|
|
3fe01d |
- logging.info("Starting " + vmName + "in resource group " + rgName)
|
|
|
3fe01d |
+ logging.info("Starting " + vmName + " in resource group " + rgName)
|
|
|
3fe01d |
compute_client.virtual_machines.start(rgName, vmName)
|
|
|
3fe01d |
|
|
|
3fe01d |
|
|
|
3fe01d |
@@ -69,8 +132,8 @@
|
|
|
3fe01d |
"getopt" : ":",
|
|
|
3fe01d |
"longopt" : "resourceGroup",
|
|
|
3fe01d |
"help" : "--resourceGroup=[name] Name of the resource group",
|
|
|
3fe01d |
- "shortdesc" : "Name of resource group.",
|
|
|
3fe01d |
- "required" : "1",
|
|
|
3fe01d |
+ "shortdesc" : "Name of resource group. Metadata service is used if the value is not provided.",
|
|
|
3fe01d |
+ "required" : "0",
|
|
|
3fe01d |
"order" : 2
|
|
|
3fe01d |
}
|
|
|
3fe01d |
all_opt["tenantId"] = {
|
|
|
3fe01d |
@@ -78,23 +141,56 @@
|
|
|
3fe01d |
"longopt" : "tenantId",
|
|
|
3fe01d |
"help" : "--tenantId=[name] Id of the Azure Active Directory tenant",
|
|
|
3fe01d |
"shortdesc" : "Id of Azure Active Directory tenant.",
|
|
|
3fe01d |
- "required" : "1",
|
|
|
3fe01d |
+ "required" : "0",
|
|
|
3fe01d |
"order" : 3
|
|
|
3fe01d |
}
|
|
|
3fe01d |
all_opt["subscriptionId"] = {
|
|
|
3fe01d |
"getopt" : ":",
|
|
|
3fe01d |
"longopt" : "subscriptionId",
|
|
|
3fe01d |
"help" : "--subscriptionId=[name] Id of the Azure subscription",
|
|
|
3fe01d |
- "shortdesc" : "Id of the Azure subscription.",
|
|
|
3fe01d |
- "required" : "1",
|
|
|
3fe01d |
+ "shortdesc" : "Id of the Azure subscription. Metadata service is used if the value is not provided.",
|
|
|
3fe01d |
+ "required" : "0",
|
|
|
3fe01d |
"order" : 4
|
|
|
3fe01d |
}
|
|
|
3fe01d |
+ all_opt["network-fencing"] = {
|
|
|
3fe01d |
+ "getopt" : "",
|
|
|
3fe01d |
+ "longopt" : "network-fencing",
|
|
|
3fe01d |
+ "help" : "--network-fencing Use network fencing. See NOTE-section of\n\
|
|
|
3fe01d |
+ metadata for required Subnet/Network Security\n\
|
|
|
3fe01d |
+ Group configuration.",
|
|
|
3fe01d |
+ "shortdesc" : "Use network fencing. See NOTE-section for configuration.",
|
|
|
3fe01d |
+ "required" : "0",
|
|
|
3fe01d |
+ "order" : 5
|
|
|
3fe01d |
+ }
|
|
|
3fe01d |
+ all_opt["msi"] = {
|
|
|
3fe01d |
+ "getopt" : "",
|
|
|
3fe01d |
+ "longopt" : "msi",
|
|
|
3fe01d |
+ "help" : "--msi Use Managed Service Identity instead of\n\
|
|
|
3fe01d |
+ username and password. If specified,\n\
|
|
|
3fe01d |
+ parameters tenantId, login and passwd are not\n\
|
|
|
3fe01d |
+ allowed.",
|
|
|
3fe01d |
+ "shortdesc" : "Determines if Managed Service Identity should be used.",
|
|
|
3fe01d |
+ "required" : "0",
|
|
|
3fe01d |
+ "order" : 6
|
|
|
3fe01d |
+ }
|
|
|
3fe01d |
+ all_opt["cloud"] = {
|
|
|
3fe01d |
+ "getopt" : ":",
|
|
|
3fe01d |
+ "longopt" : "cloud",
|
|
|
3fe01d |
+ "help" : "--cloud=[name] Name of the cloud you want to use. Supported\n\
|
|
|
3fe01d |
+ values are china, germany or usgov. Do not use\n\
|
|
|
3fe01d |
+ this parameter if you want to use public\n\
|
|
|
3fe01d |
+ Azure.",
|
|
|
3fe01d |
+ "shortdesc" : "Name of the cloud you want to use.",
|
|
|
3fe01d |
+ "required" : "0",
|
|
|
3fe01d |
+ "order" : 7
|
|
|
3fe01d |
+ }
|
|
|
3fe01d |
|
|
|
3fe01d |
# Main agent method
|
|
|
3fe01d |
def main():
|
|
|
3fe01d |
compute_client = None
|
|
|
3fe01d |
+ network_client = None
|
|
|
3fe01d |
|
|
|
3fe01d |
- device_opt = ["resourceGroup", "login", "passwd", "tenantId", "subscriptionId","port"]
|
|
|
3fe01d |
+ device_opt = ["login", "passwd", "port", "resourceGroup", "tenantId", "subscriptionId", "network-fencing", "msi", "cloud"]
|
|
|
3fe01d |
|
|
|
3fe01d |
atexit.register(atexit_handler)
|
|
|
3fe01d |
|
|
|
3fe01d |
@@ -113,36 +209,44 @@
|
|
|
3fe01d |
\n.P\n\
|
|
|
3fe01d |
For instructions to setup credentials see: https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal\
|
|
|
3fe01d |
\n.P\n\
|
|
|
3fe01d |
-Username and password are application ID and authentication key from \"App registrations\"."
|
|
|
3fe01d |
+Username and password are application ID and authentication key from \"App registrations\".\
|
|
|
3fe01d |
+\n.P\n\
|
|
|
3fe01d |
+NOTE: NETWORK FENCING\n.br\n\
|
|
|
3fe01d |
+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\
|
|
|
3fe01d |
++-----------+-----+-------------------------+------+------+-----+-----+--------+\n.br\n\
|
|
|
3fe01d |
+| DIRECTION | PRI | NAME | PORT | PROT | SRC | DST | ACTION |\n.br\n\
|
|
|
3fe01d |
++-----------+-----+-------------------------+------+------+-----+-----+--------+\n.br\n\
|
|
|
3fe01d |
+| Inbound | 100 | FENCE_DENY_ALL_INBOUND | Any | Any | Any | Any | Deny |\n.br\n\
|
|
|
3fe01d |
+| Outbound | 100 | FENCE_DENY_ALL_OUTBOUND | Any | Any | Any | Any | Deny |\n.br\n\
|
|
|
3fe01d |
++-----------+-----+-------------------------+------+------+-----+-----+--------+\
|
|
|
3fe01d |
+\n.P\n\
|
|
|
3fe01d |
+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."
|
|
|
3fe01d |
docs["vendorurl"] = "http://www.microsoft.com"
|
|
|
3fe01d |
show_docs(options, docs)
|
|
|
3fe01d |
|
|
|
3fe01d |
run_delay(options)
|
|
|
3fe01d |
|
|
|
3fe01d |
try:
|
|
|
3fe01d |
- from azure.common.credentials import ServicePrincipalCredentials
|
|
|
3fe01d |
- from azure.mgmt.compute import ComputeManagementClient
|
|
|
3fe01d |
-
|
|
|
3fe01d |
- tenantid = options["--tenantId"]
|
|
|
3fe01d |
- servicePrincipal = options["--username"]
|
|
|
3fe01d |
- spPassword = options["--password"]
|
|
|
3fe01d |
- subscriptionId = options["--subscriptionId"]
|
|
|
3fe01d |
- credentials = ServicePrincipalCredentials(
|
|
|
3fe01d |
- client_id = servicePrincipal,
|
|
|
3fe01d |
- secret = spPassword,
|
|
|
3fe01d |
- tenant = tenantid
|
|
|
3fe01d |
- )
|
|
|
3fe01d |
- compute_client = ComputeManagementClient(
|
|
|
3fe01d |
- credentials,
|
|
|
3fe01d |
- subscriptionId
|
|
|
3fe01d |
- )
|
|
|
3fe01d |
+ config = azure_fence.get_azure_config(options)
|
|
|
3fe01d |
+ compute_client = azure_fence.get_azure_compute_client(config)
|
|
|
3fe01d |
+ if "--network-fencing" in options:
|
|
|
3fe01d |
+ network_client = azure_fence.get_azure_network_client(config)
|
|
|
3fe01d |
except ImportError:
|
|
|
3fe01d |
fail_usage("Azure Resource Manager Python SDK not found or not accessible")
|
|
|
3fe01d |
except Exception as e:
|
|
|
3fe01d |
fail_usage("Failed: %s" % re.sub("^, ", "", str(e)))
|
|
|
3fe01d |
|
|
|
3fe01d |
+ if "--network-fencing" in options:
|
|
|
3fe01d |
+ # use off-action to quickly return off once network is fenced instead of
|
|
|
3fe01d |
+ # waiting for vm state to change
|
|
|
3fe01d |
+ if options["--action"] == "reboot":
|
|
|
3fe01d |
+ options["--action"] = "off"
|
|
|
3fe01d |
+ # check for devices to unfence in monitor-action
|
|
|
3fe01d |
+ elif options["--action"] == "monitor":
|
|
|
3fe01d |
+ check_unfence([compute_client, network_client], options)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
# Operate the fencing device
|
|
|
3fe01d |
- result = fence_action(compute_client, options, set_power_status, get_power_status, get_nodes_list)
|
|
|
3fe01d |
+ result = fence_action([compute_client, network_client], options, set_power_status, get_power_status, get_nodes_list)
|
|
|
3fe01d |
sys.exit(result)
|
|
|
3fe01d |
|
|
|
3fe01d |
if __name__ == "__main__":
|
|
|
3fe01d |
diff -uNr a/fence/agents/lib/azure_fence.py.py b/fence/agents/lib/azure_fence.py.py
|
|
|
3fe01d |
--- a/fence/agents/lib/azure_fence.py.py 1970-01-01 01:00:00.000000000 +0100
|
|
|
3fe01d |
+++ b/fence/agents/lib/azure_fence.py.py 2018-03-13 13:08:52.818391349 +0100
|
|
|
3fe01d |
@@ -0,0 +1,353 @@
|
|
|
3fe01d |
+import logging, re, time
|
|
|
3fe01d |
+from fencing import fail_usage
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+FENCE_SUBNET_NAME = "fence-subnet"
|
|
|
3fe01d |
+FENCE_INBOUND_RULE_NAME = "FENCE_DENY_ALL_INBOUND"
|
|
|
3fe01d |
+FENCE_INBOUND_RULE_DIRECTION = "Inbound"
|
|
|
3fe01d |
+FENCE_OUTBOUND_RULE_NAME = "FENCE_DENY_ALL_OUTBOUND"
|
|
|
3fe01d |
+FENCE_OUTBOUND_RULE_DIRECTION = "Outbound"
|
|
|
3fe01d |
+FENCE_STATE_OFF = "off"
|
|
|
3fe01d |
+FENCE_STATE_ON = "on"
|
|
|
3fe01d |
+FENCE_TAG_SUBNET_ID = "FENCE_TAG_SUBNET_ID"
|
|
|
3fe01d |
+FENCE_TAG_IP_TYPE = "FENCE_TAG_IP_TYPE"
|
|
|
3fe01d |
+FENCE_TAG_IP = "FENCE_TAG_IP"
|
|
|
3fe01d |
+IP_TYPE_DYNAMIC = "Dynamic"
|
|
|
3fe01d |
+MAX_RETRY = 10
|
|
|
3fe01d |
+RETRY_WAIT = 5
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+#BEGIN_VERSION_GENERATION
|
|
|
3fe01d |
+RELEASE_VERSION = "New fence lib agent - test release on steroids"
|
|
|
3fe01d |
+REDHAT_COPYRIGHT = ""
|
|
|
3fe01d |
+BUILD_DATE = "March, 2008"
|
|
|
3fe01d |
+#END_VERSION_GENERATION
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+class AzureSubResource:
|
|
|
3fe01d |
+ Type = None
|
|
|
3fe01d |
+ Name = None
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+class AzureResource:
|
|
|
3fe01d |
+ Id = None
|
|
|
3fe01d |
+ SubscriptionId = None
|
|
|
3fe01d |
+ ResourceGroupName = None
|
|
|
3fe01d |
+ ResourceName = None
|
|
|
3fe01d |
+ SubResources = []
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+class AzureConfiguration:
|
|
|
3fe01d |
+ RGName = None
|
|
|
3fe01d |
+ VMName = None
|
|
|
3fe01d |
+ SubscriptionId = None
|
|
|
3fe01d |
+ Cloud = None
|
|
|
3fe01d |
+ UseMSI = None
|
|
|
3fe01d |
+ Tenantid = None
|
|
|
3fe01d |
+ ApplicationId = None
|
|
|
3fe01d |
+ ApplicationKey = None
|
|
|
3fe01d |
+ Verbose = None
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def get_from_metadata(parameter):
|
|
|
3fe01d |
+ import requests
|
|
|
3fe01d |
+ try:
|
|
|
3fe01d |
+ r = requests.get('http://169.254.169.254/metadata/instance?api-version=2017-08-01', headers = {"Metadata":"true"})
|
|
|
3fe01d |
+ return str(r.json()["compute"][parameter])
|
|
|
3fe01d |
+ except:
|
|
|
3fe01d |
+ logging.warning("Not able to use metadata service. Am I running in Azure?")
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ return None
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def get_azure_resource(id):
|
|
|
3fe01d |
+ match = re.match('(/subscriptions/([^/]*)/resourceGroups/([^/]*))(/providers/([^/]*/[^/]*)/([^/]*))?((/([^/]*)/([^/]*))*)', id)
|
|
|
3fe01d |
+ if not match:
|
|
|
3fe01d |
+ fail_usage("{get_azure_resource} cannot parse resource id %s" % id)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ logging.debug("{get_azure_resource} found %s matches for %s" % (len(match.groups()), id))
|
|
|
3fe01d |
+ iGroup = 0
|
|
|
3fe01d |
+ while iGroup < len(match.groups()):
|
|
|
3fe01d |
+ logging.debug("{get_azure_resource} group %s: %s" %(iGroup, match.group(iGroup)))
|
|
|
3fe01d |
+ iGroup += 1
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ resource = AzureResource()
|
|
|
3fe01d |
+ resource.Id = id
|
|
|
3fe01d |
+ resource.SubscriptionId = match.group(2)
|
|
|
3fe01d |
+ resource.SubResources = []
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ if len(match.groups()) > 3:
|
|
|
3fe01d |
+ resource.ResourceGroupName = match.group(3)
|
|
|
3fe01d |
+ logging.debug("{get_azure_resource} resource group %s" % resource.ResourceGroupName)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ if len(match.groups()) > 6:
|
|
|
3fe01d |
+ resource.ResourceName = match.group(6)
|
|
|
3fe01d |
+ logging.debug("{get_azure_resource} resource name %s" % resource.ResourceName)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ if len(match.groups()) > 7 and match.group(7):
|
|
|
3fe01d |
+ splits = match.group(7).split("/")
|
|
|
3fe01d |
+ logging.debug("{get_azure_resource} splitting subtypes '%s' (%s)" % (match.group(7), len(splits)))
|
|
|
3fe01d |
+ i = 1 # the string starts with / so the first split is empty
|
|
|
3fe01d |
+ while i < len(splits) - 1:
|
|
|
3fe01d |
+ logging.debug("{get_azure_resource} creating subresource with type %s and name %s" % (splits[i], splits[i+1]))
|
|
|
3fe01d |
+ subRes = AzureSubResource()
|
|
|
3fe01d |
+ subRes.Type = splits[i]
|
|
|
3fe01d |
+ subRes.Name = splits[i+1]
|
|
|
3fe01d |
+ resource.SubResources.append(subRes)
|
|
|
3fe01d |
+ i += 2
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ return resource
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def get_fence_subnet_for_config(ipConfig, network_client):
|
|
|
3fe01d |
+ subnetResource = get_azure_resource(ipConfig.subnet.id)
|
|
|
3fe01d |
+ logging.debug("{get_fence_subnet_for_config} testing virtual network %s in resource group %s for a fence subnet" %(subnetResource.ResourceName, subnetResource.ResourceGroupName))
|
|
|
3fe01d |
+ vnet = network_client.virtual_networks.get(subnetResource.ResourceGroupName, subnetResource.ResourceName)
|
|
|
3fe01d |
+ return get_subnet(vnet, FENCE_SUBNET_NAME)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def get_subnet(vnet, subnetName):
|
|
|
3fe01d |
+ for avSubnet in vnet.subnets:
|
|
|
3fe01d |
+ logging.debug("{get_subnet} searching subnet %s testing subnet %s" % (subnetName, avSubnet.name))
|
|
|
3fe01d |
+ if (avSubnet.name.lower() == subnetName.lower()):
|
|
|
3fe01d |
+ logging.debug("{get_subnet} subnet found %s" % avSubnet)
|
|
|
3fe01d |
+ return avSubnet
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def test_fence_subnet(fenceSubnet, nic, network_client):
|
|
|
3fe01d |
+ logging.info("{test_fence_subnet}")
|
|
|
3fe01d |
+ testOk = True
|
|
|
3fe01d |
+ if not fenceSubnet:
|
|
|
3fe01d |
+ testOk = False
|
|
|
3fe01d |
+ logging.info("{test_fence_subnet} No fence subnet found for virtual network of network interface %s" % nic.id)
|
|
|
3fe01d |
+ else:
|
|
|
3fe01d |
+ if not fenceSubnet.network_security_group:
|
|
|
3fe01d |
+ testOk = False
|
|
|
3fe01d |
+ logging.info("{test_fence_subnet} Fence subnet %s has not network security group" % fenceSubnet.id)
|
|
|
3fe01d |
+ else:
|
|
|
3fe01d |
+ nsgResource = get_azure_resource(fenceSubnet.network_security_group.id)
|
|
|
3fe01d |
+ logging.info("{test_fence_subnet} Getting network security group %s in resource group %s" % (nsgResource.ResourceName, nsgResource.ResourceGroupName))
|
|
|
3fe01d |
+ nsg = network_client.network_security_groups.get(nsgResource.ResourceGroupName, nsgResource.ResourceName)
|
|
|
3fe01d |
+ inboundRule = get_inbound_rule_for_nsg(nsg)
|
|
|
3fe01d |
+ outboundRule = get_outbound_rule_for_nsg(nsg)
|
|
|
3fe01d |
+ if not outboundRule:
|
|
|
3fe01d |
+ testOk = False
|
|
|
3fe01d |
+ 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))
|
|
|
3fe01d |
+ elif not inboundRule:
|
|
|
3fe01d |
+ testOk = False
|
|
|
3fe01d |
+ 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))
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ return testOk
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def get_inbound_rule_for_nsg(nsg):
|
|
|
3fe01d |
+ return get_rule_for_nsg(nsg, FENCE_INBOUND_RULE_NAME, FENCE_INBOUND_RULE_DIRECTION)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def get_outbound_rule_for_nsg(nsg):
|
|
|
3fe01d |
+ return get_rule_for_nsg(nsg, FENCE_OUTBOUND_RULE_NAME, FENCE_OUTBOUND_RULE_DIRECTION)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def get_rule_for_nsg(nsg, ruleName, direction):
|
|
|
3fe01d |
+ logging.info("{get_rule_for_nsg} Looking for security rule %s with direction %s" % (ruleName, direction))
|
|
|
3fe01d |
+ if not nsg:
|
|
|
3fe01d |
+ logging.info("{get_rule_for_nsg} Network security group not set")
|
|
|
3fe01d |
+ return None
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ for rule in nsg.security_rules:
|
|
|
3fe01d |
+ logging.info("{get_rule_for_nsg} Testing a %s securiy rule %s" % (rule.direction, rule.name))
|
|
|
3fe01d |
+ if (rule.access == "Deny") and (rule.direction == direction) \
|
|
|
3fe01d |
+ and (rule.source_port_range == "*") and (rule.destination_port_range == "*") \
|
|
|
3fe01d |
+ and (rule.protocol == "*") and (rule.destination_address_prefix == "*") \
|
|
|
3fe01d |
+ and (rule.source_address_prefix == "*") and (rule.provisioning_state == "Succeeded") \
|
|
|
3fe01d |
+ and (rule.priority == 100) and (rule.name == ruleName):
|
|
|
3fe01d |
+ logging.info("{get_rule_for_nsg} %s rule found" % direction)
|
|
|
3fe01d |
+ return rule
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ return None
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def get_network_state(compute_client, network_client, rgName, vmName):
|
|
|
3fe01d |
+ result = FENCE_STATE_ON
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ try:
|
|
|
3fe01d |
+ vm = compute_client.virtual_machines.get(rgName, vmName, "instanceView")
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ allNICOK = True
|
|
|
3fe01d |
+ for nicRef in vm.network_profile.network_interfaces:
|
|
|
3fe01d |
+ nicresource = get_azure_resource(nicRef.id)
|
|
|
3fe01d |
+ nic = network_client.network_interfaces.get(nicresource.ResourceGroupName, nicresource.ResourceName)
|
|
|
3fe01d |
+ for ipConfig in nic.ip_configurations:
|
|
|
3fe01d |
+ logging.info("{get_network_state} Testing ip configuration %s" % ipConfig.name)
|
|
|
3fe01d |
+ fenceSubnet = get_fence_subnet_for_config(ipConfig, network_client)
|
|
|
3fe01d |
+ testOk = test_fence_subnet(fenceSubnet, nic, network_client)
|
|
|
3fe01d |
+ if not testOk:
|
|
|
3fe01d |
+ allNICOK = False
|
|
|
3fe01d |
+ elif fenceSubnet.id.lower() != ipConfig.subnet.id.lower():
|
|
|
3fe01d |
+ 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()))
|
|
|
3fe01d |
+ allNICOK = False
|
|
|
3fe01d |
+ if allNICOK:
|
|
|
3fe01d |
+ logging.info("{get_network_state} All IP configurations of all network interfaces are in the fence subnet. Declaring VM as off")
|
|
|
3fe01d |
+ result = FENCE_STATE_OFF
|
|
|
3fe01d |
+ except Exception as e:
|
|
|
3fe01d |
+ fail_usage("{get_network_state} Failed: %s" % e)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ return result
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def set_network_state(compute_client, network_client, rgName, vmName, operation):
|
|
|
3fe01d |
+ import msrestazure.azure_exceptions
|
|
|
3fe01d |
+ logging.info("{set_network_state} Setting state %s for %s in resource group %s" % (operation, vmName, rgName))
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ vm = compute_client.virtual_machines.get(rgName, vmName, "instanceView")
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ operations = []
|
|
|
3fe01d |
+ for nicRef in vm.network_profile.network_interfaces:
|
|
|
3fe01d |
+ for attempt in range(0, MAX_RETRY):
|
|
|
3fe01d |
+ try:
|
|
|
3fe01d |
+ nicresource = get_azure_resource(nicRef.id)
|
|
|
3fe01d |
+ nic = network_client.network_interfaces.get(nicresource.ResourceGroupName, nicresource.ResourceName)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ if not nic.tags and operation == "block":
|
|
|
3fe01d |
+ nic.tags = {}
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ logging.info("{set_network_state} Searching for tags required to unfence this virtual machine")
|
|
|
3fe01d |
+ for ipConfig in nic.ip_configurations:
|
|
|
3fe01d |
+ if operation == "block":
|
|
|
3fe01d |
+ fenceSubnet = get_fence_subnet_for_config(ipConfig, network_client)
|
|
|
3fe01d |
+ testOk = test_fence_subnet(fenceSubnet, nic, network_client)
|
|
|
3fe01d |
+ if testOk:
|
|
|
3fe01d |
+ logging.info("{set_network_state} Changing subnet of ip config of nic %s" % nic.id)
|
|
|
3fe01d |
+ nic.tags[("%s_%s" % (FENCE_TAG_SUBNET_ID, ipConfig.name))] = ipConfig.subnet.id
|
|
|
3fe01d |
+ nic.tags[("%s_%s" % (FENCE_TAG_IP_TYPE, ipConfig.name))] = ipConfig.private_ip_allocation_method
|
|
|
3fe01d |
+ nic.tags[("%s_%s" % (FENCE_TAG_IP, ipConfig.name))] = ipConfig.private_ip_address
|
|
|
3fe01d |
+ ipConfig.subnet = fenceSubnet
|
|
|
3fe01d |
+ ipConfig.private_ip_allocation_method = IP_TYPE_DYNAMIC
|
|
|
3fe01d |
+ else:
|
|
|
3fe01d |
+ fail_usage("{set_network_state} Network interface id %s does not have a network security group." % nic.id)
|
|
|
3fe01d |
+ elif operation == "unblock":
|
|
|
3fe01d |
+ if not nic.tags:
|
|
|
3fe01d |
+ fail_usage("{set_network_state} IP configuration %s is missing the required resource tags (empty)" % ipConfig.name)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ subnetId = nic.tags.pop("%s_%s" % (FENCE_TAG_SUBNET_ID, ipConfig.name))
|
|
|
3fe01d |
+ ipType = nic.tags.pop("%s_%s" % (FENCE_TAG_IP_TYPE, ipConfig.name))
|
|
|
3fe01d |
+ ipAddress = nic.tags.pop("%s_%s" % (FENCE_TAG_IP, ipConfig.name))
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ if (subnetId and ipType and (ipAddress or (ipType.lower() == IP_TYPE_DYNAMIC.lower()))):
|
|
|
3fe01d |
+ logging.info("{set_network_state} tags found (subnetId: %s, ipType: %s, ipAddress: %s)" % (subnetId, ipType, ipAddress))
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ subnetResource = get_azure_resource(subnetId)
|
|
|
3fe01d |
+ vnet = network_client.virtual_networks.get(subnetResource.ResourceGroupName, subnetResource.ResourceName)
|
|
|
3fe01d |
+ logging.info("{set_network_state} looking for subnet %s" % len(subnetResource.SubResources))
|
|
|
3fe01d |
+ oldSubnet = get_subnet(vnet, subnetResource.SubResources[0].Name)
|
|
|
3fe01d |
+ if not oldSubnet:
|
|
|
3fe01d |
+ fail_usage("{set_network_state} subnet %s not found" % subnetId)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ ipConfig.subnet = oldSubnet
|
|
|
3fe01d |
+ ipConfig.private_ip_allocation_method = ipType
|
|
|
3fe01d |
+ if ipAddress:
|
|
|
3fe01d |
+ ipConfig.private_ip_address = ipAddress
|
|
|
3fe01d |
+ else:
|
|
|
3fe01d |
+ 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))
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ logging.info("{set_network_state} updating nic %s" % (nic.id))
|
|
|
3fe01d |
+ op = network_client.network_interfaces.create_or_update(nicresource.ResourceGroupName, nicresource.ResourceName, nic)
|
|
|
3fe01d |
+ operations.append(op)
|
|
|
3fe01d |
+ break
|
|
|
3fe01d |
+ except msrestazure.azure_exceptions.CloudError as cex:
|
|
|
3fe01d |
+ logging.error("{set_network_state} CloudError in attempt %s '%s'" % (attempt, cex))
|
|
|
3fe01d |
+ if cex.error and cex.error.error and cex.error.error.lower() == "PrivateIPAddressIsBeingCleanedUp":
|
|
|
3fe01d |
+ logging.error("{set_network_state} PrivateIPAddressIsBeingCleanedUp")
|
|
|
3fe01d |
+ time.sleep(RETRY_WAIT)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ except Exception as ex:
|
|
|
3fe01d |
+ logging.error("{set_network_state} Exception of type %s: %s" % (type(ex).__name__, ex))
|
|
|
3fe01d |
+ break
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def get_azure_config(options):
|
|
|
3fe01d |
+ config = AzureConfiguration()
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ config.RGName = options.get("--resourceGroup")
|
|
|
3fe01d |
+ config.VMName = options.get("--plug")
|
|
|
3fe01d |
+ config.SubscriptionId = options.get("--subscriptionId")
|
|
|
3fe01d |
+ config.Cloud = options.get("--cloud")
|
|
|
3fe01d |
+ config.UseMSI = "--msi" in options
|
|
|
3fe01d |
+ config.Tenantid = options.get("--tenantId")
|
|
|
3fe01d |
+ config.ApplicationId = options.get("--username")
|
|
|
3fe01d |
+ config.ApplicationKey = options.get("--password")
|
|
|
3fe01d |
+ config.Verbose = options.get("--verbose")
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ if not config.RGName:
|
|
|
3fe01d |
+ logging.info("resourceGroup not provided. Using metadata service")
|
|
|
3fe01d |
+ config.RGName = get_from_metadata("resourceGroupName")
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ if not config.SubscriptionId:
|
|
|
3fe01d |
+ logging.info("subscriptionId not provided. Using metadata service")
|
|
|
3fe01d |
+ config.SubscriptionId = get_from_metadata("subscriptionId")
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ return config
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def get_azure_cloud_environment(config):
|
|
|
3fe01d |
+ cloud_environment = None
|
|
|
3fe01d |
+ if config.Cloud:
|
|
|
3fe01d |
+ if (config.Cloud.lower() == "china"):
|
|
|
3fe01d |
+ from msrestazure.azure_cloud import AZURE_CHINA_CLOUD
|
|
|
3fe01d |
+ cloud_environment = AZURE_CHINA_CLOUD
|
|
|
3fe01d |
+ elif (config.Cloud.lower() == "germany"):
|
|
|
3fe01d |
+ from msrestazure.azure_cloud import AZURE_GERMAN_CLOUD
|
|
|
3fe01d |
+ cloud_environment = AZURE_GERMAN_CLOUD
|
|
|
3fe01d |
+ elif (config.Cloud.lower() == "usgov"):
|
|
|
3fe01d |
+ from msrestazure.azure_cloud import AZURE_US_GOV_CLOUD
|
|
|
3fe01d |
+ cloud_environment = AZURE_US_GOV_CLOUD
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ return cloud_environment
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def get_azure_credentials(config):
|
|
|
3fe01d |
+ credentials = None
|
|
|
3fe01d |
+ cloud_environment = get_azure_cloud_environment(config)
|
|
|
3fe01d |
+ if config.UseMSI and cloud_environment:
|
|
|
3fe01d |
+ from msrestazure.azure_active_directory import MSIAuthentication
|
|
|
3fe01d |
+ credentials = MSIAuthentication(cloud_environment=cloud_environment)
|
|
|
3fe01d |
+ elif config.UseMSI:
|
|
|
3fe01d |
+ from msrestazure.azure_active_directory import MSIAuthentication
|
|
|
3fe01d |
+ credentials = MSIAuthentication()
|
|
|
3fe01d |
+ elif cloud_environment:
|
|
|
3fe01d |
+ from azure.common.credentials import ServicePrincipalCredentials
|
|
|
3fe01d |
+ credentials = ServicePrincipalCredentials(
|
|
|
3fe01d |
+ client_id = config.ApplicationId,
|
|
|
3fe01d |
+ secret = config.ApplicationKey,
|
|
|
3fe01d |
+ tenant = config.Tenantid,
|
|
|
3fe01d |
+ cloud_environment=cloud_environment
|
|
|
3fe01d |
+ )
|
|
|
3fe01d |
+ else:
|
|
|
3fe01d |
+ from azure.common.credentials import ServicePrincipalCredentials
|
|
|
3fe01d |
+ credentials = ServicePrincipalCredentials(
|
|
|
3fe01d |
+ client_id = config.ApplicationId,
|
|
|
3fe01d |
+ secret = config.ApplicationKey,
|
|
|
3fe01d |
+ tenant = config.Tenantid
|
|
|
3fe01d |
+ )
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ return credentials
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def get_azure_compute_client(config):
|
|
|
3fe01d |
+ from azure.mgmt.compute import ComputeManagementClient
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ cloud_environment = get_azure_cloud_environment(config)
|
|
|
3fe01d |
+ credentials = get_azure_credentials(config)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ if cloud_environment:
|
|
|
3fe01d |
+ compute_client = ComputeManagementClient(
|
|
|
3fe01d |
+ credentials,
|
|
|
3fe01d |
+ config.SubscriptionId,
|
|
|
3fe01d |
+ base_url=cloud_environment.endpoints.resource_manager
|
|
|
3fe01d |
+ )
|
|
|
3fe01d |
+ else:
|
|
|
3fe01d |
+ compute_client = ComputeManagementClient(
|
|
|
3fe01d |
+ credentials,
|
|
|
3fe01d |
+ config.SubscriptionId
|
|
|
3fe01d |
+ )
|
|
|
3fe01d |
+ return compute_client
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+def get_azure_network_client(config):
|
|
|
3fe01d |
+ from azure.mgmt.network import NetworkManagementClient
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ cloud_environment = get_azure_cloud_environment(config)
|
|
|
3fe01d |
+ credentials = get_azure_credentials(config)
|
|
|
3fe01d |
+
|
|
|
3fe01d |
+ if cloud_environment:
|
|
|
3fe01d |
+ network_client = NetworkManagementClient(
|
|
|
3fe01d |
+ credentials,
|
|
|
3fe01d |
+ config.SubscriptionId,
|
|
|
3fe01d |
+ base_url=cloud_environment.endpoints.resource_manager
|
|
|
3fe01d |
+ )
|
|
|
3fe01d |
+ else:
|
|
|
3fe01d |
+ network_client = NetworkManagementClient(
|
|
|
3fe01d |
+ credentials,
|
|
|
3fe01d |
+ config.SubscriptionId
|
|
|
3fe01d |
+ )
|
|
|
3fe01d |
+ return network_client
|
|
|
3fe01d |
diff -uNr a/fence/agents/lib/Makefile.am b/fence/agents/lib/Makefile.am
|
|
|
3fe01d |
--- a/fence/agents/lib/Makefile.am 2014-08-06 09:35:08.000000000 +0200
|
|
|
3fe01d |
+++ b/fence/agents/lib/Makefile.am 2018-03-13 13:06:02.507027744 +0100
|
|
|
3fe01d |
@@ -1,12 +1,12 @@
|
|
|
3fe01d |
MAINTAINERCLEANFILES = Makefile.in
|
|
|
3fe01d |
|
|
|
3fe01d |
-TARGET = fencing.py fencing_snmp.py
|
|
|
3fe01d |
+TARGET = fencing.py fencing_snmp.py azure_fence.py
|
|
|
3fe01d |
|
|
|
3fe01d |
if BUILD_XENAPILIB
|
|
|
3fe01d |
TARGET += XenAPI.py
|
|
|
3fe01d |
endif
|
|
|
3fe01d |
|
|
|
3fe01d |
-SRC = fencing.py.py fencing_snmp.py.py XenAPI.py.py check_used_options.py
|
|
|
3fe01d |
+SRC = fencing.py.py fencing_snmp.py.py XenAPI.py.py azure_fence.py.py check_used_options.py
|
|
|
3fe01d |
|
|
|
3fe01d |
XSL = fence2man.xsl fence2rng.xsl
|
|
|
3fe01d |
|