Last active
December 27, 2015 12:59
-
-
Save darKoram/7329543 to your computer and use it in GitHub Desktop.
Re map dynamic ips to static ips for vms hosted on vsphere.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Requires pysphere (pip install -U pysphere) and python-nmap | |
# Requires argparse (standard lib for python 2.7) | |
# This assumes you have the names of your new vms, but not the ips that were dhcp'd. | |
# It also assumes you have a pool of static ips to select from, some of which may already be taken. | |
# static_network_ips.py takes a subnet range and scans for free ips. | |
# remap_interfaces.py takes a dict object and fills in the static ips. | |
# To remap, you will need a dict with the dynamic ips associated with the names. | |
# Creating this dict can be done using pysphere's get_properties('ip_address') [not shown] | |
# Remapping requires modifying certain files which is os specific, so it is left to the client code to | |
# do the actual edits. Here is an example using ansible. | |
# common | |
# /etc/hosts | |
# ------- | |
# /etc/network/interfaces - ubuntu | |
# /etc/networks - ubuntu, centos but different flavors | |
# /etc/sysconfig/network-scripts/ifcfg-eth0 - centos | |
# /etc/udev/70-persistent-network.rules (can just delete this if you prefer) - centos | |
# Pushing the changes could be done with scp (using the dhcp ips) or using pysphere's send_file. | |
# Below is a snipped of ansible which I use. | |
requested_hosts: { meta: "", | |
hostvars: [ | |
hostname: "server1", | |
static_ip: "" # to be filled by python | |
dhcp_ip: "" # to be filled by python | |
status: "success/fail | |
}, | |
... more entries for hostvars list | |
] # end hostvars list | |
} # end requested_hosts dict | |
# With the completed dict, you can now run your favourite ssh tool or use pysphere's send_file method to put the files in place. Still to come is a third module that does that work | |
# which is os dependent. | |
# This ansible snippet uses vsphere which is not yet integrated into ansible proper. | |
# I got it from github and used the instructions on creating your own modules to put it in place. | |
# Determines settings based on ansible_os_family in OS_FAMILY here | |
# https://github.com/ansible/ansible/blob/devel/library/system/setup | |
# Good enough for centos/ubuntu. May need ansible_os_distro if | |
# there is more variation in network config files. | |
- name: Run static_network_ips.py on requested_hosts file. | |
local_action: shell python static_network_ips.py | |
"{{ANSIBLE_META_HOME}}/roles/accumulo/files/requested_hosts.json" | |
"10.0.9.0-255.96-112" | |
chdir="{{ANSIBLE_META_HOME}}/files/python" | |
register: request_hosts_new | |
- debug: msg="static_ips have been filled {{request_hosts_new.stdout}}" | |
# We can get the dhcp ips by getting facts from vsphere accessing vms by name. | |
# You could also do this using pysphere get_properties('ip_address') | |
- name: Get facts on newly created vms. | |
local_action: vsphere_facts host="{{vsphere_creds.esxserver}}" | |
login="{{vsphere_creds.esxlogin}}" | |
password="{{vsphere_creds.esxpassword}}" | |
guest="{{item}}" | |
with_items: | |
- "{{requested_hosts['hostvars'][0]['hostname']}}" | |
- "{{requested_hosts['hostvars'][1]['hostname']}}" | |
# This section is being replaced by a python module that uses jinja2 templating but not ansible directly. | |
- name: Create ifcfg files from templates | |
template: dest="/etc/sysconfig/network-scripts/ifcfg-{{item}}" | |
src="ifcfg.j2" | |
when: {{request_hosts[inventory_hostname].ansible_os_family == "RedHat"}} | |
- name: Fix /etc/network/interface | |
template: dest="/etc/network/interfaces" | |
src="ifcfg.j2" | |
when: {{request_hosts[inventory_hostname].ansible_os_family == "Debian"}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# {{ansible_managed}} | |
DEVICE={{iface}} | |
NM_CONTROLLED=yes | |
ONBOOT=yes | |
BOOTPROTO=static | |
IPADDR={{static_ip}} | |
NETMASK={{netmask_01}} | |
GATEWAY={{gateway_01}} | |
DNS2={{dns2_01}} | |
TYPE=Ethernet | |
DNS1={{dns1_01}} | |
USERCTL=no | |
PEERDNS=yes | |
IPV6INIT=no |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import sys | |
import simplejson as json | |
try | |
import pysphere | |
except ImportError as e: | |
raise ImportError(e, "You must pip install -U pysphere") | |
try: | |
import argparse | |
except ImportError as e: | |
raise ImportError(e, "remap_interfaces.py requires argparse, so python2.7 or greater.") | |
#TODO refactor to have requested_hosts as a class | |
# like vmware's "Query" or "Request" class | |
def remap_interfaces(requested_hosts, host, login, password): | |
''' | |
Remaps the ip interfaces in of vms in requested_hosts | |
@returns: requested_hosts with the static_ip field set | |
@ip_range: cidr in format "10.0.9.11/28" | |
or "10.0.9.0-255.1-16" | |
@requested_hosts: dict with a key hostvars | |
with value a list of dicts each | |
with at least the following keys: | |
static_ip, status | |
Note: scanning an ip range can take several minutes | |
for the range /n with n<30. The closer the n is to 32 | |
and the fewer hosts exist in that range the faster to scan. | |
''' | |
server = pysphere.VIServer() | |
try: | |
# server.connect(host, login, password, trace_file='debug.txt') | |
server.connect(host, login, password) | |
except Exception, e: | |
module.fail_json(msg='Failed to connect to %s: %s' % (host, e)) | |
def get_cluster(names, server, login=False, user=None, pwd=None): | |
vms = [] | |
for name in names: | |
vm = server.get_vm_by_name(name) | |
if login: | |
assert user, "Must give a user name if you want to login" | |
assert pwd, "Must give a pwd if you want to login" | |
vm.login_in_guest(user, pwd) | |
vms.append(vm) | |
return vms | |
def vm_ips(names, server, filename): | |
ips = [] | |
with open(filename, 'a') as f: | |
for name in names: | |
vm = server.get_vm_by_name(name) | |
vm.login_in_guest('accumulo', 'accumulo') | |
ip = vm.get_property("ip_address") | |
ips.append(ip) | |
f.write("{} {}\n".format(name, ip)) | |
return names, ips | |
def static_network_ips(requested_hosts, ip_range): | |
''' | |
Assigns ips to hosts from a static ip pool. | |
@requested_hosts: dict with a key hostvars | |
with value a list of dicts each | |
with at least the following keys: | |
static_ip, status | |
new_ip_pool = get_free_ip_pool(ip_range) | |
''' | |
new_ip_pool = get_free_ip_pool(ip_range) | |
for i,host in enumerate(requested_hosts['hostvars']): | |
if i < len(new_ip_pool): | |
requested_hosts['hostvars'][i]["static_ip"]=new_ip_pool[i] | |
requested_hosts['hostvars'][i]["status"]="success" | |
else: | |
requested_hosts['hostvars'][i]["status"]="fail" | |
if len(requested_hosts['hostvars']) > len(new_ip_pool): | |
#TODO log error | |
pass | |
return requested_hosts | |
main_usage = ''' | |
Usage: | |
python static_network_ips.py path ip_range | |
@path: The path to a json file with a dict | |
having key hostvars and value a list | |
of dicts each of which has at least keys: | |
static_ip, status | |
@ip_range: cidr in format "10.0.9.11/28" | |
or "10.0.9.0-255.1-16" | |
@returns: the json dict with filled static_ip | |
and status | |
@side_effect: overwrites input file with new values | |
''' | |
def main(): | |
main_usage | |
parser = argparse.ArgumentParser(usage=main_usage) | |
parser.add_argument("path", help="absolute path to a jason dict of data") | |
parser.add_argument("ip_range", help="range of ips to scan") | |
args = parser.parse_args() | |
path="" | |
if not args.path: | |
print(main_usage) | |
else: | |
assert os.path.exists(args.path), "file {} does not exist".format(args.path) | |
if not args.ip_range: | |
print(main_usage) | |
return {} | |
requested_hosts = {} | |
with open(args.path, 'r') as f: | |
requested_hosts = json.load(f) | |
assert requested_hosts | |
assert type(requested_hosts) == dict | |
requested_hosts = static_network_ips(requested_hosts, args.ip_range) | |
with open(args.path, 'w') as f: | |
json.dump(requested_hosts, f) | |
sys.stdout.write(repr(json.dumps(requested_hosts)) + "\n") | |
return json.dumps(requested_hosts) | |
if __name__ == '__main__': | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import nmap | |
import os | |
import sys | |
import simplejson as json | |
try: | |
import argparse | |
except ImportError as e: | |
raise ImportError(e, "static_network_ips.py requires argparse, so python2.7 or greater.") | |
def get_free_ip_pool(ip_range): | |
''' | |
Gets the free ip addresses within the requested range | |
@returns: a list of the free ips in an ip_range | |
@ip_range: cidr in format "10.0.9.11/28" | |
or "10.0.9.0-255.1-16" | |
Note: scanning an ip range can take several minutes | |
for the range /n with n<30. The closer the n is to 32 | |
and the fewer hosts exist in that range the faster to scan. | |
''' | |
lo,hi = -1,-1 | |
if ip_range.find('/') != -1: | |
# implement cidr math | |
raise ValueError("CIDR ranges not implimented yet") | |
pass | |
else: | |
try: | |
lo,hi = ip_range.split('.')[4].split('-') | |
lo, hi = int(lo), int(hi) | |
except Exception as e: | |
raise ValueError(e, "ip_range {} invalid".format( | |
ip_range)) | |
# validate requested ip_range | |
assert 0 < lo and lo < 255 | |
assert 0 < hi and hi < 255 | |
assert lo < hi | |
# do the scan | |
nm = nmap.PortScanner() | |
try: | |
scan = nm.scan(ip_range) | |
taken_ips = scan["scan"].keys() | |
except Exception as e: | |
raise Exception(e, | |
"Port Scan Un-successful for ip_range {}".format(ip_range)) | |
new_ip_pool = [ '10.0.9.'+ str(ip_tail) | |
for ip_tail in range(lo,hi+1) | |
if '10.0.9.' + str(ip_tail) not in scan] | |
return new_ip_pool | |
def static_network_ips(requested_hosts, ip_range): | |
''' | |
Assigns ips to hosts from a static ip pool. | |
@requested_hosts: dict with a key hostvars | |
with value a list of dicts each | |
with at least the following keys: | |
static_ip, status | |
new_ip_pool = get_free_ip_pool(ip_range) | |
''' | |
new_ip_pool = get_free_ip_pool(ip_range) | |
for i,host in enumerate(requested_hosts['hostvars']): | |
if i < len(new_ip_pool): | |
requested_hosts['hostvars'][i]["static_ip"]=new_ip_pool[i] | |
requested_hosts['hostvars'][i]["status"]="success" | |
else: | |
requested_hosts['hostvars'][i]["status"]="fail" | |
if len(requested_hosts['hostvars']) > len(new_ip_pool): | |
#TODO log error | |
pass | |
return requested_hosts | |
main_usage = ''' | |
Usage: | |
python static_network_ips.py path ip_range | |
@path: The path to a json file with a dict | |
having key hostvars and value a list | |
of dicts each of which has at least keys: | |
static_ip, status | |
@ip_range: cidr in format "10.0.9.11/28" | |
or "10.0.9.0-255.1-16" | |
@returns: the json dict with filled static_ip | |
and status | |
@side_effect: overwrites input file with new values | |
''' | |
def main(): | |
main_usage | |
parser = argparse.ArgumentParser(usage=main_usage) | |
parser.add_argument("path", help="absolute path to a jason dict of data") | |
parser.add_argument("ip_range", help="range of ips to scan") | |
args = parser.parse_args() | |
path="" | |
if not args.path: | |
print(main_usage) | |
else: | |
assert os.path.exists(args.path), "file {} does not exist".format(args.path) | |
if not args.ip_range: | |
print(main_usage) | |
return {} | |
requested_hosts = {} | |
with open(args.path, 'r') as f: | |
requested_hosts = json.load(f) | |
assert requested_hosts | |
assert type(requested_hosts) == dict | |
requested_hosts = static_network_ips(requested_hosts, args.ip_range) | |
with open(args.path, 'w') as f: | |
json.dump(requested_hosts, f) | |
sys.stdout.write(repr(json.dumps(requested_hosts)) + "\n") | |
return json.dumps(requested_hosts) | |
if __name__ == '__main__': | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from jinja2 import Environment, PackageLoader, FileSystemLoader | |
#env = Environment(loader=PackageLoader('/Users/kbroughton', 'ipy')) | |
from pysphere import VIServer | |
import yaml | |
# Determines settings based on ansible_os_family in OS_FAMILY here | |
# https://github.com/ansible/ansible/blob/devel/library/system/setup | |
# Good enough for centos/ubuntu. May need ansible_os_distro if | |
# there is more variation in network config files. | |
# This module uses jinja loading of yaml context variabls | |
# see http://docs.saltstack.com/ref/renderers/all/salt.renderers.jinja.html | |
cli_usage = ''' | |
Usage: python update_network_configs.py requested_hosts paths | |
@requested_hosts: a file containing a json dict of hosts | |
(format specified in remap_interfaces.py) | |
@paths: Quoted string of comma-separated absolute paths to files we wish to | |
template. The filename must have the form of the canonical destination | |
of the file on remote host with '/' replaced by '_'. | |
Eg. /path/to/etc_hosts becomes entry { etc_hosts: "/path/to" } | |
in self.paths_dict and templates to /etc/hosts. | |
Classes are responsible for warning if the minimum files required | |
for configuring networking for a given os are not supplied. | |
@output_paths: Optional fq path to directory to store templated files locally | |
@contexts: Optional quoted, comma-separated list (no spaces) of yaml/json FileSystemLoader | |
to load context vars from. | |
''' | |
#TODO investigate using bp/core.py for integration with ansible https://github.com/jcmcken/bp/blob/master/bp/core.py | |
class NetworkConf(requested_hosts, paths, output_paths=None, contexts=None): | |
'''Perform the necessary steps to allow ssh connections | |
Using vsphere as the hypervisor (more hypervisors to come?) | |
''' | |
def __init__(self, requested_hosts, paths): | |
self.os_family = None | |
# List of paths to template config files | |
self.paths = paths | |
# Jinja file system loader loads the parent dir of the template files | |
# paths_dict['template_filename'] = parent_dir is used for loading | |
self.paths_dict = {} | |
# json dict. See remap_interfaces.py for structure requirements. | |
self.requested_hosts | |
# Base class adds common config methods, children add extras | |
# update_network_configs executes all | |
self.config_methods = [etc_hosts, etc_networks] | |
self.vms = [] | |
# pysphere connection to vsphere hypervisor server | |
self.server = VIServer() | |
self._VIConnection = server.connect(requested_hosts) | |
connect() | |
yaml.load() | |
#TODO determine if vcenter host or esxhost is required for guest_login and document | |
#TODO encapsultate requested_hosts in class to avoid data structure dependencies | |
def connect(self, requested_hosts): | |
server.connect(requested_hosts['meta']['creds']['hypervisor_host'], | |
requested_hosts['meta']['creds']['hypervisor_login'], | |
requested_hosts['meta']['creds']['hypervisor_pwd']) | |
def vm_objects(names, server, login=False, user=None, pwd=None): | |
vms = [] | |
for name in names: | |
vm = server.get_vm_by_name(name) | |
if login: | |
assert user, "Must give a user name if you want to login" | |
assert pwd, "Must give a pwd if you want to login" | |
vm.login_in_guest(user, pwd) | |
else: | |
raise ValueError("login credentials are required") | |
self.vms.append(vm) | |
return vms | |
def os_family(self, host): | |
'''Map the vmware guest os id's to ansible_os_family names''' | |
guest_id = get_properties('guest_id') | |
if ( guest_id.lower().find('ubuntu') >= 0 ) or guest_id.lower().find('deb') >= 0) | |
self.os_family = "Debian" | |
elif ( guest_id.lower().find('rhel') >= 0 ) or guest_id.lower().find('centos') >= 0) | |
self.os_family = "RedHat" | |
def parse_paths(self, host): | |
for path in paths: | |
assert os.path.exists(path) | |
self.paths_dict[os.path.basename(path)] = os.path.dirname(path) | |
def etc_hosts(self, host): | |
env = Environment(loader=FileSystemLoader( | |
self.paths_dict(etc_hosts))) | |
env.get_template(etc_hosts) | |
def update_network_configs(self,requested_hosts, paths): | |
'''Update the essential network config files per os | |
@requested_hosts: dict of hosts being re-configured | |
@paths: list of fully qualified paths to file templates. | |
Files must be named as the destination fq paths | |
with '/' replaced by '_' | |
Eg. /local/path/to/etc_sysconfig_network-scripts_ifcfg-eth0 | |
where the destination is /etc/sysconfig/network-scripts/ifcfg-eth0 | |
''' | |
for host in get_hosts(requested_hosts): | |
for method in self.config_methods: | |
method(host) | |
class DebianNetworkConf(NetworkConf): | |
'''Network Configuration Class for ansible_os_family = Debian | |
Includes ubuntu. | |
''' | |
self.config_methods.append(etc_network_interfaces) | |
def etc_networks(self, host): | |
pass | |
def etc_network_interfaces(self, host): | |
pass | |
class RedHatNetworkConf(NetworkConf): | |
'''Network Configuration Class for ansible_os_family = Debian | |
Includes centos. | |
''' | |
self.config_methods.append(ifcfg_eth) | |
def etc_networks(self, host): | |
pass | |
def remove_udev_rules(self, host): | |
delete_file("/etc/udev/70-persistent-network.rules") | |
def main(): | |
server.disconnect() | |
env = Environment(loader=FileSystemLoader('')) | |
env.get_template('') | |
send_file(local_path, guest_path, overwrite=True) | |
vmlist = server.get_registered_vms() | |
vm1.get_status() | |
vm1.power_on(sync_run=False) | |
vm1.login_in_guest("os_username", "os_password") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment