class ApiCallerCallsUnitTest(unittest.TestCase): def setUp(self): configuration = Configuration(**dict(ApiEndpoint=API_ENDPOINT)) self.api_caller = ApiCaller(configuration) def test_get_on_empty_resource(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 200 response.headers = {} response.text = "" requests_get_mock.return_value = response link, status, status_code, response_body, headers = \ self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertFalse(status) def test_incorrect_status_code(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 500 response.headers = {} response.text = "{}" requests_get_mock.return_value = response link, status, status_code, response_body, headers = \ self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertFalse(status) def test_request_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.RequestException() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.RequestException'>:;", output.raw) def test_connection_error_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.ConnectionError() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.ConnectionError'>:;", output.raw) def test_http_error_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.HTTPError() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.HTTPError'>:", output.raw) def test_url_required_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.URLRequired() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR::Get url=http://1.2.3.4:567/resource Error <class " "'requests.exceptions.URLRequired'>:", output.raw) def test_to_many_redirects_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.TooManyRedirects() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.TooManyRedirects'>:;", output.raw) def test_timeout_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.Timeout() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.Timeout'>:;", output.raw) def test_incorrect_body(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 200 response.headers = {} response.text = "not a json" requests_get_mock.return_value = response with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR", output.raw) def test_no_content(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 204 response.headers = {} response.text = None requests_get_mock.return_value = response link, status, status_code, response_body, headers = \ self.api_caller.get_resource("/resource", DiscoveryContainer(), acceptable_return_codes = [204]) self.assertTrue(status) self.assertEqual(status_code, 204) self.assertEqual(response_body, dict()) response.json.assert_not_called() def test_async_create_without_location(self): with mock.patch('requests.post') as requests_post_mock: response = Mock() response.status_code = ReturnCodes.ACCEPTED response.headers = {} response.text = None requests_post_mock.return_value = response with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 204 response.headers = {} response.text = None requests_get_mock.return_value = response with StdoutCapture() as output: status, status_code, response_body, headers = self.api_caller.post_resource('odata_id', DiscoveryContainer(), payload={}) self.assertIn('Location header not found', '\n'.join(output)) def test_unexpected_async_post_response(self): with mock.patch('requests.post') as requests_get_mock: response = Mock() response.status_code = ReturnCodes.ACCEPTED response.headers = {} response.text = "{}" requests_get_mock.return_value = response with self.assertRaises(AsyncOperation): self.api_caller.post_resource('odata_id', DiscoveryContainer(), payload={}, wait_if_async=False) def test_async_post_response(self): with mock.patch('requests.post') as requests_post_mock: not_done = Mock() not_done.status_code = ReturnCodes.ACCEPTED not_done.headers = {'Location' : 'location'} not_done.text = None requests_post_mock.return_value = not_done with mock.patch('requests.get') as requests_get_mock: done = Mock() done.status_code = ReturnCodes.OK done.headers = {'Location' : 'location'} done.text = "{ \"done\": true }" requests_get_mock.side_effect = [not_done, not_done, done] with StdoutCapture() as output: status, status_code, response_body, headers = self.api_caller.post_resource('odata_id', DiscoveryContainer(), payload={}) self.assertTrue(response_body['done']) def test_resource_in_discovery_container_after_get_patch_delete(self): with mock.patch('requests.get') as requests_get_mock: resource = {"@odata.id": "odata.id", "something": "irrelevant"} get_response = Mock() get_response.status_code = ReturnCodes.OK get_response.headers = {} get_response.text = json.dumps(resource) requests_get_mock.return_value = get_response discovery_container = DiscoveryContainer() self.api_caller.get_resource("/resource", discovery_container) self.assertEqual(discovery_container["http://{API_ENDPOINT}/resource".format( API_ENDPOINT=API_ENDPOINT)].body, resource) patched_resource = {"@odata.id": "odata.id", "something": "relevant"} get_response.text = json.dumps(patched_resource) with mock.patch('requests.patch') as requests_patch_mock: patch_response = Mock() patch_response.status_code = ReturnCodes.OK patch_response.headers = {} patch_response.text = "{}" requests_patch_mock.return_value = patch_response _, _, _, _ = self.api_caller.patch_resource("/resource", discovery_container) self.assertEqual(discovery_container["http://{API_ENDPOINT}/resource".format( API_ENDPOINT=API_ENDPOINT)].body, patched_resource) with mock.patch('requests.delete') as requests_delete_mock: delete_response = Mock() delete_response.status_code = ReturnCodes.NO_CONTENT delete_response.headers = {} delete_response.text = "" requests_delete_mock.return_value = delete_response _, _, _, _ = self.api_caller.delete_resource("/resource", discovery_container) self.assertNotIn("/resource", discovery_container) def test_get_xml(self): with mock.patch('requests.get') as get: response = Mock() response.status_code = ReturnCodes.OK response.headers = {} response.text = "<xml></xml>" get.return_value = response link, status, status_code, response_body, headers = self.api_caller.get_xml("uri") self.assertEqual("<xml></xml>", response_body)
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" def run(self): if self.metadata_container is None: return print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover( MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.ETHERNET_SWITCH, min=1), Requirement(MetadataConstants.ETHERNET_SWITCH_PORT, min=1) ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) self.api_caller = ApiCaller(self.configuration) self.crud_acl_rule() self.crud_vlan() self.crud_static_mac() def _test_case_create_acl(self, acl_collection): print "TEST_CASE::Create ACL" self.created_acl_netloc = self.discovery_container.get_netloc( acl_collection) status, status_code, response_body, headers = self.api_caller.post_resource( acl_collection, self.discovery_container, payload=self.post_acl_payload, api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_acl = headers["Location"] print "MESSAGE::Newly created ACL URI: %s" % self.created_acl except KeyError: cts_error( "Incorrect response - header shall contain the location of the created resource" ) self.set_status_failed() return False if self.created_acl.endswith("/"): self.created_acl += self.created_acl[:-1] print "MESSAGE::ACL created" self.set_status_passed() return True def _test_case_get_created_acl(self): print "TEST_CASE::Get created ACL" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_acl, self.discovery_container, api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created ACL to given payload" if not JsonComparator.compare_json_to_golden( response_body, self.post_acl_payload): cts_error("Newly created ACL's body incorrect") self.set_status_failed() return False print "MESSAGE::Newly created ACL correct" self.set_status_passed() return True def _test_case_delete_created_acl(self): print "TEST_CASE::Delete created ACL" status, status_code, response_body, _ = self.api_caller.delete_resource( self.created_acl, self.discovery_container) if not status: cts_error( "Wrong status code of DELETE response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created ACL deleted" self.set_status_passed() return True def _test_case_get_deleted_acl(self): print "TEST_CASE::Get deleted ACL" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_acl, self.discovery_container, acceptable_return_codes=[ ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of GET response after deletion: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created ACL not found (as intended)" self.set_status_passed() return True def _test_case_bind_acl_to_port(self, port): print "TEST_CASE::Bind ACL to a port" port_id = dict() port_id["@odata.id"] = port payload = dict(Port=port_id) status, status_code, response_body, _ = \ self.api_caller.post_resource(self.created_acl + "/Actions/EthernetSwitchACL.Bind", self.discovery_container, payload=payload, acceptable_return_codes= [ReturnCodes.NO_CONTENT], expect_location=False, api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::ACL successfully bound to port" self.set_status_passed() return True def _test_case_unbind_acl_from_port(self, port): print "TEST_CASE::Unbind ACL from a port" port_id = dict() port_id["@odata.id"] = port payload = dict(Port=port_id) status, status_code, response_body, _ = \ self.api_caller.post_resource(self.created_acl + "/Actions/EthernetSwitchACL.Unbind", self.discovery_container, payload=payload, acceptable_return_codes=[ReturnCodes.NO_CONTENT], expect_location=False, api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::ACL successfully unbound from port" self.set_status_passed() return True def _test_case_bound_acl_and_port_are_linked(self, port, should_be_linked): print "TEST_CASE::Check if the ACL and port are linked " + \ ("(should be)" if should_be_linked else "(shouldn't be)") port = urlparse(port).path acl = urlparse(self.created_acl).path acl_linked, port_linked = None, None try: link, status, status_code, acl_body, _ = \ self.api_caller.get_resource(self.created_acl, self.discovery_container, api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False if not {"@odata.id": port} in acl_body["Links"]["BoundPorts"] \ and not {"@odata.id": port + "/"} in acl_body["Links"]["BoundPorts"]: acl_linked = False if should_be_linked: cts_error("No link to the bound port in the ACL's body") else: print "MESSAGE::No link to the bound port in the ACL's body (as expected)" else: acl_linked = True if should_be_linked: print "MESSAGE::ACL has a link to the bound Port" else: cts_error("ACL still has a link to the unbound Port") except KeyError: cts_error("No Links/BoundPorts array present in the ACL's body") try: link, status, status_code, port_body, _ = \ self.api_caller.get_resource(port, self.discovery_container, api_endpoint_override=self.discovery_container.get_netloc( port)) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False if not {"@odata.id": acl} in port_body["Links"]["ActiveACLs"] \ and not {"@odata.id": acl + "/"} in port_body["Links"]["ActiveACLs"]: port_linked = False if should_be_linked: cts_error("No link to the bound ACL in the port's body") else: print "MESSAGE::No link to the bound ACL in the port's body (as expected)" else: port_linked = True if should_be_linked: print "MESSAGE::Port has a link to the bound ACL" else: cts_error("Port still has a link to the unbound ACL") except KeyError: cts_error("No Links/ActiveACLs array present in the port's body") if acl_linked is not None and port_linked is not None: if acl_linked == port_linked == should_be_linked: self.set_status_passed() return True self.set_status_failed() return False def _test_case_add_rules_to_acl(self): print "TEST_CASE::Add rules to the ACL" self.created_rules = dict() optional_rules_triggered_an_error = False rule_collection = self.created_acl + "/Rules" for rule_type, rule_payload in self.rules_payloads.iteritems(): print "MESSAGE::Adding a {type} rule".format(type=rule_type) status, status_code, response_body, headers = \ self.api_caller.post_resource( rule_collection, self.discovery_container, payload=rule_payload, api_endpoint_override=self.created_acl_netloc) if not status: if rule_type not in self.mandatory_rules: optional_rules_triggered_an_error = True cts_warning( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) cts_warning( "Rule {rule_type} was defined as optional, continuing", **locals()) continue cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_rules[rule_type] = headers["Location"] print "MESSAGE::Newly created ACL Rule URI: %s" % self.created_acl except KeyError: cts_error( "Incorrect response - header shall contain the location of the created resource" ) self.set_status_failed() return False print "MESSAGE::Getting the newly created ACL Rule URI: %s" % self.created_acl link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_rules[rule_type], self.discovery_container, api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created ACL Rule to given payload" if not JsonComparator.compare_json_to_golden( response_body, rule_payload): cts_error("Newly created Rule's body incorrect") self.set_status_failed() return False else: print "MESSAGE::Newly created Rule's body correct" print "MESSAGE::Rule added" print "MESSAGE::ACL Rules created succesfully" if optional_rules_triggered_an_error: self.set_status_passed_with_warnings() else: self.set_status_passed() return True def _test_case_delete_rules_from_acl(self): print "TEST_CASE::Delete the created rules from the ACL" for rule_type, rule_uri in self.created_rules.iteritems(): print "MESSAGE::Deleting {type}".format(type=rule_type) status, status_code, response_body, _ = self.api_caller.delete_resource( rule_uri, self.discovery_container) if not status: cts_error( "Wrong status code of DELETE response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Checking if the rule was actually deleted" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(rule_uri, self.discovery_container, acceptable_return_codes=[ ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of GET response after deletion: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::The rule was not found (as intended)" print "MESSAGE::ACL Rules deleted succesfully" self.set_status_passed() return True def crud_acl_rule(self): """ Test is trying to perform CRUD (create, read, update, delete) as well as binding operations on an ACL and ACL Rule resource """ switches = \ dict(self.discovery_container.get_resources(MetadataConstants.ETHERNET_SWITCH, any_child_version=True)) all_ports = [] if not switches: cts_error( "Could not find any Ethernet Switch to perform the test on, aborting" ) return ValidationStatus.BLOCKED chosen_switch, chosen_acl_collection, chosen_port, mirrored_port = None, None, None, None for switch in switches.keys(): acl_collections = dict( self.discovery_container.get_resources( MetadataConstants.ETHERNET_SWITCH_ACL_COLLECTION, any_child_version=True, constraints=[from_collection(switch)])) if acl_collections: chosen_switch = switch chosen_acl_collection = choice(acl_collections.keys()) all_ports = [ port[1].odata_id for port in self.discovery_container.get_resources( MetadataConstants.ETHERNET_SWITCH_PORT, any_child_version=True, constraints=[from_collection(chosen_switch)]) ] try: chosen_port = choice(all_ports) all_ports.remove(chosen_port) mirrored_port = choice(all_ports) except IndexError: if not chosen_port: print "MESSAGE::Could not find a proper port for ACL binding on the switch" continue else: print "WARNING::Only one proper port found, unable to create a Mirror rule" break if not chosen_acl_collection: cts_error( "Could not find any ACL collection on any of the present Ethernet Switches, aborting" ) return ValidationStatus.BLOCKED if not chosen_port: cts_error( "Could not find any proper port for ACL binding on any of the present Ethernet Switches, aborting" ) return ValidationStatus.BLOCKED print "MESSAGE:CRUD test will be performed on ACL collection: {collection}, the test will attempt to bind and "\ "unbind the ACL from the port: {port}".format(collection=chosen_acl_collection, port=chosen_port) self.post_acl_payload = dict() self.rules_payloads = dict() self.rules_payloads["Deny"] = dict( Action="Deny", Condition=dict(MACSource=dict(MACAddress="AA:BB:CC:DD:EE:FF", Mask="FF:FF:FF:00:00:00"))) self.rules_payloads["Permit"] = dict( Action="Permit", Condition=dict(IPSource=dict(IPv4Address="192.168.0.1", Mask="255.255.255.0"))) self.rules_payloads["Forward"] = dict( Action="Forward", ForwardMirrorInterface={"@odata.id": chosen_port}, Condition=dict(IPDestination=dict(IPv4Address="1.1.1.1"))) if mirrored_port: self.rules_payloads["Mirror"] = dict( RuleId=1111, Action="Mirror", ForwardMirrorInterface={"@odata.id": chosen_port}, MirrorPortRegion=[{ "@odata.id": mirrored_port }], MirrorType="Egress", Condition=dict(VLANId=dict(Id=1, Mask=None))) # basic rules (Deny and Permit actions) should be supported by any PSME service; therefore failure in adding # them triggers FAILED status for the test. If the mandatory rules are added successfully, but there is an # error while adding the optional (Forward and Mirror actions) rules, the test's result is PASSED WITH WARNINGS self.mandatory_rules = ["Deny", "Permit"] if self._test_case_create_acl(chosen_acl_collection): self._test_case_get_created_acl() if self._test_case_add_rules_to_acl( ): # Rules need to be added before binding if self._test_case_bind_acl_to_port(chosen_port): if self._test_case_bound_acl_and_port_are_linked( chosen_port, True): if self._test_case_unbind_acl_from_port(chosen_port): self._test_case_bound_acl_and_port_are_linked( chosen_port, False) self._test_case_delete_rules_from_acl() self._test_case_delete_created_acl() self._test_case_get_deleted_acl() def _test_case_create_vlan(self, vlan_network_interface_collection): print "TEST_CASE::Create VLAN" status, status_code, response_body, headers = \ self.api_caller.post_resource( vlan_network_interface_collection, self.discovery_container, payload=self.post_vlan_payload) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_vlan_network_interface = headers["Location"] self.created_vlan_network_interface_netlock = self.discovery_container.get_netloc( vlan_network_interface_collection) print "MESSAGE::Newly created VLAN Network Interface URI: %s" % self.created_vlan_network_interface except KeyError: cts_error( "Incorrect response - header shall contain the location of the created resource" ) self.set_status_failed() return False print "MESSAGE::VLAN Network Interface created" self.set_status_passed() return True def _test_case_get_created_vlan(self): print "TEST_CASE::Get created VLAN" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_vlan_network_interface, self.discovery_container, api_endpoint_override=self.created_vlan_network_interface_netlock) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created VLAN Network Interface to given payload" # PSME is unable to set "Name" and "VLANEnable" fields properly but only "VLANEnable" is mandatory if not JsonComparator.compare_json_to_golden( response_body, self.post_vlan_payload, ignore=["/VLANEnable"]): cts_error( "Newly created VLAN Network Interface's body incorrect") self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface correct" self.set_status_passed() return True def _test_case_delete_created_vlan(self): print "TEST_CASE::Delete created VLAN" status, status_code, response_body, _ = self.api_caller.delete_resource( self.created_vlan_network_interface, self.discovery_container) if not status: cts_error( "Wrong status code of DELETE response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface deleted" self.set_status_passed() return True def _test_case_get_deleted_vlan(self): print "TEST_CASE::Get deleted VLAN" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_vlan_network_interface, self.discovery_container, acceptable_return_codes=[ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_vlan_network_interface_netlock) if not status: cts_error( "Wrong status code of GET response after deletion: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface not found (as intended)" self.set_status_passed() return True def crud_vlan(self): """ Test is trying to perform CRUD (create, read, update, delete) operations on a VLAN Network Interface resource """ requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.ETHERNET_SWITCH, min=1), Requirement(MetadataConstants.ETHERNET_SWITCH_PORT, min=1), Requirement(MetadataConstants.VLAN_NETWORK_INTERFACE_COLLECTION, min=1), ] preconditions = Preconditions(self.metadata_container, requirements) if preconditions.validate( self.discovery_container) == ValidationStatus.FAILED: return ValidationStatus.BLOCKED vlan_network_interface_collections = \ dict(self.discovery_container.get_resources(MetadataConstants.VLAN_NETWORK_INTERFACE_COLLECTION, any_child_version=True)) chosen_vlan_network_interface_collection = None new_vlan_id = 0 for vlan_network_interface_collection in vlan_network_interface_collections: present_vlan_ids = [] vlan_network_interfaces = dict( self.discovery_container.get_resources( MetadataConstants.VLAN_NETWORK_INTERFACE, constraints=[ from_collection(vlan_network_interface_collection) ], any_child_version=True)) if len(vlan_network_interfaces) > MAX_VLAN_ID - MIN_VLAN_ID: continue for resource_link, resource in vlan_network_interfaces.iteritems(): try: present_vlan_ids.append(resource.body["VLANId"]) except: cts_error("Inorrect resource {resource_link:id} structure", **locals()) for possible_id in range(MIN_VLAN_ID, MAX_VLAN_ID + 1): if possible_id not in present_vlan_ids: chosen_vlan_network_interface_collection = vlan_network_interface_collection new_vlan_id = possible_id break if chosen_vlan_network_interface_collection: break if chosen_vlan_network_interface_collection: print "MESSAGE::CRUD test will be performed on vlan network interface collection %s" % \ chosen_vlan_network_interface_collection else: cts_warning('No free VLAN id available') return ValidationStatus.BLOCKED self.post_vlan_payload = dict( VLANId=new_vlan_id, VLANEnable=True, Oem=dict(Intel_RackScale=dict(Tagged=True))) if self._test_case_create_vlan( chosen_vlan_network_interface_collection): self._test_case_get_created_vlan() self._test_case_delete_created_vlan() self._test_case_get_deleted_vlan() def _test_case_create_static_mac(self, static_mac_collection): print "TEST_CASE::Create Static MAC" status, status_code, response_body, headers = self.api_caller.post_resource( static_mac_collection, self.discovery_container, payload=self.post_static_mac_payload) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_static_mac = headers["Location"] self.created_static_mac_netloc = self.discovery_container.get_netloc( static_mac_collection) print "MESSAGE::Newly created Static MAC URI: %s" % self.created_static_mac except KeyError: cts_error( "Incorrect response - header shall contain the location of the created resource" ) self.set_status_failed() return False print "MESSAGE::Static MAC created" self.set_status_passed() return True def _test_case_get_created_static_mac(self): print "TEST_CASE::Get created Static MAC" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_static_mac, self.discovery_container, api_endpoint_override=self.created_static_mac_netloc) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created Static MAC to given payload" if not JsonComparator.compare_json_to_golden( response_body, self.post_static_mac_payload): cts_error("Newly created Static MAC's body incorrect") self.set_status_failed() return False print "MESSAGE::Newly created Static MAC correct" self.set_status_passed() return True def _test_case_delete_created_static_mac(self): print "TEST_CASE::Delete created Static MAC" status, status_code, response_body, _ = self.api_caller.delete_resource( self.created_static_mac, self.discovery_container) if not status: cts_error( "Wrong status code of DELETE response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created Static MAC deleted" self.set_status_passed() return True def _test_case_get_deleted_static_mac(self): print "TEST_CASE::Get deleted Static MAC" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_static_mac, self.discovery_container, acceptable_return_codes=[ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_static_mac_netloc) if not status: cts_error( "Wrong status code of GET response after deletion: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created Static MAC not found (as intended)" self.set_status_passed() return True def crud_static_mac(self): """ Test is trying to perform CRUD (create, read, update, delete) operations on a Static MAC resource """ def _find_unique_mac_address_vlan_id_pair(present_pairs): MAX_ADDRESS = 281474976710656 - 1 # 16^12 - 1 def address_to_string(address): parts = [] for _ in range(6): parts.append(address % 256) address = address / 256 return ":".join("{0:02x}".format(part) for part in parts).upper() address = 0 vlan_id = MIN_VLAN_ID while (address_to_string(address), vlan_id) in present_pairs: address = address + 1 if address > MAX_ADDRESS: address = 0 vlan_id = vlan_id + 1 if vlan_id > MAX_VLAN_ID: return None return (address_to_string(address), vlan_id) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.ETHERNET_SWITCH, min=1), Requirement(MetadataConstants.ETHERNET_SWITCH_PORT, min=1), Requirement(MetadataConstants.STATIC_MAC_COLLECTION, min=1), ] preconditions = Preconditions(self.metadata_container, requirements) if preconditions.validate( self.discovery_container) == ValidationStatus.FAILED: return ValidationStatus.BLOCKED static_mac_collections = \ dict(self.discovery_container.get_resources(MetadataConstants.STATIC_MAC_COLLECTION, any_child_version=True)) chosen_static_mac_collection = static_mac_collections.keys()[0] print "MESSAGE::\nMESSAGE::Static MAC CRUD test will be performed on collection: {}"\ .format(chosen_static_mac_collection) present_vlan_id_mac_address_pairs = [] for resource_link, resource in self.discovery_container.get_resources( MetadataConstants.STATIC_MAC, any_child_version=True, constraints=[from_collection(chosen_static_mac_collection)]): try: present_vlan_id_mac_address_pairs.append( (resource.body["MACAddress"], resource.body["VLANId"])) except Exception as err: cts_warning( "{link:id} Incorrect resource structure; err={err:exception}", link=resource_link, err=err) initial_values = _find_unique_mac_address_vlan_id_pair( present_vlan_id_mac_address_pairs) if not initial_values: cts_error("Cannot create a Static MAC on the chosen collection - " "all MAC addresses and VLAN ids already occupied") return ValidationStatus.BLOCKED self.post_static_mac_payload = dict(MACAddress=initial_values[0], VLANId=initial_values[1]) # PSME does not support patching Static MAC if self._test_case_create_static_mac(chosen_static_mac_collection): self._test_case_get_created_static_mac() self._test_case_delete_created_static_mac() self._test_case_get_deleted_static_mac()
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" def run(self): if self.metadata_container is None: return print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover( MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) self.api_caller = ApiCaller(self.configuration) self.crud_vlan() def _test_case_create_vlan(self, vlan_network_interface_collection): print "TEST_CASE::Create VLAN" status, status_code, response_body, headers = \ self.api_caller.post_resource( vlan_network_interface_collection, self.discovery_container, payload=self.post_vlan_payload) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_vlan_network_interface = headers["Location"] self.created_vlan_network_interface_netlock = self.discovery_container.get_netloc( vlan_network_interface_collection) print "MESSAGE::Newly created VLAN Network Interface URI: %s" % self.created_vlan_network_interface except KeyError: cts_error( "Incorrect response - header shall contain the location of the created resource" ) self.set_status_failed() return False print "MESSAGE::VLAN Network Interface created" self.set_status_passed() return True def _test_case_get_created_vlan(self): print "TEST_CASE::Get created VLAN" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_vlan_network_interface, self.discovery_container, api_endpoint_override=self.created_vlan_network_interface_netlock) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created VLAN Network Interface to given payload" # PSME is unable to set "Name" and "VLANEnable" fields properly but only "VLANEnable" is mandatory if not JsonComparator.compare_json_to_golden( response_body, self.post_vlan_payload, ignore=["/VLANEnable"]): cts_error( "Newly created VLAN Network Interface's body incorrect") self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface correct" self.set_status_passed() return True def _test_case_delete_created_vlan(self): print "TEST_CASE::Delete created VLAN" status, status_code, response_body, _ = self.api_caller.delete_resource( self.created_vlan_network_interface, self.discovery_container) if not status: cts_error( "Wrong status code of DELETE response: {status_code};" " response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface deleted" self.set_status_passed() return True def _test_case_get_deleted_vlan(self): print "TEST_CASE::Get deleted VLAN" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_vlan_network_interface, self.discovery_container, acceptable_return_codes=[ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_vlan_network_interface_netlock) if not status: cts_error( "Wrong status code of GET response after deletion: {status_code};" " response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface not found (as intended)" self.set_status_passed() return True def crud_vlan(self): """ Test is trying to perform CRUD (create, read, update, delete) operations on a VLAN Network Interface resource """ requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.VLAN_NETWORK_INTERFACE_COLLECTION, min=1), ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) if status is ValidationStatus.PASSED_WITH_WARNINGS: cts_warning( "Without some components it is not possible to perform tests.") print "STATUS::{status}".format(status=status) return ValidationStatus.PASSED_WITH_WARNINGS if status is ValidationStatus.FAILED: return ValidationStatus.BLOCKED vlan_network_interface_collections = \ dict(self.discovery_container.get_resources(MetadataConstants.VLAN_NETWORK_INTERFACE_COLLECTION, any_child_version=True)) chosen_vlan_network_interface_collection = None new_vlan_id = 0 for vlan_network_interface_collection in vlan_network_interface_collections: present_vlan_ids = [] vlan_network_interfaces = \ dict(self.discovery_container. get_resources(MetadataConstants.VLAN_NETWORK_INTERFACE, constraints=[from_collection(vlan_network_interface_collection)], any_child_version=True)) if len(vlan_network_interfaces) > MAX_VLAN_ID - MIN_VLAN_ID: continue for resource_link, resource in vlan_network_interfaces.iteritems(): try: present_vlan_ids.append(resource.body["VLANId"]) except: cts_error("Inorrect resource {resource_link:id} structure", **locals()) for possible_id in range(MIN_VLAN_ID, MAX_VLAN_ID + 1): if possible_id not in present_vlan_ids: chosen_vlan_network_interface_collection = vlan_network_interface_collection new_vlan_id = possible_id break if chosen_vlan_network_interface_collection: break if chosen_vlan_network_interface_collection: print "MESSAGE::CRUD test will be performed on vlan network interface collection %s" % \ chosen_vlan_network_interface_collection else: cts_warning('No free VLAN id available') return ValidationStatus.BLOCKED self.post_vlan_payload = dict( VLANId=new_vlan_id, VLANEnable=True, Oem=dict(Intel_RackScale=dict(Tagged=True))) if self._test_case_create_vlan( chosen_vlan_network_interface_collection): self._test_case_get_created_vlan() self._test_case_delete_created_vlan() self._test_case_get_deleted_vlan()
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" CONFIGURATION_PARAMETERS = [ TestScript.ConfigurationParameter( parameter_name="UniqueInitiatorName", parameter_description="NVMe Qualified Name (NQN) for Initiator", parameter_type=str, is_required=True, parameter_default_value=""), TestScript.ConfigurationParameter( parameter_name="UniqueTargetName", parameter_description="NVMe Qualified Name (NQN) for Target", parameter_type=str, is_required=True, parameter_default_value="") ] def run(self): if self.metadata_container is None: return self.chosen_endpoint, self.chosen_zone, self.chosen_volume, self.chosen_volume_collection = ( None, ) * 4 self.initiator_endpoint, self.target_endpoint, self.zone_endpoint, self.created_volume = ( None, ) * 4 self.initiator_unique_name, self.target_unique_name = \ self.configuration.UniqueInitiatorName, self.configuration.UniqueTargetName test_name = "Storage Services CRUD test with NVM Express (NVMe) Support" print "MESSAGE::%s starting" % test_name print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover( MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.STORAGE_SERVICE, min=1), Requirement(MetadataConstants.VOLUME, min=1), Requirement(MetadataConstants.VOLUME_COLLECTION, min=1) ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) if status == ValidationStatus.FAILED: self.set_status_failed() return self.api_caller = ApiCaller(self.configuration) status = ValidationStatus.join_statuses(status, self.crud_nvme()) print "MESSAGE::%s overall status: %s" % ( test_name, ColorPrinter.format_status(status)) def crud_nvme(self): status = ValidationStatus.PASSED cts_message("CRUD NVME") # test pre check self.__find_volumes_without_any_links(optional_pre_check=True) try: status = ValidationStatus.join_statuses(status, self._test_create_volume()) except: self._test_delete_all_created_endpoints_and_zones() status = ValidationStatus.FAILED self.__find_and_choose_endpoint_collection() self.__find_and_choose_volume() if not (self.chosen_volume and self.chosen_endpoint and self.chosen_zone): self.set_status_blocked() return ValidationStatus.BLOCKED cts_message("Chosen Volume for test: {volume_url}".format( volume_url=self.chosen_volume)) try: status = ValidationStatus.join_statuses( status, self._test_create_initiator_endpoint()) status = ValidationStatus.join_statuses( status, self._test_create_target_endpoint()) status = ValidationStatus.join_statuses( status, self._test_create_zone_with_only_one_endpoint()) status = ValidationStatus.join_statuses( status, self._test_patch_zone_with_target_endpoint()) except: status = ValidationStatus.FAILED finally: status = ValidationStatus.join_statuses( status, self._test_delete_all_created_endpoints_and_zones()) return status # Test case 1: def _test_create_volume(self): print "TEST_CASE::Create volume" post_volume_payload = dict(CapacityBytes=10000000) status, status_code, response_body, headers = self.api_caller.post_resource( self.chosen_volume_collection, self.discovery_container, payload=post_volume_payload, acceptable_return_codes=[ ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED ]) if not status: cts_error("CTS cannot create volume") self.set_status_failed() return ValidationStatus.FAILED self.created_volume = headers['location'] cts_message("Volume created at {location}".format( location=self.created_volume)) if not self._verify_size_of_created_volume(self.created_volume, post_volume_payload): cts_error("Service create volume with wrong capacity") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 2: def _test_create_initiator_endpoint(self): print "TEST_CASE::Create initiator endpoint" post_initiator_endpoint_payload = dict( Identifiers=[ dict(DurableName=self.initiator_unique_name, DurableNameFormat="NQN") ], IPTransportDetails=[], ConnectedEntities=[dict( EntityRole="Initiator", Identifiers=[], )], Links=dict(Oem=dict(Intel_RackScale=dict()))) status, status_code, response_body, headers = self.api_caller.post_resource( self.chosen_endpoint, self.discovery_container, payload=post_initiator_endpoint_payload, acceptable_return_codes=[ ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED ]) if not status: cts_error("CTS cannot create Initiator Endpoint") self.set_status_failed() return ValidationStatus.FAILED self.initiator_endpoint = headers['location'] cts_message("Initiator Endpoint created at {location}".format( location=self.initiator_endpoint)) try: verify = self.__verify_created_initiator_endpoint( self.initiator_endpoint, post_initiator_endpoint_payload) except: verify = False if not verify: cts_error("Service create initiator endpoint with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 3: def _test_create_target_endpoint(self): print "TEST_CASE::Create target endpoint" selected_volume = self.discovery_container.get(self.chosen_volume) post_target_endpoint_payload = dict( Identifiers=[ dict(DurableName=self.target_unique_name, DurableNameFormat="NQN") ], IPTransportDetails=[], ConnectedEntities=[ dict( EntityRole="Target", Identifiers=[], EntityLink={"@odata.id": selected_volume.odata_id}, ) ], Links=dict(Oem=dict(Intel_RackScale=dict()))) status, status_code, response, headers = self.api_caller.post_resource( self.chosen_endpoint, self.discovery_container, payload=post_target_endpoint_payload, acceptable_return_codes=[ ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED ]) if not status: cts_error("CTS can not create Target Endpoint") self.set_status_failed() return ValidationStatus.FAILED self.target_endpoint = headers['location'] cts_message("Target Endpoint created at {location}".format( location=self.target_endpoint)) try: verify = self.__verify_created_target_endpoint( self.target_endpoint, post_target_endpoint_payload) except: verify = False if not verify: cts_error("Service create target endpoint with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 4: def _test_create_zone_with_only_one_endpoint(self): print "TEST_CASE::Create zone with only one endpoint" try: initiator_endpoint_link = self.__get_odata_id_links( self.initiator_endpoint) except: cts_error("No valid initiator endpoint for zone creation") self.set_status_blocked() return ValidationStatus.BLOCKED post_create_zone_payload = dict(Links=dict( Endpoints=[{ "@odata.id": initiator_endpoint_link }])) status, status_code, _, headers = self.api_caller.post_resource( self.chosen_zone, self.discovery_container, payload=post_create_zone_payload, acceptable_return_codes=[ ReturnCodes.ACCEPTED, ReturnCodes.CREATED, ReturnCodes.OK ]) if not status: cts_error("CTS can not create Zone") self.set_status_failed() return ValidationStatus.FAILED self.zone_endpoint = headers['location'] cts_message( "Zone created at {location}".format(location=self.zone_endpoint)) if not self.__verify_created_zone(self.zone_endpoint, post_create_zone_payload): cts_error("Service zone created with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 5: def _test_patch_zone_with_target_endpoint(self): print "TEST_CASE::Patch zone with target endpoint" initiator_or_target_missing = False try: initiator_endpoint_link = self.__get_odata_id_links( self.initiator_endpoint) except: cts_error("No valid initiator endpoint for zone patching") initiator_or_target_missing = True try: target_endpoint_link = self.__get_odata_id_links( self.target_endpoint) except: cts_error("No valid target endpoint for zone patching") initiator_or_target_missing = True if initiator_or_target_missing: cts_error("Missing resources for zone patching") self.set_status_blocked() return ValidationStatus.BLOCKED patch_create_zone_payload = dict(Links=dict( Endpoints=[{ "@odata.id": initiator_endpoint_link }, { "@odata.id": target_endpoint_link }])) status, status_code, _, headers = self.api_caller.patch_resource( self.zone_endpoint, self.discovery_container, payload=patch_create_zone_payload, acceptable_return_codes=[ReturnCodes.ACCEPTED, ReturnCodes.OK]) if not status: cts_error("CTS can not PATCH Zone") self.set_status_failed() return ValidationStatus.FAILED cts_message( "Zone PATCHED at {location}".format(location=self.zone_endpoint)) if not self.__verify_patched_zone(self.zone_endpoint, patch_create_zone_payload): cts_error("Service zone patched with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 6: def _test_delete_all_created_endpoints_and_zones(self): status = ValidationStatus.PASSED for resource, link in OrderedDict([ ("volume", self.created_volume), ("zone", self.zone_endpoint), ("initiator endpoint", self.initiator_endpoint), ("target endpoint", self.target_endpoint) ]).iteritems(): print "TEST_CASE::Delete {resource}".format(resource=resource) if link: if not self.__delete_location(link): self.set_status_failed() status = ValidationStatus.join_statuses( status, ValidationStatus.FAILED) else: self.set_status_passed() status = ValidationStatus.join_statuses( status, ValidationStatus.PASSED) else: self.set_status_blocked() status = ValidationStatus.join_statuses( status, ValidationStatus.BLOCKED) return status def __check_resources_have_same_parent_url(self, res1, res2): return (self.discovery_container.get(res1)).parent_url == ( self.discovery_container.get(res2)).parent_url def __choose_endpoint_and_zone_from_fabric_collection( self, endpoint_collection, zone_collection): for ec in endpoint_collection: for zc in zone_collection: if self.__check_resources_have_same_parent_url(ec, zc): return ec, zc return None, None def __find_and_choose_volume(self): for volume in self.__find_volumes_without_any_links(): self.chosen_volume = volume break def __delete_location(self, location_url): status, _, _, _ = self.api_caller.delete_resource( location_url, self.discovery_container, acceptable_return_codes=[ReturnCodes.NO_CONTENT]) if not status: cts_error("Resource at %s was not properly deleted" % location_url) return False return self.__verify_resource_non_exist(location_url) def __verify_resource_non_exist(self, location_url_verification): status, _, _, _, _ = self.api_caller.get_resource( location_url_verification, self.discovery_container, acceptable_return_codes=[ ReturnCodes.BAD_REQUEST, ReturnCodes.NOT_FOUND ]) if not status: cts_error("Resource at %s was not properly deleted" % location_url_verification) return False cts_message("Resource at %s was properly deleted" % location_url_verification) return True def __find_and_choose_endpoint_collection(self): endpoint_collection = dict( self.discovery_container.get_resources( MetadataConstants.ENDPOINT_COLLECTION)) zones_collection = dict( self.discovery_container.get_resources( MetadataConstants.ZONE_COLLECTION)) if not endpoint_collection: cts_error("No Endpoint Collection") self.set_status_blocked() if not zones_collection: cts_error("No Zone Collection") self.set_status_blocked() self.chosen_endpoint, self.chosen_zone = \ self.__choose_endpoint_and_zone_from_fabric_collection( endpoint_collection, zones_collection) def __find_volumes_without_any_links(self, optional_pre_check=False): reusable_volumes = [] logical_drive_collections = dict( self.discovery_container.get_resources( MetadataConstants.VOLUME_COLLECTION, any_child_version=True)) # Allocated Volumes are connected with this same type as Volume volumes_collection_wo_allocated_volumes = [ x for x in logical_drive_collections if not "AllocatedVolumes" in x ] for collection in volumes_collection_wo_allocated_volumes: if not self.chosen_volume_collection: self.chosen_volume_collection = collection logical_drive_groups = dict( self.discovery_container.get_resources( MetadataConstants.VOLUME, any_child_version=True)) volumes_collection = [] for c in logical_drive_groups.keys(): try: # this Volume we can use to our tests if len( self.discovery_container.get(c).body["Links"] ["Oem"]["Intel_RackScale"]["Endpoints"]) == 0: volumes_collection.append(c) except KeyError: pass # if there are any free Volume, suspend test if len(volumes_collection) == 0: if optional_pre_check: return False cts_error( "No reusable Volume. Create a Volume without linked Endpoints" ) self.set_status_blocked() else: reusable_volumes.extend(volumes_collection) return reusable_volumes def __get_odata_id_links(self, link): return (self.discovery_container.get(link)).odata_id # verification code def __verify_patched_zone(self, resource_location, resource_payload): print "MESSAGE::Comparing newly patched zone to given payload" return self.__verify_zone(resource_location, resource_payload) def __verify_created_zone(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created zone to given payload" return self.__verify_zone(resource_location, resource_payload) def __verify_zone(self, resource_location, resource_payload): location_payload_to_compare = JsonComparator.odict_to_dict( self.discovery_container.get(resource_location).body["Links"] ["Endpoints"]) payload_to_compare = resource_payload["Links"]["Endpoints"] return JsonComparator.compare_lists(location_payload_to_compare, payload_to_compare) def __verify_created_target_endpoint(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created target endpoint to given payload" location_payload_to_compare = JsonComparator.odict_to_dict( self.discovery_container.get(resource_location).body) payload_to_compare = resource_payload verification_dict = { 'ConnectedEntities': ['EntityLink', 'EntityRole'], 'Identifiers': ['DurableNameFormat', 'DurableName'] } return self.__verify_body(location_payload_to_compare, payload_to_compare, verification_dict) def __verify_created_initiator_endpoint(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created initiator endpoint to given payload" location_payload_to_compare = JsonComparator.odict_to_dict( self.discovery_container.get(resource_location).body) payload_to_compare = resource_payload verification_dict = { 'ConnectedEntities': ['EntityRole'], 'Identifiers': ['DurableNameFormat', 'DurableName'] } return self.__verify_body(location_payload_to_compare, payload_to_compare, verification_dict) def _verify_size_of_created_volume(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created volume to given payload" discovery_service_capacity = self.discovery_container.get( resource_location).body["CapacityBytes"] resource_payload_capacity = resource_payload["CapacityBytes"] return self.__verify_bytes(resource_payload_capacity, discovery_service_capacity) @staticmethod def __verify_body(resource_1, resource_2, verification_dict): try: for verification_key in verification_dict.keys(): for verification_elem in verification_dict[verification_key]: if resource_1[verification_key][0][verification_elem] != \ resource_2[verification_key][0][verification_elem]: return False except KeyError: return False return True @staticmethod def __verify_bytes(resource_on_payload, resource_discovered, min_bytes=None, max_bytes=None): if not min_bytes: # default 4 mb min_bytes = 4000000 if not max_bytes: # default 4 mb max_bytes = 4000000 if (resource_on_payload - min_bytes) <= resource_discovered <= ( resource_on_payload + max_bytes): return True return False @staticmethod def __verify_strings(resource_on_payload, resource_discovered): resource_on_payload = str(resource_on_payload).lower() resource_discovered = str(resource_discovered).lower() return resource_on_payload == resource_discovered
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" def run(self): if self.metadata_container is None: return self.chosen_endpoint, self.chosen_zone, self.chosen_volume, self.chosen_volume_collection = (None,)*4 self.initiator_endpoint, self.target_endpoint, self.zone_endpoint, self.created_volume = (None,)*4 test_name = "Storage Services CRUD test" print "MESSAGE::%s starting" % test_name print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover(MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.STORAGE_SERVICE, min=1), Requirement(MetadataConstants.VOLUME, min=1), Requirement(MetadataConstants.VOLUME_COLLECTION, min=1) ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) if status == ValidationStatus.FAILED: self.set_status_failed() return self.api_caller = ApiCaller(self.configuration) status = ValidationStatus.join_statuses(status, self.crud_fabrics_target()) print "MESSAGE::%s overall status: %s" % (test_name, ColorPrinter.format_status(status)) def crud_fabrics_target(self): status = ValidationStatus.PASSED cts_message("CRUD FABRICS TARGET") # test pre check self.__find_volumes_without_any_links(optional_pre_check=True) try: status = ValidationStatus.join_statuses(status, self._test_create_volume()) except: self._test_delete_all_created_endpoints_and_zones() status = ValidationStatus.FAILED self.__find_and_choose_endpoint_collection() self.__find_and_choose_volume() if not (self.chosen_volume and self.chosen_endpoint and self.chosen_zone): self.set_status_blocked() return ValidationStatus.BLOCKED cts_message("Chosen Volume for test: {volume_url}".format(volume_url=self.chosen_volume)) try: status = ValidationStatus.join_statuses(status, self._test_create_initiator_endpoint()) status = ValidationStatus.join_statuses(status, self._test_create_target_endpoint()) status = ValidationStatus.join_statuses(status, self._test_create_zone_with_only_one_endpoint()) status = ValidationStatus.join_statuses(status, self._test_patch_zone_with_target_endpoint()) except: status = ValidationStatus.FAILED finally: status = ValidationStatus.join_statuses(status, self._test_delete_all_created_endpoints_and_zones()) return status # Test case 1: def _test_create_volume(self): print "TEST_CASE::Create volume" post_volume_payload = dict( CapacityBytes=10000000, CapacitySources=[], AccessCapabilities=[] ) status, status_code, response_body, headers = self.api_caller.post_resource( self.chosen_volume_collection, self.discovery_container, payload=post_volume_payload, acceptable_return_codes=[ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED] ) if not status: cts_error("CTS cannot create volume") self.set_status_failed() return ValidationStatus.FAILED self.created_volume = headers['location'] cts_message("Volume created at {location}".format(location=self.created_volume)) if not self._verify_size_of_created_volume(self.created_volume, post_volume_payload): cts_error("Service create volume with wrong capacity") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 2: def _test_create_initiator_endpoint(self): print "TEST_CASE::Create initiator endpoint" random_suffix = random.randrange(1, RANDOM_RANGE, 1) post_initiator_endpoint_payload = dict( Identifiers=[dict( DurableName="iqn.initiator" + str(random_suffix), DurableNameFormat="iQN" )], ConnectedEntities=[dict( EntityRole="Initiator", )], IPTransportDetails=[], Oem=dict( Intel_RackScale=dict( Authentication=dict( Username="******" + str(random_suffix), Password="******" )))) status, status_code, response_body, headers = self.api_caller.post_resource( self.chosen_endpoint, self.discovery_container, payload=post_initiator_endpoint_payload, acceptable_return_codes=[ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED] ) if not status: cts_error("CTS cannot create Initiator Endpoint") self.set_status_failed() return ValidationStatus.FAILED self.initiator_endpoint = headers['location'] cts_message("Initiator Endpoint created at {location}".format(location=self.initiator_endpoint)) try: verify = self.__verify_created_initiator_endpoint(self.initiator_endpoint, post_initiator_endpoint_payload) except: verify = False if not verify: cts_error("Service create initiator endpoint with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 3: def _test_create_target_endpoint(self): print "TEST_CASE::Create target endpoint" selected_volume = self.discovery_container.get(self.chosen_volume) random_suffix = random.randrange(1, RANDOM_RANGE, 1) post_target_endpoint_payload = dict( Identifiers=[dict( DurableName="iqn.initiator" + str(random_suffix), DurableNameFormat="iQN" )], ConnectedEntities=[dict( EntityRole="Target", EntityLink={"@odata.id": selected_volume.odata_id}, )], IPTransportDetails=[], Oem=dict( Intel_RackScale=dict( Authentication=dict( Username="******" + str(random_suffix), Password="******" )))) status, status_code, response, headers = self.api_caller.post_resource( self.chosen_endpoint, self.discovery_container, payload=post_target_endpoint_payload, acceptable_return_codes=[ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED]) if not status: cts_error("CTS can not create Target Endpoint") self.set_status_failed() return ValidationStatus.FAILED self.target_endpoint = headers['location'] cts_message("Target Endpoint created at {location}".format(location=self.target_endpoint)) try: verify = self.__verify_created_target_endpoint(self.target_endpoint, post_target_endpoint_payload) except: verify = False if not verify: cts_error("Service create target endpoint with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 4: def _test_create_zone_with_only_one_endpoint(self): print "TEST_CASE::Create zone with only one endpoint" try: initiator_endpoint_link = self.__get_odata_id_links(self.initiator_endpoint) except: cts_error("No valid initiator endpoint for zone creation") self.set_status_blocked() return ValidationStatus.BLOCKED post_create_zone_payload = dict( Links=dict( Endpoints=[{"@odata.id": initiator_endpoint_link}] ) ) status, status_code, _, headers = self.api_caller.post_resource(self.chosen_zone, self.discovery_container, payload=post_create_zone_payload, acceptable_return_codes=[ReturnCodes.ACCEPTED, ReturnCodes.CREATED, ReturnCodes.OK]) if not status: cts_error("CTS can not create Zone") self.set_status_failed() return ValidationStatus.FAILED self.zone_endpoint = headers['location'] cts_message("Zone created at {location}".format(location=self.zone_endpoint)) if not self.__verify_created_zone(self.zone_endpoint, post_create_zone_payload): cts_error("Service zone created with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 5: def _test_patch_zone_with_target_endpoint(self): print "TEST_CASE::Patch zone with target endpoint" initiator_or_target_missing = False try: initiator_endpoint_link = self.__get_odata_id_links(self.initiator_endpoint) except: cts_error("No valid initiator endpoint for zone patching") initiator_or_target_missing = True try: target_endpoint_link = self.__get_odata_id_links(self.target_endpoint) except: cts_error("No valid target endpoint for zone patching") initiator_or_target_missing = True if initiator_or_target_missing: cts_error("Missing resources for zone patching") self.set_status_blocked() return ValidationStatus.BLOCKED patch_create_zone_payload = dict( Links=dict( Endpoints=[ {"@odata.id": initiator_endpoint_link}, {"@odata.id": target_endpoint_link}] )) status, status_code, _, headers = self.api_caller.patch_resource(self.zone_endpoint, self.discovery_container, payload=patch_create_zone_payload, acceptable_return_codes=[ReturnCodes.ACCEPTED, ReturnCodes.OK]) if not status: cts_error("CTS can not PATCH Zone") self.set_status_failed() return ValidationStatus.FAILED cts_message("Zone PATCHED at {location}".format(location=self.zone_endpoint)) if not self.__verify_patched_zone(self.zone_endpoint, patch_create_zone_payload): cts_error("Service zone patched with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 6: def _test_delete_all_created_endpoints_and_zones(self): status = ValidationStatus.PASSED for resource, link in OrderedDict([("volume", self.created_volume), ("zone", self.zone_endpoint), ("initiator endpoint", self.initiator_endpoint), ("target endpoint", self.target_endpoint)]).iteritems(): print "TEST_CASE::Delete {resource}".format(resource=resource) if link: if not self.__delete_location(link): self.set_status_failed() status = ValidationStatus.join_statuses(status, ValidationStatus.FAILED) else: self.set_status_passed() status = ValidationStatus.join_statuses(status, ValidationStatus.PASSED) else: self.set_status_blocked() status = ValidationStatus.join_statuses(status, ValidationStatus.BLOCKED) return status def __check_resources_have_same_parent_url(self, res1, res2): return (self.discovery_container.get(res1)).parent_url == (self.discovery_container.get(res2)).parent_url def __choose_endpoint_and_zone_from_fabric_collection(self, endpoint_collection, zone_collection): for ec in endpoint_collection: for zc in zone_collection: if self.__check_resources_have_same_parent_url(ec, zc): return ec, zc return None, None def __find_and_choose_volume(self): for volume in self.__find_volumes_without_any_links(): self.chosen_volume = volume break def __delete_location(self, location_url): status, _, _, _ = self.api_caller.delete_resource(location_url, self.discovery_container, acceptable_return_codes=[ReturnCodes.NO_CONTENT]) if not status: cts_error("Resource at %s was not properly deleted" % location_url) return False return self.__verify_resource_non_exist(location_url) def __verify_resource_non_exist(self, location_url_verification): status, _, _, _, _ = self.api_caller.get_resource(location_url_verification, self.discovery_container, acceptable_return_codes=[ReturnCodes.BAD_REQUEST, ReturnCodes.NOT_FOUND]) if not status: cts_error("Resource at %s was not properly deleted" % location_url_verification) return False cts_message("Resource at %s was properly deleted" % location_url_verification) return True def __find_and_choose_endpoint_collection(self): endpoint_collection = dict(self.discovery_container.get_resources( MetadataConstants.ENDPOINT_COLLECTION)) zones_collection = dict(self.discovery_container.get_resources( MetadataConstants.ZONE_COLLECTION)) if not endpoint_collection: cts_error("No Endpoint Collection") self.set_status_blocked() if not zones_collection: cts_error("No Zone Collection") self.set_status_blocked() self.chosen_endpoint, self.chosen_zone = \ self.__choose_endpoint_and_zone_from_fabric_collection( endpoint_collection, zones_collection) def __find_volumes_without_any_links(self, optional_pre_check=False): reusable_volumes = [] logical_drive_collections = dict(self.discovery_container.get_resources( MetadataConstants.VOLUME_COLLECTION, any_child_version=True)) # Allocated Volumes are connected with this same type as Volume volumes_collection_wo_allocated_volumes = [x for x in logical_drive_collections if not "AllocatedVolumes" in x] for collection in volumes_collection_wo_allocated_volumes: if not self.chosen_volume_collection: self.chosen_volume_collection = collection logical_drive_groups = dict(self.discovery_container.get_resources(MetadataConstants.VOLUME, any_child_version=True)) volumes_collection = [] for c in logical_drive_groups.keys(): try: # this Volume we can use to our tests if len(self.discovery_container.get(c).body["Links"]["Oem"]["Intel_RackScale"]["Endpoints"]) == 0: volumes_collection.append(c) except KeyError: pass # if there are any free Volume, suspend test if len(volumes_collection) == 0: if optional_pre_check: return False cts_error("No reusable Volume. Create a Volume without linked Endpoints") self.set_status_blocked() else: reusable_volumes.extend(volumes_collection) return reusable_volumes def __get_odata_id_links(self, link): return (self.discovery_container.get(link)).odata_id # verification code def __verify_patched_zone(self, resource_location, resource_payload): print "MESSAGE::Comparing newly patched zone to given payload" return self.__verify_zone(resource_location, resource_payload) def __verify_created_zone(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created zone to given payload" return self.__verify_zone(resource_location, resource_payload) def __verify_zone(self, resource_location, resource_payload): location_payload_to_compare = JsonComparator.odict_to_dict( self.discovery_container.get(resource_location).body["Links"]["Endpoints"]) payload_to_compare = resource_payload["Links"]["Endpoints"] return JsonComparator.compare_lists(location_payload_to_compare, payload_to_compare) def __verify_created_target_endpoint(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created target endpoint to given payload" location_payload_to_compare = JsonComparator.odict_to_dict( self.discovery_container.get(resource_location).body) payload_to_compare = resource_payload verification_dict = {'ConnectedEntities': ['EntityLink', 'EntityRole'], 'Identifiers': ['DurableNameFormat', 'DurableName']} return self.__verify_body(location_payload_to_compare, payload_to_compare, verification_dict) def __verify_created_initiator_endpoint(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created initiator endpoint to given payload" location_payload_to_compare = JsonComparator.odict_to_dict( self.discovery_container.get(resource_location).body) payload_to_compare = resource_payload verification_dict = {'ConnectedEntities': ['EntityRole'], 'Identifiers': ['DurableNameFormat', 'DurableName']} return self.__verify_body(location_payload_to_compare, payload_to_compare, verification_dict) def _verify_size_of_created_volume(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created volume to given payload" discovery_service_capacity = self.discovery_container.get(resource_location).body["CapacityBytes"] resource_payload_capacity = resource_payload["CapacityBytes"] return self.__verify_bytes(resource_payload_capacity, discovery_service_capacity) @staticmethod def __verify_body(resource_1, resource_2, verification_dict): try: for verification_key in verification_dict.keys(): for verification_elem in verification_dict[verification_key]: if resource_1[verification_key][0][verification_elem] != \ resource_2[verification_key][0][verification_elem]: return False except KeyError: return False return True @staticmethod def __verify_bytes(resource_on_payload, resource_discovered, min_bytes=None, max_bytes=None): if not min_bytes: # default 4 mb min_bytes = 4000000 if not max_bytes: # default 4 mb max_bytes = 4000000 if (resource_on_payload - min_bytes) <= resource_discovered <= (resource_on_payload + max_bytes): return True return False @staticmethod def __verify_strings(resource_on_payload, resource_discovered): resource_on_payload = str(resource_on_payload).lower() resource_discovered = str(resource_discovered).lower() return resource_on_payload == resource_discovered
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" def run(self): if self.metadata_container is None: return test_name = "Storage Services CRUD test" print "MESSAGE::%s starting" % test_name print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover(MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.STORAGE_SERVICE, min=1), Requirement(MetadataConstants.LOGICAL_DRIVE_COLLECTION, min=1), Requirement(MetadataConstants.REMOTE_TARGET_COLLECTION, min=1), Requirement(MetadataConstants.LOGICAL_DRIVE, min=2) ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) if status == ValidationStatus.FAILED: self.set_status_blocked() return self.api_caller = ApiCaller(self.configuration) status = ValidationStatus.join_statuses(status, self.crud_logical_drive()) status = ValidationStatus.join_statuses(status, self.crud_remote_target()) print "MESSAGE::%s overall status: %s" % (test_name, ColorPrinter.format_status(status)) def _test_case_create_logical_drive(self, logical_drive_collection, payload=None): print "TEST_CASE::Create a logical drive" if not payload: payload = self.post_logical_drive_payload status, status_code, response_body, headers = self.api_caller.post_resource( logical_drive_collection, self.discovery_container, payload=payload) if not status: cts_error("Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED try: self.created_logical_drive = headers["Location"] self.created_logical_drive_netloc = \ self.discovery_container.get_netloc(logical_drive_collection) print "MESSAGE::Newly created logical drive url %s" % self.created_logical_drive except KeyError: cts_error("In header shall be provided location to created resource") self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Logical drive created" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_created_logical_drive(self, logical_drive): print "TEST_CASE::Get the created logical drive" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_logical_drive, self.discovery_container, api_endpoint_override=self.created_logical_drive_netloc) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Comparing newly created logical drive to given payload" if not JsonComparator.compare_json_to_golden(response_body, self.expected_created_logical_drive_body, ignore = ["/Name"]): # Name setting not supported by PSME REST API cts_error("Newly created logical drive's body incorrect") self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive's correct" self.set_status_passed() return ValidationStatus.PASSED def _test_case_update_created_logical_drive(self): print "TEST_CASE::Update the created logical drive" patch_logical_drive_payload = dict( Bootable = not self.post_logical_drive_payload["Bootable"] ) status, status_code, response_body, _ = self.api_caller.patch_resource(self.created_logical_drive, self.discovery_container, payload=patch_logical_drive_payload) if not status: cts_error("Error while executing PATCH; status code: {status_code}; response body: " "{response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_logical_drive, self.discovery_container, api_endpoint_override=self.created_logical_drive_netloc) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Verifying whether the logical drive's update has taken place" if not JsonComparator.compare_json_to_golden(response_body["Bootable"], patch_logical_drive_payload["Bootable"]): cts_error("Newly created logical drive could not be updated") self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive updated correctly" self.set_status_passed() return ValidationStatus.PASSED def _test_case_delete_created_logical_drive(self, logical_drive): print "TEST_CASE::Delete the created logical drive" status, status_code, response_body, _ = self.api_caller.delete_resource(self.created_logical_drive, self.discovery_container) if not status: cts_error("Wrong status code of DELETE response: {status_code}; response body: " "{response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive deleted" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_deleted_logical_drive(self, logical_drive): print "TEST_CASE::Get the deleted logical drive" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_logical_drive, self.discovery_container, acceptable_return_codes=[ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_logical_drive_netloc) if not status: cts_error("Wrong status code of GET response after deletion: {status_code}; response body: " "{response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive not found (as intended)" self.set_status_passed() return ValidationStatus.PASSED def crud_logical_drive(self, create_logical_drive_for_target_crud=None): """ Test is trying to perform CRUD (create, read, update, delete) operations on a logical drive :type create_logical_drive_for_target_crud: Boolean """ status = ValidationStatus.BLOCKED lvm_found = False chosen_logical_drive_type, chosen_logical_drive_mode = None, None chosen_logical_drive_collection, chosen_logical_drive, chosen_logical_drive_group = None, None, None inherited_bootable = None # to speed things up, always look for the lowest capacity logical drive LVM_LV_SPECIFIC_CONSTRAINTS = [equal("Mode", "LV"), greater("CapacityGiB", 0), not_equal("Snapshot", True), order_by("CapacityGiB")] CEPH_RBD_SPECIFIC_CONSTRAINTS = [equal("Mode", "RBD"), greater("CapacityGiB", 0), order_by("CapacityGiB")] logical_drive_collections = dict(self.discovery_container.get_resources( MetadataConstants.LOGICAL_DRIVE_COLLECTION, any_child_version=True)) for collection in logical_drive_collections.keys(): chosen_logical_drive_collection = collection logical_drive_groups = dict(self.discovery_container.get_resources(MetadataConstants.LOGICAL_DRIVE, any_child_version=True, constraints=[equal("Mode", "LVG"), from_collection(collection)])) if logical_drive_groups: lvm_found = True else: logical_drive_groups = dict(self.discovery_container.get_resources(MetadataConstants.LOGICAL_DRIVE, any_child_version=True, constraints=[equal("Mode", "Pool"), from_collection( collection)])) if logical_drive_groups: specific_constraints = LVM_LV_SPECIFIC_CONSTRAINTS if lvm_found else CEPH_RBD_SPECIFIC_CONSTRAINTS for lvg_link, lvg in logical_drive_groups.iteritems(): chosen_logical_drive_group = lvg.odata_id try: child_lvs = [lv["@odata.id"] for lv in lvg.body["Links"]["LogicalDrives"]] chosen_logical_drive, _ = self.discovery_container.get_resources( MetadataConstants.LOGICAL_DRIVE, any_child_version=True, constraints=specific_constraints + [odata_ids(child_lvs)])[0] lv = self.discovery_container[chosen_logical_drive] inherited_bootable = lv.body["Bootable"] chosen_logical_drive_type = lv.body["Type"] chosen_logical_drive_mode = lv.body["Mode"] except (KeyError,IndexError) as error: cts_error("Exception while parsing {id:id}: {error:exception}", id=chosen_logical_drive, error=error) chosen_logical_drive = None pass else: break if not chosen_logical_drive_group: resource = "logical drive group" if lvm_found else "Pool" cts_warning("Could not find a {resource} resource in collection {collection}", **locals()) if lvm_found and not chosen_logical_drive: cts_warning("Could not find a logical volume in collection {collection}", **locals()) if lvm_found and not chosen_logical_drive: cts_error("Could not find any logical volume") return status if not chosen_logical_drive_group: cts_error("Could not find any logical drive group, aborting") return status self.post_logical_drive_payload = dict( Name="LVM Logical Drive created by CTS", Type=chosen_logical_drive_type, Mode=chosen_logical_drive_mode, Protected=False, CapacityGiB=self.discovery_container[chosen_logical_drive].body["CapacityGiB"], Image="CTS test image", Bootable=inherited_bootable if inherited_bootable else False, Snapshot=True, Links=dict( LogicalDrives=[{"@odata.id": chosen_logical_drive_group}], MasterDrive={"@odata.id": urlparse(chosen_logical_drive).path} ) ) # Creating the first RBD if no other was found if not chosen_logical_drive: if lvm_found: print "ERROR::Could not find a logical drive suitable for snapshot" return status cts_warning("Could not find any logical drive suitable for snapshot. Since CEPH was detected, CTS will "\ "try to create the first RBD") self.post_logical_drive_payload = dict( Name="CEPH Logical Drive created by CTS", Type="CEPH", Mode="RBD", Protected=False, CapacityGiB=self.discovery_container[chosen_logical_drive_group].body["CapacityGiB"]/2, Bootable=False, Snapshot=False, Links=dict( LogicalDrives=[{"@odata.id": urlparse(chosen_logical_drive_group).path}], MasterDrive=None ) ) print "MESSAGE::Logical drive CRUD test will be performed on logical drive collection %s, creating logical " \ "drive based on %s on group %s" % (chosen_logical_drive_collection, chosen_logical_drive, chosen_logical_drive_group) self.expected_created_logical_drive_body = deepcopy(self.post_logical_drive_payload) # The logical drive in LogicalDrives link is really the volume group and is going to be seen under # UsedBy on REST self.expected_created_logical_drive_body["Links"]["UsedBy"] = \ self.expected_created_logical_drive_body["Links"]["LogicalDrives"] self.expected_created_logical_drive_body["Links"]["LogicalDrives"] = [] status = self._test_case_create_logical_drive(chosen_logical_drive_collection) if status == ValidationStatus.PASSED: # only perform other tests if creation was successful ld = self.created_logical_drive status = ValidationStatus.join_statuses(status, self._test_case_get_created_logical_drive(ld)) if create_logical_drive_for_target_crud: return status if status == ValidationStatus.PASSED: status = ValidationStatus.join_statuses(status, self._test_case_update_created_logical_drive()) status = ValidationStatus.join_statuses(status, self._test_case_delete_created_logical_drive(ld)) status = ValidationStatus.join_statuses(status, self._test_case_get_deleted_logical_drive(ld)) return status def _test_case_create_remote_target(self, remote_target_collection): print "TEST_CASE::Create a remote target" status, status_code, response_body, headers = self.api_caller.post_resource( remote_target_collection, self.discovery_container, payload=self.post_remote_target_payload) if not status: cts_error("Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED try: self.created_remote_target = headers["Location"] self.created_remote_target_netloc = \ self.discovery_container.get_netloc(remote_target_collection) print "MESSAGE::Newly created remote target url %s" % self.created_remote_target except KeyError: cts_error("In header shall be provided location to created resource") self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Remote target created" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_created_remote_target(self): print "TEST_CASE::Get the created remote target" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_remote_target, self.discovery_container, api_endpoint_override=self.created_remote_target_netloc) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Comparing newly created remote target to given payload" if not JsonComparator.compare_json_to_golden(response_body, self.post_remote_target_payload): cts_error("Newly created remote target's body incorrect") self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created remote target's body correct" self.set_status_passed() return ValidationStatus.PASSED def _test_case_logical_drive_has_link_to_created_target(self, logical_drive): print "TEST_CASE::Check for a link to the created target in logical drive's body" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(logical_drive, self.discovery_container) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED else: try: target = urlparse(self.created_remote_target).path if not {"@odata.id": target} in response_body["Links"]["Targets"]\ and not {"@odata.id": target + "/"} in response_body["Links"]["Targets"]: cts_error("No link to the created target in the logical drive's body") self.set_status_failed() return ValidationStatus.FAILED except KeyError: cts_error("No Links/Targets array present in the logical drive's body") self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::A link from the logical drive to the target is present" self.set_status_passed() return ValidationStatus.PASSED def _test_case_update_created_remote_target(self): print "TEST_CASE::Update the created remote target" patch_remote_target_payload = dict( Initiator=[dict( iSCSI=dict( InitiatorIQN="cts.initiator:patched_cts_initiator" ) )] ) status, status_code, response_body, _ = self.api_caller.patch_resource(self.created_remote_target, self.discovery_container, payload=patch_remote_target_payload) if not status: cts_error("Error while executing PATCH; status code: {status_code}; response body: " "{response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_remote_target, self.discovery_container, api_endpoint_override=self.created_remote_target_netloc) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Verifying whether the remote target's update has taken place" if not JsonComparator.compare_json_to_golden(response_body["Initiator"], patch_remote_target_payload["Initiator"]): cts_error("Newly created remote target could not be updated") self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created remote target updated correctly" self.set_status_passed() return ValidationStatus.PASSED def _test_case_delete_created_remote_target(self): print "TEST_CASE::Delete the created remote target" status, status_code, response_body, _ = self.api_caller.delete_resource(self.created_remote_target, self.discovery_container) if not status: cts_error("Wrong status code of DELETE response: {status_code}; response body: " "{response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Newly created remote target deleted" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_deleted_remote_target(self): print "TEST_CASE::Get the deleted remote target" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_remote_target, self.discovery_container, acceptable_return_codes=[ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_remote_target_netloc) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Newly created remote target not found (as intended)" self.set_status_passed() return ValidationStatus.PASSED def crud_remote_target(self): """ Test is checking for rsa compute blade presence """ ld_created_for_crud = False target_iqn = "cts.target:cts_test_target" target_iqns = set() chosen_logical_drive, chosen_remote_target_collection = None, None if not self.discovery_container.count_resources(MetadataConstants.LOGICAL_DRIVE, any_child_version=True): cts_error("Insufficient resources for API test. No logical drives found.") return ValidationStatus.BLOCKED logical_drives = dict(self.discovery_container.get_resources(MetadataConstants.LOGICAL_DRIVE, any_child_version=True, constraints=[equal("Mode", "LV"), equal("Protected", False)])) if not logical_drives: logical_drives = dict(self.discovery_container.get_resources(MetadataConstants.LOGICAL_DRIVE, any_child_version=True, constraints=[equal("Mode", "RBD"), equal("Protected", False)])) try: chosen_remote_target_collection = self.discovery_container.get_resources( MetadataConstants.REMOTE_TARGET_COLLECTION, any_child_version=True)[0][0] chosen_logical_drive = [link for link, resource in logical_drives.iteritems() if not len(resource.body["Links"]["Targets"])][0] except (KeyError, IndexError): pass if not chosen_remote_target_collection: cts_error("Insufficient resources available on API for test. Could not find any remote target collection") return ValidationStatus.BLOCKED if not chosen_logical_drive: cts_warning("Insufficient resources available on API for test. Could not find an unprotected logical drive " "with no targets attached") print "MESSAGE::Trying to create a new logical volume for the target, then proceeding with remote target "\ "CRUD test" if self.crud_logical_drive(create_logical_drive_for_target_crud=True) == ValidationStatus.PASSED: chosen_logical_drive = self.created_logical_drive ld_created_for_crud = True else: cts_error("Creating a new logical volume for the target failed, skipping remote target tests") return ValidationStatus.BLOCKED targets = [link for link, resource in self.discovery_container.get_resources(MetadataConstants.REMOTE_TARGET, any_child_version=True, constraints=[ from_collection(chosen_remote_target_collection) ])] for target in targets: addresses = self.discovery_container[target].body["Addresses"] for address in addresses: try: target_iqns.add(address["iSCSI"]["TargetIQN"]) except KeyError: pass if target_iqn in target_iqns: iqn_number = 0 while target_iqn + str(iqn_number) in target_iqns: iqn_number += 1 target_iqn = target_iqn + str(iqn_number) print "MESSAGE::Remote Target CRUD test will be performed on remote target collection %s, creating remote "\ "target based on %s with first lowest consecutive \'cts.target:cts_test_target<number>\' target IQN "\ "available: %s" % (chosen_remote_target_collection, chosen_logical_drive, target_iqn) self.post_remote_target_payload = dict( Addresses=[dict( iSCSI=dict( TargetIQN=target_iqn, TargetLUN=[dict( LUN=1, LogicalDrive={"@odata.id": urlparse(chosen_logical_drive).path} )], ) )], Initiator=[dict( iSCSI=dict( InitiatorIQN="cts.initiator:initiator_cts_test" ) )] ) status = self._test_case_create_remote_target(chosen_remote_target_collection) if status == ValidationStatus.PASSED: # only perform other tests if creation was successful status = ValidationStatus.join_statuses(status, self._test_case_get_created_remote_target()) status = ValidationStatus.join_statuses(status, self._test_case_logical_drive_has_link_to_created_target( chosen_logical_drive)) status = ValidationStatus.join_statuses(status, self._test_case_update_created_remote_target()) status = ValidationStatus.join_statuses(status, self._test_case_delete_created_remote_target()) status = ValidationStatus.join_statuses(status, self._test_case_get_deleted_remote_target()) if ld_created_for_crud: ld = self.created_logical_drive del_status = self._test_case_delete_created_logical_drive(ld) del_status = ValidationStatus.join_statuses(del_status, self._test_case_get_deleted_logical_drive(ld)) if status == ValidationStatus.PASSED and del_status != status: cts_error("Remote target CRUD test passed, but the logical drive it created failed to delete properly") status = ValidationStatus.PASSED_WITH_WARNINGS return status
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" def run(self): test_name = "Storage Services CRUD test" print "MESSAGE::%s starting" % test_name print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover(MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.STORAGE_SERVICE, min=1), Requirement(MetadataConstants.LOGICAL_DRIVE, min=2) ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) self.api_caller = ApiCaller(self.configuration) status = ValidationStatus.join_statuses(status, self.crud_logical_drive()) status = ValidationStatus.join_statuses(status, self.crud_remote_target()) print "MESSAGE::%s overall status: %s" % (test_name, ColorPrinter.format_status(status)) def _test_case_create_logical_drive(self, logical_drive_collection, payload=None): print "TEST_CASE::Create a logical drive" if not payload: payload = self.post_logical_drive_payload status, status_code, response_body, headers = self.api_caller.post_resource( logical_drive_collection, payload=payload) if not status: print "ERROR::Wrong status code of POST response: %d" % status_code self.set_status_failed() return ValidationStatus.FAILED try: self.created_logical_drive = headers["Location"] print "MESSAGE::Newly created logical drive url %s" % self.created_logical_drive except KeyError: print "ERROR::In header shall be provided location to created resource" self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Logical drive created" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_created_logical_drive(self, logical_drive=None): print "TEST_CASE::Get the created logical drive" if not logical_drive: logical_drive = self.created_logical_drive status, status_code, response_body, _ = self.api_caller.get_resource(logical_drive) if not status: print "ERROR::Wrong status code of GET response: %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED else: api_resource = ApiResource(response_body, response_body["@odata.type"]) self.discovery_container.add_resource(logical_drive, api_resource) print "MESSAGE::Comparing newly created logical drive to given payload" ignored_fields = ["/Name", "/Image"] # Name setting not supported by PSME REST API, Image is optional print "MESSAGE::Comparing newly created logical drive to given payload" if not JsonComparator.compare_json_to_golden(response_body, self.expected_created_logical_drive_body, ignore = ignored_fields): print "ERROR::Newly created logical drive's body incorrect" self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive's body correct" self.set_status_passed() return ValidationStatus.PASSED def _test_case_update_created_logical_drive(self): print "TEST_CASE::Update the created logical drive" patch_logical_drive_payload = dict( Bootable = not self.post_logical_drive_payload["Bootable"] ) status, status_code, response_body, _ = self.api_caller.patch_resource(self.created_logical_drive, payload=patch_logical_drive_payload) if not status: print "ERROR::Wrong status code of PATCH reponse %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED status, status_code, response_body, _ = self.api_caller.get_resource(self.created_logical_drive) if not status: print "ERROR::Wrong status code of GET reponse %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Verifying whether the logical drive's update has taken place" if not JsonComparator.compare_json_to_golden(response_body, patch_logical_drive_payload): print "ERROR::Newly created logical drive could not be updated" self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive updated correctly" self.set_status_passed() return ValidationStatus.PASSED def _test_case_delete_created_logical_drive(self, logical_drive=None): print "TEST_CASE::Delete the created logical drive" if not logical_drive: logical_drive = self.created_logical_drive status, status_code, response_body, _ = self._delete_resource(logical_drive) if not status: self.set_status_failed() return ValidationStatus.FAILED self.discovery_container.remove_resource(logical_drive) print "MESSAGE::Newly created logical drive deleted" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_deleted_logical_drive(self, logical_drive=None): print "TEST_CASE::Get the deleted logical drive" if not logical_drive: logical_drive = self.created_logical_drive status, status_code, response_body, _ = self.api_caller.get_resource(logical_drive, acceptable_return_codes = [ReturnCodes.NOT_FOUND]) if not status: print "ERROR::Wrong status code of GET response after deletion: %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive not found (as intended)" self.set_status_passed() return ValidationStatus.PASSED def crud_logical_drive(self, called_from_crud_remote_target=None): """ Test is trying to perform CRUD (create, read, update, delete) operations on a logical volume :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer :type self.metadata_container: cts_core.metadata.self.metadata_container.MetadataContainer """ status = ValidationStatus.BLOCKED lvm_found = False logical_volumes = dict() logical_volume_groups = dict() logical_drive_collections = dict() for resource_link, resource in self.discovery_container.iteritems(): ok = True if self.metadata_container.entities[MetadataConstants.LOGICAL_DRIVE].compare_entities(resource.odata_type): print "DEBUG::found entity of type %s in %s" % (MetadataConstants.LOGICAL_DRIVE, resource_link) try: if resource.body["Type"] == "LVM": lvm_found = True if resource.body["Mode"] == "LVG": logical_volume_groups[resource_link] = resource elif resource.body["Mode"] == "LV": if "CapacityGiB" not in resource.body.keys(): print "DEBUG::%s drive does not have specified capacity" % resource_link ok = False if "Snapshot" in resource.body.keys() and resource.body["Snapshot"] == True: print "DEBUG::%s cannot be used - drive is a snapshot volume" % resource_link ok = False if ok: logical_volumes[resource_link] = resource elif resource.body["Type"] == "CEPH": if resource.body["Mode"] == "Pool": logical_volume_groups[resource_link] = resource elif resource.body["Mode"] == "RBD": if "CapacityGiB" not in resource.body.keys(): print "DEBUG::%s drive does not have specified capacity" % resource_link ok = False if ok: logical_volumes[resource_link] = resource except: print "WARNING::Incorrect resource %s structure" % resource_link elif self.metadata_container.entities[MetadataConstants.LOGICAL_DRIVE_COLLECTION]\ .compare_entities(resource.odata_type): logical_drive_collections[resource_link] = resource if lvm_found and (not logical_volume_groups or not logical_volumes): print "ERROR::Insufficient resources available on API for test. Found %s logical volumes and %s logical " \ " volume groups" % (len(logical_volumes), len(logical_volume_groups)) return ValidationStatus.BLOCKED # to speed things up, always look for the lowest capacity logical volume lowest_capacity = float('inf') chosen_logical_drive_collection, chosen_logical_volume, chosen_logical_volume_group = None, None, None inherited_bootable = None if not logical_volume_groups: if lvm_found: print "ERROR::No LVM volume group found, aborting" else: print "ERROR::No CEPH Pool device found, aborting" return ValidationStatus.BLOCKED for lvg_link, lvg in logical_volume_groups.iteritems(): # finding a collection matching this volume group possible_collections = [collection for collection in logical_drive_collections.keys() if collection in lvg_link] if not possible_collections: continue try: for lv in lvg.body["Links"]["LogicalDrives"]: # iterating LVs that have this LVG as a parent try: lv_link = lv["@odata.id"] lv_type = logical_volumes[lv_link].body["Type"] lv_mode = logical_volumes[lv_link].body["Mode"] lv_capacity = logical_volumes[lv_link].body["CapacityGiB"] lv_bootable = logical_volumes[lv_link].body["Bootable"] if lv_capacity < lowest_capacity: lowest_capacity = lv_capacity inherited_bootable = lv_bootable chosen_logical_volume = lv_link chosen_logical_volume_type = lv_type chosen_logical_volume_mode = lv_mode chosen_logical_volume_group = lvg_link chosen_logical_drive_collection = possible_collections[0] except KeyError: pass except KeyError: pass self.post_logical_drive_payload = dict( Name="LVM Logical Drive created by CTS", Type=chosen_logical_volume_type, Mode=chosen_logical_volume_mode, Protected=False, CapacityGiB=self.discovery_container[chosen_logical_volume].body["CapacityGiB"], Bootable=inherited_bootable if inherited_bootable else False, Snapshot=True, Links=dict( LogicalDrives=[{"@odata.id": chosen_logical_volume_group}], MasterDrive={"@odata.id": chosen_logical_volume} ) ) # Creating the first RBD if no other was found if not chosen_logical_volume: if lvm_found: print "ERROR::Could not find a (logical volume, logical volume group) pair suitable for the test" return status else: self.post_logical_drive_payload = dict( Name="CEPH Logical Drive created by CTS", Type="CEPH", Mode="RBD", Protected=False, CapacityGiB=self.discovery_container[chosen_logical_volume_group].body["CapacityGiB"]/2, Bootable=False, Snapshot=False, Links=dict( LogicalDrives=[{"@odata.id": chosen_logical_volume_group}], MasterDrive=None ) ) print "MESSAGE::Logical drive CRUD test will be performed on logical drive collection %s, creating logical volume " \ "based on %s on group %s" % (chosen_logical_drive_collection, chosen_logical_volume, chosen_logical_volume_group) self.expected_created_logical_drive_body = deepcopy(self.post_logical_drive_payload) # The logical drive in LogicalDrives link is really the volume group and is going to be seen under # UsedBy on REST self.expected_created_logical_drive_body["Links"]["UsedBy"] = \ self.expected_created_logical_drive_body["Links"]["LogicalDrives"] self.expected_created_logical_drive_body["Links"]["LogicalDrives"] = [] status = self._test_case_create_logical_drive(chosen_logical_drive_collection) if status == ValidationStatus.PASSED: # only perform other tests if creation was successful status = ValidationStatus.join_statuses(status, self._test_case_get_created_logical_drive()) if called_from_crud_remote_target: status = ValidationStatus.join_statuses(status, self.crud_remote_target(called_from_crud_logical_drive=True)) else: status = ValidationStatus.join_statuses(status, self._test_case_update_created_logical_drive()) status = ValidationStatus.join_statuses(status, self._test_case_delete_created_logical_drive()) status = ValidationStatus.join_statuses(status, self._test_case_get_deleted_logical_drive()) return status def _test_case_create_remote_target(self, remote_target_collection): print "TEST_CASE::Create a remote target" status, status_code, response_body, headers = self.api_caller.post_resource( remote_target_collection, payload=self.post_remote_target_payload) if not status: print "ERROR::Wrong status code of POST response: %d" % status_code self.set_status_failed() return ValidationStatus.FAILED try: self.created_remote_target = headers["Location"] print "MESSAGE::Newly created remote target url %s" % self.created_remote_target except KeyError: print "ERROR::In header shall be provided location to created resource" self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Remote target created" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_created_remote_target(self): print "TEST_CASE::Get the created remote target" status, status_code, response_body, _ = self.api_caller.get_resource(self.created_remote_target) if not status: print "ERROR::Wrong status code of GET response: %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Comparing newly created remote target to given payload" if not JsonComparator.compare_json_to_golden(response_body, self.post_remote_target_payload): print "ERROR::Newly created remote target's body incorrect" self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created remote target's correct" self.set_status_passed() return ValidationStatus.PASSED def _test_case_logical_volume_has_link_to_created_target(self, logical_volume): print "TEST_CASE::Check for a link to the created target in logical volume's body" status, status_code, response_body, _ = self.api_caller.get_resource(logical_volume) if not status: print "ERROR::Wrong status code of GET response: %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED else: try: target = urlparse(self.created_remote_target).path if not {"@odata.id": target} in response_body["Links"]["Targets"]\ and not {"@odata.id": target + "/"} in response_body["Links"]["Targets"]: print "ERROR::No link to the created target in the logical volume's body" self.set_status_failed() return ValidationStatus.FAILED except KeyError: print "ERROR::No Links/Targets array present in the logical volume's body" self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::A link from the logical volume to the target is present" self.set_status_passed() return ValidationStatus.PASSED def _test_case_update_created_remote_target(self): print "TEST_CASE::Update the created remote target" patch_remote_target_payload = dict( Initiator=[dict( iSCSI=dict( InitiatorIQN="cts.initiator:patched_cts_initiator" ) )] ) status, status_code, response_body, _ = self.api_caller.patch_resource(self.created_remote_target, payload=patch_remote_target_payload) if not status: print "ERROR::Wrong status code of PATCH reponse %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED status, status_code, response_body, _ = self.api_caller.get_resource(self.created_remote_target) if not status: print "ERROR::Wrong status code of GET reponse %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Verifying whether the remote target's update has taken place" if not JsonComparator.compare_json_to_golden(response_body, patch_remote_target_payload): print "ERROR::Newly created remote target could not be updated" self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created remote target updated correctly" self.set_status_passed() return ValidationStatus.PASSED def _test_case_delete_created_remote_target(self): print "TEST_CASE::Delete the created remote target" status, status_code, response_body, _ = self._delete_resource(self.created_remote_target) if not status: self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Newly created remote target deleted" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_deleted_remote_target(self): print "TEST_CASE::Get the deleted remote target" status, status_code, response_body, _ = self.api_caller.get_resource(self.created_remote_target, acceptable_return_codes = [ReturnCodes.NOT_FOUND]) if not status: print "ERROR::Wrong status code of GET response after deletion: %s" % status_code self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Newly created remote target not found (as intended)" self.set_status_passed() return ValidationStatus.PASSED def _delete_resource(self, resource_link): status, status_code, response_body, headers = self.api_caller.delete_resource(resource_link) if not status: print "ERROR::Wrong status code of DELETE reponse: %s" % status_code print "ERROR::Response body:\n%s" % response_body return status, status_code, response_body, headers def crud_remote_target(self, called_from_crud_logical_drive=None): """ Test is checking for rsa compute blade presence :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer :type self.metadata_container: cts_core.metadata.self.metadata_container.MetadataContainer """ target_iqn = "cts.target:cts_test_target" target_iqns = set() logical_volumes = dict() remote_target_collections = dict() for resource_link, resource in self.discovery_container.iteritems(): if self.metadata_container.entities[MetadataConstants.LOGICAL_DRIVE].compare_entities(resource.odata_type): print "DEBUG::found entity of type %s in %s" % (MetadataConstants.LOGICAL_DRIVE, resource_link) try: # looking for unprotected LVs with no targets associated with it if ((resource.body["Mode"] == "LV" or resource.body["Mode"] == "RBD") and not len(resource.body["Links"]["Targets"]) and not resource.body["Protected"]): logical_volumes[resource_link] = resource except: print "WARNING::Incorrect resource %s structure" % resource_link elif self.metadata_container.entities[MetadataConstants.REMOTE_TARGET_COLLECTION].compare_entities(resource.odata_type): remote_target_collections[resource_link] = resource elif self.metadata_container.entities[MetadataConstants.REMOTE_TARGET].compare_entities( resource.odata_type): try: for address in resource.body["Addresses"]: target_iqns.add(address["iSCSI"]["TargetIQN"]) except KeyError: pass if target_iqn in target_iqns: iqn_number = 0 while target_iqn + str(iqn_number) in target_iqns: iqn_number += 1 target_iqn = target_iqn + str(iqn_number) if not logical_volumes: print "WARNING::Insufficient resources available on API for test. Could not find an unprotected logical volume with"\ " no targets attached" if called_from_crud_logical_drive: print "ERROR::Creating a new logical volume for the target failed, skipping remote target tests" return ValidationStatus.BLOCKED else: print "MESSAGE::Trying to create a new logical volume for the target, then proceeding with remote target CRUD test" return self.crud_logical_drive(called_from_crud_remote_target=True) chosen_logical_volume = logical_volumes.keys()[0] chosen_remote_target_collection = remote_target_collections.keys()[0] print "MESSAGE::Remote Target CRUD test will be performed on remote target collection %s, creating remote target " \ "based on %s" % (chosen_remote_target_collection, chosen_logical_volume) self.post_remote_target_payload = dict( Addresses=[dict( iSCSI=dict( TargetIQN=target_iqn, TargetLUN=[dict( LUN=1, LogicalDrive={"@odata.id": chosen_logical_volume} )], ) )], Initiator=[dict( iSCSI=dict( InitiatorIQN="cts.initiator:initiator_cts_test" ) )] ) status = self._test_case_create_remote_target(chosen_remote_target_collection) if status == ValidationStatus.PASSED: # only perform other tests if creation was successful status = ValidationStatus.join_statuses(status, self._test_case_get_created_remote_target()) status = ValidationStatus.join_statuses(status, self._test_case_logical_volume_has_link_to_created_target( chosen_logical_volume)) status = ValidationStatus.join_statuses(status, self._test_case_update_created_remote_target()) status = ValidationStatus.join_statuses(status, self._test_case_delete_created_remote_target()) status = ValidationStatus.join_statuses(status, self._test_case_get_deleted_remote_target()) return status
class ApiCallerCallsUnitTest(unittest.TestCase): def setUp(self): configuration = Configuration(**dict(ApiEndpoint=API_ENDPOINT)) self.api_caller = ApiCaller(configuration) @unittest.skip("Temporary change related to bug in Discovery mechanism") def test_get_on_empty_resource(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 200 response.headers = {} response.text = "" requests_get_mock.return_value = response with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR::url=/resource Get failed. Status code: None;", output.raw) def test_empty_service_root(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.ConnectionError() with StdoutCapture() as output: self.api_caller.get_resource("/redfish/v1", DiscoveryContainer()) self.assertIn( "ERROR::Get url=http://1.2.3.4:567/redfish/v1 Error <class 'requests.exceptions.ConnectionError'>:;", output.raw) @unittest.skip("Temporary change related to bug in Discovery mechanism") def test_incorrect_status_code(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 500 response.headers = {} response.text = "{}" requests_get_mock.return_value = response with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR::url=/resource Get failed. Status code: 500;", output.raw) def test_request_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.RequestException() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.RequestException'>:;", output.raw) def test_connection_error_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.ConnectionError() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.ConnectionError'>:;", output.raw) def test_http_error_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.HTTPError() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.HTTPError'>:", output.raw) def test_url_required_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.URLRequired() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR::Get url=http://1.2.3.4:567/resource Error <class " "'requests.exceptions.URLRequired'>:", output.raw) def test_to_many_redirects_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.TooManyRedirects() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.TooManyRedirects'>:;", output.raw) def test_timeout_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.Timeout() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.Timeout'>:;", output.raw) def test_incorrect_body(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 200 response.headers = {} response.text = "not a json" requests_get_mock.return_value = response with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR", output.raw) def test_parse_big_ordered_tree_response(self): from collections import OrderedDict response_payload = OrderedDict( [(u'@odata.context', u'/redfish/v1/$metadata#ServiceRoot.ServiceRoot'), (u'@odata.etag', u'W/"1557488360"'), (u'@odata.id', \ u'/redfish/v1/'), (u'@odata.type', u'#ServiceRoot.v1_3_1.ServiceRoot'), (u'AccountService', OrderedDict([(u'@odata.id', u'/redfish/v1/AccountService')])), (u'Chassis', OrderedDict([(u'@odata.id', u'/redfish/v1/Chassis')])), (u'Description', u'The service root \ for all Redfish requests on this host \ ' ), (u'EventService', OrderedDict([(u' @ odata.id', u' / redfish / v1 / EventService')])), (u'Id', u'RootService'), ( u'Links', OrderedDict( [(u'Oem', OrderedDict([(u'Ami', OrderedDict([(u'@odata.id', u'/redfish/v1/configurations')]))])), (u'Sessions', OrderedDict([(u'@odata.id', u'/redfish/v1/SessionService/Sessions')]))])), ( u'Managers', OrderedDict([(u'@odata.id', u'/redfish/v1/Managers')])), (u'Name', u'PSME Service Root'), ( u'Oem', OrderedDict([(u'Ami', OrderedDict([(u'Chassislocation', OrderedDict( [(u'@odata.id', u'/redfish/v1/Chassislocation')])), (u'Configurations', OrderedDict([(u'@odata.id', u'/redfish/v1/configurations')])), (u'PsmeVersion', u'2.4.181218.tb1')])), (u'Intel_RackScale', OrderedDict([(u'@odata.type', u'#Intel.Oem.ServiceRoot'), (u'ApiVersion', u'2.4.0'), ( u'TelemetryService', OrderedDict([(u'@odata.id', u'/redfish/v1/TelemetryService')]))]))])), (u'Product', u'AMI Redfish Server'), (u'ProtocolFeaturesSupported', OrderedDict([(u'ExpandQuery', OrderedDict( [(u'ExpandAll', True), (u'Levels', True), (u'Links', True), (u'MaxLevels', 5), (u'NoLinks', True)])), (u'FilterQuery', True), (u'SelectQuery', True)])), ( u'RedfishVersion', u'1.5.0'), (u'Registries', OrderedDict([(u'@odata.id', u'/redfish/v1/Registries')])), ( u'SessionService', OrderedDict([(u'@odata.id', u'/redfish/v1/SessionService')])), (u'Systems', OrderedDict([( u'@odata.id', u'/redfish/v1/Systems')])), (u'Tasks', OrderedDict([(u'@odata.id', u'/redfish/v1/TaskService')])), (u'UUID', u'ffffffff-ffff-ffff-ffff-ffffffffffff'), (u'UpdateService', OrderedDict([(u'@odata.id', u'/redfish/v1/UpdateService')]))]) with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 200 response.headers = {} response.text = response_payload requests_get_mock.return_value = response with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIsNot("ERROR", output.raw) def test_no_content(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 204 response.headers = {} response.text = None requests_get_mock.return_value = response link, status, status_code, response_body, headers = \ self.api_caller.get_resource("/resource", DiscoveryContainer(), acceptable_return_codes = [204]) self.assertTrue(status) self.assertEqual(status_code, 204) self.assertEqual(response_body, dict()) response.json.assert_not_called() def test_async_create_without_location(self): with mock.patch('requests.post') as requests_post_mock: response = Mock() response.status_code = ReturnCodes.ACCEPTED response.headers = {} response.text = None requests_post_mock.return_value = response with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 204 response.headers = {} response.text = None requests_get_mock.return_value = response with StdoutCapture() as output: status, status_code, response_body, headers = self.api_caller.post_resource( 'odata_id', DiscoveryContainer(), payload={}) self.assertIn('Location header not found', '\n'.join(output)) def test_unexpected_async_post_response(self): with mock.patch('requests.post') as requests_get_mock: response = Mock() response.status_code = ReturnCodes.ACCEPTED response.headers = {} response.text = "{}" requests_get_mock.return_value = response with self.assertRaises(AsyncOperation): self.api_caller.post_resource('odata_id', DiscoveryContainer(), payload={}, wait_if_async=False) def test_async_post_response(self): with mock.patch('requests.post') as requests_post_mock: not_done = Mock() not_done.status_code = ReturnCodes.ACCEPTED not_done.headers = {'Location': 'location'} not_done.text = None requests_post_mock.return_value = not_done with mock.patch('requests.get') as requests_get_mock: done = Mock() done.status_code = ReturnCodes.OK done.headers = {'Location': 'location'} done.text = "{ \"done\": true }" requests_get_mock.side_effect = [not_done, not_done, done] with StdoutCapture() as output: status, status_code, response_body, headers = self.api_caller.post_resource( 'odata_id', DiscoveryContainer(), payload={}) self.assertTrue(response_body['done']) @unittest.skip("Temporary change related to bug in Discovery mechanism") def test_resource_in_discovery_container_after_get_patch_delete(self): with mock.patch('requests.get') as requests_get_mock: resource = {"@odata.id": "odata.id", "something": "irrelevant"} get_response = Mock() get_response.status_code = ReturnCodes.OK get_response.headers = {} get_response.text = json.dumps(resource) requests_get_mock.return_value = get_response discovery_container = DiscoveryContainer() self.api_caller.get_resource("/resource", discovery_container) self.assertEqual( discovery_container["http://{API_ENDPOINT}/resource".format( API_ENDPOINT=API_ENDPOINT)].body, resource) patched_resource = { "@odata.id": "odata.id", "something": "relevant" } get_response.text = json.dumps(patched_resource) with mock.patch('requests.patch') as requests_patch_mock: patch_response = Mock() patch_response.status_code = ReturnCodes.OK patch_response.headers = {} patch_response.text = "{}" requests_patch_mock.return_value = patch_response _, _, _, _ = self.api_caller.patch_resource( "/resource", discovery_container) self.assertEqual( discovery_container[ "http://{API_ENDPOINT}/resource".format( API_ENDPOINT=API_ENDPOINT)].body, patched_resource) with mock.patch('requests.delete') as requests_delete_mock: delete_response = Mock() delete_response.status_code = ReturnCodes.NO_CONTENT delete_response.headers = {} delete_response.text = "" requests_delete_mock.return_value = delete_response _, _, _, _ = self.api_caller.delete_resource( "/resource", discovery_container) self.assertNotIn("/resource", discovery_container) def test_get_xml(self): with mock.patch('requests.get') as get: response = Mock() response.status_code = ReturnCodes.OK response.headers = {} response.text = "<xml></xml>" get.return_value = response link, status, status_code, response_body, headers = self.api_caller.get_xml( "uri") self.assertEqual("<xml></xml>", response_body)
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" def run(self): if self.metadata_container is None: return print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover(MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) self.api_caller = ApiCaller(self.configuration) self.crud_vlan() def _test_case_create_vlan(self, vlan_network_interface_collection): print "TEST_CASE::Create VLAN" status, status_code, response_body, headers = \ self.api_caller.post_resource( vlan_network_interface_collection, self.discovery_container, payload=self.post_vlan_payload) if not status: cts_error("Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_vlan_network_interface = headers["Location"] self.created_vlan_network_interface_netlock = self.discovery_container.get_netloc( vlan_network_interface_collection) print "MESSAGE::Newly created VLAN Network Interface URI: %s" % self.created_vlan_network_interface except KeyError: cts_error("Incorrect response - header shall contain the location of the created resource") self.set_status_failed() return False print "MESSAGE::VLAN Network Interface created" self.set_status_passed() return True def _test_case_get_created_vlan(self): print "TEST_CASE::Get created VLAN" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_vlan_network_interface, self.discovery_container, api_endpoint_override=self.created_vlan_network_interface_netlock) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created VLAN Network Interface to given payload" # PSME is unable to set "Name" and "VLANEnable" fields properly but only "VLANEnable" is mandatory if not JsonComparator.compare_json_to_golden(response_body, self.post_vlan_payload, ignore=["/VLANEnable"]): cts_error("Newly created VLAN Network Interface's body incorrect") self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface correct" self.set_status_passed() return True def _test_case_delete_created_vlan(self): print "TEST_CASE::Delete created VLAN" status, status_code, response_body, _ = self.api_caller.delete_resource(self.created_vlan_network_interface, self.discovery_container) if not status: cts_error("Wrong status code of DELETE response: {status_code};" " response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface deleted" self.set_status_passed() return True def _test_case_get_deleted_vlan(self): print "TEST_CASE::Get deleted VLAN" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_vlan_network_interface, self.discovery_container, acceptable_return_codes=[ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_vlan_network_interface_netlock) if not status: cts_error("Wrong status code of GET response after deletion: {status_code};" " response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface not found (as intended)" self.set_status_passed() return True def crud_vlan(self): """ Test is trying to perform CRUD (create, read, update, delete) operations on a VLAN Network Interface resource """ requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.VLAN_NETWORK_INTERFACE_COLLECTION, min=1), ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) if status is ValidationStatus.PASSED_WITH_WARNINGS: cts_warning("Without some components it is not possible to perform tests.") print "STATUS::{status}".format(status=status) return ValidationStatus.PASSED_WITH_WARNINGS if status is ValidationStatus.FAILED: return ValidationStatus.BLOCKED vlan_network_interface_collections = \ dict(self.discovery_container.get_resources(MetadataConstants.VLAN_NETWORK_INTERFACE_COLLECTION, any_child_version=True)) chosen_vlan_network_interface_collection = None new_vlan_id = 0 for vlan_network_interface_collection in vlan_network_interface_collections: try: parent_port = get_parent_odata_id(vlan_network_interface_collection) parent_port_id = self.discovery_container[parent_port].body["PortId"] except: continue # For arista setup, only ever try adding VLANs on the main /1 port if parent_port_id.endswith("/2") or parent_port_id.endswith("/3") or parent_port_id.endswith("/4"): continue present_vlan_ids = [] vlan_network_interfaces = \ dict(self.discovery_container. get_resources(MetadataConstants.VLAN_NETWORK_INTERFACE, constraints=[from_collection(vlan_network_interface_collection)], any_child_version=True)) if len(vlan_network_interfaces) > MAX_VLAN_ID - MIN_VLAN_ID: print "MESSAGE::No free VLAN id available on collection {}".format(vlan_network_interface_collection) continue for resource_link, resource in vlan_network_interfaces.iteritems(): try: present_vlan_ids.append(resource.body["VLANId"]) except: cts_error("Inorrect resource {resource_link:id} structure", **locals()) for possible_id in range(MIN_VLAN_ID, MAX_VLAN_ID + 1): if possible_id not in present_vlan_ids: chosen_vlan_network_interface_collection = vlan_network_interface_collection new_vlan_id = possible_id break if chosen_vlan_network_interface_collection: break if chosen_vlan_network_interface_collection: print "MESSAGE::CRUD test will be performed on vlan network interface collection %s" % \ chosen_vlan_network_interface_collection else: cts_warning('No free VLAN id available on any of the suitable ports (perhaps there are no suitable ports)') return ValidationStatus.BLOCKED self.post_vlan_payload = dict(VLANId=new_vlan_id, VLANEnable=True, Oem=dict( Intel_RackScale=dict( Tagged=True ))) if self._test_case_create_vlan(chosen_vlan_network_interface_collection): self._test_case_get_created_vlan() self._test_case_delete_created_vlan() self._test_case_get_deleted_vlan()
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" def run(self): print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) discovery_container, status = api_explorer.discover( MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.ETHERNET_SWITCH, min=1), Requirement(MetadataConstants.ETHERNET_SWITCH_PORT, min=1) ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(discovery_container) self.api_caller = ApiCaller(self.configuration) self.crud_vlan(discovery_container) def _test_case_create_vlan(self, vlan_network_interface_collection): print "TEST_CASE::Create VLAN" status, status_code, response_body, headers = self.api_caller.post_resource( vlan_network_interface_collection, payload=self.post_vlan_payload) if not status: print "ERROR::Wrong status code of POST response: %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return False try: self.created_vlan_network_interface = headers["Location"] print "MESSAGE::newly created vlan network interface url %s" % self.created_vlan_network_interface except KeyError: print "ERROR::in header shall be provided location to created resource" self.set_status_failed() return False print "MESSAGE::vlan network interface created" self.set_status_passed() return True def _test_case_get_created_vlan(self): print "TEST_CASE::Get created VLAN" status, status_code, response_body, _ = self.api_caller.get_resource( self.created_vlan_network_interface) if not status: print "ERROR::Wrong status code of GET response: %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return False else: print "MESSAGE::Comparing newly created vlan network interface to given payload" # PSME is unable to set "Name" and "VLANEnable" fields properly if not JsonComparator.compare_json_to_golden( response_body, self.post_vlan_payload, ignore=["/Name", "/VLANEnable"]): print "ERROR::newly created vlan network interface's body incorrect" self.set_status_failed() print "MESSAGE::proceeding with deleting the resource" return False print "MESSAGE::newly created vlan network interface correct" self.set_status_passed() return True def _test_case_delete_created_vlan(self): print "TEST_CASE::Delete created VLAN" status, status_code, response_body, _ = self.api_caller.delete_resource( self.created_vlan_network_interface) if not status: print "ERROR::Wrong status code of DELETE reponse %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return False print "MESSAGE::newly created vlan network interface deleted" self.set_status_passed() return True def _test_case_get_deleted_vlan(self): print "TEST_CASE::Get deleted VLAN" status, status_code, response_body, _ = self.api_caller.get_resource( self.created_vlan_network_interface, acceptable_return_codes=[ReturnCodes.NOT_FOUND]) if not status: print "ERROR::Wrong status code of GET response after deletion: %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return False print "MESSAGE::newly created vlan network interface not found (as intended)" self.set_status_passed() return True def crud_vlan(self, discovery_container): """ Test is trying to perform CRUD (create, read, update, delete) operations on a VLAN Network Interface resource :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer """ vlan_network_interface_collections = dict() for resource_link, resource in discovery_container.iteritems(): if self.metadata_container.entities[ MetadataConstants. VLAN_NETWORK_INTERFACE_COLLECTION].compare_entities( resource.odata_type): print "DEBUG::found entity of type %s in %s" % ( MetadataConstants.VLAN_NETWORK_INTERFACE_COLLECTION, resource_link) vlan_network_interface_collections[resource_link] = resource if not vlan_network_interface_collections: print "ERROR::Insufficient resources available on API for test. Found %s network interface collections" % \ len(vlan_network_interface_collections) return ValidationStatus.BLOCKED chosen_vlan_network_interface_collection = vlan_network_interface_collections.keys( )[0] print "MESSAGE::test will be performed on vlan network interface collection %s" % chosen_vlan_network_interface_collection present_vlan_ids = [] for resource_link, resource in discovery_container.iteritems(): if self.metadata_container.entities[MetadataConstants.VLAN_NETWORK_INTERFACE].compare_entities(resource.odata_type) \ and chosen_vlan_network_interface_collection in resource_link: print "DEBUG::found entity of type %s in %s" % ( MetadataConstants.VLAN_NETWORK_INTERFACE, resource_link) try: present_vlan_ids.append(resource.body["VLANId"]) except: print "WARNING::Incorrect resource %s structure" % resource_link new_vlan_id = 0 for possible_id in range(1, 4096): # VLANId is a value of 1 - 4095 if possible_id not in present_vlan_ids: new_vlan_id = possible_id break self.post_vlan_payload = dict( Name=unicode("CTS_CRUD_TEST_VLAN"), VLANId=new_vlan_id, VLANEnable=True, Oem=dict(Intel_RackScale=dict(Tagged=True))) if self._test_case_create_vlan( chosen_vlan_network_interface_collection): self._test_case_get_created_vlan() self._test_case_delete_created_vlan() self._test_case_get_deleted_vlan()