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