--- /dev/null
+# system provided modules
+import os, string, time, socket
+from socket import inet_aton
+import subprocess, signal
+import json
+from ConfigParser import ConfigParser
+
+# PlanetLab system modules
+import sioc, plnet
+
+# Quantum modules
+from quantumclient.v2_0 import client
+
+# local modules
+import logger
+
+plugin = "planetstack-net"
+
+# Helper functions for converting to CIDR notation
+def get_net_size(netmask):
+ binary_str = ''
+ for octet in netmask:
+ binary_str += bin(int(octet))[2:].zfill(8)
+ return str(len(binary_str.rstrip('0')))
+
+def to_cidr(ipaddr, netmask):
+ # validate input
+ inet_aton(ipaddr)
+ inet_aton(netmask)
+
+ ipaddr = ipaddr.split('.')
+ netmask = netmask.split('.')
+
+ net_start = [str(int(ipaddr[x]) & int(netmask[x])) for x in range(0,4)]
+ return '.'.join(net_start) + '/' + get_net_size(netmask)
+
+def ipaddr_range(network, broadcast):
+ start = network.split('.')
+ end = broadcast.split('.')
+
+ # Assume interface always claims the first address in the block
+ start[3] = str(int(start[3]) + 2)
+ end[3] = str(int(end[3]) - 1)
+
+ return '.'.join(start) + ',' + '.'.join(end)
+
+# Enable iptables MASQ for a device
+def add_iptables_masq(dev, interface):
+ ipaddr = interface['ip']
+ netmask = interface['netmask']
+
+ cidr = None
+ try:
+ cidr = to_cidr(ipaddr, netmask)
+ except:
+ logger.log('%s: could not convert ipaddr %s and netmask %s to CIDR'
+ % (plugin, ipaddr, netmask))
+ return
+
+ cmd = ['/sbin/iptables', '-t', 'nat', '-C', 'POSTROUTING', '-s', cidr,
+ '!', '-d', cidr, '-j', 'MASQUERADE']
+ try:
+ logger.log('%s: checking if NAT iptables rule present for device %s' % (plugin, dev))
+ subprocess.check_call(cmd)
+ except:
+ logger.log('%s: adding NAT iptables NAT for device %s' % (plugin, dev))
+ cmd[3] = '-A'
+ try:
+ subprocess.check_call(cmd)
+ except:
+ logger.log('%s: FAILED to add NAT iptables rule for device %s' % (plugin, dev))
+
+def get_pidfile(dev):
+ return '/var/run/dnsmasq-%s.pid' % dev
+
+def get_leasefile(dev):
+ return '/var/lib/dnsmasq/%s.leases' % dev
+
+def get_hostsfile(dev):
+ return '/var/lib/dnsmasq/%s.hosts' % dev
+
+# Check if dnsmasq already running
+def dnsmasq_running(dev):
+ pidfile = get_pidfile(dev)
+ try:
+ pid = open(pidfile, 'r').read().strip()
+ if os.path.exists('/proc/%s' % pid):
+ return True
+ except:
+ pass
+ return False
+
+def dnsmasq_sighup(dev):
+ pidfile = get_pidfile(dev)
+ try:
+ pid = open(pidfile, 'r').read().strip()
+ if os.path.exists('/proc/%s' % pid):
+ os.kill(int(pid), signal.SIGHUP)
+ logger.log("%s: Sent SIGHUP to dnsmasq on dev %s" % (plugin, dev))
+ except:
+ logger.log("%s: Sending SIGHUP to dnsmasq FAILED on dev %s" % (plugin, dev))
+
+# Enable dnsmasq for this interface
+def start_dnsmasq(dev, interface):
+ if not dnsmasq_running(dev):
+ try:
+ logger.log('%s: starting dnsmasq on device %s' % (plugin, dev))
+ iprange = ipaddr_range(interface['network'], interface['broadcast'])
+ logger.log('%s: IP range: %s' % (plugin, iprange))
+ subprocess.check_call(['/usr/sbin/dnsmasq',
+ '--strict-order',
+ '--bind-interfaces',
+ '--local=//',
+ '--domain-needed',
+ '--pid-file=%s' % get_pidfile(dev),
+ '--conf-file=',
+ '--interface=%s' % dev,
+ '--dhcp-range=%s,120' % iprange,
+ '--dhcp-leasefile=%s' % get_leasefile(dev),
+ '--dhcp-hostsfile=%s' % get_hostsfile(dev),
+ '--dhcp-no-override'])
+ except:
+ logger.log('%s: FAILED to start dnsmasq for device %s' % (plugin, dev))
+
+nat_net_name = "nat-net"
+nat_net_id = None
+quantum_auth_url = "http://viccidev1:5000/v2.0/"
+quantum_username = None
+quantum_password = None
+quantum_tenant_name = None
+
+def convert_ovs_output_to_dict(out):
+ decoded = json.loads(out.strip())
+ headings = decoded['headings']
+ data = decoded['data']
+
+ records = []
+ for rec in data:
+ mydict = {}
+ for i in range(0, len(headings) - 1):
+ if not isinstance(rec[i], list):
+ mydict[headings[i]] = rec[i]
+ else:
+ if rec[i][0] == 'set':
+ mydict[headings[i]] = rec[i][1]
+ elif rec[i][0] == 'map':
+ newdict = {}
+ for (key, value) in rec[i][1]:
+ newdict[key] = value
+ mydict[headings[i]] = newdict
+ elif rec[i][0] == 'uuid':
+ mydict['uuid'] = rec[i][1]
+ records.append(mydict)
+
+ return records
+
+# Generate a dhcp-hostsfile for dnsmasq for all the local interfaces
+# on the NAT network. The purpose is to make sure that the IP address
+# assigned by Quantum appears on NAT interfaces.
+#
+# Workflow:
+# - Get list of local VM interfaces
+# - Query Quantum to get port information for these interfaces
+# - Throw away all those not belonging to nat-net network
+# - Write the MAC addr and IPs to the dhcp-hostsfile file (and log it)
+
+def write_hostsfile(dev):
+ # Get local information for VM interfaces from OvS
+ ovs_out = subprocess.check_output(['/usr/bin/ovs-vsctl', '-f', 'json', 'find',
+ 'Interface', 'external_ids:iface-id!="absent"'])
+ records = convert_ovs_output_to_dict(ovs_out)
+
+ # Extract Quantum Port IDs from OvS records
+ port_ids = []
+ for rec in records:
+ port_ids.append(rec['external_ids']['iface-id'])
+
+ # Get the full info on these ports from Quantum
+ quantum = client.Client(username=quantum_username,
+ password=quantum_password,
+ tenant_name=quantum_tenant_name,
+ auth_url=quantum_auth_url)
+ ports = quantum.list_ports(id=port_ids)
+
+ # Write relevant entries to dnsmasq hostsfile
+ logger.log("%s: Writing hostsfile for %s" % (plugin, dev))
+ f = open(get_hostsfile(dev), 'w')
+ for port in ports['ports']:
+ if port['network_id'] == nat_net_id:
+ entry = "%s,%s\n" % (port['mac_address'], port['fixed_ips'][0]['ip_address'])
+ f.write(entry)
+ logger.log("%s: %s" % (plugin, entry))
+ f.close()
+
+ # Send SIGHUP to dnsmasq to make it re-read hostsfile
+ dnsmasq_sighup(dev)
+
+def start():
+ global quantum_username
+ global quantum_password
+ global quantum_tenant_name
+ global nat_net_id
+
+ logger.log("%s: plugin starting up..." % plugin)
+
+ parser = ConfigParser()
+ parser.read("/etc/nova/nova.conf")
+ quantum_username = parser.get("DEFAULT", "quantum_admin_username")
+ quantum_password = parser.get("DEFAULT", "quantum_admin_password")
+ quantum_tenant_name = parser.get("DEFAULT", "quantum_admin_tenant_name")
+
+ quantum = client.Client(username=quantum_username,
+ password=quantum_password,
+ tenant_name=quantum_tenant_name,
+ auth_url=quantum_auth_url)
+
+ net = quantum.list_networks(name=nat_net_name)
+ nat_net_id = net['networks'][0]['id']
+
+ logger.log("%s: %s id is %s..." % (plugin, nat_net_name, nat_net_id))
+
+def GetSlivers(data, config=None, plc=None):
+ for interface in data['interfaces']:
+ try:
+ settings = plc.GetInterfaceTags({'interface_tag_id': interface['interface_tag_ids']})
+ except:
+ continue
+
+ tags = {}
+ for setting in settings:
+ tags[setting['tagname'].upper()] = setting['value']
+
+ if 'IFNAME' in tags:
+ dev = tags['IFNAME']
+ else:
+ # Skip devices that don't have names
+ logger.log('%s: Device has no name, skipping...' % plugin)
+ continue
+
+ logger.log('%s: Processing device %s' % (plugin, dev))
+
+ if 'NAT' in tags:
+ add_iptables_masq(dev, interface)
+ write_hostsfile(dev)
+ start_dnsmasq(dev, interface)