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