Skip to content

Commit

Permalink
Merge pull request #15 from RichardORCL/main
Browse files Browse the repository at this point in the history
Added Script for controlling IP Addresses of the Target Assets in a migration plan
  • Loading branch information
lgabriel-oracle authored Nov 14, 2024
2 parents 76087bb + 1d4a4eb commit b44747e
Show file tree
Hide file tree
Showing 6 changed files with 368 additions and 1 deletion.
3 changes: 2 additions & 1 deletion Scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ These are example scripts that support the [Oracle Cloud Migrations](https://doc
|[cleanup_volumes.py](cleanup_volumes/README.md)|Terminates unattached volumes.|CloudShell|27 Mar 2024|
|[rename_move_volumes.py](rename_move_volumes/readme.md)|Renames and moves block volumes attached to an instance.|Any OCI Config|10 Jun 2024|
|[terminate_nat_gateway](terminate_nat_gateway/README.md)|Workaround for the default Hydration Agent virtual network created by the Oracle Cloud Migrations service.|Resource Manager|12 Jun 2024|
|[SetupWorkloads.ps1](setup_workloads/README.md)|Helpful scripts for setting up a training lab.|PowerShell|18 Apr 2024|
|[SetupWorkloads.ps1](setup_workloads/README.md)|Helpful scripts for setting up a training lab.|PowerShell|18 Apr 2024|
|[SetIP.py](SetIP/README.md)|Manage the private IP address of the target Assets in a migration plan|Python|11 Nov 2024|
87 changes: 87 additions & 0 deletions Scripts/SetIP/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Oracle Cloud Migration - Set IP Address for Target Assets

This script allow you to set a predetermined IP Address for your Assets in an Oracle Cloud Migration Plan

You have 4 options on how you can control the Target Asset IP Address
- Based on existing IP Address (IP Address of the Source VM)
- Based on a text file where you can Map IP addresses to VM Names
- Based on free form tags that are part of the migration plan
- Based on a Custom Attribute in vCenter set in the source VM


## Usage
```
usage: SetIP.py [-h] [-cp CONFIG_PROFILE] [-ip] [-dt] [-rg REGION]
(-targetasset_id TARGETOCID | -migrationplan_id MIGRATIONPLANOCID) (-fixip | -ipmap IPMAP | -tagmap | -vcenter)
options:
-h, --help show this help message and exit
-cp CONFIG_PROFILE Config Profile inside the config file
-ip Use Instance Principals for Authentication
-dt Use Delegation Token for Authentication
-rg REGION Region
-targetasset_id TARGETOCID Target Asset OCID
-migrationplan_id MIGRATIONPLANOCID Migration Plan OCID
-fixip Set static IP to existing Static IP
-ipmap IPMAP Set static IP to mappings based in a file
-tagmap Set static IP based on free form tags on the migration plan
-vcenter Set static IP based on vCenter Customer attributes
```

## How to run the script

### OCI CLI
You can run the script from any place where you have setup the OCI CLI tool with the correct authentication.

## Cloud Shell
You can run the script from the cloud shell, just clone this repo into your cloud shell. When you run the script
add the option **-dt** this will let you use the cloud shell delegated authentication. This means it will use the
current user logged into cloud shell to run the script

## Instance Principle
You can run the script inside a VM in OCI. If that VM belongs to a dynamic group that has the right permissions you
can use the option **-ip** to use the instance's Dynamic group to authenticate against OCI.

More info: https://www.ateam-oracle.com/post/calling-oci-cli-using-instance-principal

## Setting the IP address of your Target Assets

### Setting IP Addresses based on Source VMs
Using the **-fixip** option, the script will check the IP Address(es) of the source VM. If an IP Address is found that matches the
target Subnet's CIDR if will configure the target VM with this IP address

### Setting IP Addresses based on a Mapping File
Using the **-ipmap [filename]** option, you can create a mapping file. See the **ipmap.txt** example file. Per line in the file specify the
IP Address and Virtual Machine name, seperated with a ;

If a matching VM is found in the mapping file, it will use that IP address to setup the target VM

### Setting IP Address based on Free Form Tags
Using the **-tagmap** option, you can map IP addresses using the OCI Free-form tags. You can create one or multiple Free-form tags
on the migration plan. The **Tag Key** should match the VM Name and the **Tag Value** should contain the IP Address
you want to have configured for this VM

**IMPORTANT: ** OCI Support only up to 10 Free-form tags per object. So using Free-form tags you can only make 10
assignments per migration plan.

<img src=tag_example.png width="400">

### Setting IP Address based on Customer Attribute set in vCenter
Using the **-vcenter** option, you can create in vCenter a Custom Attribute on the source VM. The name of
the attribute can be anything, but the value must start with **OCI:** and then followed with the IP Address you want to use.

<img src=vcenter_example.png width="400">

## Validation
When the script runs, if will check if an assigned/mapped IP address is found and if this IP Address is part of the CIDR
range of the target Subnet in OCI. It will also check if there is no conflict with the default gateway in that subnet or with any
current running services that are using this subnet.


# License

Copyright (c) 2024 Oracle and/or its affiliates.

Licensed under the Universal Permissive License (UPL), Version 1.0.

See [LICENSE](https://github.com/oracle-devrel/technology-engineering/blob/main/LICENSE) for more details.
277 changes: 277 additions & 0 deletions Scripts/SetIP/SetIP.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
import oci
import sys
import argparse
import os
import ipaddress

configfile = "~/.oci/config"
configProfile = "DEFAULT"

def create_signer(config_profile, is_instance_principals, is_delegation_token):

# Instance principals authentications - for use with OCI Dynamic Groups
if is_instance_principals:
try:
signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner()
config = {'region': signer.region, 'tenancy': signer.tenancy_id}
return config, signer

except Exception:
print("Error obtaining instance principals certificate, aborting")
sys.exit(-1)

# Delegation Token authentications - for use with OCi Cloud Shell
elif is_delegation_token:

print ("Using delegation token - should only run in cloud shell")
try:
# check if env variables OCI_CONFIG_FILE, OCI_CONFIG_PROFILE exist and use them
env_config_file = os.environ.get('OCI_CONFIG_FILE')
env_config_section = os.environ.get('OCI_CONFIG_PROFILE')

# check if file exist
if env_config_file is None or env_config_section is None:
print("*** OCI_CONFIG_FILE and OCI_CONFIG_PROFILE env variables not found, abort. ***")
print("")
sys.exit(-1)

config = oci.config.from_file(env_config_file, env_config_section)
delegation_token_location = config["delegation_token_file"]

with open(delegation_token_location, 'r') as delegation_token_file:
delegation_token = delegation_token_file.read().strip()
# get signer from delegation token
signer = oci.auth.signers.InstancePrincipalsDelegationTokenSigner(delegation_token=delegation_token)

return config, signer

except KeyError:
print("* Key Error obtaining delegation_token_file")
sys.exit(-1)

except Exception:
raise

# Config file authentications - for use with OCI Config file
else:
try:
config = oci.config.from_file(
oci.config.DEFAULT_LOCATION,
(config_profile if config_profile else oci.config.DEFAULT_PROFILE)
)
signer = oci.signer.Signer(
tenancy=config["tenancy"],
user=config["user"],
fingerprint=config["fingerprint"],
private_key_file_location=config.get("key_file"),
pass_phrase=oci.config.get_config_value_or_default(config, "pass_phrase"),
private_key_content=config.get("key_content")
)
except:
print("Error obtaining authentication, did you configure config file? aborting")
sys.exit(-1)

return config, signer


def input_command_line(help=False):
parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=80, width=130))
parser.add_argument('-cp', default="", dest='config_profile', help='Config Profile inside the config file')
parser.add_argument('-ip', action='store_true', default=False, dest='is_instance_principals', help='Use Instance Principals for Authentication')
parser.add_argument('-dt', action='store_true', default=False, dest='is_delegation_token', help='Use Delegation Token for Authentication')
parser.add_argument("-rg", default="", dest='region', help="Region")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-targetasset_id", default="", dest='targetOCID', help="Target Asset OCID")
group.add_argument("-migrationplan_id", default="", dest='migrationPlanOCID', help="Migration Plan OCID")
group2 = parser.add_mutually_exclusive_group(required=True)
group2.add_argument("-fixip", action='store_true', default=False, dest='fixip', help="Set static IP to existing Static IP")
group2.add_argument("-ipmap", default="", dest='ipmap', help="Set static IP to mappings based in a file")
group2.add_argument("-tagmap", action='store_true', default=False, dest='tagmap', help="Set static IP based on free form tags on the migration plan")
group2.add_argument("-vcenter", action='store_true', default=False, dest='vcenter', help="Set static IP based on vCenter Customer attributes")

cmd = parser.parse_args()
if help:
parser.print_help()
return cmd

def clearLine():
print("\033[A \033[A")

def is_valid_ip(ip_string):
try:
# Attempt to create an IPv4 or IPv6 address object
ipaddress.ip_address(ip_string)
return True # If no exception, it’s a valid IP address
except:
return False # If exception occurs, it's not a valid IP address

# Get OCM Target Asset (Part of the OCM Migration Plan)
def getTarget(region, targetID, signer):
migrations = oci.cloud_migrations.MigrationClient(config, signer=signer)
data = migrations.get_target_asset(target_asset_id=targetID).data
return data

def processTarget(region, targetID, cmd, signer):
print(f"Getting {cmd.targetOCID}")
migrations = oci.cloud_migrations.MigrationClient(config, signer=signer)
network = oci.core.VirtualNetworkClient(config, signer=signer)
target = migrations.get_target_asset(target_asset_id=targetID).data
if target:
migration_asset = target.migration_asset
source_asset = migration_asset.source_asset_data
computeNics = source_asset['compute']['nics']
clearLine()
print(f"VM: {source_asset['displayName']}")
# Get the Target's assigned OCI VCN/Subnet
try:
targetSubnetOCID = target.user_spec.create_vnic_details.subnet_id
except:
targetSubnetOCID = target.recommended_spec.create_vnic_details.subnet_id
if not targetSubnetOCID:
targetSubnetOCID = target.recommended_spec.create_vnic_details.subnet_id

if targetSubnetOCID:
subnetDetails = network.get_subnet(subnet_id=targetSubnetOCID).data
print (f" - Target OCI Subnet: {subnetDetails.display_name} [{subnetDetails.cidr_block}]")

foundMatch = False
# Set IP based on source IP Address
if cmd.fixip:
IPs = ""
# Find know IP address of the source VM that matches the OCI VCN/Subnet range
for nic in computeNics:
if len(nic['ipAddresses']) > 0:
IPs = IPs + nic['ipAddresses'][0] + " "
if ipaddress.ip_address(nic['ipAddresses'][0]) in ipaddress.ip_network(subnetDetails.cidr_block):
MappedIP = nic['ipAddresses'][0]
foundMatch = True
break
else:
print (" - No source IP Address found")
if not foundMatch:
print (" - No possible IP match found to target subnet: " + IPs)

# Set IP based on matching IP:VM_name in ipmapping file
if cmd.ipmap:
if os.path.exists(cmd.ipmap):
with open(cmd.ipmap, "r") as file:
for line in file:
mapip, vmname = line.strip().split(";")
if vmname == source_asset['displayName']:
MappedIP = mapip
foundMatch = True
break
else:
print ("IP Map file not found")
exit()

# Set IPs based on Free form tags on the migration plan
# format should be: key: vmname value:ipaddress
if cmd.tagmap:
plan = migrations.get_migration_plan(migration_plan_id=target.migration_plan_id).data
for tag_vm, tag_ip in plan.freeform_tags.items():
if tag_vm == source_asset['displayName']:
MappedIP = tag_ip
foundMatch = True
break

if cmd.vcenter:
customFields = source_asset['vmwareVm']['customerFields']
for customField in customFields:
if customField.startswith("OCI:"):
prefix , MappedIP = customField.strip().split(":")
foundMatch = True
break

if foundMatch:
migration_asset = target.migration_asset
user_spec = oci.cloud_migrations.models.launch_instance_details.LaunchInstanceDetails()
user_spec = target.user_spec
source_asset = migration_asset.source_asset_data

conflict = False
# Check if IP addres is valid
if is_valid_ip(MappedIP):
pass
else:
print (" - ERROR: Mapped IP is not valid IP address: {}".format(MappedIP))
conflict = True

# Check if IP address is part of subnet CIDR
if not conflict:
if ipaddress.ip_address(MappedIP) in ipaddress.ip_network(subnetDetails.cidr_block):
pass
else:
print (" - Mapped IP [{}] does not match target subnet [{}]".format(mapip,subnetDetails.cidr_block))
conflict = True

# Check IP address does not conflict with default gateway of the subnet
if not conflict:
if ipaddress.ip_address(MappedIP) == ipaddress.ip_network(subnetDetails.cidr_block)[0]:
print (" - CONFLICT - You can not use the broadcast address [{}] in the CIDR range [{}]".format(MappedIP, subnetDetails.cidr_block))
conflict = True
if ipaddress.ip_address(MappedIP) == ipaddress.ip_network(subnetDetails.cidr_block)[1]:
print (" - CONFLICT - You can not use the first available address [{}] in the CIDR range [{}], this is reservered for the gateway".format(MappedIP, subnetDetails.cidr_block))
conflict = True

# Get OCI Subnet currently used IPs and check for conflict
if not conflict:
print (" - Checking for IP Conflicts...")
ip_inventory = network.get_subnet_ip_inventory(subnet_id=targetSubnetOCID).data
for usedIP in ip_inventory.ip_inventory_subnet_resource_summary:
if usedIP.ip_address == MappedIP:
clearLine()
print (" - CONFLICT - Trying to assign IP {}, but is already been used by {} - {}".format(MappedIP, usedIP.dns_host_name, usedIP.ip_id))
print (" ")
conflict = True
break
clearLine()

# Set validated mapped IP to the Target Asset
if not conflict:
vnic_details = oci.cloud_migrations.models.CreateVnicDetails()
vnic_details.private_ip = MappedIP
vnic_details.subnet_id = targetSubnetOCID
user_spec.create_vnic_details = vnic_details
print(f" - Setting target ip to [{MappedIP}]")
migrations = oci.cloud_migrations.MigrationClient(config, signer=signer)
update_details = oci.cloud_migrations.models.UpdateTargetAssetDetails()
target_details = oci.cloud_migrations.models.UpdateVmTargetAssetDetails()
target_details.type = "INSTANCE"
target_details.user_spec = user_spec
try:
result = migrations.update_target_asset(target_asset_id=targetID,update_target_asset_details=target_details, retry_strategy=oci.retry.DEFAULT_RETRY_STRATEGY)
except oci.exceptions.ServiceError as response:
print (" - ERROR: {}-{}".format(response.code, response.message))
else:
print (" - No IP match found, no fixed mapped applied")

else:
print (" - Error - No OCI Subnet assign to this target")


cmd = input_command_line()
configProfile = cmd.config_profile if cmd.config_profile else configProfile
config, signer = create_signer(cmd.config_profile, cmd.is_instance_principals, cmd.is_delegation_token)

if cmd.region: # Override default region when a specific region is specified as parameter
config["region"] = cmd.region

identity = oci.identity.IdentityClient(config, signer=signer)
tenancy = identity.get_tenancy(config['tenancy']).data

print ("Oracle Cloud Migrations (OCM) - Target Asset fixed IP tool")
print ("Tenancy: {}".format(tenancy.name))

# Process one individual Target Asset
if cmd.targetOCID:
processTarget(config["region"], cmd.targetOCID, cmd, signer)

# Process all Target Assets in one Migration Plan
elif cmd.migrationPlanOCID:
migrations = oci.cloud_migrations.MigrationClient(config, signer=signer)
targets = migrations.list_target_assets(migration_plan_id=cmd.migrationPlanOCID).data.items
for target in targets:
processTarget(config["region"], target.id, cmd, signer)
else:
print ("Please specify target asset OCID or Migration Plan OCID")
2 changes: 2 additions & 0 deletions Scripts/SetIP/ipmap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
10.100.10.11;TestVM01
10.100.10.12;TestVM02
Binary file added Scripts/SetIP/tag_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Scripts/SetIP/vcenter_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit b44747e

Please sign in to comment.