Exemple #1
0
    def __init__(self,
                 metadata_container,
                 configuration,
                 strategy,
                 requirements=None,
                 skip_list=None):
        """
        :type metadata_container: cts_core.metadata.metadata_container.MetadataContainer
        :type configuration: cts_framework.configuration.configuration.Configurations
        :type strategy: cts_core.validation.patch.patching_strategy.PatchingStrategy
        :type requirements: list
        """
        self._metadata_container = metadata_container
        self._api_caller = ApiCaller(configuration)
        self._strategy = strategy
        self._preconditions = Preconditions(metadata_container, requirements)
        self._fast_mode = getenv('CTS_PATCH_ONE_PER_TYPE')
        self._types_patched = set()
        self._skip_list = skip_list

        if self._fast_mode:
            cts_warning(
                "Test results may be UNRELIABLE - PATCHING ONLY ONE RESOURCE OF EACH TYPE!"
            )

        self._endpoints_and_keys_to_ignore = []
        if self._skip_list:
            cts_warning(
                "Test results may be UNRELIABLE - Some elements presented on REST API will be IGNORED!"
            )
            self._endpoints_and_keys_to_ignore = self._load_ignore_list_file(
                skip_list)
Exemple #2
0
    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))
Exemple #3
0
    def _check_endpoint_connection(cls, can_run, configuration):
        if can_run:
            service_root = ApiCaller(configuration)
            link, kwargs = service_root._build_request(
                configuration.ApiEndpoint)
            url = link.link[:link.link.rfind("/") + 1] + "redfish/v1"

            response, status_code = service_root._do_request(
                kwargs, url, "Get")
            if status_code in range(400, 600):
                cts_error("{url:id} Get failed. Status code: {code}",
                          url=url,
                          code=status_code)
                try:
                    cts_error(
                        '{code} : {description}. Please check your configuration.',
                        code=status_code,
                        description=ErrorReturnCodes.ErrorCodes[status_code])
                except KeyError:
                    pass
                return False
            else:
                return True
        else:
            return False
Exemple #4
0
    def run(self):
        try:
            json_loader = JsonLoader()
        except OSError:
            print('ERROR:: No proper action found. Please reinstall a CTS')
            return

        if self.metadata_container is None:
            return

        if not self.configuration.UseCase:
            json_loader.print_available_actions()
            return

        try:
            actions_to_take = json_loader.get_use_case(
                self.configuration.UseCase)
            specific_action = json_loader.get_use_case_action(
                actions_to_take['filepath'])
        except (IndexError, TypeError):
            json_loader.print_available_actions()
            print('ERROR:: The selected action was not found. Exiting..')
            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)
        configuration = self.configuration
        self.api_caller = ApiCaller(configuration)

        print('TEST_CASE::Action Runner')
        status = self.action_runner(specific_action)
        print('STATUS::{status}'.format(status=status))
Exemple #5
0
 def __init__(self, metadata_container, configuration):
     """
     :type metadata_container: cts_core.metadata.metadata_container.MetadataContainer
     :type configuration:
     """
     self._metadata_container = metadata_container
     self._api_caller = ApiCaller(configuration)
     self._config_property_reader = configuration
     self._status = ValidationStatus.UNKNOWN
     self._discovery_container = None
     self._bfs_queue = []
Exemple #6
0
class ApiCallerPayloadUnitTest(unittest.TestCase):
    def setUp(self):
        configuration = Configuration(**dict(ApiEndpoint=API_ENDPOINT))
        self.api_caller = ApiCaller(configuration)

    def test_payload_given(self):
        params, kwargs = self.api_caller._build_request(RESOURCE, payload=PAYLOAD)
        self.assertIn("data", kwargs)
        self.assertEqual(kwargs["data"], json.dumps(PAYLOAD))

    def test_payload_not_given(self):
        params, kwargs = self.api_caller._build_request(RESOURCE)
        self.assertNotIn("data", kwargs)
Exemple #7
0
    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))
Exemple #8
0
    def test_certs_given(self):
        configuration = Configuration(**dict(CertificateCertFile=CERT_FILE, CertificateKeyFile=CERT_KEY,
                                                      ApiEndpoint=API_ENDPOINT))
        params, kwargs = ApiCaller(configuration)._build_request(RESOURCE)

        self.assertIn("cert", kwargs)
        self.assertEqual(kwargs["cert"], (CERT_FILE, CERT_KEY))
Exemple #9
0
    def test_basic_auth_given(self):
        configuration = Configuration(**dict(User=USER, Password=PASSWORD,
                                                      ApiEndpoint=API_ENDPOINT))
        params, kwargs = ApiCaller(configuration)._build_request(RESOURCE)

        self.assertIn("auth", kwargs)
        self.assertEqual(kwargs["auth"], (USER, PASSWORD))
Exemple #10
0
    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()
Exemple #11
0
    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.generate_unique_names() if self.configuration.UniqueInitiatorName \
            != "generate" else self.configuration.UniqueInitiatorName
        self.target_unique_name = self.generate_unique_names() if self.configuration.UniqueTargetName \
            != "generate" else 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 __init__(self,
              metadata_container,
              configuration,
              strategy,
              requirements=None):
     """
     :type metadata_container: cts_core.metadata.metadata_container.MetadataContainer
     :type configuration: cts_framework.configuration.configuration.Configurations
     :type strategy: cts_core.validation.patch.patching_strategy.PatchingStrategy
     :type requirements: list
     """
     self._metadata_container = metadata_container
     self._api_caller = ApiCaller(configuration)
     self._strategy = strategy
     self._preconditions = Preconditions(metadata_container, requirements)
     self._fast_mode = getenv('CTS_PATCH_ONE_PER_TYPE')
     self._types_patched = set()
     if self._fast_mode:
         cts_warning(
             "Test results may be UNRELIABLE - PATCHING ONLY ONE RESOURCE OF EACH TYPE!"
         )
Exemple #13
0
    def run(self):
        json_loader = JsonLoader()
        if self.metadata_container is None:
            return

        if not self.configuration.UseCase:
            json_loader.print_available_actions()
            return

        actions_to_take = json_loader.get_use_case(self.configuration.UseCase)
        specific_action = json_loader.get_use_case_action(
            actions_to_take['filepath'])

        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)
        configuration = self.configuration
        self.api_caller = ApiCaller(configuration)

        print('TEST_CASE::Action Runner')
        status = self.action_runner(specific_action)
        print('STATUS::{status}'.format(status=status))
Exemple #14
0
    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()
Exemple #15
0
    def run(self):
        json_loader = JsonLoader()
        if self.metadata_container is None:
            return

        if not self.configuration.UseCase:
            json_loader.print_available_actions()
            return

        actions_to_take = json_loader.get_use_case(self.configuration.UseCase)
        specific_action = json_loader.get_use_case_action(actions_to_take['filepath'])

        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)
        configuration = self.configuration
        self.api_caller = ApiCaller(configuration)

        print('TEST_CASE::Action Runner')
        status = self.action_runner(specific_action)
        print('STATUS::{status}'.format(status=status))
Exemple #16
0
    def test_key_given_cert_not(self):
        configuration = Configuration(**dict(CertificateKeyFile=CERT_KEY, ApiEndpoint=API_ENDPOINT))
        params, kwargs = ApiCaller(configuration)._build_request(RESOURCE)

        self.assertNotIn("cert", kwargs)
Exemple #17
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)
Exemple #18
0
 def test_ssl_default(self):
     configuration = Configuration(**dict(ApiEndpoint=API_ENDPOINT))
     url, kwargs = ApiCaller(configuration)._build_request(RESOURCE)
     self.assertEqual(url, Link("http://%s%s" % (API_ENDPOINT, RESOURCE), API_ENDPOINT))
Exemple #19
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()
Exemple #20
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
Exemple #21
0
class ValidateUseCase(CtsTestScript):
    TIMEOUT = 600
    DESCRIPTION = """test case validating api responses against provided use case"""

    CONFIGURATION_PARAMETERS = [
        TestScript.ConfigurationParameter(parameter_name="UseCase",
                                          parameter_description="Use Case name",
                                          parameter_type=str,
                                          is_required=False,
                                          parameter_default_value=[])
    ]

    def run(self):
        json_loader = JsonLoader()
        if self.metadata_container is None:
            return

        if not self.configuration.UseCase:
            json_loader.print_available_actions()
            return

        actions_to_take = json_loader.get_use_case(self.configuration.UseCase)
        specific_action = json_loader.get_use_case_action(actions_to_take['filepath'])

        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)
        configuration = self.configuration
        self.api_caller = ApiCaller(configuration)

        print('TEST_CASE::Action Runner')
        status = self.action_runner(specific_action)
        print('STATUS::{status}'.format(status=status))

    def action_runner(self, specific_action):
        actions_status = ValidationStatus.RUNNING

        for action_step, action in enumerate(specific_action['actions'], 1):
            step_counter = "%d/%d" % (action_step, len(specific_action['actions']))
            action_type = action['type']
            if action_type == 'timeout':
                try:
                    self.perform_timeout(step_counter, action['value'])
                except IndexError:
                    self.perform_timeout(step_counter)
                continue

            retry_if_fail, retry_timeout = self.__get_retry_information(action)
            action_args = (specific_action['name'],
                           action['name'],
                           action['action'],
                           step_counter,
                           int(retry_if_fail),
                           int(retry_timeout))

            if action_type == 'external':
                actions_status = ValidationStatus.join_statuses(self.perform_external_command(*action_args))
                continue
            elif action_type == 'usecase':
                actions_status = ValidationStatus.join_statuses(self.perform_action(*action_args))
        return actions_status

    @staticmethod
    def perform_timeout(step_counter, timeout=1):
        cts_message("Step: %s" % step_counter)
        cts_message("Timeout: %s" % timeout)
        sleep(float(timeout))

    @CaseInformation
    def perform_external_command(self, test_name, case_name, action, step_counter, retry=0, timeout=0):
        """
        Perform command on external host

        :param test_name: !! used by decorator
        :param case_name: !! used by decorator
        :param action:
        :param step_counter: !! used by decorator
        :param retry:
        :param timeout:
        :return:
        """
        #  CONNECTION PHASE
        response_from_remote_host = self._connection_phase(action)

        #  VERIFICATION PHASE
        print(response_from_remote_host)
        verification_status = self._verification_phase(action, raw_response=response_from_remote_host)

        if not verification_status:
            return ValidationStatus.FAILED
        return ValidationStatus.PASSED

    @staticmethod
    def run_commands_on_host(commands, credentials):
        responses_from_ssh_target = []

        with ExternalCommandOverSSH(**credentials) as host:
            for cmd in commands:
                _, stdout, stderr = host.exec_command(cmd)
                err = stderr.read()
                if err is not '':
                    cts_error(err)
                responses_from_ssh_target.append(stdout.read())

        return responses_from_ssh_target[-1]

    def _connection_phase(self, action):
        """
        Do actions on remote and return last response
        """
        tgt_credential = action['target_credential']
        command = action['command']

        first_target = {
            "target_hostname": tgt_credential['hostname'],
            "target_username": tgt_credential['username'],
            "target_password": tgt_credential['password'],
            "target_port": tgt_credential['port']
        }

        try:
            second_tgt_credential = action['jump_target_credential']
            second_target = {
                "second_hostname": second_tgt_credential['hostname'],
                "second_username": second_tgt_credential['username'],
                "second_password": second_tgt_credential['password'],
                "second_port": second_tgt_credential['port']
            }
        except KeyError:
            return self.run_commands_on_host(command, first_target)

        return self.run_commands_on_host(command, self.__merge_dicts(first_target,
                                                                     second_target))

    def _verification_phase(self, action, raw_response=None):
        external_command = action['response']['command']
        expected_response = action['response']['result']

        command_type, command_location = self.__verify_command_type(external_command)
        command_args = self.__verify_command_args(external_command)

        external_action = ExecuteExternalScript(script_path=command_location,
                                                raw_response=raw_response,
                                                expected_result=expected_response)
        if not external_action(**command_args):
            cts_error('Script response IS NOT equal to excepted.')
            return False
        cts_message('Script response is equal to excepted.')
        return True

    @CaseInformation
    def perform_action(self, test_name, case_name, action, step_counter, retry=0, timeout=0):
        case_action = CaseAction(**action)

        cts_message("_____")
        cts_message("{name}: {step}\n\t\t {description}".format(name=test_name,
                                                                step=step_counter,
                                                                description=case_name))

        cts_message("{request_method}: {request}".format(request_method=action['request_method'],
                                                         request=action['request'].replace("{", "(").replace("}", ")")))

        resource_type, resource_id, resource_action = self._get_target_information(case_action.request)
        discovered_systems = self.discovery_container.get_resources(resource_type,
                                                                    any_child_version=True)

        if not discovered_systems:
            cts_error('Cannot find %s' % resource_type)
            return ValidationStatus.BLOCKED

        try:
            computer_odata_id = self.discovery_container.get(discovered_systems[resource_id][0]).odata_id
        except IndexError:
            cts_error('Cannot find %s' % resource_type)
            return ValidationStatus.BLOCKED

        action_endpoint = '{odata_id}/{action_name}'.format(odata_id=computer_odata_id,
                                                            action_name='/'.join(resource_action))

        status, _, _, _ = self.api_caller.post_resource(action_endpoint,
                                                        self.discovery_container,
                                                        case_action.payload,
                                                        acceptable_return_codes=case_action.response)

        if (not status) and (retry > 0):
            cts_message("Operation failed, left retry %s" % retry)
            cts_message("Timeout: %s" % timeout)
            sleep(float(timeout))

            self.perform_action(test_name, case_name, action, step_counter, retry=(retry - 1), timeout=timeout)

        if not status:
            cts_error('Cannot execute action %s' % action_endpoint)
            return ValidationStatus.FAILED

        cts_message('End of: %s' % case_name)
        return ValidationStatus.PASSED

    def _get_target_information(self, request):
        request_cleaned = request.replace('{', '').replace('}', '').split('/')

        resource_type = request_cleaned[0]
        resource_id = self._choose_resource(request_cleaned[1])
        action = request_cleaned[2:]

        return resource_type, resource_id, action

    @staticmethod
    def _choose_resource(number):
        return 0 if number == 'any' else int(number)

    @staticmethod
    def __verify_command_type(external_cmd):
        command_type, command_location = (None,) * 2
        try:
            command_type = external_cmd[0]
            if command_type.lower() != 'python':
                cts_message('CTS support only Python external scripts')
                return False

            command_location = external_cmd[1]
        except IndexError:
            cts_error('Missing verification command details')
        return command_type, command_location

    def __verify_command_args(self, external_cmd):
        command_args = None
        try:
            command_args = self.__generate_dict_from_custom(external_cmd[2:])
        except IndexError:
            pass
        return command_args

    @staticmethod
    def __get_retry_information(action):
        """
        :param action:
        :return: retry_if_fail, retry_timeout
        """
        retry_if_fail, retry_timeout = 0, 1
        try:
            retry_if_fail, retry_timeout = action['action']['retry_if_fail'], \
                                           action['action']['retry_timeout']
        except KeyError:
            pass
        return retry_if_fail, retry_timeout

    #  HELPERS
    @staticmethod
    def __generate_dict_from_custom(list_values):
        dict_from_list = {}
        for element in list_values:
            k, v = element.split(":")
            dict_from_list[k] = v
        return dict_from_list

    @staticmethod
    def __merge_dicts(a, b):
        c = a.copy()
        c.update(b)
        return c
Exemple #22
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()
Exemple #23
0
    def test_basic_auth_not_given(self):
        configuration = Configuration(**dict(ApiEndpoint=API_ENDPOINT))
        params, kwargs = ApiCaller(configuration)._build_request(RESOURCE)

        self.assertNotIn("auth", kwargs)
Exemple #24
0
 def setUp(self):
     configuration = Configuration(**dict(ApiEndpoint=API_ENDPOINT))
     self.api_caller = ApiCaller(configuration)
Exemple #25
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)
Exemple #26
0
class ApiExplorer:
    def __init__(self, metadata_container, configuration):
        """
        :type metadata_container: cts_core.metadata.metadata_container.MetadataContainer
        :type configuration:
        """
        self._metadata_container = metadata_container
        self._api_caller = ApiCaller(configuration)
        self._config_property_reader = configuration
        self._status = ValidationStatus.UNKNOWN
        self._discovery_container = None
        self._bfs_queue = []

    def discover(self,
                 url,
                 expected_odata_type,
                 discovery_container=None,
                 api_endpoint_override=None):
        """
        :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer
        :rtype: (cts_core.discovery.discovery_container.DiscoveryContainer, cts_core.validation.validation_status.ValidationStatus)
        """
        self._status = ValidationStatus.PASSED
        if getenv('CTS_UNPICKLE', None):
            self._discovery_container = pickle.load(
                open(environ['CTS_UNPICKLE'], 'rb'))
            self._discovery_container.metadata_container = self._metadata_container
            return self._discovery_container, self._status

        self._discovery_container = discovery_container if discovery_container is not None \
                                                    else DiscoveryContainer(metadata_container=self._metadata_container)

        self._enqueue_resource(url, expected_odata_type, api_endpoint_override)

        while self._bfs_queue:
            self._explore_next_resource()

        if getenv('CTS_PICKLE', None):
            # workaroud for 'TypeError: can't pickle instancemethod objects'
            metadata_container_backup = self._discovery_container.metadata_container
            self._discovery_container.metadata_container = None  # do not pickle metadata container
            pickle.dump(self._discovery_container,
                        open(environ['CTS_PICKLE'], 'wb'))
            self._discovery_container.metadata_container = metadata_container_backup

        return self._discovery_container, self._status

    def _enqueue_resource(self, url, expected_odata_type,
                          api_endpoint_override):
        self._bfs_queue.append(
            (url, expected_odata_type, api_endpoint_override))

    def _explore_next_resource(self):
        if self._bfs_queue:
            url = self._bfs_queue[0][0]
            expected_odata_type = self._bfs_queue[0][1]
            api_endpoint_override = self._bfs_queue[0][2]
            self._bfs_queue.pop(0)
            self._explore_resource(url, expected_odata_type,
                                   api_endpoint_override)

    def _explore_resource(self, url, expected_odata_type,
                          api_endpoint_override):
        """
        :type url: str
        """
        if url is None:
            return

        link = self._api_caller.links_factory.get_resource_link(
            url, api_endpoint_override=api_endpoint_override)
        url = link.link

        if self._discovery_container.is_visited(url):
            return

        self._discovery_container.register_url_pattern(url,
                                                       expected_odata_type)

        link, status, response_body = self._get_resource(
            url, api_endpoint_override=api_endpoint_override)

        try:
            self._get_members(url, response_body, expected_odata_type,
                              api_endpoint_override)
        except:
            pass

        if not response_body:
            cts_error("GET {id:id} Empty response body", id=url)
            self._status = ValidationStatus.FAILED
            return

        if status == RequestStatus.SUCCESS and response_body:
            api_resource = ApiResource(link.link, link.netloc, response_body,
                                       expected_odata_type)
            self._discovery_container.add_resource(api_resource)
            self._process_resource(api_resource)

    def _get_members(self,
                     url,
                     response_body,
                     expected_odata_type,
                     api_endpoint_override=None):
        status = ValidationStatus.BLOCKED
        link = None

        if "Members" in response_body:
            len_members_exists = len(response_body["Members"])
            founded_members = []
            for founded_member in range(0, len_members_exists):
                founded_members.append(
                    response_body["Members"][founded_member]["@odata.id"])

            for founded_member in list(set(founded_members)):
                link, status, response_body = self._get_resource(
                    founded_member,
                    api_endpoint_override=api_endpoint_override)

                if status == RequestStatus.SUCCESS and response_body:
                    api_resource = ApiResource(link.link, link.netloc,
                                               response_body,
                                               expected_odata_type)
                    self._discovery_container.add_resource(api_resource)
                    self._process_resource(api_resource)

        if not response_body:
            cts_error("GET {id:id} Empty response body", id=url)
            self._status = ValidationStatus.FAILED
            return

        if status == RequestStatus.SUCCESS and response_body:
            api_resource = ApiResource(link.link, link.netloc, response_body,
                                       expected_odata_type)
            self._discovery_container.add_resource(api_resource)
            self._process_resource(api_resource)

    def _get_resource(self, url, api_endpoint_override=None):
        link, status, status_code, response_body, headers = self._api_caller.get_resource(
            url,
            self._discovery_container,
            api_endpoint_override=api_endpoint_override)

        if status != RequestStatus.SUCCESS:
            print "ERROR::Error while executing GET %s" % url
            self._status = ValidationStatus.FAILED

        status, body = self._dereference_jsonpointer_if_any(url, response_body)
        return link, status, body

    def _dereference_jsonpointer_if_any(self, url, response_body):
        pointer_pos = url.find("#")
        if pointer_pos != -1:
            pointer = url[pointer_pos + 1:]
            if not pointer or pointer[0] != '/':
                pointer = '/' + pointer
            try:
                return RequestStatus.SUCCESS, resolve_pointer(
                    response_body, pointer)
            except JsonPointerException as exception:
                cts_error(
                    "JSON pointer exception while dereferencing {path} from {url:id} "
                    "resource; Error: {error}",
                    path=pointer,
                    url=url,
                    error=exception)
                self._status = ValidationStatus.FAILED
                return RequestStatus.FAILED, {}
        return RequestStatus.SUCCESS, response_body

    def _process_resource(self, api_resource):
        """
        :type api_resource: cts_core.discovery.api_resource.ApiResource
        """
        try:
            type_definition = self._metadata_container.entities[
                api_resource.odata_type]
        except KeyError as key:
            cts_error(
                "{url:id}: Unknown @odata.type {type}. Not able to process sub-elements",
                url=api_resource.url,
                type=key)
            self._status = ValidationStatus.FAILED
            return

        try:
            self._process_properties(api_resource.body,
                                     type_definition,
                                     url=api_resource.url)
        except Exception as err:
            self.status = ValidationStatus.FAILED
            cts_error("{id:id} Unexpected error {err:exception}",
                      id=api_resource.url,
                      **locals())

    def _process_properties(self, json_body, type_definition, url, path=None):
        if path is None:
            path = Path()

        property_description_list = type_definition.all_properties

        self._append_additional_properties(property_description_list,
                                           json_body, type_definition, url,
                                           path)

        for property_description in property_description_list:
            try:
                property_body = json_body[property_description.name]
            except KeyError:
                # this is discovery phase
                # any structure errors must be reported during subsequent analysis
                continue  # next property
            try:
                if property_description.is_collection:
                    for property_body_member in property_body:
                        self._process_property(property_description,
                                               property_body_member, url, path)
                else:
                    self._process_property(property_description, property_body,
                                           url, path)
            except TypeError:
                # this is discovery phase
                # any structure errors must be reported during subsequent analysis
                pass

    def _append_additional_properties(self, property_list, body,
                                      type_definition, url, path):
        additionals = set(body.keys()) - set(
            [property.name for property in property_list])
        # filter out special properties (properties that contain @ and #)
        additionals = filter(
            lambda property: not is_special_property(property), additionals)

        if len(additionals) > 0:
            if type_definition.allow_additional_properties:
                for property_name in additionals:
                    # process only objects
                    try:
                        property_value = body[property_name]
                        if isinstance(property_value, dict):
                            odata_type = match_dynamic_property(
                                property_name, type_definition)
                            if odata_type is None:
                                try:
                                    odata_type = get_odata_type(property_value)
                                except KeyError as key:
                                    cts_error(
                                        "{url:id}#{path}: @odata.type not found in complex "
                                        "additional property",
                                        url=url,
                                        path=path.append(property_name))
                                    continue

                            adhoc_description = self.ad_hoc_type_definition(
                                property_name, odata_type)
                            property_list.append(adhoc_description)
                    except KeyError:
                        pass
            else:
                cts_error(
                    "{url:id}#{path}: Object of type {type}; unexpected properties: [{"
                    "properties}]",
                    type=type_definition.name,
                    properties=", ".join(additionals),
                    url=url,
                    path=path)
                self._status = ValidationStatus.FAILED

    def ad_hoc_type_definition(self, property_name, odata_type):
        raw_soup = """
                    <Property Name="{name}" Type="{type}">
                    </Property>
                   """
        soup = BeautifulSoup(
            raw_soup.format(name=property_name, type=odata_type),
            "lxml").find_all("property")[0]
        adhoc_description = Property(self._metadata_container, None, soup)
        return adhoc_description

    def _process_property(self, property_description, json_body, url, path):
        if not json_body:
            return

        if property_description.type in self._metadata_container.entities.keys(
        ):
            return self._process_entity(url,
                                        property_description,
                                        json_body,
                                        path=path.append(
                                            property_description.name))
        elif property_description.type in self._metadata_container.types.keys(
        ):
            if self._metadata_container.types[
                    property_description.
                    type].type_category == MetadataTypeCategories.COMPLEX_TYPE:
                return self._process_complex_type(
                    property_description,
                    json_body,
                    url,
                    path=path.append(property_description.name))
        else:
            cts_error("{url:id}#{path} : Unknown type {type}",
                      url=url,
                      path=path,
                      type=property_description.type)

    def _process_entity(self, url, property_description, body, path):
        try:
            odata_id = body[".".join([ODATA, ID])]
        except KeyError:
            cts_error(
                "{odata_id:id}#{path}: @odata.id expected in the resource body",
                odata_id=odata_id,
                path=path)
            return

        try:
            if odata_id is not None:
                netloc = self._discovery_container.get_netloc(url)
                self._enqueue_resource(odata_id, property_description.type,
                                       netloc)

        except Exception as err:
            self.status = ValidationStatus.FAILED
            cts_error(
                "{odata_id:id} Exception {err:exception}. Not able to process sub-elements",
                **locals())
            self._status = ValidationStatus.FAILED

    def _process_complex_type(self, property_description, body, url, path):
        try:
            # use override @odata.type if present.
            # At this stage do not verify if override type is consistent with type from property_description;
            # This will be validated in validate_get_responses test
            odata_type = get_odata_type(body)
        except KeyError:
            odata_type = property_description.type

        type_definition = self._metadata_container.types[odata_type]
        self._process_properties(body, type_definition, url, path)
Exemple #27
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
Exemple #28
0
class MetadataPatchValidator(SkipListMixin, PatchNontrivialPropertyMixin):
    def __init__(self, metadata_container, configuration, strategy, requirements=None, skip_list=None):
        """
        :type metadata_container: cts_core.metadata.metadata_container.MetadataContainer
        :type configuration: cts_framework.configuration.configuration.Configurations
        :type strategy: cts_core.validation.patch.patching_strategy.PatchingStrategy
        :type requirements: list
        """
        self._metadata_container = metadata_container
        self._api_caller = ApiCaller(configuration)
        self._strategy = strategy
        self._preconditions = Preconditions(metadata_container, requirements)
        self._fast_mode = getenv('CTS_PATCH_ONE_PER_TYPE')
        self._types_patched = set()
        self._skip_list = skip_list

        if self._fast_mode:
            cts_warning("Test results may be UNRELIABLE - PATCHING ONLY ONE RESOURCE OF EACH TYPE!")

        self._endpoints_and_keys_to_ignore = []
        if self._skip_list:
            cts_warning("Test results may be UNRELIABLE - Some elements presented on REST API will be IGNORED!")
            self._endpoints_and_keys_to_ignore = self._load_ignore_list_file(skip_list)

    def validate(self, discovery_container):
        self.discovery_container = discovery_container
        status = self._check_mandatory_entities()

        for api_resource in self._enumerate_resources(discovery_container):
            context = Context(api_resource)
            resource_status = self._validate_resource(context)
            self._print_resource_footer(api_resource, context, resource_status)
            redfish_uri_compliance = discovery_container.validate_redfish_uris_consistency(api_resource, self._metadata_container)
            status = ValidationStatus.join_statuses(status, resource_status, redfish_uri_compliance)

        print "SCREEN::Overall status: %s" % status
        return status

    @staticmethod
    def _load_ignore_list_file(path_to_file=None):
        if not path_to_file:
            return []

        endpoints_to_ignore = {}
        with open(path_to_file, 'r') as f:
            for line in f.readlines():
                if '::' in line:
                    pre_formatted_value = line.split('::')
                else:
                    cts_warning("{line} is not proper format, add ::[*] to ignore entire endpoint")
                    continue
                list_of_endpoints_key = MetadataPatchValidator.__parse_into_a_list(pre_formatted_value[1])
                endpoints_to_ignore[pre_formatted_value[0]] = [value for value in list_of_endpoints_key.split(',')]
            print(endpoints_to_ignore)
        return endpoints_to_ignore

    @staticmethod
    def __parse_into_a_list(text):
        return text.replace('\n', '').replace('[', '').replace(']', '')

    def _enumerate_resources(self, discovery_container):
        count = len(discovery_container)
        resource_urls = sorted(self.discovery_container.keys())
        for idx, url in enumerate(resource_urls):
            api_resource = discovery_container[url]
            self._print_progress(api_resource, count, idx)
            if self.skip_resource(api_resource):
                continue
            if self._fast_mode:
                generalized_id = discovery_container._generalize_url(api_resource.url)
                if generalized_id in self._types_patched:
                    print "MESSAGE::skipping... other {} has already been tested". \
                        format(generalized_id)
                    continue
                else:
                    self._types_patched.add(generalized_id)

            yield api_resource

    @staticmethod
    def _print_progress(api_resource, count, idx):
        print "SCREEN::" + "-" * 120
        progress = "[%5d/%5d]" % (idx + 1, count)
        print "MESSAGE::%s - %s : %s" % \
              (progress, api_resource.odata_id, api_resource.odata_type)

    def _check_mandatory_entities(self):
        print "TEST_CASE::Checking for mandatory entities"
        status = self._preconditions.validate(self.discovery_container)
        print "STATUS::%s" % status
        return status

    def _validate_resource(self, context):
        """
        :type context: Context
        :rtype: str
        """
        api_resource = context.api_resource

        if self._metadata_container.to_be_ignored(api_resource.odata_type):
            return ValidationStatus.PASSED

        # Load Ignore Elements list and verify.
        properties_to_skip = []
        if api_resource.odata_id in self._endpoints_and_keys_to_ignore:
            properties_to_skip = self._endpoints_and_keys_to_ignore[api_resource.odata_id]

            if properties_to_skip[0] == '*':
                print('MESSAGE::Skipping patching {}. This odata_id is present on list of endpoints to ignore'.
                      format(api_resource.odata_id))
                return ValidationStatus.PASSED_WITH_WARNINGS
            else:
                print('MESSAGE::This properties from {} will be skipped from patching.'.
                      format(api_resource.odata_id))
                print('MESSAGE::Elements are on list to ignore:')
                for idp, property in enumerate(properties_to_skip, 1):
                    print('MESSAGE::\t{idp}. {property}'.format(idp=idp,
                                                                property=property))

        # do not attempt patch Virtual systems
        if "SystemType" in api_resource.body and api_resource.body["SystemType"] == "Virtual":
            print "MESSAGE::Skipping patching of a virtual system {}".format(api_resource.odata_id)
            return ValidationStatus.PASSED

        try:
            properties_list = [property for property in
                               self._metadata_container.entities[api_resource.odata_type].properties.values()
                               if str(property) not in properties_to_skip]

            try:
                if len(properties_to_skip):
                    return self._validate_property_list(context,
                                                        list(),
                                                        properties_list,
                                                        properties_to_skip)

                return self._validate_property_list(context,
                                                    list(),
                                                    properties_list)
            except SystemExit:
                raise
            except:
                cts_error("Unhandled exception {exception:stacktrace} "
                          "while handling resource {odata_id:id}",
                          exception=format_exc(),
                          odata_id=api_resource.odata_id)
                return ValidationStatus.FAILED
        except KeyError:
            cts_error("Unable to find definition of entity type {odata_type}",
                      odata_type=api_resource.odata_type)
            return ValidationStatus.FAILED

    def _validate_property_list(self, context, variable_path, property_list, ignore_list=[]):
        """
        :type context: Context
        :type variable_path: list [str or int]
        :type property_list: list[cts_core.metadata.model.property.Property]
        :rtype: str
        """
        api_resource = context.api_resource

        status = ValidationStatus.PASSED
        # use local copy - api resource will be modified while test is executed
        local_property_list = list(property_list)

        for property_description in local_property_list:

            if not len(ignore_list) or (len(ignore_list) and str(property_description) not in sum([ignored_property.split("->") for ignored_property in ignore_list], [])):
                status = ValidationStatus.join_statuses(self._validate_property(context,
                                                                                variable_path,
                                                                                property_description,
                                                                                ignore_list=[ignored_property for ignored_property in ignore_list]),
                                                        status)
                continue
            else:
                if len([ignored_subproperty for ignored_subproperty in ignored_property.split("->") for ignored_property in ignore_list]) > 1:
                   status = ValidationStatus.join_statuses(self._validate_property(context,
                                                                                   variable_path,
                                                                                   property_description,
                                                                                   ignore_list=[ignored_property.split("->")[1] for ignored_property in ignore_list]),
                                                           status)
                else:
                    status = ValidationStatus.join_statuses(self._validate_property(context,
                                                                                    variable_path,
                                                                                    property_description,
                                                                                    ignore_list=[ignored_property for ignored_property in ignore_list]),
                                                            status)

        # validate additional properties
        try:
            body = api_resource.get_value_from_path(variable_path)
        except KeyError as key:
            cts_warning("Unable to access {odataid:id}->{path}. Key={key}",
                        odataid=api_resource.odata_id, path="->".join(variable_path), key=key)
            body = None

        if body is not None:
            all_properties = set(body.keys())
            known_properties = set([property.name for property in local_property_list])
            additional_properties = all_properties - known_properties
            for additional_property_name in additional_properties:
                property_value = body[additional_property_name]
                if isinstance(property_value, dict) and "@odata.type" in property_value:
                    raw_soup = """
                    <Property Name="{name}" Type="{type}">
                        <Annotation Term="OData.Permissions"
                                    EnumMember="OData.Permission/ReadWrite"/>
                    </Property>
                    """
                    soup = BeautifulSoup(raw_soup.format(name=additional_property_name,
                                                         type=get_odata_type(property_value)),
                                         "lxml").find_all("property")[0]

                    adhoc_description = Property(self._metadata_container, None, soup)
                    status = ValidationStatus.join_statuses(
                        self._validate_property(context,
                                                variable_path,
                                                adhoc_description),
                        status)

        return status

    def _validate_property(self, context, variable_path, property_description,
                           skip_collection=False, ignore_list=[]):
        """
        :type context: Context
        :type variable_path: list [str or int]
        :type property_description: cts_core.metadata.model.property.Property
        :type skip_collection: bool
        :rtype: str
        """
        applicability = None
        if property_description.patch_status == PatchStatus.NONTRIVIAL:
            applicability, validation_result = self.handle_nontrivial(context, variable_path, property_description)
            if applicability == ApplicabilityTestResult.MATCHED:
                return validation_result
            if applicability in [ApplicabilityTestResult.NOT_MATCHED, ApplicabilityTestResult.SKIP_PROPERTY]:
                return validation_result

        api_resource = context.api_resource

        if not skip_collection:
            variable_path = variable_path + [property_description.name]

        try:
            property_body = api_resource.get_value_from_path(variable_path)
            if len(ignore_list) == 1 and ignore_list[0] in property_body:
                path_s = "->".join([str(segment) for segment in variable_path])
                cts_message("Patching %s->%s" % (api_resource.odata_id, path_s))
                cts_message("This property {ignore_element} is marked in IgnoredElement list as element to skip".format(
                    ignore_element=ignore_list[0]
                ))
        except KeyError as error:
            if property_description.is_required:
                path_s = "->".join([str(segment) for segment in variable_path])
                print "TEST_CASE::Patching %s->%s" % (api_resource.odata_id, path_s)
                cts_error("Unable to patch {error}", error=error)
                status = ValidationStatus.FAILED
                print "STATUS::%s" % status
            else:
                status = ValidationStatus.PASSED

            return status

        if property_description.is_collection and not skip_collection:
            status = ValidationStatus.PASSED

            for path_element, _ in enumerate(property_body):
                status = \
                    ValidationStatus.join_statuses(status,
                                                   self._validate_property(context,
                                                                           variable_path + [path_element],
                                                                           property_description,
                                                                           skip_collection=True,
                                                                           ignore_list=ignore_list))
            return status
        else:
            try:
                if self._metadata_container.types[property_description.type].type_category \
                        == MetadataTypeCategories.COMPLEX_TYPE:
                    return self._validate_property_list(context,
                                                        variable_path,
                                                        self._metadata_container.types[property_description.type].
                                                        properties.itervalues(),
                                                        ignore_list)
                else:
                    if str(property_description) in ignore_list:
                        return ValidationStatus.PASSED
                    return self._validate_leaf_property(context,
                                                        variable_path,
                                                        property_description)
            except KeyError as key:
                cts_error("Unable to find definition of type {type} referenced by {odata_id:id}",
                          type=key,
                          odata_id=api_resource.odata_id)
                return ValidationStatus.FAILED

    def _validate_leaf_property(self, context, variable_path, property_description):
        """
        :type context: Context
        :type variable_path: list [str or int]
        :type property_description: cts_core.metadata.model.property.Property
        :rtype: str
        """
        if property_description.patch_status in (PatchStatus.NOT_PATCHABLE, PatchStatus.NOT_DEFINED):
            return ValidationStatus.PASSED

        if property_description.patch_status == PatchStatus.NONTRIVIAL:
            applicability, validation_result = self.handle_nontrivial(context, variable_path, property_description)
            if applicability == ApplicabilityTestResult.MATCHED:
                return validation_result
            if applicability in [ApplicabilityTestResult.NOT_MATCHED, ApplicabilityTestResult.SKIP_PROPERTY]:
                return validation_result

        return self._validate_patchable_property(context, variable_path, property_description)

    def _validate_patchable_property(self, context, variable_path, property_description):
        """
        :type context: Context
        :type variable_path: list [str or int]
        :type property_description: cts_core.metadata.model.property.Property
        :rtype: str
        """
        api_resource = context.api_resource
        verify_only_response = property_description.patch_status == PatchStatus.WRITE_ONLY

        new_values_list = property_description.generate_values(property_description.annotations)
        allowed_values = api_resource.get_allowable_from_path(variable_path)
        if allowed_values:
            new_values_list = [value for value in new_values_list if value in allowed_values]

        collection = None
        if property_description.is_collection and len(variable_path) > 1:
            collection = api_resource.get_value_from_path(variable_path[:-1])

        try:
            value_pre = current_value = api_resource.get_value_from_path(variable_path)
        except KeyError:
            return ValidationStatus.PASSED

        total_status = ValidationStatus.PASSED

        for new_value in new_values_list:
            if new_value != current_value:
                status, current_value = \
                    self._patch_and_verify_property(context,
                                                    variable_path,
                                                    current_value,
                                                    new_value,
                                                    verify_only_response,
                                                    collection=collection,
                                                    property_description_type=property_description.name)

                total_status = ValidationStatus.join_statuses(total_status, status)

                if current_value is None:
                    try:
                        value_pre = current_value = api_resource.get_value_from_path(variable_path)
                    except KeyError:
                        return total_status

        if not allowed_values or value_pre in allowed_values:
            total_status = ValidationStatus.join_statuses(total_status,
                                                          self._restore_property(context,
                                                                                 value_pre,
                                                                                 variable_path,
                                                                                 collection=collection))
        else:
            print "MESSAGE::Original value '{value_pre}' is illegal. Will not _restore_property" \
                .format(value_pre=dumps(value_pre))

        return total_status

    def _patch_and_verify_property(self, context, variable_path, current_value, value_requested,
                                   verify_only_response=False, collection=None, property_description_type=None):
        """
        :type context: Context
        :type variable_path: list [str or int]
        :type current_value: *
        :type value_requested: *
        :type verify_only_response: bool
        """

        property = "%s[%s]" \
                   % (context.api_resource.odata_id, "->".join([str(path) for path in
                                                                variable_path]))

        patch_applied, status, validation_status = self._patch_property(context,
                                                                        property,
                                                                        current_value,
                                                                        value_requested,
                                                                        variable_path,
                                                                        collection=collection)
        # only _verify_property if the PATCH request succeeded and is not only writeable
        if status == RequestStatus.SUCCESS and patch_applied:
            if not verify_only_response:
                verify_status, current_value = self._verify_property(context,
                                                                     patch_applied,
                                                                     property,
                                                                     current_value,
                                                                     value_requested,
                                                                     variable_path,
                                                                     property_description_type=property_description_type)
                validation_status = ValidationStatus.join_statuses(validation_status, verify_status)
            else:
                validation_status = ValidationStatus.join_statuses(validation_status, ValidationStatus.PASSED)

        return validation_status, current_value

    def _patch_property(self, context, property_name, value_pre, value_requested, variable_path, collection=None):
        """
        Issues Patch request to update property to new requested value. Tries to determine
        if patch has been applied and sets validation status accordingly.

        :type context: Context
        :type property_name: str
        :type value_pre: *
        :type value_requested: *
        :type variable_path: list [str or int]
        :rtype: (bool, bool, str)
        """

        api_resource = context.api_resource

        print "TEST_CASE:: Patch %s := %s" % (property_name, str(value_requested))
        context.register_patch_attempt(property_name, str(value_requested))

        data = create_data(variable_path, value_requested, collection)

        status, status_code, response_body, headers = self._api_caller.patch_resource(
            api_resource.url,
            self.discovery_container,
            payload=data,
            acceptable_return_codes=self._strategy.allowable_return_codes)

        patch_applied = self._strategy.was_patch_applied(status_code)

        if status != RequestStatus.SUCCESS or not patch_applied:
            validation_status = ValidationStatus.FAILED
        else:
            validation_status = ValidationStatus.PASSED
        print "STATUS::%s" % validation_status

        return patch_applied, status, validation_status

    def _verify_property(self, context, patch_applied, property_name, value_pre,
                         value_requested, variable_path, property_description_type):
        """
        Verifies if property value after patch is as expected.
        Should be equal to original value if patch has not been applied.
        Should be equal to requested value if patch has been applied.

        :type context: Context
        :type patch_applied: bool
        :type property_name: str
        :type value_pre: *
        :type value_requested: *
        :type variable_path: list[str or int]
        :rtype: (str, *)
        """

        api_resource = self.discovery_container[context.api_resource.url] # refresh reference to the resource

        validation_status = ValidationStatus.PASSED
        print "TEST_CASE::Verify %s (expected: %s)" % (property_name, str(value_requested))

        try:
            value_post = api_resource.get_value_from_path(variable_path)

            if self._validate_corner_cases(property_description_type, value_post, value_requested) and patch_applied:
                print "STATUS::%s" % validation_status
                return validation_status, value_post

            if value_requested != value_post and patch_applied:
                cts_error("{odata_id:id} Unexpected value after patching {property} - " +
                          "IS : {post:ignore}, EXPECTED : {requested:ignore}",
                          odata_id=api_resource.odata_id,
                          property=property_name,
                          post=value_post,
                          requested=value_requested)
                validation_status = ValidationStatus.FAILED
            elif value_pre != value_post and not patch_applied:
                cts_error("{odata_id:id} Service reported that the patch has not been applied, "
                          "but '{property}' has changed unexpectedly "
                          "from {pre:ignore} to {post:ignore}",
                          odata_id=api_resource.odata_id,
                          property=property_name,
                          pre=value_pre,
                          post=value_post)
                validation_status = ValidationStatus.FAILED
        except ValueNotFound:
            validation_status = ValidationStatus.FAILED

        print "STATUS::%s" % validation_status
        return validation_status, value_post

    def _restore_property(self, context, value_pre, variable_path, collection=None):
        """
        Final patching that restores original value of the property.

        :type context: Context
        :type value_pre: *
        :type variable_path: list [int or str]
        :return: str
        """
        api_resource = context.api_resource

        validation_status = ValidationStatus.PASSED

        property = "%s" % ("->".join([str(path) for path in variable_path]))
        print "TEST_CASE::Restore %s := %s" % (property, dumps(value_pre))

        data = create_data(variable_path, value_pre, collection)
        status, status_code, response_body, headers = self._api_caller.patch_resource(
            api_resource.url,
            self.discovery_container,
            payload=data,
            acceptable_return_codes=self._strategy.allowable_return_codes)
        if status != RequestStatus.SUCCESS:
            cts_error("{odata_id:id} Restoring {property} failed. status code {code}",
                      odata_id=api_resource.odata_id,
                      property=property,
                      code=status_code)
            validation_status = ValidationStatus.FAILED
        print "STATUS::%s" % validation_status
        return validation_status

    def _print_resource_footer(self, api_resource, context, resource_status):
        if not context.attempted_patches:
            if resource_status not in [ValidationStatus.PASSED,
                                       ValidationStatus.PASSED_WITH_WARNINGS]:
                # 0 attempted patches means 0 test cases.
                # FAILED means error (visible in the log) happened during validation,
                # but not associated with any TEST_CASE
                # For the purpose of showing failing test case, we produce dummy TEST_CASE here
                print "TEST_CASE::Patching %s" % api_resource.odata_id
                print "STATUS::%s" % resource_status
            else:
                print "MESSAGE::[{resource} - 0 patchable properties found]". \
                    format(resource=api_resource.odata_id)

    def _validate_corner_cases(self, property_description_type, value_post, value_requested):
        if property_description_type == "MACAddress":
            return self._validate_canonical_form_of_mac_address(value_post, value_requested)
        return False

    def _validate_canonical_form_of_mac_address(self, value_post, value_requested):
        post = self._mac_address_standarizer(value_post)
        req = self._mac_address_standarizer(value_requested)

        return int(post, 16) == int(req, 16)

    @staticmethod
    def _mac_address_standarizer(mac_address):
        replace_dict = {":": "", "-": ""}
        for i, j in replace_dict.iteritems():
            mac_address = mac_address.replace(i, j)

        return mac_address
Exemple #29
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
Exemple #30
0
    def test_password_given_user_not(self):
        configuration = Configuration(**dict(Password=PASSWORD, ApiEndpoint=API_ENDPOINT))
        params, kwargs = ApiCaller(configuration)._build_request(RESOURCE)

        self.assertNotIn("auth", kwargs)
Exemple #31
0
class MetadataManager:
    METADATA_URL = "/redfish/v1/$metadata"

    SERVICE_TO_DIR = {
        ServiceTypes.PODM_1_2: "1.2/PODM",
        ServiceTypes.PSME_1_2: "1.2/PSME",
        ServiceTypes.RMM_1_2: "1.2/RMM",
        ServiceTypes.SS_1_2: "1.2/SS",

        ServiceTypes.PODM_2_1: "2.1.3/PODM",
        ServiceTypes.PSME_2_1: "2.1.3/PSME",
        ServiceTypes.RMM_2_1: "2.1.3/RMM",
        ServiceTypes.SS_2_1: "2.1.3/SS",

        ServiceTypes.PODM_2_1_2: "2.1.2/PODM",
        ServiceTypes.PSME_2_1_2: "2.1.2/PSME",
        ServiceTypes.RMM_2_1_2: "2.1.2/RMM",
        ServiceTypes.SS_2_1_2: "2.1.2/SS",

        ServiceTypes.PODM_2_1_3: "2.1.3/PODM",
        ServiceTypes.PSME_2_1_3: "2.1.3/PSME",
        ServiceTypes.RMM_2_1_3: "2.1.3/RMM",
        ServiceTypes.SS_2_1_3: "2.1.3/SS",

        ServiceTypes.PODM_2_1_4: "2.1.4/PODM",
        ServiceTypes.PSME_2_1_4: "2.1.4/PSME",
        ServiceTypes.RMM_2_1_4: "2.1.4/RMM",
        ServiceTypes.SS_2_1_4: "2.1.4/SS",

        ServiceTypes.PODM_2_2: "2.2/PODM",
        ServiceTypes.PSME_2_2: "2.2/PSME",
        ServiceTypes.RMM_2_2: "2.2/RMM",
        ServiceTypes.SS_2_2: "2.2/SS",

        ServiceTypes.PODM_2_3: "2.3/PODM",
        ServiceTypes.PSME_2_3: "2.3/PSME",
        ServiceTypes.RMM_2_3: "2.3/RMM",
        ServiceTypes.SS_2_3: "2.3/SS",

        ServiceTypes.PODM_2_4: "2.4/PODM",
        ServiceTypes.PSME_2_4: "2.4/PSME",
        ServiceTypes.RMM_2_4: "2.4/RMM",
        ServiceTypes.SS_2_4: "2.4/SS",

        ServiceTypes.SCENARIO_2_4: "2.4/SCENARIO",
        ServiceTypes.RACKSCALE_2_4: "2.4/RACKSCALE",

        ServiceTypes.REDFISH_2018_1: "redfish/2018.1",
        ServiceTypes.REDFISH_2018_2: "redfish/2018.2",
        ServiceTypes.REDFISH_2018_3: "redfish/2018.3"

    }

    def __init__(self, qualifiers, ignore_types=None, map_types=None):
        self.qualifiers = qualifiers
        self.ignore_types = ignore_types if ignore_types is not None else set()
        self.map_types = map_types if map_types is not None else dict()
        self._metadata_container = MetadataContainer(ignore_types=self.ignore_types,
                                                     map_types=self.map_types)
        self.loaded_xmls = set()
        self.api_caller = None

    def read_metadata_for_services(self, *services):
        """
        :type services: list[string]
        """

        digest = DirDigest(self._metadata_home(), '.xml', DirDigest.LABEL_METADATA)
        if not digest.is_valid() and not getenv('CTS_SKIP', None):
            cts_error("Metadata located in {dir} is corrupted or has been tampered. Expected: {expected}, "
                      "Is: {current}",
                      dir=self._metadata_home(), expected=digest.official_digest, current=digest.digest)
            cts_message("{count} xml files have been found in {dir}".format(count=len(digest),
                                                                            dir=self._metadata_home()))
            digest.report_differences()

        for service in services:
            try:
                self.metadata_dir = os.path.join(self._metadata_home(),
                                                 MetadataManager.SERVICE_TO_DIR[service])
                self.read_metadata_from_dir(self.metadata_dir)
            except KeyError:
                cts_error("Internal error. Unknown metadata for service {service}", service=service)
                return False
        return True

    def read_metadata_from_dir(self, dir, root_file=None):
        if root_file is None:
            root_file = ROOT_XML_FILE

        if os.path.isdir(dir):
            self.metadata_dir = dir
            self._process_schema_file(root_file)
            return True
        else:
            cts_error("Internal error. Metadata directory {metadata_dir} not found", metadata_dir=dir)
            return False

    def _validate_xml_file(self, file_uri, metadata_text):
        try:
            doc = etree.fromstring(metadata_text.lstrip())
        except etree.XMLSyntaxError as err:
            cts_error('XML Syntax Error in file {file}', file=file_uri)
            raise MetadataMalformed()

    def read_metadata_from_strings(self, file_uri, *metadata):
        """
        :type file_uri: string
        :type metadata: list[string]
        :rtype: cts_core.metadata.metadata_container.MetadataContainer
        """
        for metadata_text in metadata:
            self._validate_xml_file(file_uri, metadata_text)

            metadata_soup = Commons.text_to_soup(metadata_text)

            for reference in metadata_soup.find_all(REFERENCE):
                self._process_reference(reference)

            for schema in metadata_soup.find_all(SCHEMA):
                self._process_namespace(schema)

        return self.metadata_container

    def download_metadata(self, configuration):
        self.api_caller = ApiCaller(configuration)
        self._process_schema_file(MetadataManager.METADATA_URL)
        return True

    @staticmethod
    def _metadata_home():
        return Constants.METADATA_HOME_DIR

    def _process_reference(self, reference_soup):
        try:
            self._process_schema_file(reference_soup[URI])
        except KeyError as uri:
            cts_error("Incorrect reference. URI not found")

    @property
    def metadata_container(self):
        return self._metadata_container

    def _process_schema_file(self, file_uri):
        if file_uri in self.loaded_xmls:
            return
        self.loaded_xmls.add(file_uri)

        if self.api_caller is None:
            self._process_local_schema_file(file_uri)
        else:
            self._process_remote_schema_file(file_uri)

    def _process_local_schema_file(self, file_uri):
        try:
            full_uri = os.path.join(self.metadata_dir, file_uri)
            with open(full_uri) as f:
                self.read_metadata_from_strings(file_uri, f.read())
        except IOError:
            cts_error("Metadata file {file} not found", file=full_uri)

    def _process_remote_schema_file(self, file_uri):
        _, status, _, response_body, _ = self.api_caller.get_xml(file_uri)
        if not status:
            cts_error("Error while accessing metadata {uri}", uri=file_uri)
        else:
            self.read_metadata_from_strings(file_uri, response_body)

    def _process_namespace(self, schema):
        """
        :type schema: bs4.element.Tag
        """
        try:
            namespace_name = schema[NAMESPACE]
        except KeyError:
            cts_warning(
                "Incorrect schema definition {schema_definition},\n missing of namespace name",
                schema_definition=str(schema))

        for entity_soup in schema.find_all(ENTITY_TYPE):
            entity = Entity(self._metadata_container, namespace_name, entity_soup, self.qualifiers)
            self._metadata_container.entities[entity.name] = entity

        for type_soup in schema.find_all(ENUM_TYPE):
            enum_type = EnumType(self._metadata_container, namespace_name, type_soup,
                                 self.qualifiers)
            self._metadata_container.types[enum_type.name] = enum_type

        for type_soup in schema.find_all(COMPLEX_TYPE):
            complex_type = ComplexType(self._metadata_container, namespace_name, type_soup,
                                       self.qualifiers)
            self._metadata_container.types[complex_type.name] = complex_type

        for type_soup in schema.find_all(TYPE_DEFINITION):
            type_definition = TypeDefinition(self._metadata_container, namespace_name, type_soup,
                                             self.qualifiers)
            self._metadata_container.types[type_definition.name] = type_definition
Exemple #32
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()
Exemple #33
0
class ValidateUseCase(CtsTestScript):
    TIMEOUT = 600
    DESCRIPTION = """test case validating api responses against provided use case"""

    CONFIGURATION_PARAMETERS = [
        TestScript.ConfigurationParameter(
            parameter_name="UseCase",
            parameter_description="Use Case name",
            parameter_type=str,
            is_required=False,
            parameter_default_value=[])
    ]

    def run(self):
        json_loader = JsonLoader()
        if self.metadata_container is None:
            return

        if not self.configuration.UseCase:
            json_loader.print_available_actions()
            return

        actions_to_take = json_loader.get_use_case(self.configuration.UseCase)
        specific_action = json_loader.get_use_case_action(
            actions_to_take['filepath'])

        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)
        configuration = self.configuration
        self.api_caller = ApiCaller(configuration)

        print('TEST_CASE::Action Runner')
        status = self.action_runner(specific_action)
        print('STATUS::{status}'.format(status=status))

    def action_runner(self, specific_action):
        actions_status = ValidationStatus.RUNNING

        for action_step, action in enumerate(specific_action['actions'], 1):
            step_counter = "%d/%d" % (action_step,
                                      len(specific_action['actions']))
            action_type = action['type']
            if action_type == 'timeout':
                try:
                    self.perform_timeout(step_counter, action['value'])
                except IndexError:
                    self.perform_timeout(step_counter)
                continue

            retry_if_fail, retry_timeout = self.__get_retry_information(action)
            action_args = (specific_action['name'], action['name'],
                           action['action'], step_counter, int(retry_if_fail),
                           int(retry_timeout))

            if action_type == 'external':
                actions_status = ValidationStatus.join_statuses(
                    self.perform_external_command(*action_args))
                continue
            elif action_type == 'usecase':
                actions_status = ValidationStatus.join_statuses(
                    self.perform_action(*action_args))
        return actions_status

    @staticmethod
    def perform_timeout(step_counter, timeout=1):
        cts_message("Step: %s" % step_counter)
        cts_message("Timeout: %s" % timeout)
        sleep(float(timeout))

    @CaseInformation
    def perform_external_command(self,
                                 test_name,
                                 case_name,
                                 action,
                                 step_counter,
                                 retry=0,
                                 timeout=0):
        """
        Perform command on external host

        :param test_name: !! used by decorator
        :param case_name: !! used by decorator
        :param action:
        :param step_counter: !! used by decorator
        :param retry:
        :param timeout:
        :return:
        """
        #  CONNECTION PHASE
        response_from_remote_host = self._connection_phase(action)

        #  VERIFICATION PHASE
        print(response_from_remote_host)
        verification_status = self._verification_phase(
            action, raw_response=response_from_remote_host)

        if not verification_status:
            return ValidationStatus.FAILED
        return ValidationStatus.PASSED

    @staticmethod
    def run_commands_on_host(commands, credentials):
        responses_from_ssh_target = []

        with ExternalCommandOverSSH(**credentials) as host:
            for cmd in commands:
                _, stdout, stderr = host.exec_command(cmd)
                err = stderr.read()
                if err is not '':
                    cts_error(err)
                responses_from_ssh_target.append(stdout.read())

        return responses_from_ssh_target[-1]

    def _connection_phase(self, action):
        """
        Do actions on remote and return last response
        """
        tgt_credential = action['target_credential']
        command = action['command']

        first_target = {
            "target_hostname": tgt_credential['hostname'],
            "target_username": tgt_credential['username'],
            "target_password": tgt_credential['password'],
            "target_port": tgt_credential['port']
        }

        try:
            second_tgt_credential = action['jump_target_credential']
            second_target = {
                "second_hostname": second_tgt_credential['hostname'],
                "second_username": second_tgt_credential['username'],
                "second_password": second_tgt_credential['password'],
                "second_port": second_tgt_credential['port']
            }
        except KeyError:
            return self.run_commands_on_host(command, first_target)

        return self.run_commands_on_host(
            command, self.__merge_dicts(first_target, second_target))

    def _verification_phase(self, action, raw_response=None):
        external_command = action['response']['command']
        expected_response = action['response']['result']

        command_type, command_location = self.__verify_command_type(
            external_command)
        command_args = self.__verify_command_args(external_command)

        external_action = ExecuteExternalScript(
            script_path=command_location,
            raw_response=raw_response,
            expected_result=expected_response)
        if not external_action(**command_args):
            cts_error('Script response IS NOT equal to excepted.')
            return False
        cts_message('Script response is equal to excepted.')
        return True

    @CaseInformation
    def perform_action(self,
                       test_name,
                       case_name,
                       action,
                       step_counter,
                       retry=0,
                       timeout=0):
        case_action = CaseAction(**action)

        cts_message("_____")
        cts_message("{name}: {step}\n\t\t {description}".format(
            name=test_name, step=step_counter, description=case_name))

        cts_message("{request_method}: {request}".format(
            request_method=action['request_method'],
            request=action['request'].replace("{", "(").replace("}", ")")))

        resource_type, resource_id, resource_action = self._get_target_information(
            case_action.request)
        discovered_systems = self.discovery_container.get_resources(
            resource_type, any_child_version=True)

        if not discovered_systems:
            cts_error('Cannot find %s' % resource_type)
            return ValidationStatus.BLOCKED

        try:
            computer_odata_id = self.discovery_container.get(
                discovered_systems[resource_id][0]).odata_id
        except IndexError:
            cts_error('Cannot find %s' % resource_type)
            return ValidationStatus.BLOCKED

        action_endpoint = '{odata_id}/{action_name}'.format(
            odata_id=computer_odata_id, action_name='/'.join(resource_action))

        status, _, _, _ = self.api_caller.post_resource(
            action_endpoint,
            self.discovery_container,
            case_action.payload,
            acceptable_return_codes=case_action.response)

        if (not status) and (retry > 0):
            cts_message("Operation failed, left retry %s" % retry)
            cts_message("Timeout: %s" % timeout)
            sleep(float(timeout))

            self.perform_action(test_name,
                                case_name,
                                action,
                                step_counter,
                                retry=(retry - 1),
                                timeout=timeout)

        if not status:
            cts_error('Cannot execute action %s' % action_endpoint)
            return ValidationStatus.FAILED

        cts_message('End of: %s' % case_name)
        return ValidationStatus.PASSED

    def _get_target_information(self, request):
        request_cleaned = request.replace('{', '').replace('}', '').split('/')

        resource_type = request_cleaned[0]
        resource_id = self._choose_resource(request_cleaned[1])
        action = request_cleaned[2:]

        return resource_type, resource_id, action

    @staticmethod
    def _choose_resource(number):
        return 0 if number == 'any' else int(number)

    @staticmethod
    def __verify_command_type(external_cmd):
        command_type, command_location = (None, ) * 2
        try:
            command_type = external_cmd[0]
            if command_type.lower() != 'python':
                cts_message('CTS support only Python external scripts')
                return False

            command_location = external_cmd[1]
        except IndexError:
            cts_error('Missing verification command details')
        return command_type, command_location

    def __verify_command_args(self, external_cmd):
        command_args = None
        try:
            command_args = self.__generate_dict_from_custom(external_cmd[2:])
        except IndexError:
            pass
        return command_args

    @staticmethod
    def __get_retry_information(action):
        """
        :param action:
        :return: retry_if_fail, retry_timeout
        """
        retry_if_fail, retry_timeout = 0, 1
        try:
            retry_if_fail, retry_timeout = action['action']['retry_if_fail'], \
                                           action['action']['retry_timeout']
        except KeyError:
            pass
        return retry_if_fail, retry_timeout

    #  HELPERS
    @staticmethod
    def __generate_dict_from_custom(list_values):
        dict_from_list = {}
        for element in list_values:
            k, v = element.split(":")
            dict_from_list[k] = v
        return dict_from_list

    @staticmethod
    def __merge_dicts(a, b):
        c = a.copy()
        c.update(b)
        return c
Exemple #34
0
    def node_reset_support(self, discovery_container):
        """
        Test is checking if client is able to perform compute blade reset
        :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer
        :type self.metadata_container: cts_core.metadata.self.metadata_container.MetadataContainer
        """
        print "TEST_CASE::Node reset support"

        test_timeout = 30

        computer_systems = {}
        api_caller = ApiCaller(self.configuration)

        for resource_link, resource in discovery_container.iteritems():
            if self.metadata_container.entities[
                    MetadataConstants.COMPUTER_SYSTEM_1_0_0].is_compatible(
                        resource.odata_type):
                print "MESSAGE::Found Computer System in %s" % resource_link
                computer_systems[resource_link] = resource

        tested_computer_system, tested_computer_system_state = None, None

        for system in computer_systems.keys():
            try:
                status = computer_systems[system].body["Status"]
                memory_gib = computer_systems[system].body["MemorySummary"][
                    "TotalSystemMemoryGiB"]
                if status["Health"] == "OK" and status[
                        "State"] == "Enabled" and memory_gib is not None:
                    tested_computer_system = computer_systems[system]
                    tested_computer_system_state = computer_systems[
                        system].body["PowerState"]
                    break
            except:
                continue

        if not tested_computer_system:
            cts_error(
                "No Computer System suitable for test found (must be enabled and healthy)"
            )
            self.set_validation_status(ValidationStatus.BLOCKED)
            return

        # mapping accepted states with commands to send
        computer_system_reset_commands = {"Off": "On", "On": "ForceOff"}

        if tested_computer_system_state not in computer_system_reset_commands.keys(
        ):
            cts_error("Computer System's state neither \"Off\" nor \"On\"")
            self.set_validation_status(ValidationStatus.FAILED)
            return
        else:
            # by substracting Current System state from {"On", "Off"} set we get the state we expect the Computer System
            # to be in after Reset command
            expected_computer_system_state = list(
                set(computer_system_reset_commands.keys()) -
                {tested_computer_system_state})[0]
            print "MESSAGE::Will try to Power %s Computer System %s " % (
                expected_computer_system_state,
                tested_computer_system.odata_id)

        status, status_code, response_body, _ = \
            api_caller.post_resource("%s/Actions/ComputerSystem.Reset" %
                                     tested_computer_system.url,
                                     discovery_container,
                                     payload={
                                         "ResetType": "%s" %
                                                      computer_system_reset_commands[
                                                          tested_computer_system_state]},
                                     acceptable_return_codes=[
                                         ReturnCodes.NO_CONTENT],
                                     api_endpoint_override=tested_computer_system.netloc)

        if status:
            print "MESSAGE::Reset action accepted by server"
        else:
            cts_error(
                "Incorrect status code {status_code}, response body: {response_body:response_body}",
                **locals())
            self.set_validation_status(ValidationStatus.FAILED)
            return

        print(
            "MESSAGE::Waiting up to %ss for the Computer System to change its state to %s"
            % (test_timeout, expected_computer_system_state))

        test_duration = 0

        while test_duration < test_timeout:
            sleep(1)
            test_duration += 1
            link, status, status_code, response_body, _ = \
                api_caller.get_resource(tested_computer_system.url, discovery_container,
                                        api_endpoint_override=tested_computer_system.netloc)

            if not status:
                cts_error(
                    "Could not get {tested_computer_system} info from the server; status code: {status_code}; "
                    + "response body {response_body:response_body}",
                    **locals())
                self.set_validation_status(ValidationStatus.FAILED)
                return

            if response_body["PowerState"] == expected_computer_system_state:
                print "Message::Computer System has successfully change its state"
                self.set_validation_status(ValidationStatus.PASSED)
                return

        cts_error("Computer System did not change its state in time")
        self.set_validation_status(ValidationStatus.FAILED)
        return
Exemple #35
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
Exemple #36
0
 def download_metadata(self, configuration):
     self.api_caller = ApiCaller(configuration)
     self._process_schema_file(MetadataManager.METADATA_URL)
     return True
Exemple #37
0
    def node_reset_support(self, discovery_container):
        """
        Test is checking if client is able to perform compute blade reset
        :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer
        :type self.metadata_container: cts_core.metadata.self.metadata_container.MetadataContainer
        """
        print "TEST_CASE::Node reset support"

        test_timeout = 90

        computer_systems = {}
        api_caller = ApiCaller(self.configuration)

        for resource_link, resource in discovery_container.iteritems():
            if self.metadata_container.entities[MetadataConstants.COMPUTER_SYSTEM_1_0_0].is_compatible(resource.odata_type):
                print "MESSAGE::Found Computer System in %s" % resource_link
                computer_systems[resource_link] = resource

        tested_computer_system, tested_computer_system_state = None, None

        for system in computer_systems.keys():
            try:
                status = computer_systems[system].body["Status"]
                memory_gib = computer_systems[system].body["MemorySummary"]["TotalSystemMemoryGiB"]
                if status["Health"] == "OK" and status["State"] == "Enabled" and memory_gib is not None:
                    tested_computer_system = computer_systems[system]
                    tested_computer_system_state = computer_systems[system].body["PowerState"]
                    break
            except:
                continue

        if not tested_computer_system:
            cts_error("No Computer System suitable for test found (must be enabled and healthy)")
            self.set_validation_status(ValidationStatus.BLOCKED)
            return

        # mapping accepted states with commands to send
        computer_system_reset_commands = {"Off": "On", "On": "ForceOff"}

        if tested_computer_system_state not in computer_system_reset_commands.keys():
            cts_error("Computer System's state neither \"Off\" nor \"On\"")
            self.set_validation_status(ValidationStatus.FAILED)
            return
        else:
            # by substracting Current System state from {"On", "Off"} set we get the state we expect the Computer System
            # to be in after Reset command
            expected_computer_system_state = list(set(computer_system_reset_commands.keys()) -
                                                     {tested_computer_system_state})[0]
            print "MESSAGE::Will try to Power %s Computer System %s " % (expected_computer_system_state,
                                                                        tested_computer_system.odata_id)

        status, status_code, response_body, _ = \
            api_caller.post_resource("%s/Actions/ComputerSystem.Reset" %
                                     tested_computer_system.url,
                                     discovery_container,
                                     payload={
                                         "ResetType": "%s" %
                                                      computer_system_reset_commands[
                                                          tested_computer_system_state]},
                                     acceptable_return_codes=[
                                         ReturnCodes.NO_CONTENT],
                                     api_endpoint_override=tested_computer_system.netloc)

        if status:
            print "MESSAGE::Reset action accepted by server"
        else:
            cts_error("Incorrect status code {status_code}, response body: {response_body:response_body}", **locals())
            self.set_validation_status(ValidationStatus.FAILED)
            return

        print ("MESSAGE::Waiting up to %ss for the Computer System to change its state to %s" %
                                            (test_timeout, expected_computer_system_state))

        test_duration = 0

        while test_duration < test_timeout:
            sleep(1)
            test_duration += 1
            link, status, status_code, response_body, _ = \
                api_caller.get_resource(tested_computer_system.url, discovery_container,
                                        api_endpoint_override=tested_computer_system.netloc)

            if not status:
                cts_error("Could not get {tested_computer_system} info from the server; status code: {status_code}; " +
                          "response body {response_body:response_body}",
                          **locals())
                self.set_validation_status(ValidationStatus.FAILED)
                return

            if response_body["PowerState"] == expected_computer_system_state:
                print "Message::Computer System has successfully change its state"
                self.set_validation_status(ValidationStatus.PASSED)
                return

        cts_error("Computer System did not change its state in time")
        self.set_validation_status(ValidationStatus.FAILED)
        return