1 # system provided modules
2 import os, string, time, socket
3 from socket import inet_aton
4 import subprocess, signal
6 from ConfigParser import ConfigParser
8 # PlanetLab system modules
12 from quantumclient.v2_0 import client
17 plugin = "planetstack-net"
19 # Helper functions for converting to CIDR notation
20 def get_net_size(netmask):
23 binary_str += bin(int(octet))[2:].zfill(8)
24 return str(len(binary_str.rstrip('0')))
26 def to_cidr(ipaddr, netmask):
31 ipaddr = ipaddr.split('.')
32 netmask = netmask.split('.')
34 net_start = [str(int(ipaddr[x]) & int(netmask[x])) for x in range(0,4)]
35 return '.'.join(net_start) + '/' + get_net_size(netmask)
37 def ipaddr_range(network, broadcast):
38 start = network.split('.')
39 end = broadcast.split('.')
41 # Assume interface always claims the first address in the block
42 start[3] = str(int(start[3]) + 2)
43 end[3] = str(int(end[3]) - 1)
45 return '.'.join(start) + ',' + '.'.join(end)
47 # Enable iptables MASQ for a device
48 def add_iptables_masq(dev, interface):
49 ipaddr = interface['ip']
50 netmask = interface['netmask']
54 cidr = to_cidr(ipaddr, netmask)
56 logger.log('%s: could not convert ipaddr %s and netmask %s to CIDR'
57 % (plugin, ipaddr, netmask))
60 cmd = ['/sbin/iptables', '-t', 'nat', '-C', 'POSTROUTING', '-s', cidr,
61 '!', '-d', cidr, '-j', 'MASQUERADE']
63 logger.log('%s: checking if NAT iptables rule present for device %s' % (plugin, dev))
64 subprocess.check_call(cmd)
66 logger.log('%s: adding NAT iptables NAT for device %s' % (plugin, dev))
69 subprocess.check_call(cmd)
71 logger.log('%s: FAILED to add NAT iptables rule for device %s' % (plugin, dev))
74 return '/var/run/dnsmasq-%s.pid' % dev
76 def get_leasefile(dev):
77 return '/var/lib/dnsmasq/%s.leases' % dev
79 def get_hostsfile(dev):
80 return '/var/lib/dnsmasq/%s.hosts' % dev
82 # Check if dnsmasq already running
83 def dnsmasq_running(dev):
84 pidfile = get_pidfile(dev)
86 pid = open(pidfile, 'r').read().strip()
87 if os.path.exists('/proc/%s' % pid):
93 def dnsmasq_sighup(dev):
94 pidfile = get_pidfile(dev)
96 pid = open(pidfile, 'r').read().strip()
97 if os.path.exists('/proc/%s' % pid):
98 os.kill(int(pid), signal.SIGHUP)
99 logger.log("%s: Sent SIGHUP to dnsmasq on dev %s" % (plugin, dev))
101 logger.log("%s: Sending SIGHUP to dnsmasq FAILED on dev %s" % (plugin, dev))
103 # Enable dnsmasq for this interface
104 def start_dnsmasq(dev, interface):
105 if not dnsmasq_running(dev):
107 logger.log('%s: starting dnsmasq on device %s' % (plugin, dev))
108 iprange = ipaddr_range(interface['network'], interface['broadcast'])
109 logger.log('%s: IP range: %s' % (plugin, iprange))
110 subprocess.check_call(['/usr/sbin/dnsmasq',
115 '--pid-file=%s' % get_pidfile(dev),
117 '--interface=%s' % dev,
118 '--dhcp-range=%s,120' % iprange,
119 '--dhcp-leasefile=%s' % get_leasefile(dev),
120 '--dhcp-hostsfile=%s' % get_hostsfile(dev),
121 '--dhcp-no-override'])
123 logger.log('%s: FAILED to start dnsmasq for device %s' % (plugin, dev))
125 nat_net_name = "nat-net"
127 quantum_auth_url = "http://viccidev1:5000/v2.0/"
128 quantum_username = None
129 quantum_password = None
130 quantum_tenant_name = None
132 def convert_ovs_output_to_dict(out):
133 decoded = json.loads(out.strip())
134 headings = decoded['headings']
135 data = decoded['data']
140 for i in range(0, len(headings) - 1):
141 if not isinstance(rec[i], list):
142 mydict[headings[i]] = rec[i]
144 if rec[i][0] == 'set':
145 mydict[headings[i]] = rec[i][1]
146 elif rec[i][0] == 'map':
148 for (key, value) in rec[i][1]:
150 mydict[headings[i]] = newdict
151 elif rec[i][0] == 'uuid':
152 mydict['uuid'] = rec[i][1]
153 records.append(mydict)
157 # Generate a dhcp-hostsfile for dnsmasq for all the local interfaces
158 # on the NAT network. The purpose is to make sure that the IP address
159 # assigned by Quantum appears on NAT interfaces.
162 # - Get list of local VM interfaces
163 # - Query Quantum to get port information for these interfaces
164 # - Throw away all those not belonging to nat-net network
165 # - Write the MAC addr and IPs to the dhcp-hostsfile file (and log it)
167 def write_hostsfile(dev):
168 # Get local information for VM interfaces from OvS
169 ovs_out = subprocess.check_output(['/usr/bin/ovs-vsctl', '-f', 'json', 'find',
170 'Interface', 'external_ids:iface-id!="absent"'])
171 records = convert_ovs_output_to_dict(ovs_out)
173 # Extract Quantum Port IDs from OvS records
176 port_ids.append(rec['external_ids']['iface-id'])
178 # Get the full info on these ports from Quantum
179 quantum = client.Client(username=quantum_username,
180 password=quantum_password,
181 tenant_name=quantum_tenant_name,
182 auth_url=quantum_auth_url)
183 ports = quantum.list_ports(id=port_ids)
185 # Write relevant entries to dnsmasq hostsfile
186 logger.log("%s: Writing hostsfile for %s" % (plugin, dev))
187 f = open(get_hostsfile(dev), 'w')
188 for port in ports['ports']:
189 if port['network_id'] == nat_net_id:
190 entry = "%s,%s\n" % (port['mac_address'], port['fixed_ips'][0]['ip_address'])
192 logger.log("%s: %s" % (plugin, entry))
195 # Send SIGHUP to dnsmasq to make it re-read hostsfile
199 global quantum_username
200 global quantum_password
201 global quantum_tenant_name
204 logger.log("%s: plugin starting up..." % plugin)
206 parser = ConfigParser()
207 parser.read("/etc/nova/nova.conf")
208 quantum_username = parser.get("DEFAULT", "quantum_admin_username")
209 quantum_password = parser.get("DEFAULT", "quantum_admin_password")
210 quantum_tenant_name = parser.get("DEFAULT", "quantum_admin_tenant_name")
212 quantum = client.Client(username=quantum_username,
213 password=quantum_password,
214 tenant_name=quantum_tenant_name,
215 auth_url=quantum_auth_url)
217 net = quantum.list_networks(name=nat_net_name)
218 nat_net_id = net['networks'][0]['id']
220 logger.log("%s: %s id is %s..." % (plugin, nat_net_name, nat_net_id))
222 def GetSlivers(data, config=None, plc=None):
223 for interface in data['interfaces']:
225 settings = plc.GetInterfaceTags({'interface_tag_id': interface['interface_tag_ids']})
230 for setting in settings:
231 tags[setting['tagname'].upper()] = setting['value']
236 # Skip devices that don't have names
237 logger.log('%s: Device has no name, skipping...' % plugin)
240 logger.log('%s: Processing device %s' % (plugin, dev))
243 add_iptables_masq(dev, interface)
245 start_dnsmasq(dev, interface)