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