From: Peter Gubka Date: Sun, 30 Aug 2015 23:51:00 +0000 (+0200) Subject: crud performance test for car-people entries on 1 node cluster X-Git-Tag: release/lithium-sr3~37 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=integration%2Ftest.git;a=commitdiff_plain;h=a5780ad681b9c583de64331612c58307d8967258 crud performance test for car-people entries on 1 node cluster - measure time to configure certain amount of cars at once - measure time to purchse these cars one by one - robot test case duration is the measured time - config script uploaded to the mininet/tools vm - add debug capabilities Change-Id: I0d198fc03f2eb73026503ace1a27cf4354f7243c Signed-off-by: Peter Gubka Signed-off-by: Radovan Sajben --- diff --git a/csit/suites/controller/OneNode_Datastore/010_crud_mdsal_perf.robot b/csit/suites/controller/OneNode_Datastore/010_crud_mdsal_perf.robot new file mode 100644 index 0000000000..81c754486c --- /dev/null +++ b/csit/suites/controller/OneNode_Datastore/010_crud_mdsal_perf.robot @@ -0,0 +1,143 @@ +*** Settings *** +Documentation Test for measuring execution time of MD-SAL DataStore operations. +... +... 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 +... +... This test suite requires odl-restconf and odl-clustering-test-app modules. +... The script cluster_rest_script.py is used for generating requests for +... operations on people, car and car-people DataStore test models. +... (see the https://wiki.opendaylight.org/view/MD-SAL_Clustering_Test_Plan) +... +... Reported bugs: +... https://bugs.opendaylight.org/show_bug.cgi?id=4220 +Suite Setup Start Suite +Suite Teardown Stop Suite +Test Setup SetupUtils.Setup_Test_With_Logging_And_Without_Fast_Failing +Library RequestsLibrary +Library SSHLibrary +Library XML +Variables ../../../variables/Variables.py +Resource ../../../libraries/Utils.robot +Resource ../../../libraries/SetupUtils.robot + +*** Variables *** +${ITEM_COUNT} ${10000} +${ITEM_BATCH} ${10000} +${PROCEDURE_TIMEOUT} 5m +${addcarcmd} python cluster_rest_script.py --host ${CONTROLLER} --port ${RESTCONFPORT} add --itemtype car --itemcount ${ITEM_COUNT} --ipr ${ITEM_BATCH} +${addpeoplecmd} python cluster_rest_script.py --host ${CONTROLLER} --port ${RESTCONFPORT} add --itemtype people --itemcount ${ITEM_COUNT} --ipr ${ITEM_BATCH} +${purchasecmd} python cluster_rest_script.py --host ${CONTROLLER} --port ${RESTCONFPORT} add-rpc --itemtype car-people --itemcount ${ITEM_COUNT} --threads 5 +${carurl} /restconf/config/car:cars +${peopleurl} /restconf/config/people:people +${carpeopleurl} /restconf/config/car-people:car-people +${CONTROLLER_LOG_LEVEL} INFO +${TOOL_OPTIONS} ${EMPTY} + +*** Test Cases *** +Add Cars + [Documentation] Request to add ${ITEM_COUNT} cars (timeout in ${PROCEDURE_TIMEOUT}). + Start Tool ${addcarcmd} ${TOOL_OPTIONS} + Wait Until Tool Finish ${PROCEDURE_TIMEOUT} + +Verify Cars + [Documentation] Store logs and verify result + Stop Tool + Store File To Workspace cluster_rest_script.log cluster_rest_script_add_cars.log + ${rsp}= RequestsLibrary.Get Request session ${carurl} headers=${ACCEPT_XML} + ${count}= XML.Get Element Count ${rsp.content} xpath=car-entry + Should Be Equal As Numbers ${count} ${ITEM_COUNT} + +Add People + [Documentation] Request to add ${ITEM_COUNT} people (timeout in ${PROCEDURE_TIMEOUT}). + Start Tool ${addpeoplecmd} ${TOOL_OPTIONS} + Wait Until Tool Finish ${PROCEDURE_TIMEOUT} + +Verify People + [Documentation] Store logs and verify result + Stop Tool + Store File To Workspace cluster_rest_script.log cluster_rest_script_add_people.log + ${rsp}= RequestsLibrary.Get Request session ${peopleurl} headers=${ACCEPT_XML} + ${count}= XML.Get Element Count ${rsp.content} xpath=person + Should Be Equal As Numbers ${count} ${ITEM_COUNT} + +Purchase Cars + [Documentation] Request to purchase ${ITEM_COUNT} cars (timeout in ${PROCEDURE_TIMEOUT}). + Start Tool ${purchasecmd} ${TOOL_OPTIONS} + Wait Until Tool Finish ${PROCEDURE_TIMEOUT} + +Verify Purchases + [Documentation] Store logs and verify result + Stop Tool + Store File To Workspace cluster_rest_script.log cluster_rest_script_purchase_cars.log + ${rsp}= RequestsLibrary.Get Request session ${carpeopleurl} headers=${ACCEPT_XML} + ${count}= XML.Get Element Count ${rsp.content} xpath=car-person + Should Be Equal As Numbers ${count} ${ITEM_COUNT} + [Teardown] Report_Failure_Due_To_Bug 4220 + +Delete Cars + [Documentation] Remove cars from the datastore + ${rsp}= RequestsLibrary.Delete 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} + +Delete People + [Documentation] Remove people from the datastore + ${rsp}= RequestsLibrary.Delete session ${peopleurl} + Should Be Equal As Numbers 200 ${rsp.status_code} + ${rsp}= RequestsLibrary.Get Request session ${peopleurl} + Should Be Equal As Numbers 404 ${rsp.status_code} + +Delete CarPeople + [Documentation] Remove car-people entries from the datastore + ${rsp}= RequestsLibrary.Delete session ${carpeopleurl} + Should Be Equal As Numbers 200 ${rsp.status_code} + ${rsp}= RequestsLibrary.Get Request session ${carpeopleurl} + Should Be Equal As Numbers 404 ${rsp.status_code} + +*** Keywords *** +Start Suite + [Documentation] Suite setup keyword + SetupUtils.Setup_Utils_For_Setup_And_Teardown + KarafKeywords.Execute_Controller_Karaf_Command_On_Background log:set ${CONTROLLER_LOG_LEVEL} + ${mininet_conn_id}= SSHLibrary.Open Connection ${MININET} prompt=${DEFAULT_LINUX_PROMPT} timeout=6s + Builtin.Set Suite Variable ${mininet_conn_id} + Utils.Flexible Mininet Login ${MININET_USER} + SSHLibrary.Put File ${CURDIR}/../../../../tools/odl-mdsal-clustering-tests/scripts/cluster_rest_script.py . + ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command ls return_stdout=True return_stderr=True + ... return_rc=True + RequestsLibrary.Create Session session http://${CONTROLLER}:${RESTCONFPORT} auth=${AUTH} + +Stop Suite + [Documentation] Suite teardown keyword + SSHLibrary.Close All Connections + RequestsLibrary.Delete All Sessions + +Start_Tool + [Arguments] ${command} ${tool_opt} + [Documentation] Start the tool ${command} ${tool_opt} + BuiltIn.Log ${command} + ${output}= SSHLibrary.Write ${command} ${tool_opt} + BuiltIn.Log ${output} + +Wait_Until_Tool_Finish + [Arguments] ${timeout} + [Documentation] Wait ${timeout} for the tool exit. + BuiltIn.Wait Until Keyword Succeeds ${timeout} 1s SSHLibrary.Read Until Prompt + +Stop_Tool + [Documentation] Stop the tool if still running. + Utils.Write_Bare_Ctrl_C + ${output}= SSHLibrary.Read delay=1s + BuiltIn.Log ${output} + +Store_File_To_Workspace + [Arguments] ${source_file_name} ${target_file_name} + [Documentation] Store the ${source_file_name} to the workspace as ${target_file_name}. + ${output_log}= SSHLibrary.Execute_Command cat ${source_file_name} + BuiltIn.Log ${output_log} + Create File ${target_file_name} ${output_log} diff --git a/csit/testplans/controller-rest-cars-perf.txt b/csit/testplans/controller-rest-cars-perf.txt new file mode 100644 index 0000000000..22bea721a3 --- /dev/null +++ b/csit/testplans/controller-rest-cars-perf.txt @@ -0,0 +1,9 @@ +# 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/netconf/ready/netconfready.robot +integration/test/csit/suites/controller/OneNode_Datastore/010_crud_mdsal_perf.robot diff --git a/tools/odl-mdsal-clustering-tests/scripts/cluster_rest_script.py b/tools/odl-mdsal-clustering-tests/scripts/cluster_rest_script.py new file mode 100644 index 0000000000..81c1ecf669 --- /dev/null +++ b/tools/odl-mdsal-clustering-tests/scripts/cluster_rest_script.py @@ -0,0 +1,681 @@ +""" +The purpose of this script is the ability to perform crud operations over +the car-people data model. +""" +import threading +import Queue +import requests +import json +import copy +import argparse +import logging + + +_template_add_car = { + "car-entry": [ + { + "id": "to be replaced", + "category": "my_category", + "model": "to be replaced", + "manufacturer": "my_manufacturer", + "year": "2015" + } + ] +} + +_template_add_people = { + "person": [ + { + "id": "to be replaced", + "gender": "male", + "age": "99", + "address": "to be replaced", + "contactNo": "to be replaced" + } + ] +} + +_template_add_cp_rpc = { + "input": { + "car-purchase:person": "to be replaced", + "car-purchase:person-id": "to be replaced", + "car-purchase:car-id": "to be replaced" + } +} + + +def _build_url(odl_ip, port, uri): + """Compose URL from generic IP, port and URI fragment. + + Args: + :param odl_ip: controller's ip address or hostname + + :param port: controller's restconf port + + :param uri: URI without /restconf/ to complete URL + + Returns: + :returns url: full restconf url corresponding to params + """ + + url = "http://" + odl_ip + ":" + port + "/restconf/" + uri + return url + + +def _build_post(odl_ip, port, uri, python_data, auth): + """Create a POST http request with generic on URI and data. + + Args: + :param odl_ip: controller's ip address or hostname + + :param port: controller's restconf port + + :param uri: URI without /restconf/ to complete URL + + :param python_data: python object to serialize into textual data + + :param auth: authentication credentials + + Returns: + :returns http request object + """ + + url = _build_url(odl_ip, port, uri) + text_data = json.dumps(python_data) + header = {"Content-Type": "application/json"} + req = requests.Request("POST", url, headers=header, data=text_data, auth=auth) + return req + + +def _prepare_add_car(odl_ip, port, item_list, auth): + """Creates a POST http requests to configure a car item in configuration datastore. + + Args: + :param odl_ip: controller's ip address or hostname + + :param port: controller's restconf port + + :param item_list: controller item's list contains a list of ids of the cars + + :param auth: authentication credentials + + Returns: + :returns req: http request object + """ + + container = {"car-entry": []} + for item in item_list: + entry = copy.deepcopy(_template_add_car["car-entry"][0]) + entry["id"] = item + entry["model"] = "model" + str(item) + container["car-entry"].append(entry) + req = _build_post(odl_ip, port, "config/car:cars", container, auth) + return req + + +def _prepare_add_people(odl_ip, port, item_list, auth): + """Creates a POST http requests to configure people in configuration datastore. + + Args: + :param odl_ip: controller's ip address or hostname + + :param port: controller's restconf port + + :param item_list: controller item's list contains a list of ids of the people + + :param auth: authentication credentials + + Returns: + :returns req: http request object + """ + + container = {"person": []} + for item in item_list: + entry = copy.deepcopy(_template_add_people["person"][0]) + entry["id"] = str(item) + entry["address"] = "address" + str(item) + entry["contactNo"] = str(item) + container["person"].append(entry) + req = _build_post(odl_ip, port, "config/people:people", container, auth) + return req + + +def _prepare_add_car_people_rpc(odl_ip, port, item_list, auth): + """Creates a POST http requests to purchase cars using an rpc. + + Args: + :param odl_ip: controller's ip address or hostname + + :param port: controller's restconf port + + :param item_list: controller item's list contains a list of ids of the people + only the first item is considered + + :param auth: authentication credentials + + Returns: + :returns req: http request object + """ + + container = {"input": {}} + item = item_list[0] + entry = container["input"] + entry["car-purchase:person"] = "/people:people/people:person[people:id='" + str(item) + "']" + entry["car-purchase:person-id"] = str(item) + entry["car-purchase:car-id"] = str(item) + container["input"] = entry + req = _build_post(odl_ip, port, "operations/car-purchase:buy-car", container, auth) + return req + + +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): + """The funcion sends http requests. + + Runs in the working thread. It reads out flow details from the queue and + sends apropriate http requests to the controller + + Args: + :param thread_id: thread id + + :param preparing_function: function to prepare the http request + + :param in_queue: input queue, flow details are comming from here + + :param exit_event: event to notify working thread that the parent + (task executor) stopped filling the input queue + + :param odl_ip: ip address of ODL; default="127.0.0.1" + + :param port: restconf port; default="8181" + + :param out_queue: queue where the results should be put + + Returns: + None (results is put into the output queue) + """ + + ses = requests.Session() + counter = [0 for i in range(600)] + + while True: + try: + item_list = in_queue.get(timeout=1) + except Queue.Empty: + if exit_event.is_set() and in_queue.empty(): + break + continue + req = preparing_function(odl_ip, port, item_list, auth) + prep = req.prepare() + try: + rsp = ses.send(prep, timeout=60) + 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 + responses = {} + for response_code, count in enumerate(counter): + if count > 0: + responses[response_code] = count + out_queue.put(responses) + logger.info("Response code(s) got per number of requests: %s", responses) + + +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')): + """The main function which drives sending of http requests. + + Creates 2 queues and requested number of "working threads". + One queue is filled with flow details and working + threads read them out and send http requests. + The other queue is for sending results from working threads back. + After the threads' join, it produces a summary result. + + Args: + :param preparing_function: function to prepare http request object + + :param odl_ip: ip address of ODL; default="127.0.0.1" + + :param port: restconf port; default="8181" + + :param thread_count: number of threads used to send http requests; default=1 + + :param items_per_request: items per request, number of items sent in one http request + + :param item_countpr: number of items to be sent in total + + :param auth: authentication credentials + + Returns: + :returns dict: dictionary of http response counts like + {"http_status_code1: "count1", etc.} + """ + + items = [i+1 for i in range(item_count)] + item_groups = [] + for i in range(0, item_count, items_per_request): + item_groups.append(items[i:i+items_per_request]) + + # fill the queue with details needed for one http requests + send_queue = Queue.Queue() + for item_list in item_groups: + send_queue.put(item_list) + + # create an empty result queue + result_queue = Queue.Queue() + # create exit event + exit_event = threading.Event() + + # start threads to read details from queues and to send http requests + threads = [] + for i in range(int(thread_count)): + thr = threading.Thread(target=_request_sender, + args=(i, preparing_function, auth), + kwargs={"in_queue": send_queue, "exit_event": exit_event, + "odl_ip": odl_ip, "port": port, + "out_queue": result_queue}) + threads.append(thr) + thr.start() + + exit_event.set() + + result = {} + # wait for reqults and sum them up + for t in threads: + t.join() + # read partial resutls from sender thread + part_result = result_queue.get() + for k, v in part_result.iteritems(): + if k not in result: + result[k] = v + else: + result[k] += v + return result + + +def _build_delete(odl_ip, port, uri): + """Send DELETE to generic URI, assert status code is 200. + + Args: + :param odl_ip: ip address of ODL + + :param port: restconf port + + :param uri: URI without /restconf/ to complete URL + + Returns: + None + + Note: + Raise AssertionError if response status code != 200 + """ + + url = _build_url(odl_ip, port, uri) + rsp = requests.delete(url, auth=auth) + 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.info("%s %s", rsp, rsp.reason) + assert rsp.status_code == 200, rsp.text + + +def delete_car(odl_ip, port, thread_count, item_count, auth, items_per_request): + """Delete cars container from config datastore, assert success. + + Args: + :param odl_ip: ip address of ODL + + :param port: restconf port + + :param thread_count: ignored; only 1 thread needed + + :param item_count: ignored; whole container is deleted + + :param auth: authentication credentials + + :param items_per_request: ignored; only 1 request needed + + Returns: + None + """ + + logger.info("Delete all cars from %s:%s", odl_ip, port) + _build_delete(odl_ip, port, "config/car:cars") + + +def delete_people(odl_ip, port, thread_count, item_count, auth, items_per_request): + """Delete people container from config datastore. + + Args: + :param odl_ip: ip address of ODL + + :param port: restconf port + + :param thread_count: ignored; only 1 thread needed + + :param item_count: ignored; whole container is deleted + + :param auth: authentication credentials + + :param items_per_request: ignored; only 1 request needed + + Returns: + None + """ + + logger.info("Delete all people from %s:%s", odl_ip, port) + _build_delete(odl_ip, port, "config/people:people") + + +def delete_car_people(odl_ip, port, thread_count, item_count, auth, items_per_request): + """Delete car-people container from config datastore. + + Args: + :param odl_ip: ip address of ODL + + :param port: restconf port + + :param thread_count: ignored; only 1 thread needed + + :param item_count: ignored; whole container is deleted + + :param auth: authentication credentials + + :param items_per_request: ignored; only 1 request needed + + Returns: + None + """ + + logger.info("Delete all purchases from %s:%s", odl_ip, port) + _build_delete(odl_ip, port, "config/car-people:car-people") + + +def _build_get(odl_ip, port, uri): + """Send GET to generic URI. + + Args: + :param odl_ip: ip address of ODL + + :param port: restconf port + + :param uri: URI without /restconf/ to complete URL + + Returns: + None + + Note: + Raise AssertionError if response status code != 200 + """ + + url = _build_url(odl_ip, port, uri) + rsp = requests.get(url, auth=auth) + 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.info("%s %s", rsp, rsp.reason) + assert rsp.status_code == 200, rsp.text + + +def get_car(odl_ip, port, thread_count, item_count, auth, items_per_request): + """Reads car entries from config datastore. + + TODO: some needed logic to be added handle http response in the future, + e.g. count items in response's content + + Args: + :param odl_ip: ip address of ODL + + :param port: restconf port + + :param thread_count: ignored; only 1 thread needed + + :param item_count: ignored; whole container is deleted + + :param auth: authentication credentials + + :param items_per_request: ignored; only 1 request needed + + Returns: + None + """ + + logger.info("Get all cars from %s:%s", odl_ip, port) + _build_get(odl_ip, port, "config/car:cars") + + +def get_people(odl_ip, port, thread_count, item_count, auth, items_per_request): + """Reads people entries from config datastore. + + TODO: some needed logic to be added handle http response in the future, + e.g. count items in response's content + + Args: + :param odl_ip: ip address of ODL + + :param port: restconf port + + :param thread_count: ignored; only 1 thread needed + + :param item_count: ignored; whole container is deleted + + :param auth: authentication credentials + + :param items_per_request: ignored; only 1 request needed + + Returns: + None + """ + + logger.info("Get all people from %s:%s", odl_ip, port) + _build_get(odl_ip, port, "config/people:people") + + +def get_car_people(odl_ip, port, thread_count, item_count, auth, items_per_request): + """Reads car-people entries from config datastore. + + TODO: some needed logic to be added handle http response in the future, + e.g. count items in response's content + + Args: + :param odl_ip: ip address of ODL + + :param port: restconf port + + :param thread_count: ignored; only 1 thread needed + + :param item_count: ignored; whole container is deleted + + :param auth: authentication credentials + + :param items_per_request: ignored; only 1 request needed + + Returns: + None + """ + + logger.info("Get all purchases from %s:%s", odl_ip, port) + _build_get(odl_ip, port, "config/car-people:car-people") + + +def add_car(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) + 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) + if res.keys() != [204]: + logger.error("Not all cars were configured: " + repr(res)) + raise Exception("Not all cars were configured: " + repr(res)) + + +def add_people(odl_ip, port, thread_count, item_count, auth, items_per_request): + """Configure people entries to the config datastore. + + Args: + :param odl_ip: ip address of ODL; default="127.0.0.1" + + :param port: restconf port; default="8181" + + :param thread_count: number of threads used to send http requests; default=1 + + :param item_count: number of items to be condigured + + :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 people to %s:%s (%s per request)", + item_count, odl_ip, port, items_per_request) + res = _task_executor(_prepare_add_people, odl_ip=odl_ip, port=port, + thread_count=thread_count, item_count=item_count, + items_per_request=items_per_request, auth=auth) + if res.keys() != [204]: + logger.error("Not all people were configured: " + repr(res)) + raise Exception("Not all people were configured: " + repr(res)) + + +def add_car_people_rpc(odl_ip, port, thread_count, item_count, auth, + items_per_request): + """Configure car-people entries to the config datastore one by one using rpc + + Args: + :param odl_ip: ip address of ODL; default="127.0.0.1" + + :param port: restconf port; default="8181" + + :param thread_count: number of threads used to send http requests; default=1 + + :param item_count: number of items to be condigured + + :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 purchase(s) to %s:%s (%s per request)", + item_count, odl_ip, port, items_per_request) + if items_per_request != 1: + logger.error("Only 1 item per request is supported, " + + "you specified: {0}".format(item_count)) + raise NotImplementedError("Only 1 item per request is supported, " + + "you specified: {0}".format(item_count)) + + res = _task_executor(_prepare_add_car_people_rpc, odl_ip=odl_ip, port=port, + thread_count=thread_count, item_count=item_count, + items_per_request=items_per_request, auth=auth) + if res.keys() != [204]: + logger.error("Not all rpc calls passed: " + repr(res)) + raise Exception("Not all rpc calls passed: " + repr(res)) + + +_actions = ["add", "get", "delete", "add-rpc"] +_items = ["car", "people", "car-people"] + +_handler_matrix = { + "add": {"car": add_car, "people": add_people}, + "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}, +} + + +if __name__ == "__main__": + """ + This program executes requested action based in given parameters + + It provides "car", "people" and "car-people" crud operations. + """ + + parser = argparse.ArgumentParser(description="Cluster datastore" + "performance test script") + parser.add_argument("--host", default="127.0.0.1", + help="Host where odl controller is running" + "(default is 127.0.0.1)") + parser.add_argument("--port", default="8181", + help="Port on which odl's RESTCONF is listening" + "(default is 8181)") + parser.add_argument("--threads", type=int, default=1, + help="Number of request worker threads to start in" + "each cycle (default=1)") + parser.add_argument("action", choices=_actions, metavar="action", + help="Action to be performed.") + parser.add_argument("--itemtype", choices=_items, default="car", + help="Flows-per-Request - number of flows (batch size)" + "sent in each HTTP request (default 1)") + parser.add_argument("--itemcount", type=int, help="Items per request", + default=1) + parser.add_argument("--user", help="Restconf user name", default="admin") + parser.add_argument("--password", help="Restconf password", default="admin") + parser.add_argument("--ipr", type=int, help="Items per request", default=1) + parser.add_argument("--debug", dest="loglevel", action="store_const", + const=logging.DEBUG, default=logging.INFO, + help="Set log level to debug (default is error)") + + args = parser.parse_args() + + logger = logging.getLogger("logger") + log_formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s') + console_handler = logging.StreamHandler() + file_handler = logging.FileHandler('cluster_rest_script.log', mode="w") + console_handler.setFormatter(log_formatter) + file_handler.setFormatter(log_formatter) + logger.addHandler(console_handler) + logger.addHandler(file_handler) + logger.setLevel(args.loglevel) + + auth = (args.user, args.password) + + if (args.action not in _handler_matrix or + args.itemtype not in _handler_matrix[args.action]): + logger.error("Unsupported combination of action: " + + str(args.action) + " and item: " + str(args.itemtype)) + raise NotImplementedError("Unsupported combination of action: " + + str(args.action) + + " and item: " + str(args.itemtype)) + + # TODO: need to filter out situations when we cannot use more items + # in one rest request (rpc or delete?) + # this should be done inside handler functions + + handler_function = _handler_matrix[args.action][args.itemtype] + handler_function(args.host, args.port, args.threads, + args.itemcount, auth, args.ipr)