Bug fix
[nodemanager.git] / plugins / planetstack-net.py
index ded009d..f47b63d 100644 (file)
@@ -1,10 +1,10 @@
 """
-This plugin sets up the NAT interfaces for PlanetStack. It processes
-each interface that has a 'nat' tag set.
+This plugin sets up dnsmasq and iptables to support the "Private-Nat"
+and "Public" network models for OpenCloud.  It communicates with OvS
+on the local node and Quantum to gather information about the virtual
+interfaces instantiated by Quantum.  It uses this information to:
 
-It communicates with OvS on the local node and Quantum to gather
-information about devices.  It uses this information to:
-* add the Quantum-assigned IP address to the interface via dnsmasq
+* add the Quantum-assigned IP address to the vif via dnsmasq
 * set up port forwarding rules through the NAT using iptables
 
 The iptables configuration uses a chain called 'planetstack-net' to
@@ -32,6 +32,16 @@ import logger
 
 plugin = "planetstack-net"
 
+nat_net_name = "nat-net"
+nat_net_id = None
+site_net_id = None
+
+quantum_auth_url = None
+quantum_username = None
+quantum_password = None
+quantum_tenant_name = None
+
+
 # Helper functions for converting to CIDR notation
 def get_net_size(netmask):
     binary_str = ''
@@ -66,14 +76,17 @@ def run_iptables_cmd(args):
     logger.log('%s: %s' % (plugin, ' '.join(cmd)))
     subprocess.check_call(cmd)
     
-def add_iptables_rule(table, chain, args):
-    args = ['-t', table, '-C',  chain] + args
+def add_iptables_rule(table, chain, args, pos = None):
+    iptargs = ['-t', table, '-C',  chain] + args
     try:
-        run_iptables_cmd(args)
+        run_iptables_cmd(iptargs)
     except:
-        args[2] = '-A'
+        if pos:
+            iptargs = ['-t', table, '-I', chain, str(pos)] + args
+        else:
+            iptargs[2] = '-A'
         try:
-            run_iptables_cmd(args)
+            run_iptables_cmd(iptargs)
         except:
             logger.log('%s: FAILED to add iptables rule' % plugin)
 
@@ -87,6 +100,14 @@ def reset_iptables_chain():
 
     add_iptables_rule('nat', 'PREROUTING', ['-j', plugin]) 
 
+# Nova blocks packets from external addresses by default.
+# This is hacky but it gets around the issue.
+def unfilter_ipaddr(dev, ipaddr):
+    add_iptables_rule(table = 'filter', 
+                      chain = 'nova-compute-sg-fallback', 
+                      args = ['-d', ipaddr, '-j', 'ACCEPT'], 
+                      pos = 1)
+    
 # Enable iptables MASQ for a device
 def add_iptables_masq(dev, interface):
     ipaddr = interface['ip']
@@ -133,34 +154,37 @@ def dnsmasq_sighup(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):
+# Enable dnsmasq for this interface.
+# It's possible that we could get by with a single instance of dnsmasq running on
+# all devices but I haven't tried it.
+def start_dnsmasq(dev, interface, forward_dns=True):
     if not dnsmasq_running(dev):
+        # The '--dhcp-range=<IP addr>,static' argument to dnsmasq ensures that it only
+        # hands out IP addresses to clients listed in the hostsfile
+        cmd = ['/usr/sbin/dnsmasq',
+               '--strict-order',
+               '--bind-interfaces',
+               '--local=//',
+               '--domain-needed',
+               '--pid-file=%s' % get_pidfile(dev),
+               '--conf-file=',
+               '--interface=%s' % dev,
+               '--except-interface=lo',
+               '--dhcp-leasefile=%s' % get_leasefile(dev),
+               '--dhcp-hostsfile=%s' % get_hostsfile(dev),
+               '--dhcp-no-override',
+               '--dhcp-range=%s,static' % interface['ip']]
+
+        # Turn off forwarding DNS queries, only do DHCP
+        if forward_dns == False:
+            cmd.append('--port=0')
+
         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'])
+            subprocess.check_call(cmd)
         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
+            logger.log(' '.join(cmd))
 
 def convert_ovs_output_to_dict(out):
     decoded = json.loads(out.strip())
@@ -187,42 +211,40 @@ def convert_ovs_output_to_dict(out):
 
     return records
 
-# Do all processing associated with Quantum ports.  It first gets a
-# list of local VM interfaces and then queries Quantum to get Port
-# records for these interfaces.  Then for all interfaces on the NAT
-# network it does the following:
-#
-# 1) Generates a dhcp-hostsfile for dnsmasq.  The purpose is to make
-# sure that the IP address assigned by Quantum appears on NAT
-# interface.
-#
-# 2) Sets up iptables rules in the 'planetstack-net' chain based on 
-# the nat:forward_ports field in the Port record.  
-
-def process_quantum_ports(dev):
+
+# Get a list of local VM interfaces and then query Quantum to get
+# Port records for these interfaces.
+def get_local_quantum_ports():
+    ports = []
+
     # 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'])
+    if records:
+        # 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)['ports']
+
+    return ports
 
-    # 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)
-    # logger.log("%s: %s" % (plugin, ports))
 
-    # Write relevant entries to dnsmasq hostsfile
+# Generate a dhcp-hostsfile for dnsmasq.  The purpose is to make sure
+# that the IP address assigned by Quantum appears on NAT interface.
+def write_dnsmasq_hostsfile(dev, ports, net_id):
     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:
+    for port in ports:
+        if port['network_id'] == net_id:
             entry = "%s,%s\n" % (port['mac_address'], port['fixed_ips'][0]['ip_address'])
             f.write(entry)
             logger.log("%s: %s" % (plugin, entry))
@@ -231,23 +253,37 @@ def process_quantum_ports(dev):
     # Send SIGHUP to dnsmasq to make it re-read hostsfile
     dnsmasq_sighup(dev)
 
-    # Set up iptables rules for port forwarding
-    for port in ports['ports']:
-        if port['network_id'] == nat_net_id:
+# Set up iptables rules in the 'planetstack-net' chain based on
+# the nat:forward_ports field in the Port record.
+def set_up_port_forwarding(dev, ports):
+    for port in ports:
+        if port['network_id'] == nat_net_id and port['nat:forward_ports']:
             for fw in port['nat:forward_ports']:
                 ipaddr = port['fixed_ips'][0]['ip_address']
                 protocol = fw['l4_protocol']
                 fwport = fw['l4_port']
                 # logger.log("%s: fwd port %s/%s to %s" % (plugin, protocol, fwport, ipaddr))
+
+                unfilter_ipaddr(dev, ipaddr)
+                # Shouldn't hardcode br-eth0 here.  Maybe use '-d <node IP address>'?
                 add_iptables_rule('nat', plugin, ['-i', 'br-eth0', 
                                                   '-p', protocol, '--dport', fwport,
                                                   '-j', 'DNAT', '--to-destination', ipaddr])
-            
+
+def get_net_id_by_name(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=name)
+    return net['networks'][0]['id']
+
 def start():
     global quantum_username
     global quantum_password
     global quantum_tenant_name
-    global nat_net_id
+    global quantum_auth_url
 
     logger.log("%s: plugin starting up..." % plugin)
 
@@ -256,19 +292,33 @@ def start():
     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_auth_url = parser.get("DEFAULT", "quantum_admin_auth_url")
 
-    quantum = client.Client(username=quantum_username, 
-                            password=quantum_password,
-                            tenant_name=quantum_tenant_name,
-                            auth_url=quantum_auth_url)
+def GetSlivers(data, config=None, plc=None):
+    global nat_net_id
+    global site_net_id
 
-    net = quantum.list_networks(name=nat_net_name)
-    nat_net_id = net['networks'][0]['id']
+    if not nat_net_id:
+        try:
+            nat_net_id = get_net_id_by_name(nat_net_name)
+            logger.log("%s: %s id is %s..." % (plugin, nat_net_name, nat_net_id))
+        except:
+            logger.log("%s: no network called %s..." % (plugin, nat_net_name))
 
-    logger.log("%s: %s id is %s..." % (plugin, nat_net_name, nat_net_id))
 
-def GetSlivers(data, config=None, plc=None):
+    # Fix me
+    # site_net_name = "devel"
+    site_net_name = "sharednet1"
+    if not site_net_id:
+        try:
+            site_net_id = get_net_id_by_name(site_net_name)
+            logger.log("%s: %s id is %s..." % (plugin, site_net_name, site_net_id))
+        except:
+            logger.log("%s: no network called %s..." % (plugin, site_net_name))
+
     reset_iptables_chain()
+    ports = get_local_quantum_ports()
+
     for interface in data['interfaces']:
         try:
             settings = plc.GetInterfaceTags({'interface_tag_id': interface['interface_tag_ids']})
@@ -287,8 +337,18 @@ def GetSlivers(data, config=None, plc=None):
             continue
 
         logger.log('%s: Processing device %s' % (plugin, dev))
-        
-        if 'NAT' in tags:
+
+        # Process Private-Nat networks
+        if 'NAT' in tags and nat_net_id:
             add_iptables_masq(dev, interface)
-            process_quantum_ports(dev)
+            write_dnsmasq_hostsfile(dev, ports, nat_net_id)
+            set_up_port_forwarding(dev, ports)
             start_dnsmasq(dev, interface)
+
+        # Process Public networks
+        if interface['is_primary'] and site_net_id:
+            if 'OVS_BRIDGE' in tags:
+                dev = tags['OVS_BRIDGE']
+            write_dnsmasq_hostsfile(dev, ports, site_net_id)
+            start_dnsmasq(dev, interface, forward_dns=False)
+