ovs-xapi-sync: Make pychecker-able.
[sliver-openvswitch.git] / xenserver / usr_share_openvswitch_scripts_ovs-xapi-sync
1 #!/usr/bin/python
2 # Copyright (c) 2009, 2010, 2011 Nicira Networks
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16
17 # A daemon to monitor the external_ids columns of the Bridge and
18 # Interface OVSDB tables for changes that require interrogating XAPI.
19 # Its responsibilities include:
20 #
21 #   - Set the "bridge-id" key in the Bridge table.
22 #   - Set the "iface-id" key in the Interface table.
23 #   - Set the fail-mode on internal bridges.
24
25 import getopt
26 import logging, logging.handlers
27 import os
28 import signal
29 import subprocess
30 import sys
31 import time
32
33 import XenAPI
34
35 from ovs.db import error
36 from ovs.db import types
37 import ovs.util
38 import ovs.daemon
39 import ovs.db.idl
40
41 s_log = logging.getLogger("ovs-xapi-sync")
42 vsctl="/usr/bin/ovs-vsctl"
43 session = None
44 force_run = False
45
46 # Set up a session to interact with XAPI.
47 #
48 # On system start-up, OVS comes up before XAPI, so we can't log into the
49 # session until later.  Try to do this on-demand, since we won't
50 # actually do anything interesting until XAPI is up.
51 def init_session():
52     global session
53     if session is not None:
54         return True
55
56     try:
57         session = XenAPI.xapi_local()
58         session.xenapi.login_with_password("", "")
59     except:
60         session = None
61         s_log.warning("Couldn't login to XAPI")
62         return False
63
64     return True
65
66 def get_network_by_bridge(br_name):
67     if not init_session():
68         s_log.warning("Failed to get bridge id %s because"
69                 " XAPI session could not be initialized" % br_name)
70         return None
71
72     for n in session.xenapi.network.get_all():
73         rec = session.xenapi.network.get_record(n)
74         if rec['bridge'] == br_name:
75             return rec
76
77     return None
78
79 # By default, the "bridge-id" external id in the Bridge table is the
80 # same as "xs-network-uuids".  This may be overridden by defining a
81 # "nicira-bridge-id" key in the "other_config" field of the network
82 # record of XAPI.  If nicira-bridge-id is undefined returns default.
83 # On error returns None.
84 def get_bridge_id(br_name, default=None):
85     rec = get_network_by_bridge(br_name)
86     if rec:
87         return rec['other_config'].get('nicira-bridge-id', default)
88     return None
89
90 # By default, the "iface-id" external id in the Interface table is the
91 # same as "xs-vif-uuid".  This may be overridden by defining a
92 # "nicira-iface-id" key in the "other_config" field of the VIF
93 # record of XAPI.
94 def get_iface_id(if_name, xs_vif_uuid):
95     if not if_name.startswith("vif") and not if_name.startswith("tap"):
96         # Treat whatever was passed into 'xs_vif_uuid' as a default
97         # value for non-VIFs.
98         return xs_vif_uuid
99
100     if not init_session():
101         s_log.warning("Failed to get interface id %s because"
102                 " XAPI session could not be initialized" % if_name)
103         return xs_vif_uuid
104
105     try:
106         vif = session.xenapi.VIF.get_by_uuid(xs_vif_uuid)
107         rec = session.xenapi.VIF.get_record(vif)
108         return rec['other_config'].get('nicira-iface-id', xs_vif_uuid)
109     except XenAPI.Failure:
110         s_log.warning("Could not find XAPI entry for VIF %s" % if_name)
111         return xs_vif_uuid
112
113 def call_vsctl(args):
114     cmd = [vsctl, "--timeout=30", "-vANY:console:off"] + args
115     exitcode = subprocess.call(cmd)
116     if exitcode != 0:
117         s_log.warning("Couldn't call ovs-vsctl")
118
119 def set_external_id(table, record, key, value):
120     if value:
121         col = 'external-ids:"%s"="%s"' % (key, value)
122         call_vsctl(["set", table, record, col])
123     else:
124         call_vsctl(["remove", table, record, "external-ids", key])
125
126
127 # XenServer does not call interface-reconfigure on internal networks,
128 # which is where the fail-mode would normally be set.
129 def update_fail_mode(name):
130     rec = get_network_by_bridge(name)
131
132     if not rec:
133         return
134
135     fail_mode = rec['other_config'].get('vswitch-controller-fail-mode')
136
137     if not fail_mode:
138         pools = session.xenapi.pool.get_all()
139         if len(pools) == 1:
140             prec = session.xenapi.pool.get_record(pools[0])
141             fail_mode = prec['other_config'].get('vswitch-controller-fail-mode')
142
143     if fail_mode not in ['standalone', 'secure']:
144         fail_mode = 'standalone'
145
146     call_vsctl(["set", "bridge", name, "fail_mode=" + fail_mode])
147
148 def update_in_band_mgmt(name):
149     rec = get_network_by_bridge(name)
150
151     if not rec:
152         return
153
154     dib = rec['other_config'].get('vswitch-disable-in-band')
155     if not dib:
156         call_vsctl(['remove', 'bridge', name, 'other_config',
157                     'disable-in-band'])
158     elif dib in ['true', 'false']:
159         call_vsctl(['set', 'bridge', name,
160                     'other_config:disable-in-band=' + dib])
161     else:
162         s_log.warning('"' + dib + '"'
163                       "isn't a valid setting for other_config:disable-in-band on " +
164                       name)
165
166 def update_bridge_id(name, ids):
167     id = get_bridge_id(name, ids.get("xs-network-uuids"))
168
169     if not id:
170         return
171
172     primary_id = id.split(";")[0]
173
174     if ids.get("bridge-id") != primary_id:
175         set_external_id("Bridge", name, "bridge-id", primary_id)
176         ids["bridge-id"] = primary_id
177
178 def update_iface(name, ids):
179     id = get_iface_id(name, ids.get("xs-vif-uuid"))
180     if ids.get("iface-id") != id and id:
181         set_external_id("Interface", name, "iface-id", id)
182         ids["iface-id"] = id
183
184     status = ids.get("iface-status")
185     if status:
186         set_external_id("Interface", name, "iface-status", status)
187
188 def keep_table_columns(schema, table_name, column_types):
189     table = schema.tables.get(table_name)
190     if not table:
191         raise error.Error("schema has no %s table" % table_name)
192
193     new_columns = {}
194     for column_name, column_type in column_types.iteritems():
195         column = table.columns.get(column_name)
196         if not column:
197             raise error.Error("%s table schema lacks %s column"
198                               % (table_name, column_name))
199         if column.type != column_type:
200             raise error.Error("%s column in %s table has type \"%s\", "
201                               "expected type \"%s\""
202                               % (column_name, table_name,
203                                  column.type.toEnglish(),
204                                  column_type.toEnglish()))
205         new_columns[column_name] = column
206     table.columns = new_columns
207     return table
208
209 def monitor_uuid_schema_cb(schema):
210     string_type = types.Type(types.BaseType(types.StringType))
211     string_map_type = types.Type(types.BaseType(types.StringType),
212                                  types.BaseType(types.StringType),
213                                  0, sys.maxint)
214
215     new_tables = {}
216     for table_name in ("Bridge", "Interface"):
217         new_tables[table_name] = keep_table_columns(
218             schema, table_name, {"name": string_type,
219                                  "external_ids": string_map_type})
220     schema.tables = new_tables
221
222 def usage():
223     print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
224     print "where DATABASE is a socket on which ovsdb-server is listening."
225     ovs.daemon.usage()
226     print "Other options:"
227     print "  -h, --help               display this help message"
228     sys.exit(0)
229
230 def handler(signum, frame):
231     global force_run
232     if (signum == signal.SIGHUP):
233         force_run = True
234
235 def update_tap_from_vif(idl, tap_name, vif_name):
236     ifaces = idl.data["Interface"]
237     tap = None
238     vif = None
239
240     for i in ifaces.values():
241         name = i.name.as_scalar().strip('"')
242         if name == tap_name:
243             tap = i
244         elif name == vif_name:
245             vif = i
246
247     if vif and tap:
248         vxid = vif.external_ids
249         txid = tap.external_ids
250
251         keys = ["attached-mac", "xs-network-uuid", "xs-vif-uuid", "xs-vm-uuid"]
252         for k in keys:
253             if vxid.get(k) != txid.get(k):
254                 set_external_id("Interface", tap_name, k, vxid.get(k))
255
256 def main(argv):
257     global force_run
258
259     l_handler = logging.handlers.RotatingFileHandler(
260             "/var/log/openvswitch/ovs-xapi-sync.log")
261     l_formatter = logging.Formatter('%(filename)s: %(levelname)s: %(message)s')
262     l_handler.setFormatter(l_formatter)
263     s_log.addHandler(l_handler)
264     s_log.setLevel(logging.INFO)
265
266     try:
267         options, args = getopt.gnu_getopt(
268             argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
269     except getopt.GetoptError, geo:
270         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
271         sys.exit(1)
272
273     for key, value in options:
274         if key in ['-h', '--help']:
275             usage()
276         elif not ovs.daemon.parse_opt(key, value):
277             sys.stderr.write("%s: unhandled option %s\n"
278                              % (ovs.util.PROGRAM_NAME, key))
279             sys.exit(1)
280
281     if len(args) != 1:
282         sys.stderr.write("%s: exactly one nonoption argument is required "
283                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
284         sys.exit(1)
285
286     remote = args[0]
287     idl = ovs.db.idl.Idl(remote, "Open_vSwitch", monitor_uuid_schema_cb)
288
289     ovs.daemon.daemonize()
290
291     # This daemon is usually started before XAPI, but to complete our
292     # tasks, we need it.  Wait here until it's up.
293     while not os.path.exists("/var/run/xapi_init_complete.cookie"):
294         time.sleep(1)
295
296     signal.signal(signal.SIGHUP, handler)
297
298     bridges = {}
299     interfaces = {}
300     while True:
301         if not force_run and not idl.run():
302             poller = ovs.poller.Poller()
303             idl.wait(poller)
304             poller.block()
305             continue
306
307         if force_run:
308             s_log.info("Forced to re-run as the result of a SIGHUP")
309             bridges    = {}
310             interfaces = {}
311             force_run  = False
312
313         new_bridges = {}
314         for rec in idl.data["Bridge"].itervalues():
315             name = rec.name.as_scalar()
316             xs_network_uuids = rec.external_ids.get("xs-network-uuids")
317             bridge_id = rec.external_ids.get("bridge-id")
318             new_bridges[name] = {"xs-network-uuids": xs_network_uuids,
319                                  "bridge-id": bridge_id}
320
321         new_interfaces = {}
322         for rec in idl.data["Interface"].itervalues():
323             name = rec.name.as_scalar()
324             xs_vif_uuid = rec.external_ids.get("xs-vif-uuid")
325             iface_id = rec.external_ids.get("iface-id")
326             new_interfaces[name] = {"xs-vif-uuid": xs_vif_uuid,
327                                     "iface-id": iface_id}
328
329             if name.startswith("vif"):
330                 new_interfaces[name]["iface-status"] = "active"
331
332         #Tap devices take their xs-vif-uuid from their corresponding vif and
333         #cause that vif to be labled inactive.
334         for name in new_interfaces:
335             if not name.startswith("tap"):
336                 continue
337
338             vif = name.replace("tap", "vif", 1)
339
340             if vif in new_interfaces:
341                 xs_vif_uuid = new_interfaces[vif]["xs-vif-uuid"]
342                 new_interfaces[name]["xs-vif-uuid"] = xs_vif_uuid
343
344                 new_interfaces[vif]["iface-status"] = "inactive"
345                 new_interfaces[name]["iface-status"] = "active"
346
347                 update_tap_from_vif(idl, name, vif)
348
349         if bridges != new_bridges:
350             for name,ids in new_bridges.items():
351                 if name not in bridges:
352                     update_fail_mode(name)
353                     update_in_band_mgmt(name)
354
355                 if (name not in bridges) or (bridges[name] != ids):
356                     update_bridge_id(name, ids)
357
358             bridges = new_bridges
359
360         if interfaces != new_interfaces:
361             for name,ids in new_interfaces.items():
362                 if (name not in interfaces) or (interfaces[name] != ids):
363                     update_iface(name, ids)
364             interfaces = new_interfaces
365
366 if __name__ == '__main__':
367     try:
368         main(sys.argv)
369     except SystemExit:
370         # Let system.exit() calls complete normally
371         raise
372     except:
373         s_log.exception("traceback")
374         sys.exit(ovs.daemon.RESTART_EXIT_CODE)