#!/usr/bin/python
-# Copyright (c) 2009, 2010, 2011, 2012 Nicira Networks
+# Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
import argparse
import os
-import signal
-import subprocess
import sys
import time
import ovs.dirs
from ovs.db import error
from ovs.db import types
-import ovs.util
import ovs.daemon
import ovs.db.idl
+import ovs.unixctl
+import ovs.unixctl.server
-root_prefix = '' # Prefix for absolute file names, for testing.
vlog = ovs.vlog.Vlog("ovs-xapi-sync")
-vsctl = "/usr/bin/ovs-vsctl"
session = None
-force_run = False
+flush_cache = False
+exiting = False
+xapi_down = False
+
+
+def unixctl_exit(conn, unused_argv, unused_aux):
+ global exiting
+ exiting = True
+ conn.reply(None)
+
+
+def unixctl_flush_cache(conn, unused_argv, unused_aux):
+ global flush_cache
+ flush_cache = True
+ conn.reply(None)
# Set up a session to interact with XAPI.
" XAPI session could not be initialized" % br_name)
return None
- for n in session.xenapi.network.get_all():
- rec = session.xenapi.network.get_record(n)
- if rec['bridge'] == br_name:
- return rec
+ recs = session.xenapi.network.get_all_records_where('field "bridge"="%s"' % br_name)
+ if len(recs) > 0:
+ return recs.values()[0]
return None
+# There are possibilities when multiple xs-network-uuids are set for a bridge.
+# In cases like that, we should choose the bridge-id associated with the bridge
+# name.
+def get_single_bridge_id(bridge_ids, br_name, default=None):
+ global xapi_down
+
+ rec = get_network_by_bridge(br_name)
+ if rec and rec['uuid'] in bridge_ids:
+ return rec['uuid']
+
+ vlog.warn("Failed to get a single bridge id from Xapi.")
+ xapi_down = True
+ return default
# By default, the "bridge-id" external id in the Bridge table is the
# same as "xs-network-uuids". This may be overridden by defining a
return xs_vif_uuid
-def call_vsctl(args):
- cmd = [vsctl, "--timeout=30", "-vANY:console:off"] + args
- exitcode = subprocess.call(cmd)
- if exitcode != 0:
- vlog.warn("Couldn't call ovs-vsctl")
+# By default, the "vm-id" external id in the Interface table is the
+# same as "xs-vm-uuid". This may be overridden by defining a
+# "nicira-vm-id" key in the "other_config" field of the VM
+# record of XAPI.
+def get_vm_id(if_name, xs_vm_uuid):
+ if not if_name.startswith("vif") and not if_name.startswith("tap"):
+ # Treat whatever was passed into 'xs_vm_uuid' as a default
+ # value for non-VIFs.
+ return xs_vm_uuid
+
+ if not init_session():
+ vlog.warn("Failed to get vm id for interface id %s because"
+ " XAPI session could not be initialized" % if_name)
+ return xs_vm_uuid
+
+ try:
+ vm = session.xenapi.VM.get_by_uuid(xs_vm_uuid)
+ rec = session.xenapi.VM.get_record(vm)
+ return rec['other_config'].get('nicira-vm-id', xs_vm_uuid)
+ except XenAPI.Failure:
+ vlog.warn("Could not find XAPI entry for VIF %s" % if_name)
+ return xs_vm_uuid
def set_or_delete(d, key, value):
def set_external_id(row, key, value):
+ row.verify("external_ids")
external_ids = row.external_ids
if set_or_delete(external_ids, key, value):
row.external_ids = external_ids
if fail_mode not in ['standalone', 'secure']:
fail_mode = 'standalone'
+ row.verify("fail_mode")
if row.fail_mode != fail_mode:
row.fail_mode = fail_mode
dib = rec['other_config'].get('vswitch-disable-in-band')
+ row.verify("other_config")
other_config = row.other_config
if dib and dib not in ['true', 'false']:
vlog.warn('"%s" isn\'t a valid setting for '
row.other_config = other_config
-def update_bridge_id(row):
- id_ = get_bridge_id(row.name, row.external_ids.get("xs-network-uuids"))
- if not id_:
- return
-
- set_external_id(row, "bridge-id", id_.split(";")[0])
-
-
-def keep_table_columns(schema, table_name, columns):
- table = schema.tables.get(table_name)
- if not table:
- raise error.Error("schema has no %s table" % table_name)
-
- new_columns = {}
- for column_name in columns:
- column = table.columns.get(column_name)
- if not column:
- raise error.Error("%s table schema lacks %s column"
- % (table_name, column_name))
- new_columns[column_name] = column
- table.columns = new_columns
- return table
-
-
-def prune_schema(schema):
- new_tables = {}
- new_tables["Bridge"] = keep_table_columns(
- schema, "Bridge", ("name", "external_ids", "other_config",
- "fail_mode"))
- new_tables["Interface"] = keep_table_columns(
- schema, "Interface", ("name", "external_ids"))
- schema.tables = new_tables
-
-
-def handler(signum, _):
- global force_run
- if (signum == signal.SIGHUP):
- force_run = True
-
-
def main():
- global force_run
+ global flush_cache, xapi_down
parser = argparse.ArgumentParser()
parser.add_argument("database", metavar="DATABASE",
help="A socket on which ovsdb-server is listening.")
- parser.add_argument("--root-prefix", metavar="DIR",
+ parser.add_argument("--root-prefix", metavar="DIR", default='',
help="Use DIR as alternate root directory"
" (for testing).")
ovs.vlog.handle_args(args)
ovs.daemon.handle_args(args)
- global root_prefix
- if args.root_prefix:
- root_prefix = args.root_prefix
-
remote = args.database
- schema_file = "%s/vswitch.ovsschema" % ovs.dirs.PKGDATADIR
- schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schema_file))
- prune_schema(schema)
- idl = ovs.db.idl.Idl(remote, schema)
+ schema_helper = ovs.db.idl.SchemaHelper()
+ schema_helper.register_columns("Bridge", ["name", "external_ids",
+ "other_config", "fail_mode"])
+ schema_helper.register_columns("Interface", ["name", "external_ids"])
+ idl = ovs.db.idl.Idl(remote, schema_helper)
ovs.daemon.daemonize()
+ ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None)
+ ovs.unixctl.command_register("flush-cache", "", 0, 0, unixctl_flush_cache,
+ None)
+ error, unixctl_server = ovs.unixctl.server.UnixctlServer.create(None)
+ if error:
+ ovs.util.ovs_fatal(error, "could not create unixctl server", vlog)
+
# This daemon is usually started before XAPI, but to complete our
# tasks, we need it. Wait here until it's up.
- cookie_file = root_prefix + "/var/run/xapi_init_complete.cookie"
+ cookie_file = args.root_prefix + "/var/run/xapi_init_complete.cookie"
while not os.path.exists(cookie_file):
time.sleep(1)
- signal.signal(signal.SIGHUP, handler)
-
- bridges = {} # Map from bridge name to xs_network_uuids
+ bridges = {} # Map from bridge name to nicira-bridge-id
iface_ids = {} # Map from xs-vif-uuid to iface-id
+ vm_ids = {} # Map from xs-vm-uuid to vm-id
+ seqno = idl.change_seqno # Sequence number when we last processed the db
while True:
- if not force_run and not idl.run():
+ unixctl_server.run()
+ if exiting:
+ break;
+
+ idl.run()
+ if not xapi_down and not flush_cache and seqno == idl.change_seqno:
poller = ovs.poller.Poller()
+ unixctl_server.wait(poller)
idl.wait(poller)
poller.block()
continue
- if force_run:
- vlog.info("Forced to re-run as the result of a SIGHUP")
+ if xapi_down:
+ vlog.warn("Xapi is probably down. Retry again after a second.")
+ time.sleep(1)
+ xapi_down = False
+
+ if flush_cache:
+ vlog.info("Flushing cache as the result of unixctl.")
bridges = {}
iface_ids = {}
- force_run = False
+ vm_ids = {}
+ flush_cache = False
+ seqno = idl.change_seqno
txn = ovs.db.idl.Transaction(idl)
new_bridges = {}
for row in idl.tables["Bridge"].rows.itervalues():
- old_xnu = bridges.get(row.name)
- new_xnu = row.external_ids.get("xs-network-uuids", "")
- if old_xnu is None:
- # New bridge.
+ bridge_id = bridges.get(row.name)
+ if bridge_id is None:
+ # Configure the new bridge.
update_fail_mode(row)
update_in_band_mgmt(row)
- if new_xnu != old_xnu:
- # New bridge or bridge's xs-network-uuids has changed.
- update_bridge_id(row)
- new_bridges[row.name] = new_xnu
+
+ # Get the correct bridge_id, if we can.
+ bridge_id = get_bridge_id(row.name)
+ if bridge_id is None:
+ xs_network_uuids = row.external_ids.get("xs-network-uuids")
+ if xs_network_uuids:
+ bridge_ids = xs_network_uuids.split(";")
+ if len(bridge_ids) == 1:
+ bridge_id = bridge_ids[0]
+ else:
+ bridge_id = get_single_bridge_id(bridge_ids,
+ row.name)
+ set_external_id(row, "bridge-id", bridge_id)
+
+ if bridge_id is not None:
+ new_bridges[row.name] = bridge_id
bridges = new_bridges
iface_by_name = {}
iface_by_name[row.name] = row
new_iface_ids = {}
+ new_vm_ids = {}
for row in idl.tables["Interface"].rows.itervalues():
# Match up paired vif and tap devices.
if row.name.startswith("vif"):
iface_id = None
set_external_id(row, "iface-id", iface_id)
+ # Map from xs-vm-uuid to vm-id.
+ xvmu = row.external_ids.get("xs-vm-uuid")
+ if xvmu:
+ vm_id = (new_vm_ids.get(xvmu)
+ or vm_ids.get(xvmu)
+ or get_vm_id(row.name, xvmu))
+ new_vm_ids[xvmu] = vm_id
+ else:
+ vm_id = None
+ set_external_id(row, "vm-id", vm_id)
+
# When there's a vif and a tap, the tap is active (used for
# traffic). When there's just a vif, the vif is active.
#
else:
set_external_id(row, "iface-status", None)
iface_ids = new_iface_ids
+ vm_ids = new_vm_ids
+ txn.add_comment("ovs-xapi-sync: Updating records from XAPI")
txn.commit_block()
+ unixctl_server.close()
+ idl.close()
+
if __name__ == '__main__':
try: