From a5ff1317e4b3be39aaaec22716b438c6c12c5652 Mon Sep 17 00:00:00 2001 From: Gerard Damm Date: Tue, 27 Mar 2018 16:03:16 -0500 Subject: UC2 update with Openstack SDK interface and common test logic JIRA: AUTO-13 1) Openstack SDK usage for VM suspend/resume actions, using clouds.yaml, tested on hpe16 pod 2) common test logic (creation of test execution instances, measurement of recovery time, calling of per-use case indexed functions, saving results to CSV files) Change-Id: If84d2a0e44618d476a028d4ac4a2897da9cd5532 Signed-off-by: Gerard Damm --- lib/auto/testcase/resiliency/AutoResilItfCloud.py | 150 +++++++++++++++------ lib/auto/testcase/resiliency/AutoResilMain.py | 1 - lib/auto/testcase/resiliency/AutoResilMgTestDef.py | 120 +++++++++++++---- lib/auto/testcase/resiliency/clouds.yaml | 42 ++++-- 4 files changed, 237 insertions(+), 76 deletions(-) diff --git a/lib/auto/testcase/resiliency/AutoResilItfCloud.py b/lib/auto/testcase/resiliency/AutoResilItfCloud.py index 69c5327..302a662 100644 --- a/lib/auto/testcase/resiliency/AutoResilItfCloud.py +++ b/lib/auto/testcase/resiliency/AutoResilItfCloud.py @@ -33,14 +33,15 @@ ###################################################################### # import statements import AutoResilGlobal +import time # for method 1 and 2 -#import openstack +import openstack #for method 3 -from openstack import connection +#from openstack import connection -def os_list_servers(conn): +def openstack_list_servers(conn): """List OpenStack servers.""" # see https://docs.openstack.org/python-openstacksdk/latest/user/proxies/compute.html if conn != None: @@ -49,14 +50,20 @@ def os_list_servers(conn): try: i=1 for server in conn.compute.servers(): - print('Server',str(i),'\n',server,'n') + print('Server',str(i)) + print(' Name:',server.name) + print(' ID:',server.id) + print(' key:',server.key_name) + print(' status:',server.status) + print(' AZ:',server.availability_zone) + print('Details:\n',server) i+=1 except Exception as e: print("Exception:",type(e), e) print("No Servers\n") -def os_list_networks(conn): +def openstack_list_networks(conn): """List OpenStack networks.""" # see https://docs.openstack.org/python-openstacksdk/latest/user/proxies/network.html if conn != None: @@ -65,14 +72,14 @@ def os_list_networks(conn): try: i=1 for network in conn.network.networks(): - print('Network',str(i),'\n',network,'n') + print('Network',str(i),'\n',network,'\n') i+=1 except Exception as e: print("Exception:",type(e), e) print("No Networks\n") -def os_list_volumes(conn): +def openstack_list_volumes(conn): """List OpenStack volumes.""" # see https://docs.openstack.org/python-openstacksdk/latest/user/proxies/block_storage.html # note: The block_storage member will only be added if the service is detected. @@ -82,14 +89,20 @@ def os_list_volumes(conn): try: i=1 for volume in conn.block_storage.volumes(): - print('Volume',str(i),'\n',volume,'n') + print('Volume',str(i)) + print(' Name:',volume.name) + print(' ID:',volume.id) + print(' size:',volume.size) + print(' status:',volume.status) + print(' AZ:',volume.availability_zone) + print('Details:\n',volume) i+=1 except Exception as e: print("Exception:",type(e), e) print("No Volumes\n") - -def os_list_users(conn): + +def openstack_list_users(conn): """List OpenStack users.""" # see https://docs.openstack.org/python-openstacksdk/latest/user/guides/identity.html if conn != None: @@ -98,13 +111,13 @@ def os_list_users(conn): try: i=1 for user in conn.identity.users(): - print('User',str(i),'\n',user,'n') + print('User',str(i),'\n',user,'\n') i+=1 except Exception as e: print("Exception:",type(e), e) print("No Users\n") - -def os_list_projects(conn): + +def openstack_list_projects(conn): """List OpenStack projects.""" # see https://docs.openstack.org/python-openstacksdk/latest/user/guides/identity.html if conn != None: @@ -113,14 +126,14 @@ def os_list_projects(conn): try: i=1 for project in conn.identity.projects(): - print('Project',str(i),'\n',project,'n') + print('Project',str(i),'\n',project,'\n') i+=1 except Exception as e: print("Exception:",type(e), e) print("No Projects\n") - -def os_list_domains(conn): + +def openstack_list_domains(conn): """List OpenStack domains.""" # see https://docs.openstack.org/python-openstacksdk/latest/user/guides/identity.html if conn != None: @@ -129,7 +142,7 @@ def os_list_domains(conn): try: i=1 for domain in conn.identity.domains(): - print('Domain',str(i),'\n',domain,'n') + print('Domain',str(i),'\n',domain,'\n') i+=1 except Exception as e: print("Exception:",type(e), e) @@ -138,14 +151,17 @@ def os_list_domains(conn): - - + + def gdtest_openstack(): - # Method 1: assume there is a clouds.yaml file in PATH, starting path search with local directory + + # Method 1 (preferred) : assume there is a clouds.yaml file in PATH, starting path search with local directory #conn = openstack.connect(cloud='armopenstack', region_name='RegionOne') - #conn = openstack.connect(cloud='hpe16openstack', region_name='RegionOne') - # getting error: AttributeError: module 'openstack' has no attribute 'connect' + #conn = openstack.connect(cloud='hpe16openstackEuphrates', region_name='RegionOne') + conn = openstack.connect(cloud='hpe16openstackFraser', region_name='RegionOne') + # if getting error: AttributeError: module 'openstack' has no attribute 'connect', check that openstack is installed for this python version + # Method 2: pass arguments directly, all as strings # see details at https://docs.openstack.org/python-openstacksdk/latest/user/connection.html @@ -163,19 +179,20 @@ def gdtest_openstack(): # password='opnfv_secret', # region_name='RegionOne', # ) - # getting error: AttributeError: module 'openstack' has no attribute 'connect' + # if getting error: AttributeError: module 'openstack' has no attribute 'connect', check that openstack is installed for this python version + # Method 3: create Connection object directly - auth_args = { - #'auth_url': 'https://10.10.50.103:5000/v2.0', # Arm - #'auth_url': 'http://10.16.0.101:5000/v2.0', # hpe16, Euphrates - 'auth_url': 'http://10.16.0.107:5000/v3', # hpe16, Fraser - 'project_name': 'admin', - 'username': 'admin', - 'password': 'opnfv_secret', - 'region_name': 'RegionOne', - 'domain': 'Default'} - conn = connection.Connection(**auth_args) + # auth_args = { + # #'auth_url': 'https://10.10.50.103:5000/v2.0', # Arm + # #'auth_url': 'http://10.16.0.101:5000/v2.0', # hpe16, Euphrates + # 'auth_url': 'http://10.16.0.107:5000/v3', # hpe16, Fraser + # 'project_name': 'admin', + # 'username': 'admin', + # 'password': 'opnfv_secret', + # 'region_name': 'RegionOne', + # 'domain': 'Default'} + # conn = connection.Connection(**auth_args) #conn = connection.Connection( #auth_url='http://10.16.0.107:5000/v3', @@ -184,12 +201,65 @@ def gdtest_openstack(): #password='opnfv_secret') - os_list_servers(conn) - os_list_networks(conn) - os_list_volumes(conn) - os_list_users(conn) - os_list_projects(conn) - os_list_domains(conn) + openstack_list_servers(conn) + openstack_list_networks(conn) + openstack_list_volumes(conn) + openstack_list_users(conn) + openstack_list_projects(conn) + openstack_list_domains(conn) + + # VM: hpe16-Auto-UC2-gdtest-compute1 + gds_ID = '715c677a-7914-4ca8-8c6d-75bf29eeb940' + gds = conn.compute.get_server(gds_ID) + print('\ngds.name=',gds.name) + print('gds.status=',gds.status) + print('suspending...') + conn.compute.suspend_server(gds_ID) # NOT synchronous: returns before suspension action is completed + wait_seconds = 10 + print(' waiting',wait_seconds,'seconds...') + time.sleep(wait_seconds) + gds = conn.compute.get_server(gds_ID) # need to refresh data; not maintained live + print('gds.status=',gds.status) + print('resuming...') + conn.compute.resume_server(gds_ID) + print(' waiting',wait_seconds,'seconds...') + time.sleep(wait_seconds) + gds = conn.compute.get_server(gds_ID) # need to refresh data; not maintained live + print('gds.status=',gds.status) + + + + #VM: test3 + gds_ID = 'd3ceffc3-5967-4f18-b8b5-b1b2bd7ab76d' + gds = conn.compute.get_server(gds_ID) + print('\ngds.name=',gds.name) + print('gds.status=',gds.status) + print('suspending...') + conn.compute.suspend_server(gds_ID) # NOT synchronous: returns before suspension action is completed + wait_seconds = 10 + print(' waiting',wait_seconds,'seconds...') + time.sleep(wait_seconds) + gds = conn.compute.get_server(gds_ID) # need to refresh data; not maintained live + print('gds.status=',gds.status) + print('resuming...') + conn.compute.resume_server(gds_ID) + print(' waiting',wait_seconds,'seconds...') + time.sleep(wait_seconds) + gds = conn.compute.get_server(gds_ID) # need to refresh data; not maintained live + print('gds.status=',gds.status) + + #Volume: hpe16-Auto-UC2-gdtest-volume1 + gdv_ID = '5a6c1dbd-5097-4a9b-8f79-6f03cde18bf6' + gdv = conn.block_storage.get_volume(gdv_ID) + # no API for stopping/restarting a volume... only delete. ONAP would have to completely migrate a VNF depending on this volume + print('\ngdv.name=',gdv.name) + print('gdv.status=',gdv.status) + #gdv_recreate = gdv + #print('deleting...') + #conn.block_storage.delete_volume(gdv_ID) + #conn.block_storage.delete_volume(gdv) + #print('recreating...') + #gdv = conn.block_storage.create_volume() # get_server(server): Get a single Server @@ -211,7 +281,7 @@ def main(): gdtest_openstack() - print("Ciao\n") + print("\nCiao\n") if __name__ == "__main__": main() diff --git a/lib/auto/testcase/resiliency/AutoResilMain.py b/lib/auto/testcase/resiliency/AutoResilMain.py index 2f67bdf..1d21f6a 100644 --- a/lib/auto/testcase/resiliency/AutoResilMain.py +++ b/lib/auto/testcase/resiliency/AutoResilMain.py @@ -164,7 +164,6 @@ def main(): print("Problem with test definition: empty") sys.exit() # stop entire program, because test definition MUST be correct else: - # TODO run test: call selected test definition run_test_code() method test_def = get_indexed_item_from_list(selected_test_def_ID, AutoResilGlobal.test_definition_list) if test_def != None: test_def.run_test_code() diff --git a/lib/auto/testcase/resiliency/AutoResilMgTestDef.py b/lib/auto/testcase/resiliency/AutoResilMgTestDef.py index 9667f93..7e0b50d 100644 --- a/lib/auto/testcase/resiliency/AutoResilMgTestDef.py +++ b/lib/auto/testcase/resiliency/AutoResilMgTestDef.py @@ -320,10 +320,62 @@ class TestDefinition(AutoBaseObject): def run_test_code(self): - """Run currently selected test code.""" + """Run currently selected test code. Common code runs here, specific code is invoked through test_code_list and test_code_ID.""" try: + # here, trigger start code from challenge def (to simulate VM failure), manage Recovery time measurement, + # specific monitoring of VNF, trigger stop code from challenge def + + time1 = datetime.now() # get time as soon as execution starts + + # create challenge execution instance + chall_exec_ID = 1 # ideally, would be incremented, but need to maintain a number of challenge executions somewhere. or could be random. + chall_exec_name = 'challenge execution' # challenge def ID is already passed + chall_exec_challDefID = self.challenge_def_ID + chall_exec = ChallengeExecution(chall_exec_ID, chall_exec_name, chall_exec_challDefID) + chall_exec.log.append_to_list('challenge execution created') + + # create test execution instance + test_exec_ID = 1 # ideally, would be incremented, but need to maintain a number of text executions somewhere. or could be random. + test_exec_name = 'test execution' # test def ID is already passed + test_exec_testDefID = self.ID + test_exec_userID = '' # or get user name from getpass module: import getpass and test_exec_userID = getpass.getuser() + test_exec = TestExecution(test_exec_ID, test_exec_name, test_exec_testDefID, chall_exec_ID, test_exec_userID) + test_exec.log.append_to_list('test execution created') + + # get time1 before anything else, so the setup time is counted + test_exec.start_time = time1 + + # get challenge definition instance, and start challenge + challenge_def = get_indexed_item_from_list(self.challenge_def_ID, AutoResilGlobal.challenge_definition_list) + challenge_def.run_start_challenge_code() + + # memorize challenge start time + chall_exec.start_time = datetime.now() + test_exec.challenge_start_time = chall_exec.start_time + + # call specific test definition code, via table of functions; this code should monitor a VNF and return when restoration is observed test_code_index = self.test_code_ID - 1 # lists are indexed from 0 to N-1 - self.test_code_list[test_code_index]() # invoke corresponding method, via index + self.test_code_list[test_code_index]() # invoke corresponding method, via index; could check for return code + + # memorize restoration detection time and compute recovery time + test_exec.restoration_detection_time = datetime.now() + recovery_time_metric_def = get_indexed_item_from_file(1,FILE_METRIC_DEFINITIONS) # get Recovery Time metric definition: ID=1 + test_exec.recovery_time = recovery_time_metric_def.compute(test_exec.challenge_start_time, test_exec.restoration_detection_time) + + # stop challenge + challenge_def.run_stop_challenge_code() + + # memorize challenge stop time + chall_exec.stop_time = datetime.now() + chall_exec.log.append_to_list('challenge execution finished') + + # write results to CSV files, memorize test finish time + chall_exec.write_to_csv() + test_exec.finish_time = datetime.now() + test_exec.log.append_to_list('test execution finished') + test_exec.write_to_csv() + + except Exception as e: print(type(e), e) sys.exit() @@ -350,13 +402,10 @@ class TestDefinition(AutoBaseObject): """Test case code number 005.""" print("This is test_code005 from TestDefinition #", self.ID, ", test case #", self.test_case_ID, sep='') - # here, trigger start code from challenge def (to simulate VM failure), manage Recovery time measurement, - # monitoring of VNF, trigger stop code from challenge def, perform restoration of VNF - challenge_def = get_indexed_item_from_list(self.challenge_def_ID, AutoResilGlobal.challenge_definition_list) - if challenge_def != None: - challenge_def.run_start_challenge_code() - challenge_def.run_stop_challenge_code() - + # specific VNF recovery monitoring, specific metrics if any + # interact with ONAP, periodic query about VNF status; may also check VM or container status directly with VIM + # return when VNF is recovered + # may provision for failure to recover (max time to wait; return code: recovery OK boolean) def test_code006(self): """Test case code number 006.""" @@ -437,9 +486,9 @@ def init_test_definitions(): test_definitions = [] # add info to list in memory, one by one, following signature values - test_def_ID = 1 + test_def_ID = 5 test_def_name = "VM failure impact on virtual firewall (vFW VNF)" - test_def_challengeDefID = 1 + test_def_challengeDefID = 5 test_def_testCaseID = 5 test_def_VNFIDs = [1] test_def_associatedMetricsIDs = [2] @@ -466,14 +515,20 @@ def init_test_definitions(): ###################################################################### class ChallengeType(Enum): - # server-level failures + # physical server-level failures 1XX COMPUTE_HOST_FAILURE = 100 DISK_FAILURE = 101 LINK_FAILURE = 102 NIC_FAILURE = 103 - # network-level failures - OVS_BRIDGE_FAILURE = 200 - # security stresses + + # cloud-level failures 2XX + CLOUD_COMPUTE_FAILURE = 200 + SDN_C_FAILURE = 201 + OVS_BRIDGE_FAILURE = 202 + CLOUD_STORAGE_FAILURE = 203 + CLOUD_NETWORK_FAILURE = 204 + + # security stresses 3XX HOST_TAMPERING = 300 HOST_INTRUSION = 301 NETWORK_INTRUSION = 302 @@ -619,9 +674,26 @@ class ChallengeDefinition(AutoBaseObject): def start_challenge_code005(self): """Start Challenge code number 005.""" print("This is start_challenge_code005 from ChallengeDefinition #",self.ID, sep='') + # challenge #5, related to test case #5, i.e. test def #5 + # cloud reference (name and region) should be in clouds.yaml file + # conn = openstack.connect(cloud='cloudNameForChallenge005', region_name='regionNameForChallenge005') + # TestDef knows VNF, gets VNF->VM mapping from ONAP, passes VM ref to ChallengeDef + # ChallengeDef suspends/resumes VM + # conn.compute.servers() to get list of servers, using VM ID, check server.id and/or server.name + # conn.compute.suspend_server(this server id) + + def stop_challenge_code005(self): """Stop Challenge code number 005.""" print("This is stop_challenge_code005 from ChallengeDefinition #",self.ID, sep='') + # challenge #5, related to test case #5, i.e. test def #5 + # cloud reference (name and region) should be in clouds.yaml file + # conn = openstack.connect(cloud='cloudNameForChallenge005', region_name='regionNameForChallenge005') + # TestDef knows VNF, gets VNF->VM mapping from ONAP, passes VM ref to ChallengeDef + # ChallengeDef suspends/resumes VM + # conn.compute.servers() to get list of servers, using VM ID, check server.id and/or server.name + # conn.compute.conn.compute.resume_server(this server id) + def start_challenge_code006(self): """Start Challenge code number 006.""" @@ -711,9 +783,9 @@ def init_challenge_definitions(): challenge_defs = [] # add info to list in memory, one by one, following signature values - chall_def_ID = 1 + chall_def_ID = 5 chall_def_name = "VM failure" - chall_def_challengeType = ChallengeType.COMPUTE_HOST_FAILURE + chall_def_challengeType = ChallengeType.CLOUD_COMPUTE_FAILURE chall_def_recipientID = 1 chall_def_impactedCloudResourcesInfo = "OpenStack VM on ctl02 in Arm pod" chall_def_impactedCloudResourceIDs = [2] @@ -722,8 +794,10 @@ def init_challenge_definitions(): chall_def_startChallengeCLICommandSent = "service nova-compute stop" chall_def_stopChallengeCLICommandSent = "service nova-compute restart" # OpenStack VM Suspend vs. Pause: suspend stores the state of VM on disk while pause stores it in memory (RAM) + # in CLI: # $ nova suspend NAME # $ nova resume NAME + # but better use openstack SDK chall_def_startChallengeAPICommandSent = [] chall_def_stopChallengeAPICommandSent = [] @@ -1575,7 +1649,7 @@ def main(): challgs = init_challenge_definitions() print(challgs) - chall = get_indexed_item_from_file(1,FILE_CHALLENGE_DEFINITIONS) + chall = get_indexed_item_from_file(5,FILE_CHALLENGE_DEFINITIONS) print(chall) chall.run_start_challenge_code() chall.run_stop_challenge_code() @@ -1584,7 +1658,7 @@ def main(): tds = init_test_definitions() print(tds) - td = get_indexed_item_from_file(1,FILE_TEST_DEFINITIONS) + td = get_indexed_item_from_file(5,FILE_TEST_DEFINITIONS) print(td) #td.printout_all(0) #td.run_test_code() @@ -1604,8 +1678,8 @@ def main(): metricdef = get_indexed_item_from_file(1,FILE_METRIC_DEFINITIONS) print(metricdef) - t1 = datetime(2018,4,1,15,10,12,500000) - t2 = datetime(2018,4,1,15,13,43,200000) + t1 = datetime(2018,7,1,15,10,12,500000) + t2 = datetime(2018,7,1,15,13,43,200000) r1 = metricdef.compute(t1,t2) print(r1) print() @@ -1646,7 +1720,7 @@ def main(): print() - ce1 = ChallengeExecution(1,"essai challenge execution",1) + ce1 = ChallengeExecution(1,"essai challenge execution",5) ce1.start_time = datetime.now() ce1.log.append_to_list("challenge execution log event 1") ce1.log.append_to_list("challenge execution log event 2") @@ -1668,7 +1742,7 @@ def main(): print() - te1 = TestExecution(1,"essai test execution",1,1,"Gerard") + te1 = TestExecution(1,"essai test execution",5,1,"Gerard") te1.start_time = datetime.now() te1.challenge_start_time = ce1.start_time # illustrate how to set test execution challenge start time print("te1.challenge_start_time:",te1.challenge_start_time) diff --git a/lib/auto/testcase/resiliency/clouds.yaml b/lib/auto/testcase/resiliency/clouds.yaml index 593a07c..e6ec824 100644 --- a/lib/auto/testcase/resiliency/clouds.yaml +++ b/lib/auto/testcase/resiliency/clouds.yaml @@ -14,9 +14,9 @@ clouds: armopenstack: auth: auth_url: https://10.10.50.103:5000/v2.0 + project_name: admin username: admin password: opnfv_secret - project_name: admin region_name: RegionOne # Openstack instance on LaaS hpe16, from OPNFV Euphrates, controller IP@ (mgt: 172.16.10.101; public: 10.16.0.101) @@ -27,9 +27,9 @@ clouds: hpe16openstackEuphrates: auth: auth_url: http://10.16.0.101:5000/v2.0 + project_name: admin username: admin password: opnfv_secret - project_name: admin region_name: RegionOne # Openstack instance on LaaS hpe16, from OPNFV Fraser, controller IP@ (mgt: 172.16.10.36; public: 10.16.0.107) @@ -37,12 +37,16 @@ clouds: # admin: http://172.16.10.36:35357/v3 # internal: http://172.16.10.36:5000/v3 # public: http://10.16.0.107:5000/v3 + # Horizon: https://10.16.0.107:8078, but need SSH port forwarding through 10.10.100.26 to be reached from outside + # "If you are using Identity v3 you need to specify the user and the project domain name" hpe16openstackFraser: auth: auth_url: http://10.16.0.107:5000/v3 + project_name: admin username: admin password: opnfv_secret - project_name: admin + user_domain_name: Default + project_domain_name: Default region_name: RegionOne # ubuntu@ctl01:~$ openstack project show admin @@ -78,14 +82,28 @@ clouds: # | name | heat_user_domain | # +-------------+---------------------------------------------+ -export OS_AUTH_URL=http://10.16.0.107:5000/v3 -export OS_PROJECT_ID=04fcfe7aa83f4df79ae39ca748aa8637 -export OS_PROJECT_NAME="admin" -export OS_USER_DOMAIN_NAME="Default" -export OS_USERNAME="admin" -export OS_PASSWORD="opnfv_secret" -export OS_REGION_NAME="RegionOne" -export OS_INTERFACE=public -export OS_IDENTITY_API_VERSION=3 +# admin user (from Horizon on hpe16): +# Domain ID default +# Domain Name Default +# User Name admin +# Description None +# ID df0ea50cfcff4bbfbfdfefccdb018834 +# Email root@localhost +# Enabled Yes +# Primary Project ID 04fcfe7aa83f4df79ae39ca748aa8637 +# Primary Project Name admin + + + + +# export OS_AUTH_URL=http://10.16.0.107:5000/v3 +# export OS_PROJECT_ID=04fcfe7aa83f4df79ae39ca748aa8637 +# export OS_PROJECT_NAME="admin" +# export OS_USER_DOMAIN_NAME="Default" +# export OS_USERNAME="admin" +# export OS_PASSWORD="opnfv_secret" +# export OS_REGION_NAME="RegionOne" +# export OS_INTERFACE=public +# export OS_IDENTITY_API_VERSION=3 -- cgit 1.2.3-korg