ovs-xapi-sync: Handle exceptions from XAPI for get_single_bridge_id.
[sliver-openvswitch.git] / xenserver / usr_share_openvswitch_scripts_ovs-xapi-sync
1 #!/usr/bin/python
2 # Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
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 argparse
26 import os
27 import sys
28 import time
29
30 import XenAPI
31
32 import ovs.dirs
33 from ovs.db import error
34 from ovs.db import types
35 import ovs.daemon
36 import ovs.db.idl
37 import ovs.unixctl
38 import ovs.unixctl.server
39
40 vlog = ovs.vlog.Vlog("ovs-xapi-sync")
41 session = None
42 flush_cache = False
43 exiting = False
44
45
46 def unixctl_exit(conn, unused_argv, unused_aux):
47     global exiting
48     exiting = True
49     conn.reply(None)
50
51
52 def unixctl_flush_cache(conn, unused_argv, unused_aux):
53     global flush_cache
54     flush_cache = True
55     conn.reply(None)
56
57
58 # Set up a session to interact with XAPI.
59 #
60 # On system start-up, OVS comes up before XAPI, so we can't log into the
61 # session until later.  Try to do this on-demand, since we won't
62 # actually do anything interesting until XAPI is up.
63 def init_session():
64     global session
65     if session is not None:
66         return True
67
68     try:
69         session = XenAPI.xapi_local()
70         session.xenapi.login_with_password("", "")
71     except XenAPI.Failure, e:
72         session = None
73         vlog.warn("Couldn't login to XAPI (%s)" % e)
74         return False
75
76     return True
77
78
79 def get_network_by_bridge(br_name):
80     if not init_session():
81         vlog.warn("Failed to get bridge id %s because"
82                 " XAPI session could not be initialized" % br_name)
83         return None
84
85     recs = session.xenapi.network.get_all_records_where('field "bridge"="%s"' % br_name)
86     if len(recs) > 0:
87         return recs.values()[0]
88
89     return None
90
91 # There are possibilities when multiple xs-network-uuids are set for a bridge.
92 # In cases like that, we should choose the bridge-id whose PIF does not have a
93 # VLAN associated with it.
94 def get_single_bridge_id(bridge_ids, default=None):
95     if not init_session():
96         vlog.warn("Failed to get single bridge id from %s because"
97                   "XAPI session could not be initialized" % bridge_ids)
98         return default
99
100     for bridge_id in bridge_ids:
101         try:
102             recs = session.xenapi.network.get_all_records_where(\
103                                             'field "uuid"="%s"' % bridge_id)
104             if recs:
105                 pifs = recs.values()[0]['PIFs']
106                 for pif in pifs:
107                     try:
108                         rec = session.xenapi.PIF.get_record(pif)
109                         if rec['VLAN'] == '-1':
110                             return bridge_id
111                     except XenAPI.Failure:
112                         vlog.warn("Could not find XAPI entry for PIF %s" % pif)
113                         continue
114
115         except XenAPI.Failure:
116             vlog.warn("Could not find XAPI entry for bridge_id %s" % bridge_id)
117             continue
118
119     return default
120
121 # By default, the "bridge-id" external id in the Bridge table is the
122 # same as "xs-network-uuids".  This may be overridden by defining a
123 # "nicira-bridge-id" key in the "other_config" field of the network
124 # record of XAPI.  If nicira-bridge-id is undefined returns default.
125 # On error returns None.
126 def get_bridge_id(br_name, default=None):
127     rec = get_network_by_bridge(br_name)
128     if rec:
129         return rec['other_config'].get('nicira-bridge-id', default)
130     return None
131
132
133 # By default, the "iface-id" external id in the Interface table is the
134 # same as "xs-vif-uuid".  This may be overridden by defining a
135 # "nicira-iface-id" key in the "other_config" field of the VIF
136 # record of XAPI.
137 def get_iface_id(if_name, xs_vif_uuid):
138     if not if_name.startswith("vif") and not if_name.startswith("tap"):
139         # Treat whatever was passed into 'xs_vif_uuid' as a default
140         # value for non-VIFs.
141         return xs_vif_uuid
142
143     if not init_session():
144         vlog.warn("Failed to get interface id %s because"
145                 " XAPI session could not be initialized" % if_name)
146         return xs_vif_uuid
147
148     try:
149         vif = session.xenapi.VIF.get_by_uuid(xs_vif_uuid)
150         rec = session.xenapi.VIF.get_record(vif)
151         return rec['other_config'].get('nicira-iface-id', xs_vif_uuid)
152     except XenAPI.Failure:
153         vlog.warn("Could not find XAPI entry for VIF %s" % if_name)
154         return xs_vif_uuid
155
156
157 # By default, the "vm-id" external id in the Interface table is the
158 # same as "xs-vm-uuid".  This may be overridden by defining a
159 # "nicira-vm-id" key in the "other_config" field of the VM
160 # record of XAPI.
161 def get_vm_id(if_name, xs_vm_uuid):
162     if not if_name.startswith("vif") and not if_name.startswith("tap"):
163         # Treat whatever was passed into 'xs_vm_uuid' as a default
164         # value for non-VIFs.
165         return xs_vm_uuid
166
167     if not init_session():
168         vlog.warn("Failed to get vm id for interface id %s because"
169                 " XAPI session could not be initialized" % if_name)
170         return xs_vm_uuid
171
172     try:
173         vm = session.xenapi.VM.get_by_uuid(xs_vm_uuid)
174         rec = session.xenapi.VM.get_record(vm)
175         return rec['other_config'].get('nicira-vm-id', xs_vm_uuid)
176     except XenAPI.Failure:
177         vlog.warn("Could not find XAPI entry for VIF %s" % if_name)
178         return xs_vm_uuid
179
180
181 def set_or_delete(d, key, value):
182     if value is None:
183         if key in d:
184             del d[key]
185             return True
186     else:
187         if d.get(key) != value:
188             d[key] = value
189             return True
190     return False
191
192
193 def set_external_id(row, key, value):
194     row.verify("external_ids")
195     external_ids = row.external_ids
196     if set_or_delete(external_ids, key, value):
197         row.external_ids = external_ids
198
199
200 # XenServer does not call interface-reconfigure on internal networks,
201 # which is where the fail-mode would normally be set.
202 def update_fail_mode(row):
203     rec = get_network_by_bridge(row.name)
204     if not rec:
205         return
206
207     fail_mode = rec['other_config'].get('vswitch-controller-fail-mode')
208
209     if not fail_mode:
210         pools = session.xenapi.pool.get_all()
211         if len(pools) == 1:
212             prec = session.xenapi.pool.get_record(pools[0])
213             fail_mode = prec['other_config'].get(
214                     'vswitch-controller-fail-mode')
215
216     if fail_mode not in ['standalone', 'secure']:
217         fail_mode = 'standalone'
218
219     row.verify("fail_mode")
220     if row.fail_mode != fail_mode:
221         row.fail_mode = fail_mode
222
223
224 def update_in_band_mgmt(row):
225     rec = get_network_by_bridge(row.name)
226     if not rec:
227         return
228
229     dib = rec['other_config'].get('vswitch-disable-in-band')
230
231     row.verify("other_config")
232     other_config = row.other_config
233     if dib and dib not in ['true', 'false']:
234         vlog.warn('"%s" isn\'t a valid setting for '
235                 "other_config:disable-in-band on %s" % (dib, row.name))
236     elif set_or_delete(other_config, 'disable-in-band', dib):
237         row.other_config = other_config
238
239
240 def main():
241     global flush_cache
242
243     parser = argparse.ArgumentParser()
244     parser.add_argument("database", metavar="DATABASE",
245             help="A socket on which ovsdb-server is listening.")
246     parser.add_argument("--root-prefix", metavar="DIR", default='',
247                         help="Use DIR as alternate root directory"
248                         " (for testing).")
249
250     ovs.vlog.add_args(parser)
251     ovs.daemon.add_args(parser)
252     args = parser.parse_args()
253     ovs.vlog.handle_args(args)
254     ovs.daemon.handle_args(args)
255
256     remote = args.database
257     schema_helper = ovs.db.idl.SchemaHelper()
258     schema_helper.register_columns("Bridge", ["name", "external_ids",
259                                               "other_config", "fail_mode"])
260     schema_helper.register_columns("Interface", ["name", "external_ids"])
261     idl = ovs.db.idl.Idl(remote, schema_helper)
262
263     ovs.daemon.daemonize()
264
265     ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None)
266     ovs.unixctl.command_register("flush-cache", "", 0, 0, unixctl_flush_cache,
267                                  None)
268     error, unixctl_server = ovs.unixctl.server.UnixctlServer.create(None)
269     if error:
270         ovs.util.ovs_fatal(error, "could not create unixctl server", vlog)
271
272     # This daemon is usually started before XAPI, but to complete our
273     # tasks, we need it.  Wait here until it's up.
274     cookie_file = args.root_prefix + "/var/run/xapi_init_complete.cookie"
275     while not os.path.exists(cookie_file):
276         time.sleep(1)
277
278     bridges = {}                # Map from bridge name to nicira-bridge-id
279     iface_ids = {}              # Map from xs-vif-uuid to iface-id
280     vm_ids = {}                 # Map from xs-vm-uuid to vm-id
281     seqno = idl.change_seqno    # Sequence number when we last processed the db
282     while True:
283         unixctl_server.run()
284         if exiting:
285             break;
286
287         idl.run()
288         if not flush_cache and seqno == idl.change_seqno:
289             poller = ovs.poller.Poller()
290             unixctl_server.wait(poller)
291             idl.wait(poller)
292             poller.block()
293             continue
294
295         if flush_cache:
296             vlog.info("Flushing cache as the result of unixctl.")
297             bridges = {}
298             iface_ids = {}
299             vm_ids = {}
300             flush_cache = False
301         seqno = idl.change_seqno
302
303         txn = ovs.db.idl.Transaction(idl)
304
305         new_bridges = {}
306         for row in idl.tables["Bridge"].rows.itervalues():
307             if row.name in bridges:
308                 nbd = bridges[row.name]
309             else:
310                 # New bridge.
311                 update_fail_mode(row)
312                 update_in_band_mgmt(row)
313                 nbd = get_bridge_id(row.name)
314
315             bridge_id = nbd
316             if bridge_id is None:
317                 bridge_id = row.external_ids.get("xs-network-uuids")
318                 if bridge_id and len(bridge_id.split(";")) > 1:
319                     bridge_ids = bridge_id.split(";")
320                     bridge_id = get_single_bridge_id(bridge_ids, bridge_ids[0])
321
322             if bridge_id is not None:
323                 set_external_id(row, "bridge-id", bridge_id.split(";")[0])
324
325             new_bridges[row.name] = nbd
326         bridges = new_bridges
327
328         iface_by_name = {}
329         for row in idl.tables["Interface"].rows.itervalues():
330             iface_by_name[row.name] = row
331
332         new_iface_ids = {}
333         new_vm_ids = {}
334         for row in idl.tables["Interface"].rows.itervalues():
335             # Match up paired vif and tap devices.
336             if row.name.startswith("vif"):
337                 vif = row
338                 tap = iface_by_name.get("tap%s" % row.name[3:])
339             elif row.name.startswith("tap"):
340                 tap = row
341                 vif = iface_by_name.get("vif%s" % row.name[3:])
342             else:
343                 tap = vif = None
344
345             # Several tap external-ids need to be copied from the vif.
346             if row == tap and vif:
347                 keys = ["attached-mac",
348                         "xs-network-uuid",
349                         "xs-vif-uuid",
350                         "xs-vm-uuid"]
351                 for k in keys:
352                     set_external_id(row, k, vif.external_ids.get(k))
353
354             # Map from xs-vif-uuid to iface-id.
355             #
356             # (A tap's xs-vif-uuid comes from its vif.  That falls out
357             # naturally from the copy loop above.)
358             xvu = row.external_ids.get("xs-vif-uuid")
359             if xvu:
360                 iface_id = (new_iface_ids.get(xvu)
361                             or iface_ids.get(xvu)
362                             or get_iface_id(row.name, xvu))
363                 new_iface_ids[xvu] = iface_id
364             else:
365                 # No xs-vif-uuid therefore no iface-id.
366                 iface_id = None
367             set_external_id(row, "iface-id", iface_id)
368
369             # Map from xs-vm-uuid to vm-id.
370             xvmu = row.external_ids.get("xs-vm-uuid")
371             if xvmu:
372                 vm_id = (new_vm_ids.get(xvmu)
373                          or vm_ids.get(xvmu)
374                          or get_vm_id(row.name, xvmu))
375                 new_vm_ids[xvmu] = vm_id
376             else:
377                 vm_id = None
378             set_external_id(row, "vm-id", vm_id)
379
380             # When there's a vif and a tap, the tap is active (used for
381             # traffic).  When there's just a vif, the vif is active.
382             #
383             # A tap on its own shouldn't happen, and we don't know
384             # anything about other kinds of devices, so we don't use
385             # an iface-status for those devices at all.
386             if vif and tap:
387                 set_external_id(tap, "iface-status", "active")
388                 set_external_id(vif, "iface-status", "inactive")
389             elif vif:
390                 set_external_id(vif, "iface-status", "active")
391             else:
392                 set_external_id(row, "iface-status", None)
393         iface_ids = new_iface_ids
394         vm_ids = new_vm_ids
395
396         txn.add_comment("ovs-xapi-sync: Updating records from XAPI")
397         txn.commit_block()
398
399     unixctl_server.close()
400     idl.close()
401
402
403 if __name__ == '__main__':
404     try:
405         main()
406     except SystemExit:
407         # Let system.exit() calls complete normally
408         raise
409     except:
410         vlog.exception("traceback")
411         sys.exit(ovs.daemon.RESTART_EXIT_CODE)