Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
156 lines (148 sloc) 7.45 KB
#!/usr/bin/env python3
__author__ = "Angus Kelsey <nlseven@users.noreply.github.com>"
__copyright__ = "Copyright 2019"
__license__ = "MIT"
from xml.dom.minidom import parseString
import warnings
import sys
import subprocess
from yaml import load, dump
from yaml import CLoader as Loader, CDumper as Dumper
def live_data():
# Export the current list of network interfaces from VMware,
# as well as the current cloud-config from RancherOS
grab_vcloud = "/usr/bin/vmtoolsd --cmd 'info-get guestinfo.ovfEnv'"
grab_ros = "/usr/bin/sudo ros config export"
dasXML = subprocess.Popen(grab_vcloud,shell=True,stdout=subprocess.PIPE).stdout.read()
dasYAML = subprocess.Popen(grab_ros,shell=True,stdout=subprocess.PIPE).stdout.read()
return dasXML, dasYAML
def create_dict(rawData):
# Convert the OVFEnv XML into a dictionary
ovfData = parseString(rawData)
ovfKeys = {}
# Only get keys in the <Property> lines
for ovfkey in ovfData.getElementsByTagName('Property'):
key, value = [ ovfkey.attributes['oe:key'].value,
ovfkey.attributes['oe:value'].value ]
ovfKeys[key] = value
return ovfKeys
def builder(ovfKeys):
# Build up a dictionary of network interfaces that we can return, for up to three NICs
# Note: this could be done a whole lot better
# Check for a primary NIC, and die if it doesn't exist
# as sometimes vCloud doesn't set the guestinfo
if not 'vCloud_ip_0' in ovfKeys:
warnings.warn("Can't find a primary NIC! Machine may need to restart first.",Warning)
sys.exit(1)
ovfKeys["interfaces"] = {}
# In these loops: check for either one, two or three interfaces.
# Set the MAC addresses as the interface names because vCloud/Terraform likes to screw up the order
if all (key in ovfKeys for key in ("vCloud_ip_0", "vCloud_ip_1", "vCloud_ip_2")):
print("Found three NICs!")
ovfKeys["num"] = 3
# Convert netmask binary bits into CIDR slash notation
eth0cidr = sum(bin(int(digit)).count('1') for digit in ovfKeys["vCloud_netmask_0"].split('.'))
eth1cidr = sum(bin(int(digit)).count('1') for digit in ovfKeys["vCloud_netmask_1"].split('.'))
eth2cidr = sum(bin(int(digit)).count('1') for digit in ovfKeys["vCloud_netmask_2"].split('.'))
# Update the IPs to use as proper CIDRs
ovfKeys["vCloud_ip_0"] += "/" + str(eth0cidr)
ovfKeys["vCloud_ip_1"] += "/" + str(eth1cidr)
ovfKeys["vCloud_ip_2"] += "/" + str(eth2cidr)
# Use Rancher's format: https://rancher.com/docs/os/v1.2/en/networking/interfaces/
key1 = 'mac=' + ovfKeys["vCloud_macaddr_0"]
key2 = 'mac=' + ovfKeys["vCloud_macaddr_1"]
key3 = 'mac=' + ovfKeys["vCloud_macaddr_2"]
# Build an interfaces dictionary in case we need to save the YAML data
# Use the MAC address as the name in case the interfaces aren't in right order
ovfKeys["interfaces"] = {
key1: {'address': ovfKeys["vCloud_ip_0"],'gateway': ovfKeys["vCloud_gateway_0"], 'dhcp': False},
key2: {'address': ovfKeys["vCloud_ip_1"], 'dhcp': False},
key3: {'address': ovfKeys["vCloud_ip_2"], 'dhcp': False}
}
# Repeat for 2 NICs... then 1...
elif all (key in ovfKeys for key in ("vCloud_ip_0", "vCloud_ip_1")):
print("Found two NICs!")
ovfKeys["num"] = 2
eth0cidr = sum(bin(int(digit)).count('1') for digit in ovfKeys["vCloud_netmask_0"].split('.'))
eth1cidr = sum(bin(int(digit)).count('1') for digit in ovfKeys["vCloud_netmask_1"].split('.'))
ovfKeys["vCloud_ip_0"] += "/" + str(eth0cidr)
ovfKeys["vCloud_ip_1"] += "/" + str(eth1cidr)
# Use Rancher's format: https://rancher.com/docs/os/v1.2/en/networking/interfaces/
key1 = 'mac=' + ovfKeys["vCloud_macaddr_0"]
key2 = 'mac=' + ovfKeys["vCloud_macaddr_1"]
ovfKeys["interfaces"] = {
key1: {'address': ovfKeys["vCloud_ip_0"],'gateway': ovfKeys["vCloud_gateway_0"], 'dhcp': False},
key2: {'address': ovfKeys["vCloud_ip_1"], 'dhcp': False}
}
else:
print ("Single NIC found!")
ovfKeys["num"] = 1
eth0cidr = sum(bin(int(digit)).count('1') for digit in ovfKeys["vCloud_netmask_0"].split('.'))
ovfKeys["vCloud_ip_0"] += "/" + str(eth0cidr)
# Use Rancher's format: https://rancher.com/docs/os/v1.2/en/networking/interfaces/
key1 = 'mac=' + ovfKeys["vCloud_macaddr_0"]
ovfKeys["interfaces"] = {
key1: {'address': ovfKeys["vCloud_ip_0"],'gateway': ovfKeys["vCloud_gateway_0"], 'dhcp': False}}
return ovfKeys
def exit_lane(ovfKeys,ros_yaml):
# Check if configuration needs updating. If not, exit this script!
ovfKeys["update"] = False
# cconfig == cloud-config, which we grabbed from ros export - load it in as a dictionary
cconfig = load(ros_yaml,Loader=Loader)
# Check if we have a proper network interfaces section at all
if "interfaces" not in cconfig["rancher"]["network"]:
print("No interfaces section exists! Need to create.")
ovfKeys["update"] = True
return
# Iterate through X interfaces reported by vCloud, and check their config
for eth in range (0,ovfKeys["num"]):
currInterface = "mac=" + ovfKeys["vCloud_macaddr_"+str(eth)]
# Is there a config section for this particular MAC address/interface?
# If not, flag for update and stop checking this NIC
if currInterface not in cconfig["rancher"]["network"]["interfaces"]:
print("Configuration does not contain " + currInterface + ". Need to add.")
ovfKeys["update"] = True
continue
# If so, is the config correct?
if cconfig["rancher"]["network"]["interfaces"][currInterface]["address"] == ovfKeys["vCloud_ip_"+str(i)]:
print ("Interface " + currInterface + " is up to date.")
else:
print ("Interface " + str(eth) + " needs updating.")
ovfKeys["update"] = True
# All interfaces checked. Exist the script if no updates are needed.
if not ovfKeys["update"]:
print("Everything is up to date. Bye!")
sys.exit(0)
def ros_yaml(ovfKeys,ros_yaml):
# Update required, this function will render the YAML, merge config and reboot
#
# Create a skeleton dictionary
newSpec = {'rancher': {'network': {}}}
# Copy what we built back in builder() into the skeleton
newSpec["rancher"]["network"]["interfaces"] = ovfKeys["interfaces"].copy()
# Let the user see some pretty YAML
print(dump(newSpec,default_flow_style=False))
# Save it to /tmp
with open('/tmp/ros_yaml.yaml','w') as cloud_config:
cloud_config.write(dump(newSpec,default_flow_style=False))
# Ensure it doesn't trip up RancherOS - wait for verify to complete
ros_verify = "/usr/bin/sudo ros config validate -i /tmp/ros_yaml.yaml"
validator = subprocess.Popen(ros_verify,shell=True,stdout=subprocess.PIPE)
validator.communicate()[0]
is_valid = validator.returncode
# Confusing variable name - an exit code of 0 means it IS valid
if is_valid == 0:
print("Rancher has verified the change. Merging and rebooting ...")
ros_merge = "/usr/bin/sudo ros config merge -i /tmp/ros_yaml.yaml"
subprocess.Popen(ros_merge,shell=True,stdout=subprocess.PIPE)
subprocess.run(["sudo","reboot"])
else:
# I don't know what else to check for if it doesn't validate
print("Something went wrong. ROS returned " + str(is_valid) + ",\ntried running "+ ros_verify)
sys.exit(1)
# Now do it in the correct order
data, ros_yaml = live_data()
parsed = create_dict(data)
parsed = builder(parsed)
exit_lane(parsed,ros_yaml)
ros_yaml(parsed,ros_yaml)
You can’t perform that action at this time.