UC2 update with Openstack SDK interface and common test logic 31/54531/1
authorGerard Damm <gerard.damm@wipro.com>
Tue, 27 Mar 2018 21:03:16 +0000 (16:03 -0500)
committerGerard Damm <gerard.damm@wipro.com>
Tue, 27 Mar 2018 21:03:16 +0000 (16:03 -0500)
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 <gerard.damm@wipro.com>
lib/auto/testcase/resiliency/AutoResilItfCloud.py
lib/auto/testcase/resiliency/AutoResilMain.py
lib/auto/testcase/resiliency/AutoResilMgTestDef.py
lib/auto/testcase/resiliency/clouds.yaml

index 69c5327..302a662 100644 (file)
 ######################################################################
 # 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(<attributes saved in gdv_recreate>)
 
 
     # 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()
index 2f67bdf..1d21f6a 100644 (file)
@@ -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()
index 9667f93..7e0b50d 100644 (file)
@@ -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)
index 593a07c..e6ec824 100644 (file)
@@ -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