From 4a47e20c723791c9ea23a0f93a2ae61c1275a761 Mon Sep 17 00:00:00 2001 From: jbowdre Date: Thu, 29 Oct 2020 14:38:44 -0500 Subject: [PATCH] Add actions for ip deallocation --- Dockerfile | 10 +- src/main/python/allocate_ip/requirements.txt | 4 +- src/main/python/allocate_ip/source.py | 137 ++++++++---------- .../python/deallocate_ip/requirements.txt | 3 +- src/main/python/deallocate_ip/source.py | 60 +------- src/main/python/get_ip_ranges/source.py | 18 --- src/main/python/validate_endpoint/source.py | 15 -- 7 files changed, 79 insertions(+), 168 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0dee94d..dcd86fd 100755 --- a/Dockerfile +++ b/Dockerfile @@ -2,11 +2,11 @@ FROM photon:3.0-20200609 ADD target/python /ipam/python -RUN tdnf install -y python3-pip.noarch python3-devel gcc glibc-devel binutils linux-api-headers shadow && \ - pip3 install --upgrade pip setuptools && \ - pip3 install certifi && \ - tdnf clean all && \ - rm -fr /var/cache/tdnf/* +RUN tdnf install -y python3-pip.noarch python3-devel gcc glibc-devel binutils linux-api-headers shadow +RUN pip3 install --upgrade pip setuptools +RUN pip3 install certifi +RUN tdnf clean all +RUN rm -fr /var/cache/tdnf/* RUN pip3 install -r /ipam/python/allocate_ip/requirements.txt --target=/ipam/python/allocate_ip RUN pip3 install -r /ipam/python/deallocate_ip/requirements.txt --target=/ipam/python/deallocate_ip diff --git a/src/main/python/allocate_ip/requirements.txt b/src/main/python/allocate_ip/requirements.txt index f700642..0a2e2a8 100755 --- a/src/main/python/allocate_ip/requirements.txt +++ b/src/main/python/allocate_ip/requirements.txt @@ -1,2 +1,4 @@ requests==2.21.0 -OrionSDK +orionsdk +ipaddress +datetime diff --git a/src/main/python/allocate_ip/source.py b/src/main/python/allocate_ip/source.py index 3050cb3..1e8a661 100755 --- a/src/main/python/allocate_ip/source.py +++ b/src/main/python/allocate_ip/source.py @@ -12,64 +12,11 @@ conditions of the subcomponent's license, as noted in the LICENSE file. import requests from vra_ipam_utils.ipam import IPAM import logging -from OrionSDK import SWisClient +from orionsdk import SwisClient +from datetime import datetime +import ipaddress +import socket -""" -Example payload - -"inputs": { - "resourceInfo": { - "id": "11f912e71454a075574a728848458", - "name": "external-ipam-it-mcm-323412", - "description": "test", - "type": "VM", - "owner": "mdzhigarov@vmware.com", - "orgId": "ce811934-ea1a-4f53-b6ec-465e6ca7d126", - "properties": { - "osType": "WINDOWS", - "vcUuid": "ff257ed9-070b-45eb-b2e7-d63926d5bdd7", - "__moref": "VirtualMachine:vm-288560", - "memoryGB": "4", - "datacenter": "Datacenter:datacenter-2", - "provisionGB": "1", - "__dcSelfLink": "/resources/groups/b28c7b8de065f07558b1612fce028", - "softwareName": "Microsoft Windows XP Professional (32-bit)", - "__computeType": "VirtualMachine", - "__hasSnapshot": "false", - "__placementLink": "/resources/compute/9bdc98681fb8b27557252188607b8", - "__computeHostLink": "/resources/compute/9bdc98681fb8b27557252188607b8" - } - }, - "ipAllocations": [ - { - "id": "111bb2f0-02fd-4983-94d2-8ac11768150f", - "ipRangeIds": [ - "network/ZG5zLm5ldHdvcmskMTAuMjMuMTE3LjAvMjQvMA:10.23.117.0/24/default" - ], - "nicIndex": "0", - "isPrimary": "true", - "size": "1", - "properties": { - "__moref": "DistributedVirtualPortgroup:dvportgroup-307087", - "__dvsUuid": "0c 8c 0b 50 46 b6 1c f2-e8 63 f4 24 24 d7 24 6c", - "__dcSelfLink": "/resources/groups/abe46b8cfa663a7558b28a6ffe088", - "__computeType": "DistributedVirtualPortgroup", - "__portgroupKey": "dvportgroup-307087" - } - } - ], - "endpoint": { - "id": "f097759d8736675585c4c5d272cd", - "endpointProperties": { - "hostName": "sampleipam.sof-mbu.eng.vmware.com", - "projectId": "111bb2f0-02fd-4983-94d2-8ac11768150f", - "providerId": "d8a5e3f2-d839-4365-af5b-f48de588fdc1", - "certificate": "-----BEGIN CERTIFICATE-----\nMIID0jCCArqgAwIBAgIQQaJF55UCb58f9KgQLD/QgTANBgkqhkiG9w0BAQUFADCB\niTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1\nbm55dmFsZTERMA8GA1UEChMISW5mb2Jsb3gxFDASBgNVBAsTC0VuZ2luZWVyaW5n\nMSgwJgYDVQQDEx9pbmZvYmxveC5zb2YtbWJ1LmVuZy52bXdhcmUuY29tMB4XDTE5\nMDEyOTEzMDExMloXDTIwMDEyOTEzMDExMlowgYkxCzAJBgNVBAYTAlVTMRMwEQYD\nVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQHEwlTdW5ueXZhbGUxETAPBgNVBAoTCElu\nZm9ibG94MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEoMCYGA1UEAxMfaW5mb2Jsb3gu\nc29mLW1idS5lbmcudm13YXJlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\nAQoCggEBAMMLNTqbAri6rt/H8iC4UgRdN0qj+wk0R2blmD9h1BiZJTeQk1r9i2rz\nzUOZHvE8Bld8m8xJ+nysWHaoFFGTX8bOd/p20oJBGbCLqXtoLMMBGAlP7nzWGBXH\nBYUS7kMv/CG+PSX0uuB0pRbhwOFq8Y69m4HRnn2X0WJGuu+v0FmRK/1m/kCacHga\nMBKaIgbwN72rW1t/MK0ijogmLR1ASY4FlMn7OBHIEUzO+dWFBh+gPDjoBECTTH8W\n5AK9TnYdxwAtJRYWmnVqtLoT3bImtSfI4YLUtpr9r13Kv5FkYVbXov1KBrQPbYyp\n72uT2ZgDJT4YUuWyKpMppgw1VcG3MosCAwEAAaM0MDIwMAYDVR0RBCkwJ4cEChda\nCoIfaW5mb2Jsb3guc29mLW1idS5lbmcudm13YXJlLmNvbTANBgkqhkiG9w0BAQUF\nAAOCAQEAXFPIh00VI55Sdfx+czbBb4rJz3c1xgN7pbV46K0nGI8S6ufAQPgLvZJ6\ng2T/mpo0FTuWCz1IE9PC28276vwv+xJZQwQyoUq4lhT6At84NWN+ZdLEe+aBAq+Y\nxUcIWzcKv8WdnlS5DRQxnw6pQCBdisnaFoEIzngQV8oYeIemW4Hcmb//yeykbZKJ\n0GTtK5Pud+kCkYmMHpmhH21q+3aRIcdzOYIoXhdzmIKG0Och97HthqpvRfOeWQ/A\nPDbxqQ2R/3D0gt9jWPCG7c0lB8Ynl24jLBB0RhY6mBrYpFbtXBQSEciUDRJVB2zL\nV8nJiMdhj+Q+ZmtSwhNRvi2qvWAUJQ==\n-----END CERTIFICATE-----\n" - }, - "authCredentialsLink": "/core/auth/credentials/13c9cbade08950755898c4b89c4a0" - } - } -""" def handler(context, inputs): ipam = IPAM(context, inputs) @@ -78,10 +25,14 @@ def handler(context, inputs): return ipam.allocate_ip() def do_allocate_ip(self, auth_credentials, cert): - # Your implemention goes here - username = auth_credentials["privateKeyId"] password = auth_credentials["privateKey"] + hostname = self.inputs['endpoint']['endpointProperties']['hostName'] + + global swis + swis = SwisClient(hostname, username, password) + requests.packages.urllib3.disable_warnings() + allocation_result = [] try: resource = self.inputs["resourceInfo"] @@ -117,28 +68,62 @@ def allocate(resource, allocation, context, endpoint): def allocate_in_range(range_id, resource, allocation, context, endpoint): + if int(allocation['size']) == 1: + vmName = resource['name'] + owner = resource['owner'] + + logging.info(f"Grabbing details about IP range ID {range_id}...") + qNetwork = swis.query(f"SELECT DISTINCT Address, CIDR FROM IPAM.GroupNode WHERE GroupTypeText LIKE 'Subnet' AND GroupID = {range_id}") + network = ipaddress.ip_network(f"{qNetwork['results'][0]['Address']}/{qNetwork['results'][0]['CIDR']}") + maxOrdinal = network.num_addresses -5 - ## Plug your implementation here to allocate an ip address - ## ... - ## Allocation successful - - result = { - "ipAllocationId": allocation["id"], - "ipRangeId": range_id, - "ipVersion": "IPv4" - } - - result["ipAddresses"] = ["10.23.117.5"] - result["properties"] = {"customPropertyKey1": "customPropertyValue1"} - - return result + logging.info(f"Grabbing next available IPs...") + qAddresses = swis.query(f"SELECT TOP 5 IpNodeId, IpAddress FROM IPAM.IPNode WHERE SubnetId = {range_id} AND IPOrdinal > 9 AND IPOrdinal < {maxOrdinal} AND Status = 2 AND LastSync IS NULL") + for address in qAddresses['results']: + ipAddress = address['IpAddress'] + nodeId = address['IpNodeId'] + if check_dns(ipAddress) == True: + logging.info(f"Reserving IP address {ipAddress}...") + uri = f'swis://localhost/Orion/IPAM.IPNode/IpNodeId={nodeId}' + swis.invoke("IPAM.SubnetManagement", "ChangeIPStatus", ipAddress, "Reserved") + swis.update(uri, Comments=f"Reserved by {owner} at {datetime.now()}.", DnsBackward=vmName) + logging.info(f"Successfully reserved {ipAddress} for {vmName}.") + result = { + "ipAllocationId": allocation['id'], + "ipRangeId": range_id, + "ipVersion": f"IPv{network.version}", + "ipAddresses": [ipAddress] + } + break + return result + + else: + # TODO: allocate continuous block of ips + pass + raise Exception("Not implemented") ## Rollback any previously allocated addresses in case this allocation request contains multiple ones and failed in the middle def rollback(allocation_result): for allocation in reversed(allocation_result): logging.info(f"Rolling back allocation {str(allocation)}") ipAddresses = allocation.get("ipAddresses", None) - - ## release the address - + for ipAddress in ipAddresses: + swis.invoke("IPAM.SubnetManagement", "ChangeIPStatus", ipAddress, "Available") return + +def check_dns(ipAddress): + logging.info(f"Checking DNS for {ipAddress}...") + try: + socket.gethostbyaddr(ipAddress) + except Exception as e: + if '[Errno 1]' in str(e): + # No PTR record for that IP + logging.info(f"Great news: No DNS record found.") + return True + else: + logging.info(f"Encountered an error: {e}.") + return False + else: + # There might be a conflict + return False + logging.info(f"Uh-oh, found a conflicting DNS record.") diff --git a/src/main/python/deallocate_ip/requirements.txt b/src/main/python/deallocate_ip/requirements.txt index 8024749..7f28500 100755 --- a/src/main/python/deallocate_ip/requirements.txt +++ b/src/main/python/deallocate_ip/requirements.txt @@ -1 +1,2 @@ -requests==2.21.0 \ No newline at end of file +requests==2.21.0 +orionsdk \ No newline at end of file diff --git a/src/main/python/deallocate_ip/source.py b/src/main/python/deallocate_ip/source.py index 268be82..6585675 100755 --- a/src/main/python/deallocate_ip/source.py +++ b/src/main/python/deallocate_ip/source.py @@ -12,52 +12,8 @@ conditions of the subcomponent's license, as noted in the LICENSE file. import requests from vra_ipam_utils.ipam import IPAM import logging +from orionsdk import SwisClient -""" -Example payload: - -"inputs": { - "resourceInfo": { - "id": "11f912e71454a075574a728848458", - "name": "external-ipam-it-mcm-323412", - "description": "test", - "type": "VM", - "owner": "mdzhigarov@vmware.com", - "orgId": "ce811934-ea1a-4f53-b6ec-465e6ca7d126", - "properties": { - "osType": "WINDOWS", - "vcUuid": "ff257ed9-070b-45eb-b2e7-d63926d5bdd7", - "__moref": "VirtualMachine:vm-288560", - "memoryGB": "4", - "datacenter": "Datacenter:datacenter-2", - "provisionGB": "1", - "__dcSelfLink": "/resources/groups/b28c7b8de065f07558b1612fce028", - "softwareName": "Microsoft Windows XP Professional (32-bit)", - "__computeType": "VirtualMachine", - "__hasSnapshot": "false", - "__placementLink": "/resources/compute/9bdc98681fb8b27557252188607b8", - "__computeHostLink": "/resources/compute/9bdc98681fb8b27557252188607b8" - } - }, - "ipDeallocations": [ - { - "id": "111bb2f0-02fd-4983-94d2-8ac11768150f", - "ipRangeId": "network/ZG5zLm5ldHdvcmskMTAuMjMuMTE3LjAvMjQvMA:10.23.117.0/24/default", - "ipAddress": "10.23.117.5" - } - ], - "endpoint": { - "id": "f097759d8736675585c4c5d272cd", - "endpointProperties": { - "hostName": "sampleipam.sof-mbu.eng.vmware.com", - "projectId": "111bb2f0-02fd-4983-94d2-8ac11768150f", - "providerId": "d8a5e3f2-d839-4365-af5b-f48de588fdc1", - "certificate": "-----BEGIN CERTIFICATE-----\nMIID0jCCArqgAwIBAgIQQaJF55UCb58f9KgQLD/QgTANBgkqhkiG9w0BAQUFADCB\niTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1\nbm55dmFsZTERMA8GA1UEChMISW5mb2Jsb3gxFDASBgNVBAsTC0VuZ2luZWVyaW5n\nMSgwJgYDVQQDEx9pbmZvYmxveC5zb2YtbWJ1LmVuZy52bXdhcmUuY29tMB4XDTE5\nMDEyOTEzMDExMloXDTIwMDEyOTEzMDExMlowgYkxCzAJBgNVBAYTAlVTMRMwEQYD\nVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQHEwlTdW5ueXZhbGUxETAPBgNVBAoTCElu\nZm9ibG94MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEoMCYGA1UEAxMfaW5mb2Jsb3gu\nc29mLW1idS5lbmcudm13YXJlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\nAQoCggEBAMMLNTqbAri6rt/H8iC4UgRdN0qj+wk0R2blmD9h1BiZJTeQk1r9i2rz\nzUOZHvE8Bld8m8xJ+nysWHaoFFGTX8bOd/p20oJBGbCLqXtoLMMBGAlP7nzWGBXH\nBYUS7kMv/CG+PSX0uuB0pRbhwOFq8Y69m4HRnn2X0WJGuu+v0FmRK/1m/kCacHga\nMBKaIgbwN72rW1t/MK0ijogmLR1ASY4FlMn7OBHIEUzO+dWFBh+gPDjoBECTTH8W\n5AK9TnYdxwAtJRYWmnVqtLoT3bImtSfI4YLUtpr9r13Kv5FkYVbXov1KBrQPbYyp\n72uT2ZgDJT4YUuWyKpMppgw1VcG3MosCAwEAAaM0MDIwMAYDVR0RBCkwJ4cEChda\nCoIfaW5mb2Jsb3guc29mLW1idS5lbmcudm13YXJlLmNvbTANBgkqhkiG9w0BAQUF\nAAOCAQEAXFPIh00VI55Sdfx+czbBb4rJz3c1xgN7pbV46K0nGI8S6ufAQPgLvZJ6\ng2T/mpo0FTuWCz1IE9PC28276vwv+xJZQwQyoUq4lhT6At84NWN+ZdLEe+aBAq+Y\nxUcIWzcKv8WdnlS5DRQxnw6pQCBdisnaFoEIzngQV8oYeIemW4Hcmb//yeykbZKJ\n0GTtK5Pud+kCkYmMHpmhH21q+3aRIcdzOYIoXhdzmIKG0Och97HthqpvRfOeWQ/A\nPDbxqQ2R/3D0gt9jWPCG7c0lB8Ynl24jLBB0RhY6mBrYpFbtXBQSEciUDRJVB2zL\nV8nJiMdhj+Q+ZmtSwhNRvi2qvWAUJQ==\n-----END CERTIFICATE-----\n" - }, - "authCredentialsLink": "/core/auth/credentials/13c9cbade08950755898c4b89c4a0" - } - } -""" def handler(context, inputs): ipam = IPAM(context, inputs) @@ -66,10 +22,14 @@ def handler(context, inputs): return ipam.deallocate_ip() def do_deallocate_ip(self, auth_credentials, cert): - # Your implemention goes here - username = auth_credentials["privateKeyId"] password = auth_credentials["privateKey"] + hostname = self.inputs['endpoint']['endpointProperties']['hostName'] + + global swis + swis = SwisClient(hostname, username, password) + requests.packages.urllib3.disable_warnings() + deallocation_result = [] for deallocation in self.inputs["ipDeallocations"]: deallocation_result.append(deallocate(self.inputs["resourceInfo"], deallocation)) @@ -85,11 +45,7 @@ def deallocate(resource, deallocation): resource_id = resource["id"] logging.info(f"Deallocating ip {ip} from range {ip_range_id}") - - ## Plug your implementation here to deallocate an already allocated ip address - ## ... - ## Deallocation successful - + swis.invoke("IPAM.SubnetManagement", "ChangeIPStatus", ip, "Available") return { "ipDeallocationId": deallocation["id"], "message": "Success" diff --git a/src/main/python/get_ip_ranges/source.py b/src/main/python/get_ip_ranges/source.py index 9ae44e4..6d5dbf7 100755 --- a/src/main/python/get_ip_ranges/source.py +++ b/src/main/python/get_ip_ranges/source.py @@ -15,24 +15,6 @@ import logging from orionsdk import SwisClient import ipaddress -''' -Example payload: - -"inputs": { - "endpoint": { - "id": "f097759d8736675585c4c5d272cd", - "authCredentialsLink": "/core/auth/credentials/13c9cbade08950755898c4b89c4a0", - "endpointProperties": { - "hostName": "sampleipam.sof-mbu.eng.vmware.com", - "certificate": "-----BEGIN CERTIFICATE-----\nMIID0jCCArqgAwIBAgIQQaJF55UCb58f9KgQLD/QgTANBgkqhkiG9w0BAQUFADCB\niTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVN1\nbm55dmFsZTERMA8GA1UEChMISW5mb2Jsb3gxFDASBgNVBAsTC0VuZ2luZWVyaW5n\nMSgwJgYDVQQDEx9pbmZvYmxveC5zb2YtbWJ1LmVuZy52bXdhcmUuY29tMB4XDTE5\nMDEyOTEzMDExMloXDTIwMDEyOTEzMDExMlowgYkxCzAJBgNVBAYTAlVTMRMwEQYD\nVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQHEwlTdW5ueXZhbGUxETAPBgNVBAoTCElu\nZm9ibG94MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEoMCYGA1UEAxMfaW5mb2Jsb3gu\nc29mLW1idS5lbmcudm13YXJlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\nAQoCggEBAMMLNTqbAri6rt/H8iC4UgRdN0qj+wk0R2blmD9h1BiZJTeQk1r9i2rz\nzUOZHvE8Bld8m8xJ+nysWHaoFFGTX8bOd/p20oJBGbCLqXtoLMMBGAlP7nzWGBXH\nBYUS7kMv/CG+PSX0uuB0pRbhwOFq8Y69m4HRnn2X0WJGuu+v0FmRK/1m/kCacHga\nMBKaIgbwN72rW1t/MK0ijogmLR1ASY4FlMn7OBHIEUzO+dWFBh+gPDjoBECTTH8W\n5AK9TnYdxwAtJRYWmnVqtLoT3bImtSfI4YLUtpr9r13Kv5FkYVbXov1KBrQPbYyp\n72uT2ZgDJT4YUuWyKpMppgw1VcG3MosCAwEAAaM0MDIwMAYDVR0RBCkwJ4cEChda\nCoIfaW5mb2Jsb3guc29mLW1idS5lbmcudm13YXJlLmNvbTANBgkqhkiG9w0BAQUF\nAAOCAQEAXFPIh00VI55Sdfx+czbBb4rJz3c1xgN7pbV46K0nGI8S6ufAQPgLvZJ6\ng2T/mpo0FTuWCz1IE9PC28276vwv+xJZQwQyoUq4lhT6At84NWN+ZdLEe+aBAq+Y\nxUcIWzcKv8WdnlS5DRQxnw6pQCBdisnaFoEIzngQV8oYeIemW4Hcmb//yeykbZKJ\n0GTtK5Pud+kCkYmMHpmhH21q+3aRIcdzOYIoXhdzmIKG0Och97HthqpvRfOeWQ/A\nPDbxqQ2R/3D0gt9jWPCG7c0lB8Ynl24jLBB0RhY6mBrYpFbtXBQSEciUDRJVB2zL\nV8nJiMdhj+Q+ZmtSwhNRvi2qvWAUJQ==\n-----END CERTIFICATE-----\n" - } - }, - "pagingAndSorting": { - "maxResults": 1000, - "pageToken": "789c55905d6e02310c84df7d91452a456481168ec04b55950344f9db55dadd384abc056e5f3b42adfa12299f279ec9ac7c5670e9b0045a4ad2430c93af7a465f3bc83d4f9e3aa8976e6681ce660c827770de2aa1a68c72dfc3cae74393999b2e4df302e72691373aa60199bd827398efac18810f87a952591c61817c849513999df0b6c11436d6d400effcfacc14f2099cd6768913c5a435a0fd0c8e20ab2dbcd147564a2228c93b60b99ae2d94efde6ac640a09d9331130c539367078c41c915067ac9122268dc350439bf3379e9bc01b32025e9bd111aa65c829e89e83f0135ba740572c5f525c73f95faa608e39e55e62c6fcbd37de9775b891212a758d59bceb7a0eb30d7c7f6cd35c1399984291053b30f29fc5feed6cedf7adfe21962ab17b8ebde5089b1fec0d97d7-e5c4e5a1d726f600c22ebfd9f186148a1449755fd79a69ceabfe2aa" - } - } -''' def handler(context, inputs): ipam = IPAM(context, inputs) diff --git a/src/main/python/validate_endpoint/source.py b/src/main/python/validate_endpoint/source.py index 136625b..0b0e473 100755 --- a/src/main/python/validate_endpoint/source.py +++ b/src/main/python/validate_endpoint/source.py @@ -15,19 +15,6 @@ from vra_ipam_utils.exceptions import InvalidCertificateException import logging from orionsdk import SwisClient - -''' -Example payload: - -"inputs": { - "authCredentialsLink": "/core/auth/credentials/13c9cbade08950755898c4b89c4a0", - "endpointProperties": { - "hostName": "sampleipam.sof-mbu.eng.vmware.com" - } - } -''' - - def handler(context, inputs): ipam = IPAM(context, inputs) @@ -36,8 +23,6 @@ def handler(context, inputs): return ipam.validate_endpoint() def do_validate_endpoint(self, auth_credentials, cert): - # Your implemention goes here - username = auth_credentials["privateKeyId"] password = auth_credentials["privateKey"] hostname = self.inputs["endpointProperties"]["hostName"]