Add new suite for adding cars during shard leader change. 71/51471/29
authorPeter Gubka <pgubka@cisco.com>
Mon, 6 Feb 2017 15:12:49 +0000 (16:12 +0100)
committerVratko Polák <vrpolak@cisco.com>
Thu, 2 Mar 2017 15:16:00 +0000 (15:16 +0000)
Change-Id: I4840c2bc0a77163bc9c67804db7a907effe0424f
Signed-off-by: Peter Gubka <pgubka@cisco.com>
csit/libraries/CarPeople.robot
csit/suites/controller/ThreeNodes_Datastore/puts_during_isolation.robot [new file with mode: 0644]
csit/testplans/controller-rest-clust-cars-perf-beryllium.txt [new file with mode: 0644]
csit/testplans/controller-rest-clust-cars-perf-boron.txt [new file with mode: 0644]
csit/testplans/controller-rest-clust-cars-perf.txt
tools/odl-mdsal-clustering-tests/scripts/cluster_rest_script.py

index 6c692322a8a0713be47c5b7f5acba31844521875..767e25b9debf6b2ccf1abaf0a81376dc41ab6a2b 100644 (file)
@@ -69,7 +69,11 @@ Set_Tmp_Variables_For_Shard_For_Nodes
     ...    ${new_leader_session} - http session for the leader node
     ...    ${new_follower_sessions} - list of http sessions for the follower nodes
     ...    ${new_first_follower_session} - http session for the first follower node
+    ...    ${new_leader_index} - index of the shard leader
+    ...    ${new_followers_list} - list of followers indexes
     ${leader}    ${follower_list} =    ClusterManagement.Get_Leader_And_Followers_For_Shard    shard_name=${shard_name}    shard_type=${shard_type}    member_index_list=${member_index_list}
+    BuiltIn.Set_Suite_Variable    \${new_leader_index}    ${leader}
+    BuiltIn.Set_Suite_Variable    \${new_followers_list}    ${follower_list}
     ${leader_session} =    ClusterManagement.Resolve_Http_Session_For_Member    member_index=${leader}
     BuiltIn.Set_Suite_Variable    \${new_leader_session}    ${leader_session}
     ${sessions} =    BuiltIn.Create_List
diff --git a/csit/suites/controller/ThreeNodes_Datastore/puts_during_isolation.robot b/csit/suites/controller/ThreeNodes_Datastore/puts_during_isolation.robot
new file mode 100644 (file)
index 0000000..311faf1
--- /dev/null
@@ -0,0 +1,142 @@
+*** Settings ***
+Documentation     Test when a car shard leader is isolated while configuring cars.
+...
+...               Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved.
+...
+...               This program and the accompanying materials are made available under the
+...               terms of the Eclipse Public License v1.0 which accompanies this distribution,
+...               and is available at http://www.eclipse.org/legal/epl-v10.html
+...
+...               This test suite requires odl-restconf and odl-clustering-test-app modules.
+...               The script cluster_rest_script.py is used for generating requests for
+...               PUTing car items while the car shard leader is isolated.
+Suite Setup       Start_Suite
+Suite Teardown    Stop_Suite
+Test Setup        SetupUtils.Setup_Test_With_Logging_And_Without_Fast_Failing
+Library           RequestsLibrary
+Library           SSHLibrary
+Resource          ${CURDIR}/../../../variables/Variables.robot
+Resource          ${CURDIR}/../../../libraries/Utils.robot
+Resource          ${CURDIR}/../../../libraries/SetupUtils.robot
+Resource          ${CURDIR}/../../../libraries/ClusterManagement.robot
+Resource          ${CURDIR}/../../../libraries/CarPeople.robot
+
+*** Variables ***
+${ITEM_COUNT}     ${10000}
+${THREADS}        10
+${ADDCMD}         python ${TOOL_NAME} --port ${RESTCONFPORT} add-with-retries --itemtype car --itemcount ${ITEM_COUNT} --threads ${THREADS}
+${CARURL}         /restconf/config/car:cars
+${SHARD_NAME}     car
+${SHARD_TYPE}     config
+${TEST_LOG_LEVEL}    info
+@{TEST_LOG_COMPONENTS}    org.opendaylight.controller
+${TOOL_OPTIONS}    ${EMPTY}
+${TOOL_NAME}      cluster_rest_script.py
+
+*** Test Cases ***
+Start_Adding_Cars_To_Follower
+    [Documentation]    Start the script to configure ${ITEM_COUNT} cars in the background.
+    ${idx} =    Collections.Get_From_List    ${car_follower_indices}    0
+    ${follower_ip} =    ClusterManagement.Resolve_IP_Address_For_Member    member_index=${idx}
+    Start Tool    ${ADDCMD}    --host ${follower_ip} ${TOOL_OPTIONS}
+    ${session} =    Resolve_Http_Session_For_Member    member_index=${car_leader_index}
+    BuiltIn.Wait_Until_Keyword_Succeeds    5x    2s    Ensure_Cars_Being_Configured    ${session}
+
+Isolate_Current_Car_Leader
+    [Documentation]    Isolating cluster node which is the car shard leader.
+    ClusterManagement.Isolate_Member_From_List_Or_All    ${car_leader_index}
+    BuiltIn.Set Suite variable    ${old_car_leader}    ${car_leader_index}
+    BuiltIn.Set Suite variable    ${old_car_followers}    ${car_follower_indices}
+
+Verify_New_Car_Leader_Elected
+    [Documentation]    Verify new owner of the car shard is elected.
+    [Tags]    critical
+    BuiltIn.Wait_Until_Keyword_Succeeds    5x    2s    ClusterManagement.Verify_Shard_Leader_Elected    ${SHARD_NAME}    ${SHARD_TYPE}    ${True}
+    ...    ${old_car_leader}    member_index_list=${old_car_followers}
+    CarPeople.Set_Tmp_Variables_For_Shard_For_Nodes    ${old_car_followers}    shard_name=${SHARD_NAME}    shard_type=${SHARD_TYPE}
+
+Verify_Cars_Configured
+    [Documentation]    Verify that all cars are configured.
+    [Tags]    critical
+    BuiltIn.Wait_Until_Keyword_Succeeds    120x    2s    SSHLibrary.Read_Until_Prompt
+    ${session} =    Resolve_Http_Session_For_Member    member_index=${new_leader_index}
+    Verify_Cars_Count    ${ITEM_COUNT}    ${session}
+
+Rejoin_Isolated_Member
+    [Documentation]    Rejoin isolated node
+    [Tags]    @{NO_TAGS}
+    ClusterManagement.Rejoin_Member_From_List_Or_All    ${old_car_leader}
+
+Delete Cars
+    [Documentation]    Remove cars from the datastore
+    ${session} =    Resolve_Http_Session_For_Member    member_index=${new_leader_index}
+    ${rsp}=    RequestsLibrary.Delete Request    ${session}    ${CARURL}
+    Should Be Equal As Numbers    200    ${rsp.status_code}
+    ${rsp}=    RequestsLibrary.Get Request    ${session}    ${CARURL}
+    Should Be Equal As Numbers    404    ${rsp.status_code}
+
+*** Keywords ***
+Start Suite
+    [Documentation]    Upload the script file and create a virtual env
+    SetupUtils.Setup_Utils_For_Setup_And_Teardown
+    SetupUtils.Setup_Logging_For_Debug_Purposes_On_List_Or_All    ${TEST_LOG_LEVEL}    ${TEST_LOG_COMPONENTS}
+    ${mininet_conn_id} =    SSHKeywords.Open_Connection_To_Tools_System
+    Builtin.Set Suite Variable    ${mininet_conn_id}
+    SSHLibrary.Put File    ${CURDIR}/../../../../tools/odl-mdsal-clustering-tests/scripts/${TOOL_NAME}    .
+    ${stdout}    ${stderr}    ${rc}=    SSHLibrary.Execute Command    ls    return_stdout=True    return_stderr=True
+    ...    return_rc=True
+    ${out_file} =    Utils.Get_Log_File_Name    ${TOOL_NAME}
+    BuiltIn.Set_Suite_Variable    ${out_file}
+    SSHKeywords.Virtual_Env_Create
+    SSHKeywords.Virtual_Env_Install_Package    requests
+    CarPeople.Set_Variables_For_Shard    ${SHARD_NAME}    shard_type=${SHARD_TYPE}
+
+Stop Suite
+    [Documentation]    Stop the tool, remove virtual env and close ssh connection towards tools vm.
+    Stop_Tool
+    SSHKeywords.Virtual_Env_Delete
+    Store_File_To_Workspace    ${out_file}    ${out_file}
+    SSHLibrary.Close All Connections
+
+Start_Tool
+    [Arguments]    ${command}    ${tool_opt}
+    [Documentation]    Start the tool
+    # TODO: https://trello.com/c/rXsMu7iz/444-create-keywords-for-the-tool-start-and-stop-in-remotebash-robot
+    BuiltIn.Log    ${command}
+    SSHKeywords.Virtual_Env_Activate_On_Current_Session    log_output=${True}
+    ${output}=    SSHLibrary.Write    ${command} ${tool_opt} 2>&1 | tee ${out_file}
+    BuiltIn.Log    ${output}
+
+Stop_Tool
+    [Documentation]    Stop the tool if still running.
+    # TODO: https://trello.com/c/rXsMu7iz/444-create-keywords-for-the-tool-start-and-stop-in-remotebash-robot
+    ${output}=    SSHLibrary.Read
+    BuiltIn.Log    ${output}
+    Utils.Write_Bare_Ctrl_C
+    ${output}=    SSHLibrary.Read_Until_Prompt
+    BuiltIn.Log    ${output}
+    SSHKeywords.Virtual_Env_Deactivate_On_Current_Session    log_output=${True}
+
+Verify_Cars_Count
+    [Arguments]    ${exp_count}    ${session}
+    [Documentation]    Count car items in config ds and compare with expected number.
+    ${count} =    Get_Cars_Count    ${session}
+    BuiltIn.Should_Be_Equal_As_Numbers    ${count}    ${exp_count}
+
+Get_Cars_Count
+    [Arguments]    ${session}
+    [Documentation]    Count car items in config ds.
+    ${resp}=    RequestsLibrary.Get_Request    ${session}    ${CARURL}
+    ${count} =    BuiltIn.Evaluate    len(${resp.json()}["cars"]["car-entry"])
+    BuiltIn.Return_From_Keyword    ${count}
+
+Ensure_Cars_Being_Configured
+    [Arguments]    ${session}
+    ${count1} =    Get_Cars_Count    ${session}
+    ${count2} =    Get_Cars_Count    ${session}
+    BuiltIn.Should_Not_Be_Equal_As_Integers    ${count1}    ${count2}
+
+Store_File_To_Workspace
+    [Arguments]    ${source_file_name}    ${target_file_name}
+    [Documentation]    Store the ${source_file_name} to the workspace as ${target_file_name}.
+    SSHLibrary.Get_File    ${source_file_name}    ${target_file_name}
diff --git a/csit/testplans/controller-rest-clust-cars-perf-beryllium.txt b/csit/testplans/controller-rest-clust-cars-perf-beryllium.txt
new file mode 100644 (file)
index 0000000..bfda0c5
--- /dev/null
@@ -0,0 +1,8 @@
+# Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v1.0 which accompanies this distribution,
+# and is available at http://www.eclipse.org/legal/epl-v10.html
+
+# Place the suites in run order:
+integration/test/csit/suites/controller/ThreeNodes_Datastore/010_crud_mdsal_perf.robot
diff --git a/csit/testplans/controller-rest-clust-cars-perf-boron.txt b/csit/testplans/controller-rest-clust-cars-perf-boron.txt
new file mode 100644 (file)
index 0000000..bfda0c5
--- /dev/null
@@ -0,0 +1,8 @@
+# Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v1.0 which accompanies this distribution,
+# and is available at http://www.eclipse.org/legal/epl-v10.html
+
+# Place the suites in run order:
+integration/test/csit/suites/controller/ThreeNodes_Datastore/010_crud_mdsal_perf.robot
index bfda0c59354055434b0e36ea333204eb044f3f08..a089fa35d26900f2f87333d4e131965ee3ca0d70 100644 (file)
@@ -6,3 +6,4 @@
 
 # Place the suites in run order:
 integration/test/csit/suites/controller/ThreeNodes_Datastore/010_crud_mdsal_perf.robot
+integration/test/csit/suites/controller/ThreeNodes_Datastore/puts_during_isolation.robot
index beefeed70dd6cc63349950186718a1484e2e8252..3fea8b9f55758a41845b690150ae63a656249968 100644 (file)
@@ -9,6 +9,7 @@ import json
 import copy
 import argparse
 import logging
+import time
 
 
 _template_add_car = {
@@ -169,7 +170,8 @@ def _prepare_add_car_people_rpc(odl_ip, port, item_list, auth):
 
 
 def _request_sender(thread_id, preparing_function, auth, in_queue=None,
-                    exit_event=None, odl_ip="127.0.0.1", port="8181", out_queue=None):
+                    exit_event=None, odl_ip="127.0.0.1", port="8181", out_queue=None,
+                    req_timeout=60, retry_timeout=15, retry_rcs=[]):
     """The funcion sends http requests.
 
     Runs in the working thread. It reads out flow details from the queue and
@@ -191,6 +193,12 @@ def _request_sender(thread_id, preparing_function, auth, in_queue=None,
 
         :param out_queue: queue where the results should be put
 
+        :param req_timeout: http request timeout
+
+        :param retry_timeout: timout to give up retry attempts to send http requests
+
+        :param retry_rcs: list of return codes when retry should be performed
+
     Returns:
         None (results is put into the output queue)
     """
@@ -207,18 +215,25 @@ def _request_sender(thread_id, preparing_function, auth, in_queue=None,
             continue
         req = preparing_function(odl_ip, port, item_list, auth)
         prep = req.prepare()
-        try:
-            rsp = ses.send(prep, timeout=600)
-        except requests.exceptions.Timeout:
-            counter[99] += 1
-            logger.error("No response from %s", odl_ip)
-            continue
-        logger.debug("%s %s", rsp.request, rsp.request.url)
-        logger.debug("Headers %s:", rsp.request.headers)
-        logger.debug("Body: %s", rsp.request.body)
-        logger.debug("Response: %s", rsp.text)
-        logger.debug("%s %s", rsp, rsp.reason)
-        counter[rsp.status_code] += 1
+        start_time = time_now = time.time()
+        while start_time + retry_timeout > time_now:
+            try:
+                rsp = ses.send(prep, timeout=req_timeout)
+            except requests.exceptions.Timeout:
+                counter[99] += 1
+                logger.error("No response from %s", odl_ip)
+                rc = 99
+            else:
+                logger.debug("%s %s", rsp.request, rsp.request.url)
+                logger.debug("Headers %s:", rsp.request.headers)
+                logger.debug("Body: %s", rsp.request.body)
+                logger.debug("Response: %s", rsp.text)
+                logger.debug("%s %s", rsp, rsp.reason)
+                counter[rsp.status_code] += 1
+                rc = rsp.status_code
+            if rc not in retry_rcs:
+                break
+            time_now = time.time()
     responses = {}
     for response_code, count in enumerate(counter):
         if count > 0:
@@ -229,7 +244,7 @@ def _request_sender(thread_id, preparing_function, auth, in_queue=None,
 
 def _task_executor(preparing_function, odl_ip="127.0.0.1", port="8181",
                    thread_count=1, item_count=1, items_per_request=1,
-                   auth=('admin', 'admin')):
+                   auth=('admin', 'admin'), req_timeout=600, retry_timeout=15, retry_rcs=[]):
     """The main function which drives sending of http requests.
 
     Creates 2 queues and requested number of "working threads".
@@ -253,6 +268,12 @@ def _task_executor(preparing_function, odl_ip="127.0.0.1", port="8181",
 
         :param auth: authentication credentials
 
+        :param req_timeout: http request timeout
+
+        :param retry_timeout: timout to give up retry attempts to send http requests
+
+        :param retry_rcs: list of return codes when retry should be performed
+
     Returns:
         :returns dict: dictionary of http response counts like
                        {"http_status_code1: "count1", etc.}
@@ -284,7 +305,8 @@ def _task_executor(preparing_function, odl_ip="127.0.0.1", port="8181",
                                args=(i, preparing_function, auth),
                                kwargs={"in_queue": send_queue, "exit_event": exit_event,
                                        "odl_ip": hosts[i % nrhosts], "port": port,
-                                       "out_queue": result_queue})
+                                       "out_queue": result_queue, "req_timeout": req_timeout,
+                                       "retry_timeout": retry_timeout, "retry_rcs": retry_rcs})
         threads.append(thr)
         thr.start()
 
@@ -542,6 +564,41 @@ def add_car(odl_ip, port, thread_count, item_count, auth, items_per_request):
         raise Exception("Not all cars were configured: " + repr(res))
 
 
+def add_car_with_retries(odl_ip, port, thread_count, item_count, auth, items_per_request):
+    """Configure car entries to the config datastore.
+
+    Args:
+        :param odl_ip: ip address of ODL
+
+        :param port: restconf port
+
+        :param thread_count: number of threads used to send http requests; default=1
+
+        :param item_count: number of items to be configured
+
+        :param auth: authentication credentials
+
+        :param items_per_request: items per request, not used here,
+                                  just to keep the same api
+
+    Returns:
+        None
+    """
+
+    logger.info("Add %s car(s) to %s:%s (%s per request)",
+                item_count, odl_ip, port, items_per_request)
+    retry_rcs = [401, 404, 503]
+    res = _task_executor(_prepare_add_car, odl_ip=odl_ip, port=port,
+                         thread_count=thread_count, item_count=item_count,
+                         items_per_request=items_per_request, auth=auth,
+                         req_timeout=15, retry_timeout=30, retry_rcs=retry_rcs)
+    acceptable_rcs = [204] + retry_rcs
+    for key in res.keys():
+        if key not in acceptable_rcs:
+            logger.error("Problems during cars' configuration appeared: " + repr(res))
+            raise Exception("Problems during cars' configuration appeared: " + repr(res))
+
+
 def add_people_rpc(odl_ip, port, thread_count, item_count, auth, items_per_request):
     """Configure people entries to the config datastore.
 
@@ -616,7 +673,7 @@ def add_car_people_rpc(odl_ip, port, thread_count, item_count, auth,
         raise Exception("Not all rpc calls passed: " + repr(res))
 
 
-_actions = ["add", "get", "delete", "add-rpc"]
+_actions = ["add", "get", "delete", "add-rpc", "add-with-retries"]
 _items = ["car", "people", "car-people"]
 
 _handler_matrix = {
@@ -624,6 +681,7 @@ _handler_matrix = {
     "get": {"car": get_car, "people": get_people, "car-people": get_car_people},
     "delete": {"car": delete_car, "people": delete_people, "car-people": delete_car_people},
     "add-rpc": {"car-people": add_car_people_rpc, "people": add_people_rpc},
+    "add-with-retries": {"car": add_car_with_retries},
 }