2 # Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
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:
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
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:
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.
33 from ovs.db import error
34 from ovs.db import types
38 import ovs.unixctl.server
40 vlog = ovs.vlog.Vlog("ovs-xapi-sync")
47 def unixctl_exit(conn, unused_argv, unused_aux):
53 def unixctl_flush_cache(conn, unused_argv, unused_aux):
59 # Set up a session to interact with XAPI.
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.
66 if session is not None:
70 session = XenAPI.xapi_local()
71 session.xenapi.login_with_password("", "")
72 except XenAPI.Failure, e:
74 vlog.warn("Couldn't login to XAPI (%s)" % e)
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)
86 recs = session.xenapi.network.get_all_records_where('field "bridge"="%s"' % br_name)
88 return recs.values()[0]
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):
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)
102 for bridge_id in bridge_ids:
104 recs = session.xenapi.network.get_all_records_where(\
105 'field "uuid"="%s"' % bridge_id)
107 pifs = recs.values()[0]['PIFs']
110 rec = session.xenapi.PIF.get_record(pif)
111 if rec['VLAN'] == '-1':
113 except XenAPI.Failure:
114 vlog.warn("Could not find XAPI entry for PIF %s" % pif)
117 except XenAPI.Failure:
118 vlog.warn("Could not find XAPI entry for bridge_id %s" % bridge_id)
121 vlog.warn("Failed to get a single bridge id from Xapi.")
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)
133 return rec['other_config'].get('nicira-bridge-id', default)
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
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.
147 if not init_session():
148 vlog.warn("Failed to get interface id %s because"
149 " XAPI session could not be initialized" % if_name)
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)
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
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.
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)
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)
185 def set_or_delete(d, key, value):
191 if d.get(key) != value:
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
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)
211 fail_mode = rec['other_config'].get('vswitch-controller-fail-mode')
214 pools = session.xenapi.pool.get_all()
216 prec = session.xenapi.pool.get_record(pools[0])
217 fail_mode = prec['other_config'].get(
218 'vswitch-controller-fail-mode')
220 if fail_mode not in ['standalone', 'secure']:
221 fail_mode = 'standalone'
223 row.verify("fail_mode")
224 if row.fail_mode != fail_mode:
225 row.fail_mode = fail_mode
228 def update_in_band_mgmt(row):
229 rec = get_network_by_bridge(row.name)
233 dib = rec['other_config'].get('vswitch-disable-in-band')
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
245 global flush_cache, xapi_down
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"
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)
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)
267 ovs.daemon.daemonize()
269 ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None)
270 ovs.unixctl.command_register("flush-cache", "", 0, 0, unixctl_flush_cache,
272 error, unixctl_server = ovs.unixctl.server.UnixctlServer.create(None)
274 ovs.util.ovs_fatal(error, "could not create unixctl server", vlog)
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):
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
292 if not xapi_down and not flush_cache and seqno == idl.change_seqno:
293 poller = ovs.poller.Poller()
294 unixctl_server.wait(poller)
300 vlog.warn("Xapi is probably down. Retry again after a second.")
305 vlog.info("Flushing cache as the result of unixctl.")
310 seqno = idl.change_seqno
312 txn = ovs.db.idl.Transaction(idl)
315 for row in idl.tables["Bridge"].rows.itervalues():
316 if row.name in bridges:
317 nbd = bridges[row.name]
320 update_fail_mode(row)
321 update_in_band_mgmt(row)
322 nbd = get_bridge_id(row.name)
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, "")
331 if bridge_id is not None:
332 set_external_id(row, "bridge-id", bridge_id.split(";")[0])
334 new_bridges[row.name] = nbd
335 bridges = new_bridges
338 for row in idl.tables["Interface"].rows.itervalues():
339 iface_by_name[row.name] = row
343 for row in idl.tables["Interface"].rows.itervalues():
344 # Match up paired vif and tap devices.
345 if row.name.startswith("vif"):
347 tap = iface_by_name.get("tap%s" % row.name[3:])
348 elif row.name.startswith("tap"):
350 vif = iface_by_name.get("vif%s" % row.name[3:])
354 # Several tap external-ids need to be copied from the vif.
355 if row == tap and vif:
356 keys = ["attached-mac",
361 set_external_id(row, k, vif.external_ids.get(k))
363 # Map from xs-vif-uuid to iface-id.
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")
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
374 # No xs-vif-uuid therefore no iface-id.
376 set_external_id(row, "iface-id", iface_id)
378 # Map from xs-vm-uuid to vm-id.
379 xvmu = row.external_ids.get("xs-vm-uuid")
381 vm_id = (new_vm_ids.get(xvmu)
383 or get_vm_id(row.name, xvmu))
384 new_vm_ids[xvmu] = vm_id
387 set_external_id(row, "vm-id", vm_id)
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.
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.
396 set_external_id(tap, "iface-status", "active")
397 set_external_id(vif, "iface-status", "inactive")
399 set_external_id(vif, "iface-status", "active")
401 set_external_id(row, "iface-status", None)
402 iface_ids = new_iface_ids
405 txn.add_comment("ovs-xapi-sync: Updating records from XAPI")
408 unixctl_server.close()
412 if __name__ == '__main__':
416 # Let system.exit() calls complete normally
419 vlog.exception("traceback")
420 sys.exit(ovs.daemon.RESTART_EXIT_CODE)