From 1f95f48f41d92ce376ed651b57b2364d8776f7eb Mon Sep 17 00:00:00 2001
From: Cedric Roux <cedric.roux@eurecom.fr>
Date: Fri, 6 Jan 2017 17:48:35 +0100
Subject: [PATCH] first version of v2 test setup

The old one is still usable by replacing .gitlab-ci.yml with
the previous version.
---
 .gitlab-ci.yml                                |  51 +-
 cmake_targets/autotests/test_case_list.xml    |   2 +-
 .../autotests/v2/actions/alu_epc.bash         |   2 +
 .../autotests/v2/actions/alu_epc_stop.bash    |   1 +
 .../autotests/v2/actions/alu_hss.bash         |   2 +
 .../autotests/v2/actions/bandrich.txt         | 112 +++
 .../autotests/v2/actions/client_tcp.bash      |   2 +
 .../autotests/v2/actions/client_udp.bash      |   3 +
 .../v2/actions/clone_repository.bash          |   6 +
 .../autotests/v2/actions/compilation.bash     |   9 +
 .../autotests/v2/actions/execution.bash       |   2 +
 .../v2/actions/execution_compile.bash         |   8 +
 cmake_targets/autotests/v2/actions/modem.py   | 103 +++
 .../autotests/v2/actions/run_enb.bash         |  10 +
 .../autotests/v2/actions/server_tcp.bash      |   4 +
 .../autotests/v2/actions/server_udp.bash      |   4 +
 .../autotests/v2/actions/start_bandrich.bash  |  24 +
 .../autotests/v2/actions/start_bandrich.py    |  55 ++
 .../autotests/v2/actions/stop_bandrich.py     |  29 +
 .../autotests/v2/actions/wvdial.bandrich.conf |  36 +
 cmake_targets/autotests/v2/alu_test.py        | 389 ++++++++++
 .../config/enb.band7.tm1.usrpb210.10MHz.conf  | 175 +++++
 .../config/enb.band7.tm1.usrpb210.20MHz.conf  | 175 +++++
 .../config/enb.band7.tm1.usrpb210.5MHz.conf   | 175 +++++
 cmake_targets/autotests/v2/connection.py      | 104 +++
 cmake_targets/autotests/v2/machine_list.py    |  79 ++
 cmake_targets/autotests/v2/main.py            | 687 ++++++++++++++++++
 cmake_targets/autotests/v2/task.py            | 370 ++++++++++
 cmake_targets/autotests/v2/utils.py           |  84 +++
 29 files changed, 2667 insertions(+), 36 deletions(-)
 create mode 100644 cmake_targets/autotests/v2/actions/alu_epc.bash
 create mode 100644 cmake_targets/autotests/v2/actions/alu_epc_stop.bash
 create mode 100644 cmake_targets/autotests/v2/actions/alu_hss.bash
 create mode 100644 cmake_targets/autotests/v2/actions/bandrich.txt
 create mode 100644 cmake_targets/autotests/v2/actions/client_tcp.bash
 create mode 100644 cmake_targets/autotests/v2/actions/client_udp.bash
 create mode 100644 cmake_targets/autotests/v2/actions/clone_repository.bash
 create mode 100644 cmake_targets/autotests/v2/actions/compilation.bash
 create mode 100644 cmake_targets/autotests/v2/actions/execution.bash
 create mode 100644 cmake_targets/autotests/v2/actions/execution_compile.bash
 create mode 100644 cmake_targets/autotests/v2/actions/modem.py
 create mode 100644 cmake_targets/autotests/v2/actions/run_enb.bash
 create mode 100644 cmake_targets/autotests/v2/actions/server_tcp.bash
 create mode 100644 cmake_targets/autotests/v2/actions/server_udp.bash
 create mode 100644 cmake_targets/autotests/v2/actions/start_bandrich.bash
 create mode 100644 cmake_targets/autotests/v2/actions/start_bandrich.py
 create mode 100644 cmake_targets/autotests/v2/actions/stop_bandrich.py
 create mode 100644 cmake_targets/autotests/v2/actions/wvdial.bandrich.conf
 create mode 100644 cmake_targets/autotests/v2/alu_test.py
 create mode 100644 cmake_targets/autotests/v2/config/enb.band7.tm1.usrpb210.10MHz.conf
 create mode 100644 cmake_targets/autotests/v2/config/enb.band7.tm1.usrpb210.20MHz.conf
 create mode 100644 cmake_targets/autotests/v2/config/enb.band7.tm1.usrpb210.5MHz.conf
 create mode 100644 cmake_targets/autotests/v2/connection.py
 create mode 100644 cmake_targets/autotests/v2/machine_list.py
 create mode 100644 cmake_targets/autotests/v2/main.py
 create mode 100644 cmake_targets/autotests/v2/task.py
 create mode 100644 cmake_targets/autotests/v2/utils.py

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 15c44aed22..311f3952aa 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,38 +1,19 @@
 job1:
   script:
-   - echo $PWD
-   - echo $USER
-   - echo $OAI_USER
-   - echo $OAI_EXTRA_ARGS
-   - echo $NFS_SHARE_DIR
-   - echo $EXTERNAL_SHARE_DIR
-   - echo $SHELL
-   - echo $OAI_TEST_CASE_GROUP
-   - echo $MACHINELIST
-   - echo $MACHINELISTGENERIC
-   - git rev-parse --abbrev-ref HEAD
-   - git_repo=`git config --get remote.origin.url`
-   - git_head=`git rev-parse HEAD`
-   - echo $git_head
-   - tmp=`git show-ref --head | grep $git_head`
-   - tmp=(${tmp///// })
-   - git_branch=${tmp[@]:(-1)}
-   - echo $git_branch
-   - source oaienv
-   - echo $OPENAIR_DIR
-   - NFS_TEST_RESULTS_DIR=$NFS_SHARE_DIR/$git_branch/$git_head
-   - EXTERNAL_SHARE_RESULTS_DIR=$EXTERNAL_SHARE_DIR/$git_branch/$git_head
-   - echo $NFS_TEST_RESULTS_DIR
-   - echo $EXTERNAL_SHARE_RESULTS_DIR
-   - echo $NRUNS_LTE_SOFTMODEM
-   - echo $TIMEOUT_CMD
-   - mkdir -p $OPENAIR_DIR/cmake_targets/autotests/log
-   - python $OPENAIR_DIR/cmake_targets/autotests/run_exec_lte-softmodem_tests.py -c -5GRepo $git_repo -MachineList "$MACHINELIST" -MachineListGeneric "$MACHINELISTGENERIC"  -5GRepoHeadVersion $git_head -n $NFS_SHARE_DIR -u $OAI_USER -p $OAI_PASS  $OAI_EXTRA_ARGS -g "$OAI_TEST_CASE_GROUP">& $OPENAIR_DIR/cmake_targets/autotests/python_autotest_cleanup.log
-   - python $OPENAIR_DIR/cmake_targets/autotests/run_exec_lte-softmodem_tests.py -r -5GRepo $git_repo -MachineList "$MACHINELIST" -MachineListGeneric "$MACHINELISTGENERIC" -5GRepoHeadVersion $git_head -n $NFS_SHARE_DIR -u $OAI_USER -p $OAI_PASS `echo $OAI_EXTRA_ARGS` -g "$OAI_TEST_CASE_GROUP" --nrun_lte_softmodem $NRUNS_LTE_SOFTMODEM --timeout_cmd $TIMEOUT_CMD >& $OPENAIR_DIR/cmake_targets/autotests/python_autotest.log
-   - mv $OPENAIR_DIR/cmake_targets/autotests/python_autotest.log $OPENAIR_DIR/cmake_targets/autotests/log/python_autotest.log
-   - mv $OPENAIR_DIR/cmake_targets/autotests/python_autotest_cleanup.log $OPENAIR_DIR/cmake_targets/autotests/log/python_autotest_cleanup.log
-   - sshpass -p "$OAI_PASS" rsync -az -e "ssh -o StrictHostKeyChecking=no "  --rsync-path="mkdir -p $NFS_TEST_RESULTS_DIR && rsync" $OPENAIR_DIR/cmake_targets/autotests/log $OAI_USER@localhost:$NFS_TEST_RESULTS_DIR
-   - sshpass -p "$OAI_PASS" rsync -az -e "ssh -o StrictHostKeyChecking=no "  --rsync-path="mkdir -p $EXTERNAL_SHARE_DIR && rsync" $OPENAIR_DIR/cmake_targets/autotests/log $OAI_USER@localhost:$EXTERNAL_SHARE_DIR
-   - cat $OPENAIR_DIR/cmake_targets/autotests/log/results_autotests.xml
+    - date
+    - pwd
+    - echo $OAI_USER
+    - echo $OAI_PASS
+    - echo $OAI_TEST_CASE_GROUP
+    - echo $MACHINELIST
+    - echo $MACHINELISTGENERIC
+    - echo $RESULT_DIR
+    - echo $NRUNS_LTE_SOFTMODEM
+    - source oaienv
+    - rm -rf cmake_targets/autotests/log
+    - mkdir cmake_targets/autotests/log
+    - cd cmake_targets/autotests/v2
+    - python main.py
+    - date
   only:
-   - triggers
+    - triggers
diff --git a/cmake_targets/autotests/test_case_list.xml b/cmake_targets/autotests/test_case_list.xml
index bf2a1ed36c..7f2847a5ea 100644
--- a/cmake_targets/autotests/test_case_list.xml
+++ b/cmake_targets/autotests/test_case_list.xml
@@ -13,7 +13,7 @@
  <Timeout_execution>36000</Timeout_execution>
  <TestCaseExclusionList>010141 0102+ 010301 010303 010304 010305 0104+ 015508 015511 015520 015523 015518 015519 015520 015521 015522 015523 015602 015605 015818 015819 015820 015821 015822 015823 016102 016105 016502 016505 017002 017005 018002 018005 018502 018505  025514 025517 025520 025523 025518 025519 025520 025521 025522 025523</TestCaseExclusionList>
  <nruns_lte-softmodem>3</nruns_lte-softmodem>
- <MachineListGeneric>amerique mozart hutch starsky stevens calisson superserver </MachineListGeneric>
+ <MachineListGeneric>nano mozart hutch starsky stevens calisson superserver </MachineListGeneric>
      <testCase id="010101" >
      <class>compilation</class>
      <desc>Build oaisim.Rel8</desc>
diff --git a/cmake_targets/autotests/v2/actions/alu_epc.bash b/cmake_targets/autotests/v2/actions/alu_epc.bash
new file mode 100644
index 0000000000..b2106693bd
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/alu_epc.bash
@@ -0,0 +1,2 @@
+sudo /opt/ltebox/tools/stop_ltebox || true
+sudo /opt/ltebox/tools/start_ltebox
diff --git a/cmake_targets/autotests/v2/actions/alu_epc_stop.bash b/cmake_targets/autotests/v2/actions/alu_epc_stop.bash
new file mode 100644
index 0000000000..ca91a27ab1
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/alu_epc_stop.bash
@@ -0,0 +1 @@
+sudo /opt/ltebox/tools/stop_ltebox
diff --git a/cmake_targets/autotests/v2/actions/alu_hss.bash b/cmake_targets/autotests/v2/actions/alu_hss.bash
new file mode 100644
index 0000000000..ded1e352ce
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/alu_hss.bash
@@ -0,0 +1,2 @@
+sudo /opt/ltebox/tools/stop_ltebox || true
+sudo /opt/hss_sim0609/starthss_real
diff --git a/cmake_targets/autotests/v2/actions/bandrich.txt b/cmake_targets/autotests/v2/actions/bandrich.txt
new file mode 100644
index 0000000000..94a6f3f4c0
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/bandrich.txt
@@ -0,0 +1,112 @@
+To get /dev/bandrich (so that you don't need to look for which /dev/ttyUSBx
+is used by your dongle, and always use /dev/bandrich), add a udev file:
+
+    /etc/udev/rules.d/bandrich.rules
+
+containing one line:
+
+    SUBSYSTEM=="tty", ENV{ID_VENDOR_ID}=="1a8d", ENV{ID_MODEL_ID}=="100d", ENV{ID_SERIAL_SHORT}=="357473040068155", ENV{ID_USB_INTERFACE_NUM}=="02", SYMLINK+="bandrich", MODE="0666"
+
+Change vendor_id/model_id/serial/interface num to match yours.
+Use lsusb -v to find values. For interface num, I'm not sure.
+It corresponds to /dev/ttyUSBx (the x). I did:
+
+    cat /dev/ttyUSBx
+
+in one terminal, and:
+
+    echo -e "AT\r" > /dev/ttyUSBx
+
+in another one. And I took a number for which the first terminal displayed:
+
+    AT
+    OK
+
+"02" (/dev/ttyUSB2) worked for me.
+Here, I get, as result of lsusb -v:
+
+    [SNIP]
+    Bus 003 Device 009: ID 1a8d:100d BandRich, Inc. 4G LTE adapter
+    Device Descriptor:
+      bLength                18
+      bDescriptorType         1
+      bcdUSB               2.00
+      bDeviceClass            0 (Defined at Interface level)
+      bDeviceSubClass         0 
+      bDeviceProtocol         0 
+      bMaxPacketSize0        64
+      idVendor           0x1a8d BandRich, Inc.
+      idProduct          0x100d 4G LTE adapter
+      bcdDevice            0.00
+      iManufacturer          10 BandRich, Inc.
+      iProduct                9 BandLuxe HSPA-LTE Adapter
+      iSerial                11 357473040068155
+    [SNIP]
+
+You can also run:
+
+    udevadm monitor
+
+and unplug/replug the dongle. It will print some information.
+
+The command:
+
+    udevadm info --export-db
+
+is also important to get the right identifier to put in ENV{}. (It also
+gives the correct value.)
+
+Here is extracted what I have for my dongle:
+
+    P: /devices/pci0000:00/0000:00:1a.0/usb3/3-1/3-1.2/3-1.2:1.2/ttyUSB2/tty/ttyUSB2
+    N: ttyUSB2
+    S: bandrich
+    S: serial/by-id/usb-BandRich__Inc._BandLuxe_HSPA-LTE_Adapter_357473040068155-if02-port0
+    S: serial/by-path/pci-0000:00:1a.0-usb-0:1.2:1.2-port0
+    E: DEVLINKS=/dev/bandrich /dev/serial/by-id/usb-BandRich__Inc._BandLuxe_HSPA-LTE_Adapter_357473040068155-if02-port0 /dev/serial/by-path/pci-0000:00:1a.0-usb-0:1.2:1.2-port0
+    E: DEVNAME=/dev/ttyUSB2
+    E: DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb3/3-1/3-1.2/3-1.2:1.2/ttyUSB2/tty/ttyUSB2
+    E: ID_BUS=usb
+    E: ID_MM_CANDIDATE=1
+    E: ID_MODEL=BandLuxe_HSPA-LTE_Adapter
+    E: ID_MODEL_ENC=BandLuxe\x20HSPA-LTE\x20Adapter
+    E: ID_MODEL_FROM_DATABASE=4G LTE adapter
+    E: ID_MODEL_ID=100d
+    E: ID_PATH=pci-0000:00:1a.0-usb-0:1.2:1.2
+    E: ID_PATH_TAG=pci-0000_00_1a_0-usb-0_1_2_1_2
+    E: ID_REVISION=0000
+    E: ID_SERIAL=BandRich__Inc._BandLuxe_HSPA-LTE_Adapter_357473040068155
+    E: ID_SERIAL_SHORT=357473040068155
+    E: ID_TYPE=generic
+    E: ID_USB_DRIVER=option
+    E: ID_USB_INTERFACES=:ffffff:020600:0a0000:080650:
+    E: ID_USB_INTERFACE_NUM=02
+    E: ID_VENDOR=BandRich__Inc.
+    E: ID_VENDOR_ENC=BandRich\x2c\x20Inc.
+    E: ID_VENDOR_FROM_DATABASE=BandRich, Inc.
+    E: ID_VENDOR_ID=1a8d
+    E: MAJOR=188
+    E: MINOR=2
+    E: SUBSYSTEM=tty
+    E: USEC_INITIALIZED=672068596
+
+Note: you need to unplyg/replug your dongle for /dev/bandrich to appear.
+
+Note: the mode 0666 is for everyone to access the dongle (no need for
+      root privileges). If you prefer you can set it to 0600 (only root)
+      or 0660 (root and group).
+
+Then you need to configure pppd, to get correct 'route' information when
+you run wvdial.
+
+The file /etc/ppp/peers/wvdial should have the following content:
+
+    noauth
+    name wvdial
+    usepeerdns
+    defaultroute
+    replacedefaultroute
+
+The file wvdial.bandrich.conf has been created by copying some information
+found on the Internet. Its content may not be fully correct. Adapt to your
+situation. It seems to work here.
diff --git a/cmake_targets/autotests/v2/actions/client_tcp.bash b/cmake_targets/autotests/v2/actions/client_tcp.bash
new file mode 100644
index 0000000000..1079f9aee8
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/client_tcp.bash
@@ -0,0 +1,2 @@
+echo $SERVER_IP
+timeout -s 9 20s iperf -c $SERVER_IP -i1
diff --git a/cmake_targets/autotests/v2/actions/client_udp.bash b/cmake_targets/autotests/v2/actions/client_udp.bash
new file mode 100644
index 0000000000..00151aa1f8
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/client_udp.bash
@@ -0,0 +1,3 @@
+echo $SERVER_IP
+echo $UDP_BANDWIDTH
+timeout -s 9 20s iperf -c $SERVER_IP -i1 -u -b $UDP_BANDWIDTH
diff --git a/cmake_targets/autotests/v2/actions/clone_repository.bash b/cmake_targets/autotests/v2/actions/clone_repository.bash
new file mode 100644
index 0000000000..2f1769a623
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/clone_repository.bash
@@ -0,0 +1,6 @@
+sudo rm -rf /tmp/oai_test_setup
+mkdir /tmp/oai_test_setup
+cd /tmp/oai_test_setup
+git clone $REPOSITORY_URL oai
+cd oai
+git checkout $COMMIT_ID
diff --git a/cmake_targets/autotests/v2/actions/compilation.bash b/cmake_targets/autotests/v2/actions/compilation.bash
new file mode 100644
index 0000000000..44966616fa
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/compilation.bash
@@ -0,0 +1,9 @@
+cd /tmp/oai_test_setup/oai
+source oaienv
+cd cmake_targets
+rm -rf log
+mkdir -p log
+echo $BUILD_ARGUMENTS
+./build_oai $BUILD_ARGUMENTS
+echo $BUILD_OUTPUT
+ls $BUILD_OUTPUT
diff --git a/cmake_targets/autotests/v2/actions/execution.bash b/cmake_targets/autotests/v2/actions/execution.bash
new file mode 100644
index 0000000000..503b6b963d
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/execution.bash
@@ -0,0 +1,2 @@
+echo $EXEC $EXEC_ARGS
+$EXEC $EXEC_ARGS
diff --git a/cmake_targets/autotests/v2/actions/execution_compile.bash b/cmake_targets/autotests/v2/actions/execution_compile.bash
new file mode 100644
index 0000000000..6791e93b0d
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/execution_compile.bash
@@ -0,0 +1,8 @@
+cd /tmp/oai_test_setup/oai
+source oaienv
+cd cmake_targets
+rm -rf log
+mkdir -p log
+bash -c "$PRE_BUILD"
+$BUILD_PROG $BUILD_ARGUMENTS
+bash -c "$PRE_EXEC"
diff --git a/cmake_targets/autotests/v2/actions/modem.py b/cmake_targets/autotests/v2/actions/modem.py
new file mode 100644
index 0000000000..a6c49fa9bf
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/modem.py
@@ -0,0 +1,103 @@
+import sys, os, select, re, time
+
+def quit(r):
+    sys.stdout.flush()
+    os._exit(r)
+
+class ModemResponse:
+    def __init__(self, retcode, retstring):
+        self.ret = retcode
+        self.data = retstring
+
+class Modem:
+    def open(self):
+        self.i = os.open(self.devname, os.O_RDONLY)
+        self.o = os.open(self.devname, os.O_WRONLY)
+        #clear output of modem, if any is pending (not sure of this)
+        while True:
+            (ri, ro, re) = select.select([self.i], [], [self.i], 0)
+            if len(ri) == 0:
+                break
+            l = os.read(self.i, 65536)
+            print "WARNING: modem had unread data: '" + \
+                  l.replace('\r', '\\r') + "'"
+
+    def __init__(self, devname):
+        self.devname = devname
+        self.i = -1
+        self.o = -1
+        self.open()
+
+    def send(self, s):
+        print "DEBUG: SEND TO MODEM: '" + s + "'"
+        os.write(self.o, s+"\r")
+
+    def recv(self):
+        return os.read(self.i, 65536)
+
+    def wait(self):
+        ok         = '\r\nOK\r\n'
+        error      = '\r\nERROR\r\n'
+        cme_error  = '\r\nCME ERROR:[^\r]*\r\n'
+        no_carrier = '\r\nNO CARRIER\r\n'
+        l = ''
+        while True:
+            l = l + self.recv()
+            print "DEBUG: CURRENT MODEM RESPONSE: '" + \
+                  l.replace('\r','\\r').replace('\n','\\n') + "'"
+
+            #AT returned 'things' are "\r\nXXXX\r\n", look for that.
+            #Check if last one matches 'ok', 'error' or 'cme_error'.
+            #(Hopefully this is enough and no other reply is possible.)
+            #This code accepts invalid responses from modem, ie. all
+            #that does not fit in the 'findall' is thrashed away, maybe
+            #we want to do something in this case?
+
+            res = re.findall('\r\n[^\r]*\r\n', l)
+            if len(res) == 0:
+                print "DEBUG: NO MATCH: wait for more input from modem"
+                continue
+            last_res = res[len(res)-1]
+            print "DEBUG: CURRENT LAST LINE: '" + \
+                  last_res.replace('\r','\\r').replace('\n','\\n')+"'"
+            if re.match(ok, last_res) != None:
+                return ModemResponse(True, l)
+            if (   re.match(error,      last_res) != None or
+                   re.match(cme_error,  last_res) != None or
+                   re.match(no_carrier, last_res) != None):
+                return ModemResponse(False, l)
+        #TODO purge?
+        #re.purge()
+
+    def modem_reset_cycle(self):
+        #close all
+        os.close(self.i)
+        os.close(self.o)
+        self.i = -1
+        self.o = -1
+
+        print "DEBUG: RESET CYCLE: wait for modem to go away"
+        #wait for file descriptor to go away
+        while True:
+            try:
+                test = os.open(self.devname, os.O_RDONLY)
+                os.close(test)
+                time.sleep(0.1)
+            except BaseException, e:
+                break
+        print "DEBUG: RESET CYCLE: modem has gone away"
+
+        print "DEBUG: RESET CYCLE: wait for modem to come back"
+        #wait for file descriptor to be back, try to open it over and over
+        #TODO: use inotify here? (it's not in basic python as it seems)
+        while True:
+            try:
+                test = os.open(self.devname, os.O_RDONLY)
+                os.close(test)
+                break
+            except BaseException, e:
+                time.sleep(0.1)
+        print "DEBUG: RESET CYCLE: modem is back"
+
+        #back to business
+        self.open()
diff --git a/cmake_targets/autotests/v2/actions/run_enb.bash b/cmake_targets/autotests/v2/actions/run_enb.bash
new file mode 100644
index 0000000000..1a4c5368a7
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/run_enb.bash
@@ -0,0 +1,10 @@
+#enable control+C reception (to be refined if it does not work)
+stty isig intr ^C
+
+cd /tmp/oai_test_setup/oai
+source oaienv
+cd cmake_targets/lte_build_oai/build
+ulimit -c unlimited
+sudo rm -f core
+#sudo -E ./lte-softmodem -O $OPENAIR_DIR/cmake_targets/autotests/v2/config/enb.band7.tm1.usrpb210.conf
+sudo -E ./lte-softmodem -O /tmp/enb.conf
diff --git a/cmake_targets/autotests/v2/actions/server_tcp.bash b/cmake_targets/autotests/v2/actions/server_tcp.bash
new file mode 100644
index 0000000000..3e9c4aafc4
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/server_tcp.bash
@@ -0,0 +1,4 @@
+stty isig intr ^C
+
+#timeout -s 9 20s iperf -s -i1
+iperf -s -i1
diff --git a/cmake_targets/autotests/v2/actions/server_udp.bash b/cmake_targets/autotests/v2/actions/server_udp.bash
new file mode 100644
index 0000000000..22fba07092
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/server_udp.bash
@@ -0,0 +1,4 @@
+stty isig intr ^C
+
+#timeout -s 9 20s iperf -s -i1 -u
+iperf -s -i1 -u
diff --git a/cmake_targets/autotests/v2/actions/start_bandrich.bash b/cmake_targets/autotests/v2/actions/start_bandrich.bash
new file mode 100644
index 0000000000..e562dfc29b
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/start_bandrich.bash
@@ -0,0 +1,24 @@
+#enable control+C reception (to be refined if it does not work)
+stty isig intr ^C
+
+#If /dev/bandrich is not present when we start the test, what to do?
+#The following commented lines are an attempt at solving this.
+#This is not satisfying, so we rather do nothing.
+
+##the UE got stuck once, I had to run usb_modeswitch by hand.
+##So at this point, if /dev/bandrich does not exist, maybe the UE is
+##stuck for whatever reason. Let's forcefully run usb_modeswitch.
+#if [ ! -e /dev/bandrich ]; then sudo usb_modeswitch -v 1a8d -p 1000 -I -W -K; true; fi
+#
+##wait for /dev/bandrich (TODO: use inotify?)
+##may fail if the bandrich is in a bad state
+#while [ ! -e /dev/bandrich ]; do sleep 1; done
+
+cd /tmp/oai_test_setup/oai
+source oaienv
+cd cmake_targets/autotests/v2/actions
+python start_bandrich.py
+
+sudo wvdial -C wvdial.bandrich.conf || true
+
+python stop_bandrich.py
diff --git a/cmake_targets/autotests/v2/actions/start_bandrich.py b/cmake_targets/autotests/v2/actions/start_bandrich.py
new file mode 100644
index 0000000000..edd7464406
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/start_bandrich.py
@@ -0,0 +1,55 @@
+import time
+
+from modem import quit, Modem
+
+try:
+    modem = Modem("/dev/bandrich")
+
+    #test that modem is there
+    print "INFO: check modem's presence"
+    modem.send('AT')
+    if modem.wait().ret != True:
+        print "ERROR: no modem?"
+        quit(1)
+
+    #activate the modem, be brutal and reset it too!
+    print "INFO: reset and activate the modem"
+    modem.send('AT+CFUN=1,1')
+    if modem.wait().ret != True:
+        print "ERROR: failed asking modem for activation"
+        quit(1)
+
+    #modem has gone! wait for it to pop up again
+    modem.modem_reset_cycle()
+
+    #wait for modem to be connected
+    #timeout after one minute
+    print "INFO: wait for modem to be connected (timeout: one minute)"
+    start_time = time.time()
+    while True:
+        modem.send('AT+CGATT?')
+        r = modem.wait()
+        if r.ret != True:
+            print "ERROR: failed checking attachment status of modem"
+            quit(1)
+        if "+CGATT: 1" in r.data:
+            break
+        if not "CGATT: 0" in r.data:
+            print "ERROR: bad data when checking attachment status of modem"
+            quit(1)
+        time.sleep(0.1)
+        if time.time() > start_time + 60:
+            print "ERROR: modem not connected after one minute, close modem"
+            modem.send('AT+CFUN=4')
+            r = modem.wait()
+            if r.ret != True:
+                print "ERROR: closing modem failed"
+            quit(1)
+
+    print "INFO: modem is connected"
+
+except BaseException, e:
+    print "ERROR: " + str(e)
+    quit(1)
+
+quit(0)
diff --git a/cmake_targets/autotests/v2/actions/stop_bandrich.py b/cmake_targets/autotests/v2/actions/stop_bandrich.py
new file mode 100644
index 0000000000..cb449d8890
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/stop_bandrich.py
@@ -0,0 +1,29 @@
+import time
+
+from modem import quit, Modem
+
+try:
+    modem = Modem("/dev/bandrich")
+
+    #test that modem is there
+    print "INFO: check modem's presence"
+    modem.send('AT')
+    r = modem.wait()
+    if r.ret != True and "NO CARRIER" not in r.data:
+        print "ERROR: no modem?"
+        quit(1)
+    if "NO CARRIER" in r.data:
+        print "WARNING: 'NO CARRIER' detected, not sure if handled correctly"
+
+    #deactivate the modem
+    print "INFO: deactivate the modem"
+    modem.send('AT+CFUN=4')
+    if modem.wait().ret != True:
+        print "ERROR: failed asking modem for deactivation"
+        quit(1)
+
+except BaseException, e:
+    print "ERROR: " + str(e)
+    quit(1)
+
+quit(0)
diff --git a/cmake_targets/autotests/v2/actions/wvdial.bandrich.conf b/cmake_targets/autotests/v2/actions/wvdial.bandrich.conf
new file mode 100644
index 0000000000..2825b67ff3
--- /dev/null
+++ b/cmake_targets/autotests/v2/actions/wvdial.bandrich.conf
@@ -0,0 +1,36 @@
+[Dialer Defaults]
+Modem = /dev/bandrich
+ISDN = off
+Modem Type = Analog Modem
+Baud = 9600
+Init = ATZ
+Init2 = AT+CPIN?
+Init3 = AT+CGREG?
+Init4 = AT+COPS?
+Init5 = AT+CSQ
+Init6 =
+Init7 = AT+CGATT=1
+Init8 = 
+Init9 = 
+Phone = *99***1#
+Phone1 = 
+Phone2 = 
+Phone3 = 
+Phone4 = 
+Dial Prefix = 
+Dial Attempts = 1
+Dial Command = ATM1L3DT
+Ask Password = off
+Password = ''
+Username = ImaginLab
+Auto Reconnect = off
+Abort on Busy = off
+Carrier Check = on
+Check Def Route = on
+Abort on No Dialtone = on
+Stupid Mode = on
+Idle Seconds = 0
+Auto DNS = on
+;Minimize = off
+;Dock = off
+;Do NOT edit this file by hand!
diff --git a/cmake_targets/autotests/v2/alu_test.py b/cmake_targets/autotests/v2/alu_test.py
new file mode 100644
index 0000000000..72203be2f1
--- /dev/null
+++ b/cmake_targets/autotests/v2/alu_test.py
@@ -0,0 +1,389 @@
+import threading, os, re
+
+from utils import quickshell, log, TestFailed, do_tests
+from task import Task, WAITLOG_SUCCESS
+
+class alu_test:
+    def __init__(self,
+                 epc, enb, ue,
+                 openair,
+                 user, password,
+                 log_subdir,
+                 env):
+        self.epc_machine  = epc
+        self.enb_machine  = enb
+        self.ue_machine   = ue
+        self.openair_dir  = openair
+        self.oai_user     = user
+        self.oai_password = password
+        self.env          = env
+
+        self.task_hss = None
+        self.task_enb = None
+        self.task_ue  = None
+
+        self.logdir = openair + '/cmake_targets/autotests/log/' + log_subdir
+        quickshell('mkdir -p ' + self.logdir)
+
+        #event object used to wait for several tasks at once
+        self.event = threading.Event()
+
+    ##########################################################################
+    # finish
+    ##########################################################################
+    def finish(self):
+        #brutally kill tasks still running
+        #TODO: call 'wait', some processes may still be there as zombies
+        if self.task_hss != None and self.task_hss.alive():
+            self.task_hss.kill()
+        if self.task_enb != None and self.task_enb.alive():
+            self.task_enb.kill()
+        if self.task_ue != None and self.task_ue.alive():
+            self.task_ue.kill()
+
+    ##########################################################################
+    # start_epc
+    ##########################################################################
+    def start_epc(self):
+        #launch HSS, wait for prompt
+        log("INFO: ALU test: run HSS")
+        self.task_hss = Task("actions/alu_hss.bash",
+                "alu_hss",
+                self.epc_machine,
+                self.oai_user,
+                self.oai_password,
+                self.env,
+                self.logdir + "/alu_hss." + self.epc_machine,
+                event=self.event)
+        self.task_hss.waitlog('S6AS_SIM-> ')
+
+        #then launch EPC, wait for connection on HSS side
+        log("INFO: ALU test: run EPC")
+        task_epc = Task("actions/alu_epc.bash",
+                "ALU EPC",
+                self.epc_machine,
+                self.oai_user,
+                self.oai_password,
+                self.env,
+                self.logdir + "/alu_epc." + self.epc_machine)
+        ret = task_epc.wait()
+        if ret != 0:
+            log("ERROR: EPC start failure");
+            raise TestFailed()
+        self.task_hss.waitlog('Connected\n')
+
+    ##########################################################################
+    # stop_epc
+    ##########################################################################
+    def stop_epc(self):
+        #stop EPC, wait for disconnection on HSS side
+        log("INFO: ALU test: stop EPC")
+        task_epc = Task("actions/alu_epc_stop.bash",
+                "alu_epc_stop",
+                self.epc_machine,
+                self.oai_user,
+                self.oai_password,
+                self.env,
+                self.logdir + "/alu_epc_stop." + self.epc_machine)
+        ret = task_epc.wait()
+        if ret != 0:
+            log("ERROR: ALU test: ALU EPC stop failed")
+            raise TestFailed()
+        self.task_hss.waitlog('Disconnected\n')
+
+        log("INFO: ALU test: stop HSS")
+        self.task_hss.sendnow("exit\n")
+        ret = self.task_hss.wait()
+        if ret != 0:
+            log("ERROR: ALU test: ALU HSS failed")
+            raise TestFailed()
+
+    ##########################################################################
+    # compile_enb
+    ##########################################################################
+    def compile_enb(self, build_arguments):
+        log("INFO: ALU test: compile softmodem")
+        envcomp = list(self.env)
+        envcomp.append('BUILD_ARGUMENTS="' + build_arguments + '"')
+        #we don't care about BUILD_OUTPUT but required (TODO: change that)
+        envcomp.append('BUILD_OUTPUT=/')
+        logdir = self.logdir + "/compile_log"
+        remote_files = "'/tmp/oai_test_setup/oai/cmake_targets/log/*'"
+        post_action = "mkdir -p "+ logdir + \
+                " && sshpass -p " + self.oai_password + \
+                " scp -r " + self.oai_user + \
+                "@" + self.enb_machine + ":" + remote_files + " " + logdir + \
+                " || true"
+        task = Task("actions/compilation.bash",
+                "compile_softmodem",
+                self.enb_machine,
+                self.oai_user,
+                self.oai_password,
+                envcomp,
+                self.logdir + "/compile_softmodem." + self.enb_machine,
+                post_action=post_action)
+        ret = task.wait()
+        if ret != 0:
+            log("ERROR: softmodem compilation failure");
+            raise TestFailed()
+        task.postaction()
+
+    ##########################################################################
+    # start_enb
+    ##########################################################################
+    def start_enb(self, config_file):
+        #copy wanted configuration file
+        quickshell("sshpass -p " + self.oai_password +
+                   " scp config/" + config_file + " " +
+                   self.oai_user + "@" + self.enb_machine + ":/tmp/enb.conf")
+
+        #run softmodem
+        log("INFO: ALU test: run softmodem with configuration file " +
+            config_file)
+        self.task_enb = Task("actions/run_enb.bash",
+                "run_softmodem",
+                self.enb_machine,
+                self.oai_user,
+                self.oai_password,
+                self.env,
+                self.logdir + "/run_softmodem." + self.enb_machine,
+                event=self.event)
+        self.task_enb.waitlog('got sync')
+
+    ##########################################################################
+    # stop_enb
+    ##########################################################################
+    def stop_enb(self):
+        log("INFO: ALU test: stop softmodem")
+        self.task_enb.sendnow("%c" % 3)
+        ret = self.task_enb.wait()
+        if ret != 0:
+            log("ERROR: ALU test: softmodem failed")
+            #not sure if we have to quit here or not
+            #os._exit(1)
+
+    ##########################################################################
+    # start_bandrich_ue
+    ##########################################################################
+    def start_bandrich_ue(self):
+        log("INFO: ALU test: start bandrich UE")
+        self.task_ue = Task("actions/start_bandrich.bash",
+                "start_bandrich",
+                self.ue_machine,
+                self.oai_user,
+                self.oai_password,
+                self.env,
+                self.logdir + "/start_bandrich." + self.ue_machine,
+                event=self.event)
+        self.task_ue.waitlog("local  IP address", event=self.event)
+        self.event.wait()
+
+        #at this point one task has died or we have the line in the log
+        if self.task_ue.waitlog_state != WAITLOG_SUCCESS:
+            log("ERROR: ALU test: bandrich UE did not connect")
+            raise TestFailed()
+
+        self.event.clear()
+
+        if (    not self.task_enb.alive() or
+                not self.task_hss.alive() or
+                not self.task_ue.alive()):
+            log("ERROR: ALU test: eNB, HSS or UE task died")
+            raise TestFailed()
+
+        #get bandrich UE IP
+        l = open(self.task_ue.logfile, "r").read()
+        self.bandrich_ue_ip = re.search("local  IP address (.*)\n", l) \
+                                .groups()[0]
+        log("INFO: ALU test: bandrich UE IP address: " + self.bandrich_ue_ip)
+
+    ##########################################################################
+    # stop_bandrich_ue
+    ##########################################################################
+    def stop_bandrich_ue(self):
+        log("INFO: ALU test: stop bandrich UE")
+        self.task_ue.sendnow("%c" % 3)
+        ret = self.task_ue.wait()
+        if ret != 0:
+            log("ERROR: ALU test: task bandrich UE failed")
+            #not sure if we have to quit here or not
+            #os._exit(1)
+
+    ##########################################################################
+    # _do_traffic (internal function, do not call out of the class)
+    ##########################################################################
+    def _do_traffic(self, name,
+            server_code, server_machine, server_ip,
+            client_code, client_machine,
+            waitlog,
+            server_logfile, client_logfile,
+            udp_bandwidth=None):
+        log("INFO: ALU test: run traffic: " + name)
+
+        log("INFO: ALU test:     launch server")
+        task_traffic_server = Task("actions/" + server_code + ".bash",
+                server_logfile,
+                server_machine,
+                self.oai_user,
+                self.oai_password,
+                self.env,
+                self.logdir + "/" + server_logfile + "." + server_machine,
+                event=self.event)
+        task_traffic_server.waitlog(waitlog)
+
+        env = list(self.env)
+        if udp_bandwidth != None:
+            env.append("UDP_BANDWIDTH=" + udp_bandwidth)
+        env.append("SERVER_IP=" + server_ip)
+
+        log("INFO: ALU test:     launch client")
+        task_traffic_client = Task("actions/" + client_code + ".bash",
+                client_logfile,
+                client_machine,
+                self.oai_user,
+                self.oai_password,
+                env,
+                self.logdir + "/" + client_logfile + "." + client_machine,
+                event=self.event)
+        log("INFO: ALU test:     wait for client to finish (or some error)")
+
+        self.event.wait()
+        log("DEBUG: event.wait() done")
+
+        if (    not self.task_enb.alive() or
+                not self.task_hss.alive() or
+                not self.task_ue.alive()):
+            log("ERROR: unexpected task exited, test failed, kill all")
+            if task_traffic_epc.alive():
+                task_traffic_epc.kill()
+            if self.task_enb.alive():
+                self.task_enb.kill()
+            if self.task_ue.alive():
+                self.task_ue.kill()
+
+        ret = task_traffic_client.wait()
+        if ret != 0:
+            log("ERROR: ALU test: downlink traffic failed")
+            #not sure if we have to quit here or not
+            #os._exit(1)
+
+        #stop downlink server
+        #log("INFO: ALU test:     stop server (kill ssh connection)")
+        #task_traffic_server.kill()
+        log("INFO: ALU test:     stop server (ctrl+z then kill -9 %1)")
+        task_traffic_server.sendnow("%ckill -9 %%1 || true\n" % 26)
+        log("INFO: ALU test:     wait for server to quit")
+        task_traffic_server.wait()
+
+        self.event.clear()
+
+        if (    not self.task_enb.alive() or
+                not self.task_hss.alive() or
+                not self.task_ue.alive()):
+            log("ERROR: ALU test: eNB, HSS or UE task died")
+            raise TestFailed()
+
+    ##########################################################################
+    # dl_tcp
+    ##########################################################################
+    def dl_tcp(self):
+        self._do_traffic("bandrich downlink TCP",
+                         "server_tcp", self.ue_machine, self.bandrich_ue_ip,
+                         "client_tcp", self.epc_machine,
+                         "Server listening on TCP port 5001",
+                         "bandrich_downlink_tcp_server",
+                         "bandrich_downlink_tcp_client")
+
+    ##########################################################################
+    # ul_tcp
+    ##########################################################################
+    def ul_tcp(self):
+        self._do_traffic("bandrich uplink TCP",
+                         "server_tcp", self.epc_machine, "192.172.0.1",
+                         "client_tcp", self.ue_machine,
+                         "Server listening on TCP port 5001",
+                         "bandrich_uplink_tcp_server",
+                         "bandrich_uplink_tcp_client")
+
+    ##########################################################################
+    # dl_udp
+    ##########################################################################
+    def dl_udp(self, bandwidth):
+        self._do_traffic("bandrich downlink UDP",
+                         "server_udp", self.ue_machine, self.bandrich_ue_ip,
+                         "client_udp", self.epc_machine,
+                         "Server listening on UDP port 5001",
+                         "bandrich_downlink_udp_server",
+                         "bandrich_downlink_udp_client",
+                         udp_bandwidth=bandwidth)
+
+    ##########################################################################
+    # ul_udp
+    ##########################################################################
+    def ul_udp(self, bandwidth):
+        self._do_traffic("bandrich uplink UDP",
+                         "server_udp", self.epc_machine, "192.172.0.1",
+                         "client_udp", self.ue_machine,
+                         "Server listening on UDP port 5001",
+                         "bandrich_uplink_udp_server",
+                         "bandrich_uplink_udp_client",
+                         udp_bandwidth=bandwidth)
+
+##############################################################################
+# run_b210_alu
+##############################################################################
+
+def run_b210_alu(tests, openair_dir, oai_user, oai_password, env):
+    if not do_tests(tests['b210']['alu']):
+        return
+
+    #compile eNB
+
+    alu = alu_test(epc='amerique', enb='hutch', ue='stevens',
+                   openair=openair_dir,
+                   user=oai_user, password=oai_password,
+                   log_subdir='enb_tests/b210_alu/compile_enb',
+                   env=env)
+
+    try:
+        alu.compile_enb("--eNB -w USRP -x -c --disable-cpu-affinity")
+    except BaseException, e:
+        log("ERROR: ALU test failed: eNB compilation failed: " + str(e))
+        return
+
+    #run tests
+
+    udp_dl_bandwidth = { "5"  : "15M",
+                         "10" : "30M",
+                         "20" : "60M" }
+
+    udp_ul_bandwidth = { "5"  : "7M",
+                         "10" : "15M",
+                         "20" : "15M" }
+
+    for bw in ('5', '10', '20'):
+        if do_tests(tests['b210']['alu'][bw]):
+            log("INFO: ALU test: run tests for bandwidth " + bw + " MHz")
+            ctest = tests['b210']['alu'][bw]
+            alu = alu_test(epc='amerique', enb='hutch', ue='stevens',
+                           openair=openair_dir,
+                           user=oai_user, password=oai_password,
+                           log_subdir='enb_tests/b210_alu/' + bw,
+                           env=env)
+            try:
+                alu.start_epc()
+                alu.start_enb("enb.band7.tm1.usrpb210." + bw + "MHz.conf")
+                if do_tests(ctest['bandrich']):
+                    alu.start_bandrich_ue()
+                    if do_tests(ctest['bandrich']['tcp']['dl']): alu.dl_tcp()
+                    if do_tests(ctest['bandrich']['tcp']['ul']): alu.ul_tcp()
+                    if do_tests(ctest['bandrich']['udp']['dl']):
+                        alu.dl_udp(udp_dl_bandwidth[bw])
+                    if do_tests(ctest['bandrich']['udp']['ul']):
+                        alu.ul_udp(udp_ul_bandwidth[bw])
+                    alu.stop_bandrich_ue()
+                alu.stop_enb()
+                alu.stop_epc()
+            except BaseException, e:
+                log("ERROR: ALU test failed: " + str(e))
+                alu.finish()
diff --git a/cmake_targets/autotests/v2/config/enb.band7.tm1.usrpb210.10MHz.conf b/cmake_targets/autotests/v2/config/enb.band7.tm1.usrpb210.10MHz.conf
new file mode 100644
index 0000000000..3b15799eb7
--- /dev/null
+++ b/cmake_targets/autotests/v2/config/enb.band7.tm1.usrpb210.10MHz.conf
@@ -0,0 +1,175 @@
+Active_eNBs = ( "eNB_Eurecom_LTEBox");
+# Asn1_verbosity, choice in: none, info, annoying
+Asn1_verbosity = "none";
+
+eNBs =
+(
+ {
+    ////////// Identification parameters:
+    eNB_ID    =  0xe00;
+
+    cell_type =  "CELL_MACRO_ENB";
+
+    eNB_name  =  "eNB_Eurecom_LTEBox";
+
+    // Tracking area code, 0x0000 and 0xfffe are reserved values
+    tracking_area_code  =  "1";
+
+    mobile_country_code =  "208";
+
+    mobile_network_code =  "92";
+
+       ////////// Physical parameters:
+
+    component_carriers = (
+      {
+        node_function                                         = "eNodeB_3GPP";
+	node_timing                                           = "synch_to_ext_device";
+	node_synch_ref                                        = 0;
+        frame_type					      = "FDD";
+        tdd_config 					      = 3;
+        tdd_config_s            			      = 0;
+        prefix_type             			      = "NORMAL";
+        eutra_band              			      = 7;
+        downlink_frequency      			      = 2660000000L;
+        uplink_frequency_offset 			      = -120000000;
+        Nid_cell					      = 0;
+        N_RB_DL                 			      = 50;
+        Nid_cell_mbsfn          			      = 0;
+        nb_antenna_ports          			      = 1;
+        nb_antennas_tx          			      = 1;
+        nb_antennas_rx          			      = 1;
+        tx_gain                                            = 90;
+        rx_gain                                            = 125;
+        prach_root              			      = 0;
+        prach_config_index      			      = 0;
+        prach_high_speed        			      = "DISABLE";
+        prach_zero_correlation  			      = 1;
+        prach_freq_offset       			      = 2;
+        pucch_delta_shift       			      = 1;
+        pucch_nRB_CQI           			      = 1;
+        pucch_nCS_AN            			      = 0;
+        pucch_n1_AN             			      = 32;
+        pdsch_referenceSignalPower 			      = -24;
+        pdsch_p_b                  			      = 0;
+        pusch_n_SB                 			      = 1;
+        pusch_enable64QAM          			      = "DISABLE";
+        pusch_hoppingMode                                  = "interSubFrame";
+        pusch_hoppingOffset                                = 0;
+        pusch_groupHoppingEnabled  			      = "ENABLE";
+        pusch_groupAssignment      			      = 0;
+        pusch_sequenceHoppingEnabled		   	      = "DISABLE";
+        pusch_nDMRS1                                       = 1;
+        phich_duration                                     = "NORMAL";
+        phich_resource                                     = "ONESIXTH";
+        srs_enable                                         = "DISABLE";
+        /*  srs_BandwidthConfig                                =;
+        srs_SubframeConfig                                 =;
+        srs_ackNackST                                      =;
+        srs_MaxUpPts                                       =;*/
+
+        pusch_p0_Nominal                                   = -96;
+        pusch_alpha                                        = "AL1";
+        pucch_p0_Nominal                                   = -103;
+        msg3_delta_Preamble                                = 6;
+        pucch_deltaF_Format1                               = "deltaF2";
+        pucch_deltaF_Format1b                              = "deltaF3";
+        pucch_deltaF_Format2                               = "deltaF0";
+        pucch_deltaF_Format2a                              = "deltaF0";
+        pucch_deltaF_Format2b		    	      = "deltaF0";
+
+        rach_numberOfRA_Preambles                          = 64;
+        rach_preamblesGroupAConfig                         = "DISABLE";
+        /*
+        rach_sizeOfRA_PreamblesGroupA                      = ;
+        rach_messageSizeGroupA                             = ;
+        rach_messagePowerOffsetGroupB                      = ;
+        */
+        rach_powerRampingStep                              = 4;
+        rach_preambleInitialReceivedTargetPower            = -104;
+        rach_preambleTransMax                              = 10;
+        rach_raResponseWindowSize                          = 10;
+        rach_macContentionResolutionTimer                  = 48;
+        rach_maxHARQ_Msg3Tx                                = 4;
+
+        pcch_default_PagingCycle                           = 128;
+        pcch_nB                                            = "oneT";
+        bcch_modificationPeriodCoeff			      = 2;
+        ue_TimersAndConstants_t300			      = 1000;
+        ue_TimersAndConstants_t301			      = 1000;
+        ue_TimersAndConstants_t310			      = 1000;
+        ue_TimersAndConstants_t311			      = 10000;
+        ue_TimersAndConstants_n310			      = 20;
+        ue_TimersAndConstants_n311			      = 1;
+
+	ue_TransmissionMode				      = 1;
+      }
+    );
+
+    srb1_parameters :
+    {
+        # timer_poll_retransmit = (ms) [5, 10, 15, 20,... 250, 300, 350, ... 500]
+        timer_poll_retransmit    = 80;
+
+        # timer_reordering = (ms) [0,5, ... 100, 110, 120, ... ,200]
+        timer_reordering         = 35;
+
+        # timer_reordering = (ms) [0,5, ... 250, 300, 350, ... ,500]
+        timer_status_prohibit    = 0;
+
+        # poll_pdu = [4, 8, 16, 32 , 64, 128, 256, infinity(>10000)]
+        poll_pdu                 =  4;
+
+        # poll_byte = (kB) [25,50,75,100,125,250,375,500,750,1000,1250,1500,2000,3000,infinity(>10000)]
+        poll_byte                =  99999;
+
+        # max_retx_threshold = [1, 2, 3, 4 , 6, 8, 16, 32]
+        max_retx_threshold       =  4;
+    }
+
+    # ------- SCTP definitions
+    SCTP :
+    {
+        # Number of streams to use in input/output
+        SCTP_INSTREAMS  = 2;
+        SCTP_OUTSTREAMS = 2;
+    };
+
+    ////////// MME parameters:
+
+    mme_ip_address      = ( { ipv4       = "192.168.12.26";
+                              ipv6       = "192:168:30::17";
+                              active     = "yes";
+                              preference = "ipv4";
+                            }
+                          );
+
+    NETWORK_INTERFACES :
+    {
+
+        ENB_INTERFACE_NAME_FOR_S1_MME            = "eth0";
+        ENB_IPV4_ADDRESS_FOR_S1_MME              = "192.168.12.19/24";
+        ENB_INTERFACE_NAME_FOR_S1U               = "eth0";
+        ENB_IPV4_ADDRESS_FOR_S1U                 = "192.168.12.19/24";
+        ENB_PORT_FOR_S1U                         = 2152; # Spec 2152
+    };
+
+    log_config :
+    {
+      global_log_level                      ="info";
+      global_log_verbosity                  ="medium";
+      hw_log_level                          ="info";
+      hw_log_verbosity                      ="medium";
+      phy_log_level                         ="info";
+      phy_log_verbosity                     ="medium";
+      mac_log_level                         ="info";
+      mac_log_verbosity                     ="high";
+      rlc_log_level                         ="info";
+      rlc_log_verbosity                     ="medium";
+      pdcp_log_level                        ="info";
+      pdcp_log_verbosity                    ="medium";
+      rrc_log_level                         ="info";
+      rrc_log_verbosity                     ="medium";
+   };
+  }
+);
diff --git a/cmake_targets/autotests/v2/config/enb.band7.tm1.usrpb210.20MHz.conf b/cmake_targets/autotests/v2/config/enb.band7.tm1.usrpb210.20MHz.conf
new file mode 100644
index 0000000000..869c7f05f1
--- /dev/null
+++ b/cmake_targets/autotests/v2/config/enb.band7.tm1.usrpb210.20MHz.conf
@@ -0,0 +1,175 @@
+Active_eNBs = ( "eNB_Eurecom_LTEBox");
+# Asn1_verbosity, choice in: none, info, annoying
+Asn1_verbosity = "none";
+
+eNBs =
+(
+ {
+    ////////// Identification parameters:
+    eNB_ID    =  0xe00;
+
+    cell_type =  "CELL_MACRO_ENB";
+
+    eNB_name  =  "eNB_Eurecom_LTEBox";
+
+    // Tracking area code, 0x0000 and 0xfffe are reserved values
+    tracking_area_code  =  "1";
+
+    mobile_country_code =  "208";
+
+    mobile_network_code =  "92";
+
+       ////////// Physical parameters:
+
+    component_carriers = (
+      {
+        node_function                                         = "eNodeB_3GPP";
+	node_timing                                           = "synch_to_ext_device";
+	node_synch_ref                                        = 0;
+        frame_type					      = "FDD";
+        tdd_config 					      = 3;
+        tdd_config_s            			      = 0;
+        prefix_type             			      = "NORMAL";
+        eutra_band              			      = 7;
+        downlink_frequency      			      = 2660000000L;
+        uplink_frequency_offset 			      = -120000000;
+        Nid_cell					      = 0;
+        N_RB_DL                 			      = 100;
+        Nid_cell_mbsfn          			      = 0;
+        nb_antenna_ports          			      = 1;
+        nb_antennas_tx          			      = 1;
+        nb_antennas_rx          			      = 1;
+        tx_gain                                            = 90;
+        rx_gain                                            = 125;
+        prach_root              			      = 0;
+        prach_config_index      			      = 0;
+        prach_high_speed        			      = "DISABLE";
+        prach_zero_correlation  			      = 1;
+        prach_freq_offset       			      = 2;
+        pucch_delta_shift       			      = 1;
+        pucch_nRB_CQI           			      = 1;
+        pucch_nCS_AN            			      = 0;
+        pucch_n1_AN             			      = 32;
+        pdsch_referenceSignalPower 			      = -24;
+        pdsch_p_b                  			      = 0;
+        pusch_n_SB                 			      = 1;
+        pusch_enable64QAM          			      = "DISABLE";
+        pusch_hoppingMode                                  = "interSubFrame";
+        pusch_hoppingOffset                                = 0;
+        pusch_groupHoppingEnabled  			      = "ENABLE";
+        pusch_groupAssignment      			      = 0;
+        pusch_sequenceHoppingEnabled		   	      = "DISABLE";
+        pusch_nDMRS1                                       = 1;
+        phich_duration                                     = "NORMAL";
+        phich_resource                                     = "ONESIXTH";
+        srs_enable                                         = "DISABLE";
+        /*  srs_BandwidthConfig                                =;
+        srs_SubframeConfig                                 =;
+        srs_ackNackST                                      =;
+        srs_MaxUpPts                                       =;*/
+
+        pusch_p0_Nominal                                   = -96;
+        pusch_alpha                                        = "AL1";
+        pucch_p0_Nominal                                   = -103;
+        msg3_delta_Preamble                                = 6;
+        pucch_deltaF_Format1                               = "deltaF2";
+        pucch_deltaF_Format1b                              = "deltaF3";
+        pucch_deltaF_Format2                               = "deltaF0";
+        pucch_deltaF_Format2a                              = "deltaF0";
+        pucch_deltaF_Format2b		    	      = "deltaF0";
+
+        rach_numberOfRA_Preambles                          = 64;
+        rach_preamblesGroupAConfig                         = "DISABLE";
+        /*
+        rach_sizeOfRA_PreamblesGroupA                      = ;
+        rach_messageSizeGroupA                             = ;
+        rach_messagePowerOffsetGroupB                      = ;
+        */
+        rach_powerRampingStep                              = 4;
+        rach_preambleInitialReceivedTargetPower            = -104;
+        rach_preambleTransMax                              = 10;
+        rach_raResponseWindowSize                          = 10;
+        rach_macContentionResolutionTimer                  = 48;
+        rach_maxHARQ_Msg3Tx                                = 4;
+
+        pcch_default_PagingCycle                           = 128;
+        pcch_nB                                            = "oneT";
+        bcch_modificationPeriodCoeff			      = 2;
+        ue_TimersAndConstants_t300			      = 1000;
+        ue_TimersAndConstants_t301			      = 1000;
+        ue_TimersAndConstants_t310			      = 1000;
+        ue_TimersAndConstants_t311			      = 10000;
+        ue_TimersAndConstants_n310			      = 20;
+        ue_TimersAndConstants_n311			      = 1;
+
+	ue_TransmissionMode				      = 1;
+      }
+    );
+
+    srb1_parameters :
+    {
+        # timer_poll_retransmit = (ms) [5, 10, 15, 20,... 250, 300, 350, ... 500]
+        timer_poll_retransmit    = 80;
+
+        # timer_reordering = (ms) [0,5, ... 100, 110, 120, ... ,200]
+        timer_reordering         = 35;
+
+        # timer_reordering = (ms) [0,5, ... 250, 300, 350, ... ,500]
+        timer_status_prohibit    = 0;
+
+        # poll_pdu = [4, 8, 16, 32 , 64, 128, 256, infinity(>10000)]
+        poll_pdu                 =  4;
+
+        # poll_byte = (kB) [25,50,75,100,125,250,375,500,750,1000,1250,1500,2000,3000,infinity(>10000)]
+        poll_byte                =  99999;
+
+        # max_retx_threshold = [1, 2, 3, 4 , 6, 8, 16, 32]
+        max_retx_threshold       =  4;
+    }
+
+    # ------- SCTP definitions
+    SCTP :
+    {
+        # Number of streams to use in input/output
+        SCTP_INSTREAMS  = 2;
+        SCTP_OUTSTREAMS = 2;
+    };
+
+    ////////// MME parameters:
+
+    mme_ip_address      = ( { ipv4       = "192.168.12.26";
+                              ipv6       = "192:168:30::17";
+                              active     = "yes";
+                              preference = "ipv4";
+                            }
+                          );
+
+    NETWORK_INTERFACES :
+    {
+
+        ENB_INTERFACE_NAME_FOR_S1_MME            = "eth0";
+        ENB_IPV4_ADDRESS_FOR_S1_MME              = "192.168.12.19/24";
+        ENB_INTERFACE_NAME_FOR_S1U               = "eth0";
+        ENB_IPV4_ADDRESS_FOR_S1U                 = "192.168.12.19/24";
+        ENB_PORT_FOR_S1U                         = 2152; # Spec 2152
+    };
+
+    log_config :
+    {
+      global_log_level                      ="info";
+      global_log_verbosity                  ="medium";
+      hw_log_level                          ="info";
+      hw_log_verbosity                      ="medium";
+      phy_log_level                         ="info";
+      phy_log_verbosity                     ="medium";
+      mac_log_level                         ="info";
+      mac_log_verbosity                     ="high";
+      rlc_log_level                         ="info";
+      rlc_log_verbosity                     ="medium";
+      pdcp_log_level                        ="info";
+      pdcp_log_verbosity                    ="medium";
+      rrc_log_level                         ="info";
+      rrc_log_verbosity                     ="medium";
+   };
+  }
+);
diff --git a/cmake_targets/autotests/v2/config/enb.band7.tm1.usrpb210.5MHz.conf b/cmake_targets/autotests/v2/config/enb.band7.tm1.usrpb210.5MHz.conf
new file mode 100644
index 0000000000..d3c1a5354a
--- /dev/null
+++ b/cmake_targets/autotests/v2/config/enb.band7.tm1.usrpb210.5MHz.conf
@@ -0,0 +1,175 @@
+Active_eNBs = ( "eNB_Eurecom_LTEBox");
+# Asn1_verbosity, choice in: none, info, annoying
+Asn1_verbosity = "none";
+
+eNBs =
+(
+ {
+    ////////// Identification parameters:
+    eNB_ID    =  0xe00;
+
+    cell_type =  "CELL_MACRO_ENB";
+
+    eNB_name  =  "eNB_Eurecom_LTEBox";
+
+    // Tracking area code, 0x0000 and 0xfffe are reserved values
+    tracking_area_code  =  "1";
+
+    mobile_country_code =  "208";
+
+    mobile_network_code =  "92";
+
+       ////////// Physical parameters:
+
+    component_carriers = (
+      {
+        node_function                                         = "eNodeB_3GPP";
+	node_timing                                           = "synch_to_ext_device";
+	node_synch_ref                                        = 0;
+        frame_type					      = "FDD";
+        tdd_config 					      = 3;
+        tdd_config_s            			      = 0;
+        prefix_type             			      = "NORMAL";
+        eutra_band              			      = 7;
+        downlink_frequency      			      = 2660000000L;
+        uplink_frequency_offset 			      = -120000000;
+        Nid_cell					      = 0;
+        N_RB_DL                 			      = 25;
+        Nid_cell_mbsfn          			      = 0;
+        nb_antenna_ports          			      = 1;
+        nb_antennas_tx          			      = 1;
+        nb_antennas_rx          			      = 1;
+        tx_gain                                            = 90;
+        rx_gain                                            = 125;
+        prach_root              			      = 0;
+        prach_config_index      			      = 0;
+        prach_high_speed        			      = "DISABLE";
+        prach_zero_correlation  			      = 1;
+        prach_freq_offset       			      = 2;
+        pucch_delta_shift       			      = 1;
+        pucch_nRB_CQI           			      = 1;
+        pucch_nCS_AN            			      = 0;
+        pucch_n1_AN             			      = 32;
+        pdsch_referenceSignalPower 			      = -24;
+        pdsch_p_b                  			      = 0;
+        pusch_n_SB                 			      = 1;
+        pusch_enable64QAM          			      = "DISABLE";
+        pusch_hoppingMode                                  = "interSubFrame";
+        pusch_hoppingOffset                                = 0;
+        pusch_groupHoppingEnabled  			      = "ENABLE";
+        pusch_groupAssignment      			      = 0;
+        pusch_sequenceHoppingEnabled		   	      = "DISABLE";
+        pusch_nDMRS1                                       = 1;
+        phich_duration                                     = "NORMAL";
+        phich_resource                                     = "ONESIXTH";
+        srs_enable                                         = "DISABLE";
+        /*  srs_BandwidthConfig                                =;
+        srs_SubframeConfig                                 =;
+        srs_ackNackST                                      =;
+        srs_MaxUpPts                                       =;*/
+
+        pusch_p0_Nominal                                   = -96;
+        pusch_alpha                                        = "AL1";
+        pucch_p0_Nominal                                   = -103;
+        msg3_delta_Preamble                                = 6;
+        pucch_deltaF_Format1                               = "deltaF2";
+        pucch_deltaF_Format1b                              = "deltaF3";
+        pucch_deltaF_Format2                               = "deltaF0";
+        pucch_deltaF_Format2a                              = "deltaF0";
+        pucch_deltaF_Format2b		    	      = "deltaF0";
+
+        rach_numberOfRA_Preambles                          = 64;
+        rach_preamblesGroupAConfig                         = "DISABLE";
+        /*
+        rach_sizeOfRA_PreamblesGroupA                      = ;
+        rach_messageSizeGroupA                             = ;
+        rach_messagePowerOffsetGroupB                      = ;
+        */
+        rach_powerRampingStep                              = 4;
+        rach_preambleInitialReceivedTargetPower            = -104;
+        rach_preambleTransMax                              = 10;
+        rach_raResponseWindowSize                          = 10;
+        rach_macContentionResolutionTimer                  = 48;
+        rach_maxHARQ_Msg3Tx                                = 4;
+
+        pcch_default_PagingCycle                           = 128;
+        pcch_nB                                            = "oneT";
+        bcch_modificationPeriodCoeff			      = 2;
+        ue_TimersAndConstants_t300			      = 1000;
+        ue_TimersAndConstants_t301			      = 1000;
+        ue_TimersAndConstants_t310			      = 1000;
+        ue_TimersAndConstants_t311			      = 10000;
+        ue_TimersAndConstants_n310			      = 20;
+        ue_TimersAndConstants_n311			      = 1;
+
+	ue_TransmissionMode				      = 1;
+      }
+    );
+
+    srb1_parameters :
+    {
+        # timer_poll_retransmit = (ms) [5, 10, 15, 20,... 250, 300, 350, ... 500]
+        timer_poll_retransmit    = 80;
+
+        # timer_reordering = (ms) [0,5, ... 100, 110, 120, ... ,200]
+        timer_reordering         = 35;
+
+        # timer_reordering = (ms) [0,5, ... 250, 300, 350, ... ,500]
+        timer_status_prohibit    = 0;
+
+        # poll_pdu = [4, 8, 16, 32 , 64, 128, 256, infinity(>10000)]
+        poll_pdu                 =  4;
+
+        # poll_byte = (kB) [25,50,75,100,125,250,375,500,750,1000,1250,1500,2000,3000,infinity(>10000)]
+        poll_byte                =  99999;
+
+        # max_retx_threshold = [1, 2, 3, 4 , 6, 8, 16, 32]
+        max_retx_threshold       =  4;
+    }
+
+    # ------- SCTP definitions
+    SCTP :
+    {
+        # Number of streams to use in input/output
+        SCTP_INSTREAMS  = 2;
+        SCTP_OUTSTREAMS = 2;
+    };
+
+    ////////// MME parameters:
+
+    mme_ip_address      = ( { ipv4       = "192.168.12.26";
+                              ipv6       = "192:168:30::17";
+                              active     = "yes";
+                              preference = "ipv4";
+                            }
+                          );
+
+    NETWORK_INTERFACES :
+    {
+
+        ENB_INTERFACE_NAME_FOR_S1_MME            = "eth0";
+        ENB_IPV4_ADDRESS_FOR_S1_MME              = "192.168.12.19/24";
+        ENB_INTERFACE_NAME_FOR_S1U               = "eth0";
+        ENB_IPV4_ADDRESS_FOR_S1U                 = "192.168.12.19/24";
+        ENB_PORT_FOR_S1U                         = 2152; # Spec 2152
+    };
+
+    log_config :
+    {
+      global_log_level                      ="info";
+      global_log_verbosity                  ="medium";
+      hw_log_level                          ="info";
+      hw_log_verbosity                      ="medium";
+      phy_log_level                         ="info";
+      phy_log_verbosity                     ="medium";
+      mac_log_level                         ="info";
+      mac_log_verbosity                     ="high";
+      rlc_log_level                         ="info";
+      rlc_log_verbosity                     ="medium";
+      pdcp_log_level                        ="info";
+      pdcp_log_verbosity                    ="medium";
+      rrc_log_level                         ="info";
+      rrc_log_verbosity                     ="medium";
+   };
+  }
+);
diff --git a/cmake_targets/autotests/v2/connection.py b/cmake_targets/autotests/v2/connection.py
new file mode 100644
index 0000000000..1952b8b72f
--- /dev/null
+++ b/cmake_targets/autotests/v2/connection.py
@@ -0,0 +1,104 @@
+import os, subprocess, time, fcntl, termios, tty, signal, thread
+
+from utils import log
+
+class connection:
+    def __init__(self, description, host, user, password):
+        self.description = description
+        self.host = host
+        self.user = user
+        self.password = password
+        self.sendlock = thread.allocate_lock()
+
+        try:
+            (pid, fd) = os.forkpty()
+        except BaseException, e:
+            log("ERROR: forkpty for '" + description + "': " + e)
+            (pid, fd) = (-1, -1)
+
+        if pid == -1:
+            log("ERROR: creating connection for '" + description + "'")
+            os._exit(1)
+
+        # child process, run ssh
+        if pid == 0:
+            try:
+                os.execvp('sshpass', ['sshpass', '-p', password,
+                          'ssh', user + '@' + host])
+            except BaseException, e:
+                log("ERROR: execvp for '" + description + "': " + e)
+            log("ERROR: execvp failed for '" + description + "'")
+            os._exit(1)
+
+        # parent process
+        # make the TTY raw to avoid getting input printed and no ^M
+        try:
+            tty.setraw(fd, termios.TCSANOW)
+        except BaseException, e:
+            log("ERROR: failed configuring TTY: " + str(e))
+            os._exit(1)
+
+#        try:
+#            fcntl.fcntl(fd, fcntl.F_SETFL,
+#                        fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
+#        except:
+#            log("ERROR: fcntl failed for '" + description + "'")
+#            os._exit(1)
+
+        self.pid = pid
+        self.fd = fd
+        self.active = True
+        self.retcode = -1
+
+    def send(self, string):
+        if self.active == False:
+            log("ERROR: send: child is dead for '" + self.description + "'")
+            return -1
+
+        try:
+            (pid, out) = os.waitpid(self.pid, os.WNOHANG)
+        except BaseException, e:
+            log("ERROR: send: waitpid failed for '" + self.description +
+                "': " + str(e))
+            (pid, out) = (self.pid, 1)
+        if pid != 0:
+            log("ERROR: send: child process dead for '" +
+                self.description + "'")
+            try:
+                os.close(self.fd)
+            except BaseException, e:
+                #we don't care about errors at this point
+                pass
+            self.active = False
+            self.retcode = out
+            return -1
+
+        self.sendlock.acquire()
+
+        length = len(string)
+        while length != 0:
+            try:
+                ret = os.write(self.fd, string)
+            except BaseException, e:
+                log("ERROR: send fails for '" + self.description + "': " +
+                    str(e))
+                os._exit(1)
+
+            if ret == 0:
+                log("ERROR: send: write returns 0 for '" +
+                    self.description + "'")
+                os._exit(1)
+
+            length = length - ret
+            string = string[ret:]
+
+        self.sendlock.release()
+
+        return 0
+
+    def kill(self, signal=signal.SIGTERM):
+        log("INFO: kill connection '" + self.description + "'")
+        try:
+            os.kill(self.pid, signal)
+        except BaseException, e:
+            log("ERROR: connection.kill: " + str(e))
diff --git a/cmake_targets/autotests/v2/machine_list.py b/cmake_targets/autotests/v2/machine_list.py
new file mode 100644
index 0000000000..dc41c504fe
--- /dev/null
+++ b/cmake_targets/autotests/v2/machine_list.py
@@ -0,0 +1,79 @@
+import threading, os
+
+from utils import log
+
+class MachineWaiterThread(threading.Thread):
+    def __init__(self, machine, tasks):
+        threading.Thread.__init__(self)
+        self.machine = machine
+        self.tasks = tasks
+
+    def run(self):
+        try:
+            for task in self.tasks:
+                ret = task.wait()
+                if ret != 0:
+                    log("ERROR: task '" + task.description + "' failed " +
+                        "on machine " + self.machine.name)
+                task.postaction()
+            self.machine.unbusy()
+        except BaseException, e:
+            log("ERROR: MachineWaiterThread: " + str(e))
+            os._exit(1)
+
+class Machine():
+    def __init__(self, machine, cond):
+        self.name = machine
+        self.free = True
+        self.cond = cond
+    def busy(self, tasks):
+        waiter = MachineWaiterThread(self, tasks)
+        waiter.start()
+    def unbusy(self):
+        self.cond.acquire()
+        self.free = True
+        self.cond.notify()
+        self.cond.release()
+
+class MachineList():
+    def __init__(self, list):
+        self.list = []
+        self.cond = threading.Condition()
+        for m in list:
+            self.list.append(Machine(m, self.cond))
+
+    def get_free_machine(self):
+        try:
+            self.cond.acquire()
+            while True:
+                free_machine = None
+                for m in self.list:
+                    if m.free == True:
+                        free_machine = m
+                        break
+                if free_machine != None:
+                    break
+                self.cond.wait()
+            free_machine.free = False
+            self.cond.release()
+        except BaseException, e:
+            log("ERROR: machine_list: " + str(e))
+            os._exit(1)
+        return free_machine
+
+    def wait_all_free(self):
+        try:
+            self.cond.acquire()
+            while True:
+                all_free = True
+                for m in self.list:
+                    if m.free == False:
+                        all_free = False
+                        break
+                if all_free == True:
+                    break
+                self.cond.wait()
+            self.cond.release()
+        except BaseException, e:
+            log("ERROR: machine_list: " + str(e))
+            os._exit(1)
diff --git a/cmake_targets/autotests/v2/main.py b/cmake_targets/autotests/v2/main.py
new file mode 100644
index 0000000000..a006a63d14
--- /dev/null
+++ b/cmake_targets/autotests/v2/main.py
@@ -0,0 +1,687 @@
+#!/usr/bin/python
+
+import os, re, sys, time, threading, thread
+import xml.etree.ElementTree as ET
+
+from utils import test_in_list, quickshell, log
+from task import Task, WAITLOG_SUCCESS, WAITLOG_FAILURE
+from machine_list import MachineList
+
+oai_user         = os.environ.get('OAI_USER')
+oai_password     = os.environ.get('OAI_PASS')
+requested_tests  = os.environ.get('OAI_TEST_CASE_GROUP')
+machines         = os.environ.get('MACHINELIST')
+generic_machines = os.environ.get('MACHINELISTGENERIC')
+result_dir       = os.environ.get('RESULT_DIR')
+nruns_softmodem  = os.environ.get('NRUNS_LTE_SOFTMODEM')
+openair_dir      = os.environ.get('OPENAIR_DIR')
+
+some_undef = False
+if (oai_user         == None) :
+        log("variable OAI_USER is not defined")
+        some_undef = True
+if (oai_password     == None) :
+        log("variable OAI_PASS is not defined")
+        some_undef = True
+if (requested_tests  == None) :
+        log("variable OAI_TEST_CASE_GROUP is not defined")
+        some_undef = True
+if (machines         == None) :
+        log("variable MACHINELIST is not defined")
+        some_undef = True
+if (generic_machines == None) :
+        log("variable MACHINELISTGENERIC is not defined")
+        some_undef = True
+if (result_dir       == None) :
+        log("variable RESULT_DIR is not defined")
+        some_undef = True
+if (nruns_softmodem  == None) :
+        log("variable NRUNS_LTE_SOFTMODEM is not defined")
+        some_undef = True
+if (openair_dir      == None) :
+        log("variable OPENAIR_DIR is not defined")
+        some_undef = True
+if (some_undef == True):
+    os._exit(1)
+
+requested_tests  = requested_tests  .replace('"','')
+machines         = machines         .replace('"','')
+generic_machines = generic_machines .replace('"','')
+
+xml_test_file = os.environ.get('OPENAIR_DIR') + \
+                "/cmake_targets/autotests/test_case_list.xml"
+
+xmlTree = ET.parse(xml_test_file)
+xmlRoot = xmlTree.getroot()
+
+exclusion_tests=xmlRoot.findtext('TestCaseExclusionList',default='')
+all_tests=xmlRoot.findall('testCase')
+
+exclusion_tests=exclusion_tests.split()
+requested_tests=requested_tests.split()
+
+#check that exclusion tests are well formatted
+#(6 digits or less than 6 digits followed by +)
+for test in exclusion_tests:
+    if     (not re.match('^[0-9]{6}$', test) and
+            not re.match('^[0-9]{1,5}\+$', test)):
+        log("ERROR: exclusion test is invalidly formatted: " + test)
+        os._exit(1)
+
+#check that requested tests are well formatted
+#(6 digits or less than 6 digits followed by +)
+#be verbose
+for test in requested_tests:
+    if     (re.match('^[0-9]{6}$', test) or
+            re.match('^[0-9]{1,5}\+$', test)):
+        log("INFO: test group/case requested: " + test)
+    else:
+        log("ERROR: requested test is invalidly formatted: " + test)
+        os._exit(1)
+
+#get the list of tests to be done
+todo_tests=[]
+for test in all_tests:
+    if     (test_in_list(test.get('id'), requested_tests) and
+            not test_in_list(test.get('id'), exclusion_tests)):
+        log("INFO: test will be run: " + test.get('id'))
+        todo_tests.append(test)
+    else:
+        log("INFO: test will be skipped: " + test.get('id'))
+
+#get commit ID to use
+commit_id = quickshell("git rev-parse --verify HEAD").replace('\n','')
+if (len(commit_id) != 20*2):
+    log("ERROR: bad commit '" + commit_id + "'")
+log("INFO: test for commit " + commit_id)
+
+#get repository URL
+repository_url = quickshell("git config remote.origin.url").replace('\n','')
+log("INFO: repository URL: " + repository_url)
+
+#prepare environment for tasks
+env = []
+env.append("REPOSITORY_URL=" + repository_url)
+env.append("COMMIT_ID="      + commit_id)
+
+#clone repository on all machines in the test setup
+tasks=[]
+for machine in machines.split():
+    tasks.append(Task("actions/clone_repository.bash",
+                      "clone repository on " + machine,
+                      machine,
+                      oai_user,
+                      oai_password,
+                      env,
+                      openair_dir + "/cmake_targets/autotests/log/clone." \
+                         + machine))
+for task in tasks:
+    log("INFO: wait for task: " + task.description)
+    ret = task.wait()
+    if ret != 0 or not "TEST_SETUP_SUCCESS" in open(task.logfile).read():
+        log("ERROR: task failed: " + task.description)
+        os._exit(1)
+
+##############################################################################
+# run compilation tests                                                      #
+##############################################################################
+
+machine_list = MachineList(generic_machines.split())
+
+for test in todo_tests:
+    action = test.findtext('class')
+    if action != 'compilation':
+        continue
+    id = test.get('id')
+    machine = machine_list.get_free_machine()
+    log("INFO: start " + action + " test " + id + " on machine " +
+        machine.name)
+    tasks = []
+    runenv = list(env)
+    runenv.append('OPENAIR_DIR=/tmp/oai_test_setup/oai')
+    runenv.append('BUILD_ARGUMENTS="'
+                    + test.findtext('compile_prog_args')
+                    + '"')
+    runenv.append('BUILD_OUTPUT="'
+                    + test.findtext('compile_prog_out')
+                          .replace('\n','')
+                    + '"')
+    logdir = openair_dir +"/cmake_targets/autotests/log/"+ id +"/compile_log"
+    remote_files = "'/tmp/oai_test_setup/oai/cmake_targets/log/*'"
+    post_action = "mkdir -p "+ logdir + \
+                  " && sshpass -p " + oai_password + " scp -r " + oai_user + \
+                  "@" + machine.name + ":" + remote_files + " " + logdir + \
+                  " || true"
+    tasks.append(Task("actions/" + action + ".bash",
+                      action + " of test " + id + " on " + machine.name,
+                      machine.name,
+                      oai_user,
+                      oai_password,
+                      runenv,
+                      openair_dir + "/cmake_targets/autotests/log/"
+                         + id + "."
+                         + machine.name,
+                      post_action=post_action))
+    machine.busy(tasks)
+
+##############################################################################
+# run execution tests                                                        #
+##############################################################################
+
+class ExecutionThread(threading.Thread):
+    def __init__(self, id, machine, test):
+        threading.Thread.__init__(self)
+        self.machine = machine
+        self.id = id
+        self.test = test
+
+    def run(self):
+        id = self.id
+        machine = self.machine
+        test = self.test
+
+        # step 1: compile
+
+        log("INFO: start compilation of test " + id + " on machine " +
+            machine.name)
+        tasks = []
+        runenv = list(env)
+        runenv.append('OPENAIR_DIR=/tmp/oai_test_setup/oai')
+        runenv.append('PRE_BUILD="'
+                        + test.findtext('pre_compile_prog')
+                        + '"')
+        runenv.append('BUILD_PROG="'
+                        + test.findtext('compile_prog')
+                        + '"')
+        runenv.append('BUILD_ARGUMENTS="'
+                        + test.findtext('compile_prog_args')
+                        + '"')
+        runenv.append('PRE_EXEC="'
+                        + test.findtext('pre_exec') + " "
+                        + test.findtext('pre_exec_args')
+                        + '"')
+        logdir = openair_dir +"/cmake_targets/autotests/log/"+ id + \
+                 "/compile_log"
+        remote_files = "'/tmp/oai_test_setup/oai/cmake_targets/log/*'"
+        post_action = "mkdir -p "+ logdir + \
+                      " && sshpass -p " + oai_password + \
+                      " scp -r " + oai_user + "@" + machine.name + ":" + \
+                                   remote_files + " " + logdir + \
+                      " || true"
+        task = Task("actions/execution_compile.bash",
+                    "compilation of test " + id + " on " + machine.name,
+                    machine.name,
+                    oai_user,
+                    oai_password,
+                    runenv,
+                    openair_dir + "/cmake_targets/autotests/log/"
+                       + id + "_compile."
+                       + machine.name,
+                    post_action=post_action)
+        ret = task.wait()
+        task.postaction()
+        if ret != 0:
+            log("ERROR: compilation of test " + id + " failed: " + str(ret))
+            machine.unbusy()
+            return
+
+        #step 2: run all tests
+
+        nruns = test.findtext('nruns')
+        args = test.findtext('main_exec_args')
+        i = 0
+        for arg in args.splitlines():
+            i = i+1
+            runenv2 = list(runenv)
+            runenv2.append('OPENAIR_TARGET=/tmp/oai_test_setup/oai/targets')
+            runenv2.append('EXEC="'
+                             + test.findtext('main_exec')
+                             + '"')
+            runenv2.append('EXEC_ARGS="' + arg + '"')
+            for run in range(int(nruns)):
+                log("INFO: start execution of test " + id + " case " +
+                    str(i) + " run " + str(run) + " on machine " +
+                    machine.name)
+                task =Task("actions/execution.bash",
+                           "execution of test " + id + " on " + machine.name,
+                           machine.name,
+                           oai_user,
+                           oai_password,
+                           runenv2,
+                           openair_dir + "/cmake_targets/autotests/log/"
+                              + id + "_case_" + str(i) + "_run_" + str(run)
+                              + "." + machine.name)
+                ret = task.wait()
+                if ret != 0:
+                    log("ERROR: execution of test " +id+ " failed: " +
+                        str(ret))
+
+        machine.unbusy()
+
+for test in todo_tests:
+    action = test.findtext('class')
+    if action != 'execution':
+        continue
+    id = test.get('id')
+    machine = machine_list.get_free_machine()
+    ExecutionThread(id, machine, test).start()
+
+#wait for compilation/execution tests to be finished
+#log only if some compilation/execution tests are actually done
+for test in todo_tests:
+    action = test.findtext('class')
+    if action == 'execution' or action == 'compilation':
+        log("INFO: requested compilation/execution tests " +
+            "have been launched, waiting for completion")
+        break
+machine_list.wait_all_free()
+
+##############################################################################
+# run eNB softmodem tests                                                    #
+##############################################################################
+
+tests = {
+  'b210' : {
+    'alu' : {
+      '5' : {
+        'bandrich' : {
+          'tcp' : { 'ul': False, 'dl' : False },
+          'udp' : { 'ul': False, 'dl' : False }},
+        'sony' : {
+          'tcp' : { 'ul': False, 'dl' : False },
+          'udp' : { 'ul': False, 'dl' : False }}},
+      '10' : {
+        'bandrich' : {
+          'tcp' : { 'ul': False, 'dl' : False },
+          'udp' : { 'ul': False, 'dl' : False }},
+        'sony' : {
+          'tcp' : { 'ul': False, 'dl' : False },
+          'udp' : { 'ul': False, 'dl' : False }}},
+      '20' : {
+        'bandrich' : {
+          'tcp' : { 'ul': False, 'dl' : False },
+          'udp' : { 'ul': False, 'dl' : False }},
+        'sony' : {
+          'tcp' : { 'ul': False, 'dl' : False },
+          'udp' : { 'ul': False, 'dl' : False }}}},
+    'openair-cn' : {}
+  },
+  'x310' : {
+    'alu' : {},
+    'openair-cn' : {}
+  },
+  'exmimo2' : {
+    'alu' : {},
+    'openair-cn' : {}
+  }
+}
+
+todo_tests_ids = []
+for test in todo_tests:
+    todo_tests_ids.append(test.get('id'))
+
+for test in todo_tests_ids:
+  if test=='015500':tests['b210']['alu'][ '5']['bandrich']['udp']['ul']=True
+  if test=='015501':tests['b210']['alu']['10']['bandrich']['udp']['ul']=True
+  if test=='015502':tests['b210']['alu']['20']['bandrich']['udp']['ul']=True
+  if test=='015503':tests['b210']['alu'][ '5']['bandrich']['udp']['dl']=True
+  if test=='015504':tests['b210']['alu']['10']['bandrich']['udp']['dl']=True
+  if test=='015505':tests['b210']['alu']['20']['bandrich']['udp']['dl']=True
+  if test=='015506':log('WARNING: skip test ' + test) #TODO
+  if test=='015507':log('WARNING: skip test ' + test) #TODO
+  if test=='015508':log('WARNING: skip test ' + test) #TODO
+  if test=='015509':log('WARNING: skip test ' + test) #TODO
+  if test=='015510':log('WARNING: skip test ' + test) #TODO
+  if test=='015511':log('WARNING: skip test ' + test) #TODO
+  if test=='015512':tests['b210']['alu'][ '5']['bandrich']['tcp']['ul']=True
+  if test=='015513':tests['b210']['alu']['10']['bandrich']['tcp']['ul']=True
+  if test=='015514':tests['b210']['alu']['20']['bandrich']['tcp']['ul']=True
+  if test=='015515':tests['b210']['alu'][ '5']['bandrich']['tcp']['dl']=True
+  if test=='015516':tests['b210']['alu']['10']['bandrich']['tcp']['dl']=True
+  if test=='015517':tests['b210']['alu']['20']['bandrich']['tcp']['dl']=True
+  if test=='015518':log('WARNING: skip test ' + test) #TODO
+  if test=='015519':log('WARNING: skip test ' + test) #TODO
+  if test=='015520':log('WARNING: skip test ' + test) #TODO
+  if test=='015521':log('WARNING: skip test ' + test) #TODO
+  if test=='015522':log('WARNING: skip test ' + test) #TODO
+  if test=='015523':log('WARNING: skip test ' + test) #TODO
+
+  if test=='015600':log('WARNING: skip test ' + test) #TODO
+  if test=='015601':log('WARNING: skip test ' + test) #TODO
+  if test=='015602':log('WARNING: skip test ' + test) #TODO
+  if test=='015603':log('WARNING: skip test ' + test) #TODO
+  if test=='015604':log('WARNING: skip test ' + test) #TODO
+  if test=='015605':log('WARNING: skip test ' + test) #TODO
+
+  if test=='015700':log('WARNING: skip test ' + test) #TODO
+  if test=='015701':log('WARNING: skip test ' + test) #TODO
+  if test=='015702':log('WARNING: skip test ' + test) #TODO
+  if test=='015703':log('WARNING: skip test ' + test) #TODO
+  if test=='015704':log('WARNING: skip test ' + test) #TODO
+  if test=='015705':log('WARNING: skip test ' + test) #TODO
+
+  if test=='015800':log('WARNING: skip test ' + test) #TODO
+  if test=='015801':log('WARNING: skip test ' + test) #TODO
+  if test=='015802':log('WARNING: skip test ' + test) #TODO
+  if test=='015803':log('WARNING: skip test ' + test) #TODO
+  if test=='015804':log('WARNING: skip test ' + test) #TODO
+  if test=='015805':log('WARNING: skip test ' + test) #TODO
+  if test=='015806':log('WARNING: skip test ' + test) #TODO
+  if test=='015807':log('WARNING: skip test ' + test) #TODO
+  if test=='015808':log('WARNING: skip test ' + test) #TODO
+  if test=='015809':log('WARNING: skip test ' + test) #TODO
+  if test=='015810':log('WARNING: skip test ' + test) #TODO
+  if test=='015811':log('WARNING: skip test ' + test) #TODO
+  if test=='015812':log('WARNING: skip test ' + test) #TODO
+  if test=='015813':log('WARNING: skip test ' + test) #TODO
+  if test=='015814':log('WARNING: skip test ' + test) #TODO
+  if test=='015815':log('WARNING: skip test ' + test) #TODO
+  if test=='015816':log('WARNING: skip test ' + test) #TODO
+  if test=='015817':log('WARNING: skip test ' + test) #TODO
+  if test=='015818':log('WARNING: skip test ' + test) #TODO
+  if test=='015819':log('WARNING: skip test ' + test) #TODO
+  if test=='015820':log('WARNING: skip test ' + test) #TODO
+  if test=='015821':log('WARNING: skip test ' + test) #TODO
+  if test=='015822':log('WARNING: skip test ' + test) #TODO
+  if test=='015823':log('WARNING: skip test ' + test) #TODO
+
+  if test=='016000':log('WARNING: skip test ' + test) #TODO
+  if test=='016001':log('WARNING: skip test ' + test) #TODO
+  if test=='016002':log('WARNING: skip test ' + test) #TODO
+  if test=='016003':log('WARNING: skip test ' + test) #TODO
+  if test=='016004':log('WARNING: skip test ' + test) #TODO
+  if test=='016005':log('WARNING: skip test ' + test) #TODO
+
+  if test=='016100':log('WARNING: skip test ' + test) #TODO
+  if test=='016101':log('WARNING: skip test ' + test) #TODO
+  if test=='016102':log('WARNING: skip test ' + test) #TODO
+  if test=='016103':log('WARNING: skip test ' + test) #TODO
+  if test=='016104':log('WARNING: skip test ' + test) #TODO
+  if test=='016105':log('WARNING: skip test ' + test) #TODO
+
+  if test=='016300':log('WARNING: skip test ' + test) #TODO
+  if test=='016301':log('WARNING: skip test ' + test) #TODO
+  if test=='016302':log('WARNING: skip test ' + test) #TODO
+  if test=='016303':log('WARNING: skip test ' + test) #TODO
+  if test=='016304':log('WARNING: skip test ' + test) #TODO
+  if test=='016305':log('WARNING: skip test ' + test) #TODO
+
+  if test=='016500':log('WARNING: skip test ' + test) #TODO
+  if test=='016501':log('WARNING: skip test ' + test) #TODO
+  if test=='016502':log('WARNING: skip test ' + test) #TODO
+  if test=='016503':log('WARNING: skip test ' + test) #TODO
+  if test=='016504':log('WARNING: skip test ' + test) #TODO
+  if test=='016505':log('WARNING: skip test ' + test) #TODO
+
+  if test=='017000':log('WARNING: skip test ' + test) #TODO
+  if test=='017001':log('WARNING: skip test ' + test) #TODO
+  if test=='017002':log('WARNING: skip test ' + test) #TODO
+  if test=='017003':log('WARNING: skip test ' + test) #TODO
+  if test=='017004':log('WARNING: skip test ' + test) #TODO
+  if test=='017005':log('WARNING: skip test ' + test) #TODO
+
+  if test=='017500':log('WARNING: skip test ' + test) #TODO
+  if test=='017501':log('WARNING: skip test ' + test) #TODO
+  if test=='017502':log('WARNING: skip test ' + test) #TODO
+  if test=='017503':log('WARNING: skip test ' + test) #TODO
+  if test=='017504':log('WARNING: skip test ' + test) #TODO
+  if test=='017505':log('WARNING: skip test ' + test) #TODO
+
+  if test=='018000':log('WARNING: skip test ' + test) #TODO
+  if test=='018001':log('WARNING: skip test ' + test) #TODO
+  if test=='018002':log('WARNING: skip test ' + test) #TODO
+  if test=='018003':log('WARNING: skip test ' + test) #TODO
+  if test=='018004':log('WARNING: skip test ' + test) #TODO
+  if test=='018005':log('WARNING: skip test ' + test) #TODO
+
+  if test=='018500':log('WARNING: skip test ' + test) #TODO
+  if test=='018501':log('WARNING: skip test ' + test) #TODO
+  if test=='018502':log('WARNING: skip test ' + test) #TODO
+  if test=='018503':log('WARNING: skip test ' + test) #TODO
+  if test=='018504':log('WARNING: skip test ' + test) #TODO
+  if test=='018505':log('WARNING: skip test ' + test) #TODO
+
+  if test=='025500':log('WARNING: skip test ' + test) #TODO
+  if test=='025501':log('WARNING: skip test ' + test) #TODO
+  if test=='025502':log('WARNING: skip test ' + test) #TODO
+  if test=='025503':log('WARNING: skip test ' + test) #TODO
+  if test=='025504':log('WARNING: skip test ' + test) #TODO
+  if test=='025505':log('WARNING: skip test ' + test) #TODO
+  if test=='025506':log('WARNING: skip test ' + test) #TODO
+  if test=='025507':log('WARNING: skip test ' + test) #TODO
+  if test=='025508':log('WARNING: skip test ' + test) #TODO
+  if test=='025509':log('WARNING: skip test ' + test) #TODO
+  if test=='025510':log('WARNING: skip test ' + test) #TODO
+  if test=='025511':log('WARNING: skip test ' + test) #TODO
+  if test=='025512':log('WARNING: skip test ' + test) #TODO
+  if test=='025513':log('WARNING: skip test ' + test) #TODO
+  if test=='025514':log('WARNING: skip test ' + test) #TODO
+  if test=='025515':log('WARNING: skip test ' + test) #TODO
+  if test=='025516':log('WARNING: skip test ' + test) #TODO
+  if test=='025517':log('WARNING: skip test ' + test) #TODO
+  if test=='025518':log('WARNING: skip test ' + test) #TODO
+  if test=='025519':log('WARNING: skip test ' + test) #TODO
+  if test=='025520':log('WARNING: skip test ' + test) #TODO
+  if test=='025521':log('WARNING: skip test ' + test) #TODO
+  if test=='025522':log('WARNING: skip test ' + test) #TODO
+  if test=='025523':log('WARNING: skip test ' + test) #TODO
+
+  if test=='025700':log('WARNING: skip test ' + test) #TODO
+  if test=='025701':log('WARNING: skip test ' + test) #TODO
+  if test=='025702':log('WARNING: skip test ' + test) #TODO
+  if test=='025703':log('WARNING: skip test ' + test) #TODO
+  if test=='025704':log('WARNING: skip test ' + test) #TODO
+  if test=='025705':log('WARNING: skip test ' + test) #TODO
+
+from alu_test import run_b210_alu
+
+#B210 ALU tests
+
+run_b210_alu(tests, openair_dir, oai_user, oai_password, env)
+
+#for test in todo_tests:
+#    action = test.findtext('class')
+#    if action != 'lte-softmodem':
+#        continue
+#    if not "start_ltebox" in test.findtext('EPC_main_exec'):
+#        continue
+#    id = test.get('id')
+#    log("INFO: Running ALU test " + id)
+#    logdir = openair_dir + "/cmake_targets/autotests/log/" + id
+#    quickshell("mkdir -p " + logdir)
+#    epc_machine = test.findtext('EPC')
+#    enb_machine = test.findtext('eNB')
+#    ue_machine = test.findtext('UE')
+#
+#    #event object used to wait for several tasks at once
+#    event = threading.Event()
+#
+#    #launch HSS, wait for prompt
+#    log("INFO: " + id + ": run HSS")
+#    task_hss = Task("actions/alu_hss.bash",
+#                    "ALU HSS",
+#                    epc_machine,
+#                    oai_user,
+#                    oai_password,
+#                    env,
+#                    logdir + "/alu_hss." + epc_machine, event=event)
+#    task_hss.waitlog('S6AS_SIM-> ')
+#
+#    #then launch EPC, wait for connection on HSS side
+#    log("INFO: " + id + ": run EPC")
+#    task = Task("actions/alu_epc.bash",
+#                "ALU EPC",
+#                epc_machine,
+#                oai_user,
+#                oai_password,
+#                env,
+#                logdir + "/alu_epc." + epc_machine)
+#    ret = task.wait()
+#    if ret != 0:
+#        log("ERROR: EPC start failure");
+#        os._exit(1)
+#    task_hss.waitlog('Connected\n')
+#
+#    #compile softmodem
+#    log("INFO: " + id + ": compile softmodem")
+#    envcomp = list(env)
+#    envcomp.append('BUILD_ARGUMENTS="' +
+#                   test.findtext('eNB_compile_prog_args') + '"')
+#    #we don't care about BUILD_OUTPUT but it's required (TODO: change that)
+#    envcomp.append('BUILD_OUTPUT=/')
+#    task = Task("actions/compilation.bash",
+#                "compile softmodem",
+#                enb_machine,
+#                oai_user,
+#                oai_password,
+#                envcomp,
+#                logdir + "/compile_softmodem." + enb_machine)
+#    ret = task.wait()
+#    if ret != 0:
+#        log("ERROR: softmodem compilation failure");
+#        os._exit(1)
+#
+##    #copy wanted configuration file
+##    quickshell("sshpass -p " + oai_password +
+##               " scp config/enb.band7.tm1.usrpb210.conf " +
+##                     oai_user + "@" + enb_machine + ":/tmp/enb.conf")
+#
+#    #run softmodem
+#    log("INFO: " + id + ": run softmodem")
+#    task_enb = Task("actions/run_enb.bash",
+#                    "run softmodem",
+#                    enb_machine,
+#                    oai_user,
+#                    oai_password,
+#                    env,
+#                    logdir + "/run_softmodem." + enb_machine, event=event)
+#    task_enb.waitlog('got sync')
+#
+#    #start UE
+#    log("INFO: " + id + ": start bandrich UE")
+#    task_ue = Task("actions/start_bandrich.bash",
+#                   "start bandrich UE",
+#                   ue_machine,
+#                   oai_user,
+#                   oai_password,
+#                   env,
+#                   logdir + "/start_bandrich." + ue_machine, event=event)
+#    task_ue.waitlog("local  IP address", event=event)
+#
+#    event.wait()
+#
+#    #at this point one task has died or we have the line in the log
+#    if task_ue.waitlog_state != WAITLOG_SUCCESS:
+#        log("ERROR: " + id + ": bandrich UE did not connect")
+#        os._exit(1)
+#
+#    event.clear()
+#
+#    if (    not task_enb.alive() or
+#            not task_hss.alive() or
+#            not task_ue.alive()):
+#        log("ERROR: " + id + ": eNB or UE tasks died")
+#        os._exit(1)
+#
+#    #get bandrich UE IP
+#    l = open(task_ue.logfile, "r").read()
+#    ue_ip = re.search("local  IP address (.*)\n", l).groups()[0]
+#    log("INFO: " + id + ": bandrich UE IP address: " + ue_ip)
+#
+#    #run traffic
+#    log("INFO: " + id + ": run downlink TCP traffic")
+#
+#    log("INFO: " + id + ":     launch server")
+#    task_traffic_ue = Task("actions/downlink_bandrich.bash",
+#                           "start iperf on bandrich UE as server",
+#                           ue_machine,
+#                           oai_user,
+#                           oai_password,
+#                           env,
+#                           logdir + "/downlink_bandrich." + ue_machine,
+#                           event=event)
+#    task_traffic_ue.waitlog("Server listening on TCP port 5001")
+#
+#    log("INFO: " + id + ":     launch client")
+#    envepc = list(env)
+#    envepc.append("UE_IP=" + ue_ip)
+#    task = Task("actions/downlink_epc.bash",
+#                "start iperf on EPC as client",
+#                epc_machine,
+#                oai_user,
+#                oai_password,
+#                envepc,
+#                logdir + "/downlink_epc." + epc_machine, event=event)
+#    log("INFO: " + id + ":     wait for client (or some error)")
+#
+#    event.wait()
+#    log("DEBUG: event.wait() done")
+#
+#    if (    not task_enb.alive() or
+#            not task_hss.alive() or
+#            not task_ue.alive()):
+#        log("ERROR: unexpected task exited, test failed, kill all")
+#        if task.alive():
+#            task.kill()
+#        if task_enb.alive():
+#            task_enb.kill()
+#        if task_ue.alive():
+#            task_ue.kill()
+#
+#    ret = task.wait()
+#    if ret != 0:
+#        log("ERROR: " + id + ": downlink traffic failed")
+#        #not sure if we have to quit here or not
+#        #os._exit(1)
+#
+#    #stop downlink server
+#    log("INFO: " + id + ":     stop server (kill ssh connection)")
+#    task_traffic_ue.kill()
+#    log("INFO: " + id + ":     wait for server to quit")
+#    task_traffic_ue.wait()
+#
+#    #stop UE
+#    log("INFO: " + id + ": stop bandrich UE")
+#    task_ue.sendnow("%c" % 3)
+#    ret = task_ue.wait()
+#    if ret != 0:
+#        log("ERROR: " + id + ": task bandrich UE failed")
+#        #not sure if we have to quit here or not
+#        #os._exit(1)
+#
+#    #stop softmodem
+#    log("INFO: " + id + ": stop softmodem")
+#    task_enb.sendnow("%c" % 3)
+#    ret = task_enb.wait()
+#    if ret != 0:
+#        log("ERROR: " + id + ": softmodem failed")
+#        #not sure if we have to quit here or not
+#        #os._exit(1)
+#
+#    #stop EPC, wait for disconnection on HSS side
+#    log("INFO: " + id + ": stop EPC")
+#    task = Task("actions/alu_epc_stop.bash",
+#                "ALU EPC stop",
+#                epc_machine,
+#                oai_user,
+#                oai_password,
+#                env,
+#                logdir + "/alu_epc_stop." + epc_machine)
+#    ret = task.wait()
+#    if ret != 0:
+#        log("ERROR: " + id + ": ALU EPC stop failed")
+#        os._exit(1)
+#    task_hss.waitlog('Disconnected\n')
+#
+#    log("INFO: " + id + ": stop HSS")
+#    task_hss.sendnow("exit\n")
+#    ret = task_hss.wait()
+#    if ret != 0:
+#        log("ERROR: " + id + ": ALU HSS failed")
+#        os._exit(1)
+
+import utils
+log(utils.GREEN + "GOODBYE" + utils.RESET)
+#os._exit(0)
+
+#run lte softmodem tests
diff --git a/cmake_targets/autotests/v2/task.py b/cmake_targets/autotests/v2/task.py
new file mode 100644
index 0000000000..68293e33e4
--- /dev/null
+++ b/cmake_targets/autotests/v2/task.py
@@ -0,0 +1,370 @@
+import os, time, threading, sys, os, errno, select
+from connection import connection
+
+import utils
+from utils import log
+
+#this class is to communicate between reader and sender threads
+#the reader increments a count each time it receives the prompt
+#and wakes up the sender
+#it also indicates when it exits so the sender can exit too
+#the sender (calling get) waits for the count to be greated than passed
+#argument or if reader exited
+#it returns argument+1 if things okay, -1 if reader exited
+class Counter:
+    def __init__(self):
+        self.count = 0
+        self.cond = threading.Condition()
+
+    def inc(self):
+        self.cond.acquire()
+        self.count = self.count + 1
+        self.cond.notify()
+        self.cond.release()
+
+    def set(self, value):
+        self.cond.acquire()
+        self.count = value
+        self.cond.notify()
+        self.cond.release()
+
+    def get(self, current):
+        self.cond.acquire()
+        while True:
+            if self.count == -1:
+                ret = -1
+                break
+            if self.count >= current + 1:
+                ret = current + 1
+                break
+            self.cond.wait()
+        self.cond.release()
+        return ret
+
+#this class is used for the main application to wait for some specific
+#output from the remote program (typically the ALU HSS prompt, indicating
+#it is ready and we can proceed further)
+#(too much classes...)
+class ProducerConsumer:
+    def __init__(self):
+        self.count = 0
+        self.cond = threading.Condition()
+
+    def add(self, v):
+        self.cond.acquire()
+        self.count = self.count + v
+        self.cond.notify()
+        self.cond.release()
+
+    def set(self, v):
+        self.cond.acquire()
+        self.count = v
+        self.cond.notify()
+        self.cond.release()
+
+    def get(self, old):
+        self.cond.acquire()
+        while True:
+            if self.count == -1:
+                ret = -1
+                break
+            if self.count > old:
+                ret = self.count
+                break
+            self.cond.wait()
+        self.cond.release()
+        return ret
+
+#this thread gets input from the child process of the task
+#it removes the prompts it gets (be carefully to use a prompt
+#that will not be output of any command otherwise you dead)
+class ReaderThread(threading.Thread):
+    def __init__(self, fdin, logfile, prompt, prompt_counter, producer):
+        threading.Thread.__init__(self)
+        self.fdin = fdin
+        self.logfile = logfile
+        self.prompt_counter = prompt_counter
+        self.prompt = prompt
+        self.promptsize = len(prompt)
+        self.stack = ""
+        self.stacksize = 0
+        self.producer = producer
+
+    def run(self):
+        try:
+            outfile = open(self.logfile, "w")
+        except BaseException, e:
+            log("ERROR: ReaderThread: " + self.logfile + ": " + str(e))
+            os._exit(1)
+        while True:
+            try:
+               (a, b, c) = select.select([ self.fdin ], [], [ self.fdin ])
+            except BaseException, e:
+               log("ERROR: ReaderThread: " + self.logfile +
+                   ": select failed: " + str(e))
+               os._exit(1)
+            try:
+                z = os.read(self.fdin, 1024)
+            except OSError, e:
+                if e.errno == errno.EIO:
+                  #pipe has died, quit the thread
+                  break
+                else:
+                  log("ERROR: ReaderThread: " + self.logfile +
+                      ": unhandled error: " + str(e))
+            except BaseException, e:
+                log("ERROR: ReaderThread: " + self.logfile +
+                    ": unhandled error: " + str(e))
+                break
+            try:
+                produced = 0
+                #this part is to remove the prompt
+                for x in z:
+                    if x == self.prompt[self.stacksize]:
+                        self.stack = self.stack + x
+                        self.stacksize = self.stacksize + 1
+                        if self.stacksize == self.promptsize:
+                            self.prompt_counter.inc()
+                            self.stack = ""
+                            self.stacksize = 0
+                    else:
+                        outfile.write(self.stack)
+                        outfile.write(x)
+                        produced = produced + len(self.stack) + len(x)
+                        self.stack = ""
+                        self.stacksize = 0
+                outfile.flush()
+                self.producer.add(produced)
+            except BaseException, e:
+                log("ERROR: ReaderThread: " + self.logfile + ": " + str(e))
+                os._exit(1)
+        try:
+            outfile.close()
+        except BaseException, e:
+            log("ERROR: ReaderThread: " + self.logfile + ": " + str(e))
+            os._exit(1)
+        #close the pipe, don't care about errors
+        try:
+            os.close(self.fdin)
+        except:
+            pass
+        #signal sender to quit
+        self.prompt_counter.set(-1)
+        self.producer.set(-1)
+
+class SenderQuit(Exception):
+    pass
+
+#this thread sends commands to the child process of the task
+#it waits for the prompt between each command
+#'event' is used for the main thread to wait for one of several tasks
+#to quit, meaning error or end-of-test
+class SenderThread(threading.Thread):
+    def __init__(self, fdout, prompt_counter, connection, env, action,
+                 description, prompt, event=None):
+        threading.Thread.__init__(self)
+        self.fdin = fdout
+        self.prompt_counter = prompt_counter
+        self.connection = connection
+        self.env = env
+        self.action = action
+        self.description = description
+        self.prompt = prompt
+        self.count = 0
+        self.event = event
+        self.alive = True
+
+    def wait_prompt(self):
+        self.count = self.prompt_counter.get(self.count)
+        if self.count == -1:
+            raise SenderQuit()
+
+    def _run(self):
+        self.connection.send('export PS1=' + self.prompt + '\n')
+        self.wait_prompt()
+        self.connection.send('set +o emacs\n')
+        self.wait_prompt()
+        self.connection.send('echo\n')
+        self.wait_prompt()
+        self.connection.send('echo\n')
+        self.wait_prompt()
+        self.connection.send("echo -e '" + utils.GREEN +
+                             '---------------------------------------------'
+                             + utils.RESET + "'\n")
+        self.wait_prompt()
+        self.connection.send('echo\n')
+        self.wait_prompt()
+        self.connection.send("echo -n -e '" + utils.YELLOW +
+                             "COMMANDS START: " +
+                             utils.RESET + "'\n")
+        self.wait_prompt()
+        self.connection.send('date\n')
+        self.wait_prompt()
+
+        for l in self.env:
+            self.connection.send('export ' + l + '\n')
+            self.wait_prompt()
+
+        with open(self.action) as f:
+            for line in f:
+                self.connection.send("echo -n -e '" + utils.GREEN +
+                                     "RUNNING: " + utils.RESET + "'\n")
+                self.wait_prompt()
+                self.connection.send("echo '" +
+                                     line.replace('\n','')
+                                         .replace("'", "'\\''") + "'\n")
+                self.wait_prompt()
+                self.connection.send(line)
+                self.wait_prompt()
+                self.connection.send("if [ $? != 0 ]; then " +
+                                     "echo -e '" + utils.RED +
+                                     "TEST_SETUP_ERROR: " + utils.RESET +
+                                     "last command failed, exiting'; " +
+                                     "date; exit 1; fi\n")
+                self.wait_prompt()
+
+        self.connection.send("echo -n -e '" + utils.YELLOW +
+                             "COMMANDS DONE: " +
+                             utils.RESET + "'\n")
+        self.wait_prompt()
+        self.connection.send('date\n')
+        self.wait_prompt()
+        self.connection.send("echo -e '" + utils.GREEN +
+                             "TEST_SETUP_SUCCESS" + utils.RESET + "'\n")
+        self.wait_prompt()
+        self.connection.send('exit\n')
+
+    def run(self):
+        try:
+            self._run()
+        except SenderQuit:
+            log("WARNING: '" + self.description + "' exits too early?")
+            pass
+        except BaseException, e:
+            log("ERROR: task failed:    " + str(e))
+            log("ERROR: action is:      " + self.action)
+            log("ERROR: description is: " + self.description)
+            os._exit(1)
+
+        self.alive = False
+
+        if self.event != None:
+            self.event.set()
+
+WAITLOG_RUNNING = 0
+WAITLOG_SUCCESS = 1
+WAITLOG_FAILURE = 2
+
+class WaitlogFailed(Exception):
+    pass
+
+#'event' is used by main thread to wait for any of several threads to quit.
+#'Task' passes it the 'SenderThread' above, which sets it when it finishes.
+class Task:
+    def __init__(self, action, description, machine, user, password, env,
+                 logfile, post_action = None, event=None):
+        self.action      = action
+        self.description = description
+        self.machine     = machine
+        self.user        = user
+        self.password    = password
+        self.post_action = post_action
+        self.producer    = ProducerConsumer()
+        self.logfile     = logfile
+
+        prompt = "__OAI_TEST_SETUP_PROMPT__:"
+        prompt_counter = Counter()
+
+        self.connection = connection(description, machine, user, password)
+
+        self.reader = ReaderThread(self.connection.fd, logfile, prompt,
+                                   prompt_counter, self.producer)
+        self.reader.start()
+
+        self.sender = SenderThread(self.connection.fd, prompt_counter,
+                                   self.connection, env, action, description,
+                                   prompt, event)
+        self.sender.start()
+
+    def wait(self, timeout=-1):
+        if self.connection.active == False:
+            return self.connection.retcode
+        try:
+            (pid, ret) = os.waitpid(self.connection.pid, 0)
+        except KeyboardInterrupt, e:
+            log("ERROR: ctrl+c catched! " + str(e))
+            os._exit(1)
+        except BaseException, e:
+            log("ERROR: " + str(e))
+            os._exit(1)
+        self.sender.join()
+        self.reader.join()
+        return ret
+
+    #this function should not be called, it is used internally by 'waitlog'
+    #in mode 'background thread'
+    #TODO: join() the thread at some point
+    def _waitlog_thread(self, task, s):
+        consumed = 0
+        while True:
+            consumed = task.producer.get(consumed)
+            if consumed == -1:
+                log("ERROR: string '" + s + "' not found in logfile " +
+                    task.logfile)
+                task.waitlog_state = WAITLOG_FAILURE
+                task.waitlog_event.set()
+                return
+            if s in open(task.logfile).read():
+                task.waitlog_state = WAITLOG_SUCCESS
+                task.waitlog_event.set()
+                return
+
+    #two ways to wait for a string in the log file:
+    #  - blocking way
+    #  - background thread, using an Event to signal success/failure
+    def waitlog(self, s, event=None):
+        if event != None:
+            self.waitlog_state = WAITLOG_RUNNING
+            self.waitlog_event = event
+            self.waitlog_thread = \
+                threading.Thread(target=self._waitlog_thread,
+                                 args=(self, s))
+            self.waitlog_thread.start()
+            return
+
+        #TODO: optimize, do not process all the file at each wakeup
+        consumed = 0
+        while True:
+            consumed = self.producer.get(consumed)
+            if consumed == -1:
+                log("ERROR: string '" + s + "' not found in logfile " +
+                    self.logfile)
+                raise WaitlogFailed()
+            if s in open(self.logfile).read():
+                return
+
+    def sendnow(self, x):
+        self.connection.send(x)
+
+    def alive(self):
+        return self.sender.alive
+
+    def kill(self):
+        self.connection.kill()
+        #put some error log in logfile, for verbosity
+        try:
+            f = open(self.logfile, "a+")
+            f.write("\n\n" + utils.RED + "TEST_SETUP_ERROR: " + utils.RESET +
+                    "task killed by test setup\n\n");
+            close(f)
+        except BaseException, e:
+            pass
+
+    def postaction(self):
+        if self.post_action != None:
+            out = utils.quickshell(self.post_action)
+            if len(out):
+                log("INFO: task '" + self.description +
+                    "' post_action '" + self.post_action + "' says: ")
+                for l in out.splitlines():
+                    log("INFO:     " + l)
diff --git a/cmake_targets/autotests/v2/utils.py b/cmake_targets/autotests/v2/utils.py
new file mode 100644
index 0000000000..e54bd45805
--- /dev/null
+++ b/cmake_targets/autotests/v2/utils.py
@@ -0,0 +1,84 @@
+import subprocess, os, thread, sys, time
+
+#logging facitiliy
+#TODO: simplify (a first version redefined stdout, then we replaced 'print'
+#instead, keeping stuff as is)
+class Logger:
+   def __init__(self, stream):
+       self.stream = stream
+       self.start_of_line = True
+       self.lock = thread.allocate_lock()
+       openair_dir = os.environ.get('OPENAIR_DIR')
+       if openair_dir == None:
+           print "FATAL: no OPENAIR_DIR"
+           os._exit(1)
+       try:
+           self.logfile = open(openair_dir +
+                            "/cmake_targets/autotests/log/python.stdout", "w")
+       except BaseException, e:
+           print "FATAL:  cannot create log file"
+           print e
+           os._exit(1)
+   def put(self, data):
+       self.stream.write(data)
+       self.logfile.write(data)
+   def write(self, data):
+       self.lock.acquire()
+       for c in data:
+           if self.start_of_line:
+               self.start_of_line = False
+               t = time.strftime("%H:%M:%S", time.localtime()) + ": "
+               self.stream.write(t)
+               self.logfile.write(t)
+           self.put(c)
+           if c == '\n':
+               self.start_of_line = True
+       self.stream.flush()
+       self.logfile.flush()
+       self.lock.release()
+logger = Logger(sys.stdout)
+def log(x):
+    logger.write(x + "\n")
+
+#check if given test is in list
+#it is in list if one of the strings in 'list' is at the beginning of 'test'
+def test_in_list(test, list):
+    for check in list:
+        check=check.replace('+','')
+        if (test.startswith(check)):
+            return True
+    return False
+
+#run a local command in a shell
+def quickshell(command):
+    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
+                               stderr=subprocess.STDOUT)
+    (retout, reterr) = process.communicate()
+    if (process.returncode != 0):
+        log("ERROR: shell command failed: " + command)
+        if len(retout):
+            log("ERROR: command says: ")
+            for l in retout.splitlines():
+                log("ERROR:     " + l)
+        os._exit(1)
+    return retout
+
+RED="\x1b[31m"
+GREEN="\x1b[32m"
+YELLOW="\x1b[33m"
+RESET="\x1b[m"
+
+#an exception raised when a test fails
+class TestFailed(Exception):
+    pass
+
+#this function returns True if a test in 'x' is set to True
+#to be used as: if do_tests(tests['b210']['alu']) ...
+def do_tests(x):
+    if type(x) == list:
+        for v in x:
+            if do_tests(v): return True
+        return False
+    if type(x) == dict: return do_tests(x.values())
+    if x == True: return True
+    return False
-- 
GitLab