Test: Get the cord-tester ready for real CORD deployment.
Fabric test, manifest, option to specify container image prefix, gradle build changes to publish and a host of other changes for cord.
Change-Id: I4bd1c8d9ff4c0a6d117219ca847ae03c61784096
diff --git a/Dockerfile.tester b/Dockerfile.tester
index 2030ed2..e47ccff 100644
--- a/Dockerfile.tester
+++ b/Dockerfile.tester
@@ -6,7 +6,8 @@
unzip libpcre3-dev flex bison libboost-dev \
python python-pip python-setuptools python-scapy tcpdump doxygen doxypy wget \
openvswitch-common openvswitch-switch \
- python-twisted python-sqlite sqlite3 python-pexpect telnet arping isc-dhcp-server
+ python-twisted python-sqlite sqlite3 python-pexpect telnet arping isc-dhcp-server \
+ python-paramiko python-maas-client
RUN easy_install nose
RUN mkdir -p /root/ovs
WORKDIR /root
diff --git a/build.gradle b/build.gradle
index 6ef8a83..7737296 100644
--- a/build.gradle
+++ b/build.gradle
@@ -27,6 +27,7 @@
dockerPath = project.hasProperty('dockerPath') ? project.getProperty('dockerPath') : '/usr/bin'
cordTesterImages = [ 'cord-test/radius:latest' : 'Dockerfile.radius', 'cord-test/quagga:latest' : 'Dockerfile.quagga', 'cord-test/nose:latest' : 'Dockerfile.tester' ]
+
}
// ~~~~~~~~~~~~~~~~~~~ Global tasks ~~~~~~~~~~~~~~~~~~~~~~~
@@ -49,8 +50,56 @@
}
}
+task buildRadiusImage(type: Exec) {
+ commandLine "$dockerPath/docker", 'build', '-t', 'cord-test/radius', '-f', 'Dockerfile.radius', '.'
+}
+
+task tagRadiusImage(type: Exec) {
+ dependsOn buildRadiusImage
+ commandLine "$dockerPath/docker", 'tag', 'cord-test/radius', "$targetReg/cord-test/radius:$targetTag"
+}
+
+task publishRadiusImage(type: Exec) {
+ dependsOn tagRadiusImage
+ commandLine "$dockerPath/docker", 'push', "$targetReg/cord-test/radius:$targetTag"
+}
+
+task buildQuaggaImage(type: Exec) {
+ commandLine "$dockerPath/docker", 'build', '-t', 'cord-test/quagga', '-f', 'Dockerfile.quagga', '.'
+}
+
+task tagQuaggaImage(type: Exec) {
+ dependsOn buildQuaggaImage
+ commandLine "$dockerPath/docker", 'tag', 'cord-test/quagga', "$targetReg/cord-test/quagga:$targetTag"
+}
+
+task publishQuaggaImage(type: Exec) {
+ dependsOn tagQuaggaImage
+ commandLine "$dockerPath/docker", 'push', "$targetReg/cord-test/quagga:$targetTag"
+}
+
+task buildTesterImage(type: Exec) {
+ commandLine "$dockerPath/docker", 'build', '-t', 'cord-test/nose', '-f', 'Dockerfile.tester', '.'
+}
+
+task tagTesterImage(type: Exec) {
+ dependsOn buildTesterImage
+ commandLine "$dockerPath/docker", 'tag', 'cord-test/nose', "$targetReg/cord-test/nose:$targetTag"
+}
+
+task publishTesterImage(type: Exec) {
+ dependsOn tagTesterImage
+ commandLine "$dockerPath/docker", 'push', "$targetReg/cord-test/nose:$targetTag"
+}
+
// Publish image(s) built during the build step into targetReg registry using the targetTag
// tag. See maas subproject for examples on how to do this.
+task publishImages {
+ dependsOn publishTesterImage
+ dependsOn publishQuaggaImage
+ dependsOn publishRadiusImage
+}
+
task publish {
- println "$targetTag"
+ dependsOn publishImages
}
diff --git a/src/test/cordSubscriber/cordSubscriberTest.py b/src/test/cordSubscriber/cordSubscriberTest.py
index 0447fcf..f4f32d6 100644
--- a/src/test/cordSubscriber/cordSubscriberTest.py
+++ b/src/test/cordSubscriber/cordSubscriberTest.py
@@ -1,3 +1,18 @@
+#
+# Copyright 2016-present Ciena Corporation
+#
+# 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.
+#
import unittest
from nose.tools import *
from nose.twistedtools import reactor, deferred
diff --git a/src/test/fabric/__init__.py b/src/test/fabric/__init__.py
new file mode 100644
index 0000000..b3dcc62
--- /dev/null
+++ b/src/test/fabric/__init__.py
@@ -0,0 +1,22 @@
+#
+# Copyright 2016-present Ciena Corporation
+#
+# 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.
+#
+import os,sys
+##add the python path to lookup the utils
+working_dir = os.path.dirname(os.path.realpath(sys.argv[-1]))
+utils_dir = os.path.join(working_dir, '../utils')
+fsm_dir = os.path.join(working_dir, '../fsm')
+__path__.append(utils_dir)
+__path__.append(fsm_dir)
diff --git a/src/test/fabric/fabricTest.py b/src/test/fabric/fabricTest.py
new file mode 100644
index 0000000..ec17e49
--- /dev/null
+++ b/src/test/fabric/fabricTest.py
@@ -0,0 +1,57 @@
+#
+# Copyright 2016-present Ciena Corporation
+#
+# 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.
+#
+import unittest
+from nose.tools import *
+from nose.twistedtools import reactor, deferred
+from twisted.internet import defer
+from TestManifest import TestManifest
+from Fabric import FabricMAAS, Fabric
+from scapy.all import *
+import os
+log.setLevel('INFO')
+
+class fabric_exchange(unittest.TestCase):
+
+ node_list = []
+ fabric = None
+ FABRIC_TEST_TIMEOUT = 30
+ key_file = os.getenv('SSH_KEY_FILE', None)
+ api_key = os.getenv('MAAS_API_KEY', 'UNKNOWN')
+
+ @classmethod
+ def setUpClass(cls):
+ if cls.api_key == 'UNKNOWN':
+ return
+ maas = FabricMAAS(api_key = cls.api_key)
+ cls.node_list = maas.get_node_list()
+ cls.fabric = Fabric(cls.node_list, key_file = cls.key_file, verbose = False)
+
+ @deferred(FABRIC_TEST_TIMEOUT)
+ def test_fabric(self):
+ """Test the connectivity between the compute nodes"""
+ df = defer.Deferred()
+ def verify_fabric(df):
+ assert_not_equal(self.fabric, None)
+ failed_nodes = []
+ failed_nodes = self.fabric.ping_neighbors()
+ if failed_nodes:
+ log.info('Failed nodes: %s' %failed_nodes)
+ for node, neighbor, _ in failed_nodes:
+ log.info('Ping from node %s to neighbor %s Failed' %(node, neighbor))
+ assert_equal(len(failed_nodes), 0)
+ df.callback(0)
+ reactor.callLater(0, verify_fabric, df)
+ return df
diff --git a/src/test/setup/cord-test.py b/src/test/setup/cord-test.py
index 8bc17ea..2108410 100755
--- a/src/test/setup/cord-test.py
+++ b/src/test/setup/cord-test.py
@@ -16,6 +16,7 @@
#
from argparse import ArgumentParser
import os,sys,time,socket,errno
+import shutil, platform
utils_dir = os.path.join( os.path.dirname(os.path.realpath(__file__)), '../utils')
sys.path.append(utils_dir)
from OnosCtrl import OnosCtrl
@@ -23,6 +24,11 @@
from threadPool import ThreadPool
from CordContainer import *
from CordTestServer import cord_test_server_start, cord_test_server_stop, CORD_TEST_HOST, CORD_TEST_PORT
+from TestManifest import TestManifest
+try:
+ from Fabric import FabricMAAS
+except:
+ FabricMAAS = None
class CordTester(Container):
sandbox = '/root/test'
@@ -42,19 +48,19 @@
ALL_TESTS = ('tls', 'dhcp', 'dhcprelay','igmp', 'subscriber', 'cordSubscriber', 'vrouter', 'flows', 'proxyarp', 'acl')
def __init__(self, tests, instance = 0, num_instances = 1, ctlr_ip = None,
- name = '', image = IMAGE, tag = 'latest',
+ name = '', image = IMAGE, prefix = '', tag = 'candidate',
env = None, rm = False, update = False):
self.tests = tests
self.ctlr_ip = ctlr_ip
self.rm = rm
self.name = name or self.get_name()
- super(CordTester, self).__init__(self.name, image = image, tag = tag)
+ super(CordTester, self).__init__(self.name, image = image, prefix = prefix, tag = tag)
host_config = self.create_host_config(host_guest_map = self.host_guest_map, privileged = True)
volumes = []
for _, g in self.host_guest_map:
volumes.append(g)
if update is True or not self.img_exists():
- self.build_image(image)
+ self.build_image(self.image_name)
self.create = True
#check if are trying to run tests on existing container
if not name or not self.exists():
@@ -221,7 +227,8 @@
unzip libpcre3-dev flex bison libboost-dev \
python python-pip python-setuptools python-scapy tcpdump doxygen doxypy wget \
openvswitch-common openvswitch-switch \
- python-twisted python-sqlite sqlite3 python-pexpect telnet arping isc-dhcp-server
+ python-twisted python-sqlite sqlite3 python-pexpect telnet arping isc-dhcp-server \
+ python-paramiko python-maas-client
RUN easy_install nose
RUN mkdir -p /root/ovs
WORKDIR /root
@@ -285,12 +292,27 @@
##default onos/radius/test container images and names
onos_image_default='onosproject/onos:latest'
-nose_image_default= '{}:latest'.format(CordTester.IMAGE)
+nose_image_default= '{}:candidate'.format(CordTester.IMAGE)
test_type_default='dhcp'
onos_app_version = '2.0-SNAPSHOT'
cord_tester_base = os.path.dirname(os.path.realpath(__file__))
onos_app_file = os.path.abspath('{0}/../apps/ciena-cordigmp-'.format(cord_tester_base) + onos_app_version + '.oar')
cord_test_server_address = '{}:{}'.format(CORD_TEST_HOST, CORD_TEST_PORT)
+identity_file_default = '/etc/maas/ansible/id_rsa'
+
+##sets up the ssh key file for the test container
+def set_ssh_key_file(identity_file):
+ ssh_key_file = None
+ if os.access(identity_file, os.F_OK):
+ ##copy it to setup directory
+ identity_dest = os.path.join(CordTester.tester_base, 'id_rsa')
+ if os.path.abspath(identity_file) != identity_dest:
+ try:
+ shutil.copy(identity_file, identity_dest)
+ ssh_key_file = os.path.join(CordTester.sandbox_setup, 'id_rsa')
+ except: pass
+
+ return ssh_key_file
def runTest(args):
#Start the cord test tcp server
@@ -317,7 +339,7 @@
tests_parallel = [ t for t in tests if t.split(':')[0] not in tests_exempt ]
tests_not_parallel = [ t for t in tests if t.split(':')[0] in tests_exempt ]
onos_cnt = {'tag':'latest'}
- nose_cnt = {'image': CordTester.IMAGE, 'tag': 'latest'}
+ nose_cnt = {'image': CordTester.IMAGE, 'tag': 'candidate'}
update_map = { 'quagga' : False, 'test' : False, 'radius' : False }
update_map[args.update.lower()] = True
@@ -325,7 +347,25 @@
for c in update_map.keys():
update_map[c] = True
+ onos_ip = None
radius_ip = None
+ head_node = platform.node()
+ use_manifest = False
+ if args.manifest:
+ if os.access(args.manifest, os.F_OK):
+ ##copy it to setup directory
+ dest = os.path.join(CordTester.tester_base, 'manifest.json')
+ if os.path.abspath(args.manifest) != dest:
+ try:
+ shutil.copy(args.manifest, dest)
+ except: pass
+ test_manifest = TestManifest(dest)
+ onos_ip = test_manifest.onos_ip
+ radius_ip = test_manifest.radius_ip
+ head_node = test_manifest.head_node
+ use_manifest = True
+ else:
+ print('Unable to access test manifest: %s' %args.manifest)
#don't spawn onos if the user has specified external test controller with test interface config
if args.test_controller:
@@ -335,40 +375,68 @@
radius_ip = ips[1]
else:
radius_ip = None
- else:
- onos_cnt['image'] = args.onos.split(':')[0]
- if args.onos.find(':') >= 0:
- onos_cnt['tag'] = args.onos.split(':')[1]
- onos = Onos(image = onos_cnt['image'], tag = onos_cnt['tag'], boot_delay = 60)
+ Container.IMAGE_PREFIX = args.prefix
+ if onos_ip is None:
+ image_names = args.onos.rsplit(':', 1)
+ onos_cnt['image'] = image_names[0]
+ if len(image_names) > 1:
+ if image_names[1].find('/') < 0:
+ onos_cnt['tag'] = image_names[1]
+ else:
+ #tag cannot have slashes
+ onos_cnt['image'] = args.onos
+
+ Onos.IMAGE = onos_cnt['image']
+ Onos.PREFIX = args.prefix
+ Onos.TAG = onos_cnt['tag']
+ onos = Onos(image = Onos.IMAGE,
+ tag = Onos.TAG, boot_delay = 60)
onos_ip = onos.ip()
print('Onos IP %s, Test type %s' %(onos_ip, args.test_type))
- if args.test_controller:
+ if use_manifest or args.test_controller:
print('Installing ONOS cord apps')
- Onos.install_cord_apps(onos_ip = onos_ip)
+ try:
+ Onos.install_cord_apps(onos_ip = onos_ip)
+ except: pass
print('Installing cord tester ONOS app %s' %onos_app_file)
- OnosCtrl.install_app(args.app, onos_ip = onos_ip)
+ try:
+ OnosCtrl.install_app(args.app, onos_ip = onos_ip)
+ except: pass
if radius_ip is None:
##Start Radius container
- radius = Radius( update = update_map['radius'])
+ radius = Radius(prefix = Container.IMAGE_PREFIX, update = update_map['radius'])
radius_ip = radius.ip()
print('Radius server running with IP %s' %radius_ip)
if args.quagga == True:
#Start quagga. Builds container if required
- quagga = Quagga(update = update_map['quagga'])
+ quagga = Quagga(prefix = Container.IMAGE_PREFIX, update = update_map['quagga'])
+ try:
+ maas_api_key = FabricMAAS.get_api_key()
+ except:
+ maas_api_key = 'UNKNOWN'
+
+ ssh_key_file = set_ssh_key_file(args.identity_file)
test_cnt_env = { 'ONOS_CONTROLLER_IP' : onos_ip,
'ONOS_AAA_IP' : radius_ip if radius_ip is not None else '',
'QUAGGA_IP': test_host,
'CORD_TEST_HOST' : test_host,
'CORD_TEST_PORT' : test_port,
'ONOS_RESTART' : 0 if args.olt and args.test_controller else 1,
+ 'MANIFEST': int(use_manifest),
+ 'HEAD_NODE': head_node if head_node else CORD_TEST_HOST,
+ 'MAAS_API_KEY': maas_api_key
}
+
+ if ssh_key_file:
+ test_cnt_env['SSH_KEY_FILE'] = ssh_key_file
+
if args.olt:
olt_conf_test_loc = os.path.join(CordTester.sandbox_setup, 'olt_config.json')
test_cnt_env['OLT_CONFIG'] = olt_conf_test_loc
@@ -390,7 +458,10 @@
test_cnt = CordTester(tests_parallel[test_slice_start:test_slice_end],
instance = container, num_instances = num_test_containers,
ctlr_ip = onos_ip,
- name = args.container, image = nose_cnt['image'], tag = nose_cnt['tag'],
+ name = args.container,
+ image = nose_cnt['image'],
+ prefix = Container.IMAGE_PREFIX,
+ tag = nose_cnt['tag'],
env = test_cnt_env,
rm = False if args.keep else True,
update = update_map['test'])
@@ -401,7 +472,8 @@
if not test_cnt.create:
continue
if test_cnt.create and (args.start_switch or not args.olt):
- test_cnt.start_switch()
+ if not args.no_switch:
+ test_cnt.start_switch()
if test_cnt.create and test_cnt.olt:
_, port_num = test_cnt.setup_intfs(port_num = port_num)
@@ -414,14 +486,18 @@
if tests_not_parallel:
test_cnt = CordTester(tests_not_parallel,
ctlr_ip = onos_ip,
- name = args.container, image = nose_cnt['image'], tag = nose_cnt['tag'],
+ name = args.container,
+ image = nose_cnt['image'],
+ prefix = Container.IMAGE_PREFIX,
+ tag = nose_cnt['tag'],
env = test_cnt_env,
rm = False if args.keep else True,
update = update_map['test'])
if test_cnt.create and (args.start_switch or not args.olt):
#For non parallel tests, we just restart the switch also for OLT's
CordTester.switch_on_olt = False
- test_cnt.start_switch()
+ if not args.no_switch:
+ test_cnt.start_switch()
if test_cnt.create and test_cnt.olt:
test_cnt.setup_intfs(port_num = port_num)
test_cnt.run_tests()
@@ -432,7 +508,7 @@
##Starts onos/radius/quagga containers as appropriate
def setupCordTester(args):
onos_cnt = {'tag':'latest'}
- nose_cnt = {'image': CordTester.IMAGE, 'tag': 'latest'}
+ nose_cnt = {'image': CordTester.IMAGE, 'tag': 'candidate'}
update_map = { 'quagga' : False, 'radius' : False, 'test': False }
update_map[args.update.lower()] = True
@@ -452,6 +528,22 @@
#Disable test container provisioning on the ONOS compute node
args.dont_provision = True
+ head_node = platform.node()
+ use_manifest = False
+ if args.manifest:
+ if os.access(args.manifest, os.F_OK):
+ ##copy it to setup directory
+ dest = os.path.join(CordTester.tester_base, 'manifest.json')
+ if os.path.abspath(args.manifest) != dest:
+ try:
+ shutil.copy(args.manifest, dest)
+ except: pass
+ test_manifest = TestManifest(dest)
+ onos_ip = test_manifest.onos_ip
+ radius_ip = test_manifest.radius_ip
+ head_node = test_manifest.head_node
+ use_manifest = True
+
##If onos/radius was already started
if args.test_controller:
ips = args.test_controller.split('/')
@@ -469,33 +561,46 @@
sys.exit(1)
onos_cord = OnosCord(onos_ip, onos_cord_loc)
+ Container.IMAGE_PREFIX = args.prefix
#don't spawn onos if the user had started it externally
- onos_cnt['image'] = args.onos.split(':')[0]
- if args.onos.find(':') >= 0:
- onos_cnt['tag'] = args.onos.split(':')[1]
+ image_names = args.onos.rsplit(':', 1)
+ onos_cnt['image'] = image_names[0]
+ if len(image_names) > 1:
+ if image_names[1].find('/') < 0:
+ onos_cnt['tag'] = image_names[1]
+ else:
+ #tag cannot have slashes
+ onos_cnt['image'] = args.onos
+ Onos.IMAGE = onos_cnt['image']
+ Onos.PREFIX = args.prefix
+ Onos.TAG = onos_cnt['tag']
if onos_ip is None:
- onos = Onos(image = onos_cnt['image'], tag = onos_cnt['tag'], boot_delay = 60)
+ onos = Onos(image = Onos.IMAGE, tag = Onos.TAG, boot_delay = 60)
onos_ip = onos.ip()
print('Onos IP %s' %onos_ip)
- if args.test_controller:
+ if use_manifest or args.test_controller:
print('Installing ONOS cord apps')
- Onos.install_cord_apps(onos_ip = onos_ip)
+ try:
+ Onos.install_cord_apps(onos_ip = onos_ip)
+ except: pass
print('Installing cord tester ONOS app %s' %onos_app_file)
- OnosCtrl.install_app(args.app, onos_ip = onos_ip)
+ try:
+ OnosCtrl.install_app(args.app, onos_ip = onos_ip)
+ except: pass
##Start Radius container if not started
if radius_ip is None:
- radius = Radius( update = update_map['radius'])
+ radius = Radius(prefix = Container.IMAGE_PREFIX, update = update_map['radius'])
radius_ip = radius.ip()
print('Radius server running with IP %s' %radius_ip)
if args.quagga == True:
#Start quagga. Builds container if required
- quagga = Quagga(update = update_map['quagga'])
+ quagga = Quagga(prefix = Container.IMAGE_PREFIX, update = update_map['quagga'])
print('Quagga started')
params = args.server.split(':')
@@ -504,6 +609,13 @@
if len(params) > 1:
port = int(params[1])
+ try:
+ maas_api_key = FabricMAAS.get_api_key()
+ except:
+ maas_api_key = 'UNKNOWN'
+
+ ssh_key_file = set_ssh_key_file(args.identity_file)
+
#provision the test container
if not args.dont_provision:
test_cnt_env = { 'ONOS_CONTROLLER_IP' : onos_ip,
@@ -512,7 +624,13 @@
'CORD_TEST_HOST' : ip,
'CORD_TEST_PORT' : port,
'ONOS_RESTART' : 0 if args.olt and args.test_controller else 1,
+ 'MANIFEST': int(use_manifest),
+ 'HEAD_NODE': head_node if head_node else CORD_TEST_HOST,
+ 'MAAS_API_KEY': maas_api_key
}
+
+ if ssh_key_file:
+ test_cnt_env['SSH_KEY_FILE'] = ssh_key_file
if args.olt:
olt_conf_test_loc = os.path.join(CordTester.sandbox_setup, 'olt_config.json')
test_cnt_env['OLT_CONFIG'] = olt_conf_test_loc
@@ -520,6 +638,7 @@
test_cnt = CordTester((),
ctlr_ip = onos_ip,
image = nose_cnt['image'],
+ prefix = Container.IMAGE_PREFIX,
tag = nose_cnt['tag'],
env = test_cnt_env,
rm = False,
@@ -540,7 +659,10 @@
sys.exit(0)
def cleanupTests(args):
- test_container = '{}:latest'.format(CordTester.IMAGE)
+ prefix = args.prefix
+ if prefix:
+ prefix += '/'
+ test_container = '{}{}:candidate'.format(prefix, CordTester.IMAGE)
print('Cleaning up Test containers ...')
Container.cleanup(test_container)
if args.olt:
@@ -555,28 +677,44 @@
CordTester.list_tests(tests)
def buildImages(args):
+ tag = 'candidate'
+ prefix = args.prefix
+ if prefix:
+ prefix += '/'
if args.image == 'all' or args.image == 'quagga':
- Quagga.build_image(Quagga.IMAGE)
+ image_name = '{}{}:{}'.format(prefix, Quagga.IMAGE, tag)
+ Quagga.build_image(image_name)
if args.image == 'all' or args.image == 'radius':
- Radius.build_image(Radius.IMAGE)
+ image_name = '{}{}:{}'.format(prefix, Radius.IMAGE, tag)
+ Radius.build_image(image_name)
if args.image == 'all' or args.image == 'test':
- CordTester.build_image(CordTester.IMAGE)
+ image_name = '{}{}:{}'.format(prefix, CordTester.IMAGE, tag)
+ CordTester.build_image(image_name)
def startImages(args):
-
##starts the latest ONOS image
+ onos_cnt = {'tag': 'latest'}
+ image_names = args.onos.rsplit(':', 1)
+ onos_cnt['image'] = image_names[0]
+ if len(image_names) > 1:
+ if image_names[1].find('/') < 0:
+ onos_cnt['tag'] = image_names[1]
+ else:
+ #tag cannot have slashes
+ onos_cnt['image'] = args.onos
+
if args.image == 'all' or args.image == 'onos':
- onos = Onos()
+ onos = Onos(image = onos_cnt['image'], tag = onos_cnt['tag'])
print('ONOS started with ip %s' %(onos.ip()))
if args.image == 'all' or args.image == 'quagga':
- quagga = Quagga()
+ quagga = Quagga(prefix = args.prefix)
print('Quagga started with ip %s' %(quagga.ip()))
if args.image == 'all' or args.image == 'radius':
- radius = Radius()
+ radius = Radius(prefix = args.prefix)
print('Radius started with ip %s' %(radius.ip()))
if __name__ == '__main__':
@@ -587,7 +725,7 @@
parser_run.add_argument('-o', '--onos', default=onos_image_default, type=str, help='ONOS container image')
parser_run.add_argument('-q', '--quagga',action='store_true',help='Provision quagga container for vrouter')
parser_run.add_argument('-a', '--app', default=onos_app_file, type=str, help='Cord ONOS app filename')
- parser_run.add_argument('-p', '--olt', action='store_true', help='Use OLT config')
+ parser_run.add_argument('-l', '--olt', action='store_true', help='Use OLT config')
parser_run.add_argument('-e', '--test-controller', default='', type=str, help='External test controller ip for Onos and/or radius server. '
'Eg: 10.0.0.2/10.0.0.3 to specify ONOS and Radius ip to connect')
parser_run.add_argument('-r', '--server', default=cord_test_server_address, type=str,
@@ -602,6 +740,11 @@
parser_run.add_argument('-n', '--num-containers', default=1, type=int,
help='Specify number of test containers to spawn for tests')
parser_run.add_argument('-c', '--container', default='', type=str, help='Test container name for running tests')
+ parser_run.add_argument('-m', '--manifest', default='', type=str, help='Provide test configuration manifest')
+ parser_run.add_argument('-p', '--prefix', default='', type=str, help='Provide container image prefix')
+ parser_run.add_argument('-d', '--no-switch', action='store_true', help='Dont start test switch.')
+ parser_run.add_argument('-i', '--identity-file', default=identity_file_default,
+ type=str, help='ssh identity file to access compute nodes from test container')
parser_run.set_defaults(func=runTest)
@@ -618,10 +761,14 @@
' --update=radius to rebuild radius server image.'
' --update=all to rebuild all cord tester images.')
parser_setup.add_argument('-d', '--dont-provision', action='store_true', help='Dont start test container.')
- parser_setup.add_argument('-p', '--olt', action='store_true', help='Use OLT config')
+ parser_setup.add_argument('-l', '--olt', action='store_true', help='Use OLT config')
parser_setup.add_argument('-s', '--start-switch', action='store_true', help='Start OVS when running under OLT config')
parser_setup.add_argument('-c', '--onos-cord', default='', type=str,
help='Specify cord location for ONOS cord when running on podd')
+ parser_setup.add_argument('-m', '--manifest', default='', type=str, help='Provide test configuration manifest')
+ parser_setup.add_argument('-p', '--prefix', default='', type=str, help='Provide container image prefix')
+ parser_setup.add_argument('-i', '--identity-file', default=identity_file_default,
+ type=str, help='ssh identity file to access compute nodes from test container')
parser_setup.set_defaults(func=setupCordTester)
parser_list = subparser.add_parser('list', help='List test cases')
@@ -633,14 +780,18 @@
parser_build = subparser.add_parser('build', help='Build cord test container images')
parser_build.add_argument('image', choices=['quagga', 'radius', 'test', 'all'])
+ parser_build.add_argument('-p', '--prefix', default='', type=str, help='Provide container image prefix')
parser_build.set_defaults(func=buildImages)
parser_start = subparser.add_parser('start', help='Start cord tester containers')
+ parser_start.add_argument('-p', '--prefix', default='', type=str, help='Provide container image prefix')
+ parser_start.add_argument('-o', '--onos', default=onos_image_default, type=str, help='ONOS container image')
parser_start.add_argument('image', choices=['onos', 'quagga', 'radius', 'all'])
parser_start.set_defaults(func=startImages)
parser_cleanup = subparser.add_parser('cleanup', help='Cleanup test containers')
- parser_cleanup.add_argument('-p', '--olt', action = 'store_true', help = 'Cleanup OLT config')
+ parser_cleanup.add_argument('-p', '--prefix', default='', type=str, help='Provide container image prefix')
+ parser_cleanup.add_argument('-l', '--olt', action = 'store_true', help = 'Cleanup OLT config')
parser_cleanup.set_defaults(func=cleanupTests)
args = parser.parse_args()
diff --git a/src/test/setup/manifest.json b/src/test/setup/manifest.json
new file mode 100644
index 0000000..366558e
--- /dev/null
+++ b/src/test/setup/manifest.json
@@ -0,0 +1,5 @@
+{
+ "onos" : "172.17.0.2",
+ "radius" : "172.17.0.3",
+ "head_node" : "cord-r6-s1.cord.lab"
+}
\ No newline at end of file
diff --git a/src/test/utils/CordContainer.py b/src/test/utils/CordContainer.py
index e5a3a1d..11f257b 100644
--- a/src/test/utils/CordContainer.py
+++ b/src/test/utils/CordContainer.py
@@ -48,8 +48,14 @@
class Container(object):
dckr = Client()
- def __init__(self, name, image, tag = 'latest', command = 'bash', quagga_config = None):
+ IMAGE_PREFIX = '' ##for saving global prefix for all test classes
+
+ def __init__(self, name, image, prefix='', tag = 'candidate', command = 'bash', quagga_config = None):
self.name = name
+ self.prefix = prefix
+ if prefix:
+ self.prefix += '/'
+ image = '{}{}'.format(self.prefix, image)
self.image = image
self.tag = tag
if tag:
@@ -108,7 +114,7 @@
return '/{0}'.format(self.name) in list(flatten(n['Names'] for n in self.dckr.containers()))
def img_exists(self):
- return self.image_name in [ctn['RepoTags'][0] for ctn in self.dckr.images()]
+ return self.image_name[len(self.prefix):] in [ctn['RepoTags'][0] for ctn in self.dckr.images()]
def ip(self):
cnt_list = filter(lambda c: c['Image'] == self.image_name, self.dckr.containers())
@@ -286,6 +292,9 @@
NAME = 'cord-onos'
##the ip of ONOS in default cluster.json in setup/onos-config
CLUSTER_CFG_IP = '172.17.0.2'
+ IMAGE = 'onosproject/onos'
+ TAG = 'latest'
+ PREFIX = ''
@classmethod
def onos_generate_cluster_cfg(cls, ip):
@@ -294,7 +303,7 @@
os.system(cmd)
except: pass
- def __init__(self, name = NAME, image = 'onosproject/onos', tag = 'latest',
+ def __init__(self, name = NAME, image = 'onosproject/onos', prefix = '', tag = 'latest',
boot_delay = 60, restart = False, network_cfg = None):
if restart is True:
##Find the right image to restart
@@ -306,7 +315,7 @@
tag = image_name.split(':')[1]
except: pass
- super(Onos, self).__init__(name, image, tag = tag, quagga_config = self.quagga_config)
+ super(Onos, self).__init__(name, image, prefix = prefix, tag = tag, quagga_config = self.quagga_config)
if restart is True and self.exists():
self.kill()
if not self.exists():
@@ -369,11 +378,11 @@
IMAGE = 'cord-test/radius'
NAME = 'cord-radius'
- def __init__(self, name = NAME, image = IMAGE, tag = 'latest',
+ def __init__(self, name = NAME, image = IMAGE, prefix = '', tag = 'candidate',
boot_delay = 10, restart = False, update = False):
- super(Radius, self).__init__(name, image, tag = tag, command = self.start_command)
+ super(Radius, self).__init__(name, image, prefix = prefix, tag = tag, command = self.start_command)
if update is True or not self.img_exists():
- self.build_image(image)
+ self.build_image(self.image_name)
if restart is True and self.exists():
self.kill()
if not self.exists():
@@ -416,11 +425,11 @@
IMAGE = 'cord-test/quagga'
NAME = 'cord-quagga'
- def __init__(self, name = NAME, image = IMAGE, tag = 'latest',
+ def __init__(self, name = NAME, image = IMAGE, prefix = '', tag = 'candidate',
boot_delay = 15, restart = False, config_file = quagga_config_file, update = False):
- super(Quagga, self).__init__(name, image, tag = tag, quagga_config = self.quagga_config)
+ super(Quagga, self).__init__(name, image, prefix = prefix, tag = tag, quagga_config = self.quagga_config)
if update is True or not self.img_exists():
- self.build_image(image)
+ self.build_image(self.image_name)
if restart is True and self.exists():
self.kill()
if not self.exists():
diff --git a/src/test/utils/CordTestServer.py b/src/test/utils/CordTestServer.py
index 3b97814..47b84e3 100644
--- a/src/test/utils/CordTestServer.py
+++ b/src/test/utils/CordTestServer.py
@@ -30,8 +30,8 @@
CORD_TEST_PORT = 25000
class QuaggaStopWrapper(Container):
- def __init__(self, name = Quagga.NAME, image = Quagga.IMAGE, tag = 'latest'):
- super(QuaggaStopWrapper, self).__init__(name, image, tag = tag)
+ def __init__(self, name = Quagga.NAME, image = Quagga.IMAGE, tag = 'candidate'):
+ super(QuaggaStopWrapper, self).__init__(name, image, prefix = Container.IMAGE_PREFIX, tag = tag)
if self.exists():
self.kill()
@@ -53,7 +53,7 @@
if self.onos_cord:
self.onos_cord.start(restart = True, network_cfg = config)
else:
- Onos(restart = True, network_cfg = config)
+ Onos(restart = True, network_cfg = config, image = Onos.IMAGE, tag = Onos.TAG)
return 'DONE'
def restart_onos(self, kwargs):
@@ -67,7 +67,7 @@
with open(quagga_config, 'w+') as fd:
fd.write(str(config))
print('Restarting QUAGGA with config file %s, delay %d' %(config_file, boot_delay))
- Quagga(restart = True, config_file = config_file, boot_delay = boot_delay)
+ Quagga(prefix = Container.IMAGE_PREFIX, restart = True, config_file = config_file, boot_delay = boot_delay)
return 'DONE'
def restart_quagga(self, kwargs):
@@ -103,7 +103,7 @@
def restart_radius(self):
print('Restarting RADIUS Server')
- Radius(restart = True)
+ Radius(prefix = Container.IMAGE_PREFIX, restart = True)
return 'DONE'
@nottest
diff --git a/src/test/utils/Fabric.py b/src/test/utils/Fabric.py
new file mode 100644
index 0000000..c7a3e21
--- /dev/null
+++ b/src/test/utils/Fabric.py
@@ -0,0 +1,133 @@
+#
+# Copyright 2016-present Ciena Corporation
+#
+# 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.
+#
+import os, sys
+import json
+import platform
+import subprocess
+from apiclient.maas_client import MAASOAuth, MAASDispatcher, MAASClient
+from paramiko import SSHClient, WarningPolicy, AutoAddPolicy
+from CordTestServer import CORD_TEST_HOST
+
+class FabricMAAS(object):
+ head_node = os.getenv('HEAD_NODE', CORD_TEST_HOST)
+ maas_url = 'http://{}/MAAS/api/1.0/'.format(head_node)
+
+ def __init__(self, api_key = None, url = maas_url):
+ if api_key == None:
+ self.api_key = self.get_api_key()
+ else:
+ self.api_key = api_key
+ self.auth = MAASOAuth(*self.api_key.split(':'))
+ self.url = url
+ self.client = MAASClient(self.auth, MAASDispatcher(), self.url)
+
+ @classmethod
+ def get_api_key(cls):
+ api_key = os.getenv('MAAS_API_KEY', None)
+ if api_key:
+ return api_key
+ cmd = ['maas-region-admin', 'apikey', '--username=cord']
+ try:
+ p = subprocess.Popen(cmd, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ except:
+ return 'UNKNOWN'
+ out, err = p.communicate()
+ if err:
+ raise Exception('Cannot get api key for MAAS')
+ return out.strip()
+
+ def get_node_list(self):
+ nodes = self.client.get(u'nodes/', 'list').read()
+ node_list = json.loads(nodes)
+ hosts = [ self.head_node ] + map(lambda n: n['hostname'], node_list)
+ return hosts
+
+class Fabric(object):
+ entropy = 1
+ simulation = False
+ def __init__(self, node_list, user = 'ubuntu', passwd = 'ubuntu', key_file = None, verbose = False):
+ self.cur_node = None
+ if Fabric.simulation:
+ self.cur_node = FabricMAAS.head_node
+ self.node_list = node_list
+ self.user = user
+ self.passwd = passwd
+ self.key_file = key_file
+ self.verbose = verbose
+ self.client = SSHClient()
+ self.client.load_system_host_keys()
+ self.client.set_missing_host_key_policy(AutoAddPolicy())
+
+ def run_cmd(self, node, neighbor, cmd, simulation = False):
+ if simulation is True:
+ Fabric.entropy = Fabric.entropy ^ 1
+ return bool(Fabric.entropy)
+ if node == self.cur_node:
+ res = os.system(cmd)
+ return res == 0
+ try:
+ self.client.connect(node, username = self.user, key_filename = self.key_file, timeout = 5)
+ except:
+ print('Unable to ssh to node %s for neighbor %s' %(node, neighbor))
+ return False
+ channel = self.client.get_transport().open_session()
+ channel.exec_command(cmd)
+ status = channel.recv_exit_status()
+ channel.close()
+ if self.verbose:
+ print('Cmd %s returned with status %d on node %s for neighbor %s' %(cmd, status, node, neighbor))
+ return status == 0
+
+ def ping_neighbor(self, node, neighbor):
+ cmd = 'ping -c 1 -w 2 {}'.format(neighbor)
+ return self.run_cmd(node, neighbor, cmd, Fabric.simulation)
+
+ def ping_neighbors(self):
+ result_map = []
+ for n in self.node_list:
+ for adj in self.node_list:
+ if adj == n:
+ continue
+ res = self.ping_neighbor(n, adj)
+ result_map.append((n,adj,res))
+
+ ##report
+ if self.verbose:
+ for node, neighbor, res in result_map:
+ print('Ping from node %s to neighbor %s returned %s\n' %(node, neighbor, res))
+
+ failed_nodes = filter(lambda f: f[2] == False, result_map)
+ return failed_nodes
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1:
+ nodes_file = sys.argv[1]
+ with open(nodes_file, 'r') as fd:
+ nodes = json.load(fd)
+ node_list = nodes['node_list']
+ else:
+ m = FabricMAAS()
+ node_list = m.get_node_list()
+ print('Node list: %s' %node_list)
+ Fabric.simulation = True
+ fab = Fabric(node_list, verbose = False)
+ failed_nodes = fab.ping_neighbors()
+ if failed_nodes:
+ print('Failed nodes: %s' %failed_nodes)
+ for node, neighbor, _ in failed_nodes:
+ print('Ping from node %s to neighbor %s Failed' %(node, neighbor))
+ else:
+ print('Fabric test between nodes %s is successful' %node_list)
diff --git a/src/test/utils/TestManifest.py b/src/test/utils/TestManifest.py
new file mode 100644
index 0000000..2156972
--- /dev/null
+++ b/src/test/utils/TestManifest.py
@@ -0,0 +1,28 @@
+#
+# Copyright 2016-present Ciena Corporation
+#
+# 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.
+#
+import json
+import os
+import shutil
+
+class TestManifest(object):
+
+ def __init__(self, manifest):
+ self.manifest = manifest
+ with open(self.manifest, 'r') as fd:
+ data = json.load(fd)
+ self.onos_ip = data.get('onos', None)
+ self.radius_ip = data.get('radius', None)
+ self.head_node = data.get('head_node', None)