Beispiel #1
0
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)
Beispiel #2
0
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()
Beispiel #3
0
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()
Beispiel #4
0
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
Beispiel #5
0
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
Beispiel #6
0
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
Beispiel #7
0
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
Beispiel #8
0
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)
Beispiel #9
0
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()
Beispiel #10
0
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()