make dos linefeeds go away
[opencloud-plugin.git] / opencloud-net / opencloud-net-watcher.py
1 #!/usr/bin/python
2
3 """
4     Poll neutron for changes in ports. Run opencloud-net if a change is
5     detected.
6 """
7
8 import json
9 import logging
10 import optparse
11 import os
12 import sys
13 import subprocess
14 import time
15 import traceback
16 from ConfigParser import ConfigParser
17 from neutronclient.v2_0 import client
18
19 class OpenCloudNetWatcher:
20     def __init__(self, daemonize):
21         self.ports = {}
22         parser = ConfigParser()
23         parser.read("/etc/nova/nova.conf")
24         self.neutron_username = parser.get("DEFAULT", "neutron_admin_username")
25         self.neutron_password = parser.get("DEFAULT", "neutron_admin_password")
26         self.neutron_tenant_name = parser.get("DEFAULT", "neutron_admin_tenant_name")
27         self.neutron_auth_url = parser.get("DEFAULT", "neutron_admin_auth_url")
28
29         if daemonize:
30             self.daemon()
31
32     # from opencloud-net.py
33     def convert_ovs_output_to_dict(self, out):
34         decoded = json.loads(out.strip())
35         headings = decoded['headings']
36         data = decoded['data']
37
38         records = []
39         for rec in data:
40             mydict = {}
41             for i in range(0, len(headings) - 1):
42                 if not isinstance(rec[i], list):
43                     mydict[headings[i]] = rec[i]
44                 else:
45                     if rec[i][0] == 'set':
46                         mydict[headings[i]] = rec[i][1]
47                     elif rec[i][0] == 'map':
48                         newdict = {}
49                         for (key, value) in rec[i][1]:
50                             newdict[key] = value
51                         mydict[headings[i]] = newdict
52                     elif rec[i][0] == 'uuid':
53                         mydict['uuid'] = rec[i][1]
54             records.append(mydict)
55
56         return records
57
58     # from opencloud-net.py
59     def get_local_neutron_ports(self):
60         ports = []
61
62         # Get local information for VM interfaces from OvS
63         ovs_out = subprocess.check_output(['/usr/bin/ovs-vsctl', '-f', 'json', 'find',
64                                            'Interface', 'external_ids:iface-id!="absent"'])
65         records = self.convert_ovs_output_to_dict(ovs_out)
66
67         if records:
68             # Extract Neutron Port IDs from OvS records
69             port_ids = []
70             for rec in records:
71                 port_ids.append(rec['external_ids']['iface-id'])
72
73             # Get the full info on these ports from Neutron
74             neutron = client.Client(username=self.neutron_username,
75                                     password=self.neutron_password,
76                                     tenant_name=self.neutron_tenant_name,
77                                     auth_url=self.neutron_auth_url)
78             ports = neutron.list_ports(id=port_ids)['ports']
79
80         return ports
81
82     def did_something_change(self):
83         ports = self.get_local_neutron_ports()
84         ids = [port["id"] for port in ports]
85
86         something_changed = False
87
88         for port in ports:
89             port_id = port["id"]
90             if not port_id in self.ports:
91                 logging.info("new port %s" % port_id)
92                 something_changed = True
93             else:
94                 existing_port = self.ports[port_id]
95                 if port.get("nat:forward_ports",None) != existing_port.get("nat:forward_ports", None):
96                     logging.info("forwarding on port %s changed" % port_id)
97                     something_changed = True
98
99             self.ports[port_id] = port
100
101         for port_id in self.ports.keys():
102             if not port_id in ids:
103                 logging.info("deleted port %s" % port_id)
104                 del self.ports[port_id]
105                 something_changed = True
106
107         return something_changed
108
109     def run_once(self):
110         try:
111             if self.did_something_change():
112                 logging.info("something changed - running opencloud-net.py")
113                 os.system("/usr/local/sbin/opencloud-net.py")
114             else:
115                 pass
116         except:
117             logging.error("Error in run_once: BEG TRACEBACK"+"\n"+traceback.format_exc().strip("\n"))
118             logging.error("Error in run_once: END TRACEBACK")
119
120
121     def run_loop(self):
122         while True:
123             self.run_once()
124             time.sleep(30)
125
126     # after http://www.erlenstar.demon.co.uk/unix/faq_2.html
127     def daemon(self):
128         """Daemonize the current process."""
129         if os.fork() != 0: os._exit(0)
130         os.setsid()
131         if os.fork() != 0: os._exit(0)
132         os.chdir('/')
133         os.umask(0022)
134         devnull = os.open(os.devnull, os.O_RDWR)
135         os.dup2(devnull, 0)
136         # xxx fixme - this is just to make sure that nothing gets stupidly lost - should use devnull
137         crashlog = os.open('/var/log/opencloud-net-watcher.daemon', os.O_RDWR | os.O_APPEND | os.O_CREAT, 0644)
138         os.dup2(crashlog, 1)
139         os.dup2(crashlog, 2)
140
141 def main():
142     parser = optparse.OptionParser()
143     parser.add_option('-d', '--daemon', action='store_true', dest='daemon', default=False,
144                       help='run daemonized')
145     parser.add_option('-l', '--logfile', action='store', dest='logfile', default="/var/log/opencloud-net-watcher",
146                   help='log file name')
147
148     (options, args) = parser.parse_args()
149
150     logging.basicConfig(filename=options.logfile,level=logging.INFO)
151
152     watcher = OpenCloudNetWatcher(daemonize = options.daemon)
153     watcher.run_loop()
154
155 if __name__ == "__main__":
156    main()
157