Skip to content

Instantly share code, notes, and snippets.

@darKoram
Last active December 27, 2015 12:59
Show Gist options
  • Save darKoram/7329543 to your computer and use it in GitHub Desktop.
Save darKoram/7329543 to your computer and use it in GitHub Desktop.
Re map dynamic ips to static ips for vms hosted on vsphere.
# 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"}}
# {{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
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()
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()
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