ovs-xapi-sync: Add unit test.
authorBen Pfaff <blp@nicira.com>
Mon, 26 Sep 2011 20:07:29 +0000 (13:07 -0700)
committerBen Pfaff <blp@nicira.com>
Mon, 26 Sep 2011 20:08:58 +0000 (13:08 -0700)
tests/MockXenAPI.py [new file with mode: 0644]
tests/automake.mk
tests/ovs-xapi-sync.at [new file with mode: 0644]
tests/testsuite.at
xenserver/usr_share_openvswitch_scripts_ovs-xapi-sync

diff --git a/tests/MockXenAPI.py b/tests/MockXenAPI.py
new file mode 100644 (file)
index 0000000..f6ac22f
--- /dev/null
@@ -0,0 +1,89 @@
+# Copyright (c) 2011 Nicira Networks
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+def xapi_local():
+    return Session()
+
+
+class Session(object):
+    def __init__(self):
+        self.xenapi = XenAPI()
+
+
+class Failure(Exception):
+    pass
+
+
+class XenAPI(object):
+    def __init__(self):
+        self.network = Network()
+        self.pool = Pool()
+        self.VIF = VIF()
+
+    def login_with_password(self, unused_username, unused_password):
+        pass
+
+
+class RecordRef(object):
+    def __init__(self, attrs):
+        self.attrs = attrs
+
+
+class Table(object):
+    def __init__(self, records):
+        self.records = records
+
+    def get_all(self):
+        return [RecordRef(rec) for rec in self.records]
+
+    def get_by_uuid(self, uuid):
+        recs = [rec for rec in self.records if rec["uuid"] == uuid]
+        if len(recs) != 1:
+            raise Failure("No record with UUID %s" % uuid)
+        return RecordRef(recs[0])
+
+    def get_record(self, record_ref):
+        return record_ref.attrs
+
+
+class Network(Table):
+    __records = ({"uuid": "9b66c68b-a74e-4d34-89a5-20a8ab352d1e",
+                  "bridge": "xenbr0",
+                  "other_config":
+                      {"vswitch-controller-fail-mode": "secure",
+                       "nicira-bridge-id": "custom bridge ID"}},
+                 {"uuid": "e1c9019d-375b-45ac-a441-0255dd2247de",
+                  "bridge": "xenbr1",
+                  "other_config":
+                      {"vswitch-disable-in-band": "true"}})
+
+    def __init__(self):
+        Table.__init__(self, Network.__records)
+
+
+class Pool(Table):
+    __records = ({"uuid": "7a793edf-e5f4-4994-a0f9-cee784c0cda3",
+                  "other_config":
+                      {"vswitch-controller-fail-mode": "secure"}},)
+
+    def __init__(self):
+        Table.__init__(self, Pool.__records)
+
+class VIF(Table):
+    __records = ({"uuid": "6ab1b260-398e-49ba-827b-c7696108964c",
+                  "other_config":
+                      {"nicira-iface-id": "custom iface ID"}},)
+
+    def __init__(self):
+        Table.__init__(self, VIF.__records)
index dcf6026..8d019e7 100644 (file)
@@ -52,6 +52,7 @@ TESTSUITE_AT = \
        tests/ovsdb-idl.at \
        tests/ovs-vsctl.at \
        tests/ovs-monitor-ipsec.at \
+       tests/ovs-xapi-sync.at \
        tests/interface-reconfigure.at
 TESTSUITE = $(srcdir)/tests/testsuite
 DISTCLEANFILES += tests/atconfig tests/atlocal
@@ -335,7 +336,8 @@ EXTRA_DIST += \
        tests/test-json.py \
        tests/test-jsonrpc.py \
        tests/test-ovsdb.py \
-       tests/test-reconnect.py
+       tests/test-reconnect.py \
+       tests/MockXenAPI.py
 
 if HAVE_OPENSSL
 TESTPKI_FILES = \
diff --git a/tests/ovs-xapi-sync.at b/tests/ovs-xapi-sync.at
new file mode 100644 (file)
index 0000000..b2bfff5
--- /dev/null
@@ -0,0 +1,74 @@
+AT_BANNER([ovs-xapi-sync])
+
+AT_SETUP([ovs-xapi-sync])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+
+# Mock up the XenAPI.
+cp "$top_srcdir/tests/MockXenAPI.py" XenAPI.py
+PYTHONPATH=`pwd`:$PYTHONPATH
+export PYTHONPATH
+
+OVS_PKGDATADIR=`pwd`; export OVS_PKGDATADIR
+cp "$top_srcdir/vswitchd/vswitch.ovsschema" .
+
+cp "$top_srcdir/xenserver/usr_share_openvswitch_scripts_ovs-xapi-sync" \
+   ovs-xapi-sync
+
+trap 'kill `cat pid ovs-xapi-sync.pid`' 0
+
+mkdir var var/run
+touch var/run/xapi_init_complete.cookie
+
+ovs_vsctl () {
+    ovs-vsctl --timeout=5 --no-wait -vreconnect:ANY:emer --db=unix:socket "$@"
+}
+
+# Start ovsdb-server.
+OVS_VSCTL_SETUP
+
+# Start ovs-xapi-sync.
+AT_CHECK([$PYTHON ./ovs-xapi-sync "--pidfile-name=`pwd`/ovs-xapi-sync.pid" \
+                  "--root-prefix=`pwd`" unix:socket >log 2>&1 &])
+AT_CAPTURE_FILE([log])
+
+# Add bridges and check ovs-xapi-sync's work.
+AT_CHECK([ovs_vsctl -- add-br xenbr0 -- add-br xenbr1])
+OVS_WAIT_UNTIL([test "X`ovs_vsctl get bridge xenbr0 fail-mode`" != "X[[]]"])
+AT_CHECK([ovs_vsctl \
+                -- get bridge xenbr0 fail-mode other-config external-ids \
+                -- get bridge xenbr1 fail-mode other-config external-ids], [0],
+         [[secure
+{}
+{bridge-id="custom bridge ID"}
+secure
+{disable-in-band="true"}
+{}
+]])
+
+# Add vif and check daemon's work.
+AT_CHECK([ovs_vsctl \
+                -- add-port xenbr0 vif1.0 \
+                -- set Interface vif1.0 'external-ids={attached-mac="00:11:22:33:44:55", xs-network-uuid="9b66c68b-a74e-4d34-89a5-20a8ab352d1e", xs-vif-uuid="6ab1b260-398e-49ba-827b-c7696108964c", xs-vm-uuid="fcb8a3f6-dc04-41d2-8b8a-55afd2b755b8"'}])
+OVS_WAIT_UNTIL([ovs_vsctl get interface vif1.0 external-ids:iface-id >/dev/null 2>&1])
+AT_CHECK([ovs_vsctl get interface vif1.0 external-ids], [0],
+  [{attached-mac="00:11:22:33:44:55", iface-id="custom iface ID", iface-status=active, xs-network-uuid="9b66c68b-a74e-4d34-89a5-20a8ab352d1e", xs-vif-uuid="6ab1b260-398e-49ba-827b-c7696108964c", xs-vm-uuid="fcb8a3f6-dc04-41d2-8b8a-55afd2b755b8"}
+])
+
+# Add corresponding tap and check daemon's work.
+AT_CHECK([ovs_vsctl add-port xenbr0 tap1.0])
+OVS_WAIT_UNTIL([ovs_vsctl get interface tap1.0 external-ids:iface-id >/dev/null 2>&1])
+AT_CHECK([ovs_vsctl \
+                -- get interface vif1.0 external-ids \
+                -- get interface tap1.0 external-ids], [0],
+  [{attached-mac="00:11:22:33:44:55", iface-id="custom iface ID", iface-status=inactive, xs-network-uuid="9b66c68b-a74e-4d34-89a5-20a8ab352d1e", xs-vif-uuid="6ab1b260-398e-49ba-827b-c7696108964c", xs-vm-uuid="fcb8a3f6-dc04-41d2-8b8a-55afd2b755b8"}
+{attached-mac="00:11:22:33:44:55", iface-id="custom iface ID", iface-status=active, xs-network-uuid="9b66c68b-a74e-4d34-89a5-20a8ab352d1e", xs-vif-uuid="6ab1b260-398e-49ba-827b-c7696108964c", xs-vm-uuid="fcb8a3f6-dc04-41d2-8b8a-55afd2b755b8"}
+])
+
+# Remove corresponding tap and check daemon's work.
+AT_CHECK([ovs_vsctl del-port tap1.0])
+OVS_WAIT_UNTIL([test `ovs_vsctl get interface vif1.0 external-ids:iface-status` = active])
+AT_CHECK([ovs_vsctl get interface vif1.0 external-ids], [0],
+  [{attached-mac="00:11:22:33:44:55", iface-id="custom iface ID", iface-status=active, xs-network-uuid="9b66c68b-a74e-4d34-89a5-20a8ab352d1e", xs-vif-uuid="6ab1b260-398e-49ba-827b-c7696108964c", xs-vm-uuid="fcb8a3f6-dc04-41d2-8b8a-55afd2b755b8"}
+])
+
+AT_CLEANUP
index 19b7802..8608572 100644 (file)
@@ -65,4 +65,5 @@ m4_include([tests/ofproto-dpif.at])
 m4_include([tests/ovsdb.at])
 m4_include([tests/ovs-vsctl.at])
 m4_include([tests/ovs-monitor-ipsec.at])
+m4_include([tests/ovs-xapi-sync.at])
 m4_include([tests/interface-reconfigure.at])
index 7109609..f458654 100755 (executable)
@@ -40,6 +40,7 @@ import ovs.util
 import ovs.daemon
 import ovs.db.idl
 
+root_prefix = ''                # Prefix for absolute file names, for testing.
 s_log = logging.getLogger("ovs-xapi-sync")
 vsctl = "/usr/bin/ovs-vsctl"
 session = None
@@ -59,9 +60,9 @@ def init_session():
     try:
         session = XenAPI.xapi_local()
         session.xenapi.login_with_password("", "")
-    except:
+    except XenAPI.Failure, e:
         session = None
-        s_log.warning("Couldn't login to XAPI")
+        s_log.warning("Couldn't login to XAPI (%s)" % e)
         return False
 
     return True
@@ -218,8 +219,10 @@ def usage():
     print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
     print "where DATABASE is a socket on which ovsdb-server is listening."
     ovs.daemon.usage()
-    print "Other options:"
-    print "  -h, --help               display this help message"
+    print """\
+Other options:
+    --root-prefix=DIR   Use DIR as alternate root directory (for testing).
+    -h, --help               display this help message"""
     sys.exit(0)
 
 
@@ -232,16 +235,21 @@ def handler(signum, _):
 def main(argv):
     global force_run
 
-    l_handler = logging.handlers.RotatingFileHandler(
+    s_log.addHandler(logging.StreamHandler())
+    try:
+        l_handler = logging.handlers.RotatingFileHandler(
             "/var/log/openvswitch/ovs-xapi-sync.log")
-    l_formatter = logging.Formatter('%(filename)s: %(levelname)s: %(message)s')
-    l_handler.setFormatter(l_formatter)
-    s_log.addHandler(l_handler)
+        l_formatter = logging.Formatter('%(filename)s: %(levelname)s: %(message)s')
+        l_handler.setFormatter(l_formatter)
+        s_log.addHandler(l_handler)
+    except IOError, e:
+        logging.basicConfig()
+        s_log.warn("failed to open logfile (%s)" % e)
     s_log.setLevel(logging.INFO)
 
     try:
         options, args = getopt.gnu_getopt(
-            argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
+            argv[1:], 'h', ['help', 'root-prefix='] + ovs.daemon.LONG_OPTIONS)
     except getopt.GetoptError, geo:
         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
         sys.exit(1)
@@ -249,6 +257,9 @@ def main(argv):
     for key, value in options:
         if key in ['-h', '--help']:
             usage()
+        elif key == '--root-prefix':
+            global root_prefix
+            root_prefix = value
         elif not ovs.daemon.parse_opt(key, value):
             sys.stderr.write("%s: unhandled option %s\n"
                              % (ovs.util.PROGRAM_NAME, key))
@@ -269,7 +280,8 @@ def main(argv):
 
     # This daemon is usually started before XAPI, but to complete our
     # tasks, we need it.  Wait here until it's up.
-    while not os.path.exists("/var/run/xapi_init_complete.cookie"):
+    cookie_file = root_prefix + "/var/run/xapi_init_complete.cookie"
+    while not os.path.exists(cookie_file):
         time.sleep(1)
 
     signal.signal(signal.SIGHUP, handler)