def test_discovery_from_nested_additional_properties(self): metadata = """ <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="N"> <EntityType Name="Outer" Abstract="false"> <Annotation Term="OData.AdditionalProperties" Bool="true"/> <Property Name="Oem" Type="N.Oem"> </Property> </EntityType> <ComplexType Name="Oem" Abstract="false"> <Annotation Term="OData.AdditionalProperties" Bool="true"/> </ComplexType> <ComplexType Name="Inner" Abstract="false"> <NavigationProperty Name="Link" Type="N.Referenced" ContainsTarget="true"> </NavigationProperty> </ComplexType> <EntityType Name="Referenced" Abstract="false"> </EntityType> </Schema> """ resource = \ { "@odata.id": "resource.id", "@odata.type": "#N.Outer", "Oem": { "additional_property": { "@odata.type": "#N.Inner", "Link": { "@odata.id": "/this/is/the/link/to/be/discovered" }, } } } referenced = \ { "@odata.id": "referenced.id", "@odata.type": "#N.Referenced", } metadata_manager = MetadataManager(["qualifier"]) metadata_container = metadata_manager.read_metadata_from_strings("Unknown", metadata) discovery_container = DiscoveryContainer() configuration = Configuration( **dict(UseSSL='True', ApiEndpoint=API_ENDPOINT)) api_explorer = ApiExplorer(metadata_container, configuration) with mock.patch( 'cts_core.discovery.api_explorer.ApiExplorer._get_resource') as get_resource: get_resource.side_effect = [(Link('link', 'netloc'), RequestStatus.SUCCESS, resource, None), (Link('link', 'netloc'), RequestStatus.SUCCESS, referenced, None)] with StdoutCapture() as output: api_explorer.discover("/outer", "N.Outer", discovery_container) self.assertEqual(2, get_resource.call_count) self.assertNotIn("ERROR::", '\n'.join(output))
def test_handle_collection_with_typeless_elements_referenced_by_json_pointer( self): metadata = """ <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="N"> <EntityType Name="Outer" Abstract="false"> <NavigationProperty Name="MyCollection" Type="Collection(ReferencedEntity)" ContainsTarget="true"> <Annotation Term="OData.Permissions" EnumMember="OData.Permission/Read"/> </NavigationProperty> </EntityType> <EntityType Name="ReferencedEntity" Abstract="false"> <Property Name="name" Type="Edm.String"/> </EntityType> </Schema> """ resource = \ { "@odata.id": "resource.id", "@odata.type": "N.Outer", "MyCollection": [ { # implicit odata.type = ReferencedEntity "@odata.id": "/outer#MyCollection/0", "name": "MyName 0" }, { # implicit odata_type = ReferencedEntity "@odata.id": "/outer#MyCollection/1", "name": "MyName 1" } ] } metadata_manager = MetadataManager(["qualifier"]) metadata_container = metadata_manager.read_metadata_from_strings( metadata) discovery_container = DiscoveryContainer() configuration = Configuration( **dict(UseSSL='True', ApiEndpoint=API_ENDPOINT)) api_explorer = ApiExplorer(metadata_container, configuration) with mock.patch('cts_core.commons.api_caller.ApiCaller.get_resource' ) as get_resource: get_resource.side_effect = [(Link('link', 'netloc'), RequestStatus.SUCCESS, ReturnCodes.OK, resource, None), (Link('link', 'netloc'), RequestStatus.SUCCESS, ReturnCodes.OK, resource, None), (Link('link', 'netloc'), RequestStatus.SUCCESS, ReturnCodes.OK, resource, None)] with StdoutCapture() as output: api_explorer.discover("/outer", "N.Outer", discovery_container) self.assertEqual(3, get_resource.call_count) self.assertEqual(0, len(re.findall('ERROR::', ';'.join(output))))
def test_empty_body(self): self.api_explorer._get_resource = MagicMock( return_value=(Link('link', 'netloc'), RequestStatus.SUCCESS, {})) with StdoutCapture() as output: self.api_explorer.discover("odata_id", None) self.assertIn('Empty response body', '\n'.join(output))
def test_correct_body_processing(self): discovery_container = DiscoveryContainer() self.api_explorer._metadata_container = MagicMock() self.api_explorer._get_resource = MagicMock(return_value=(Link('link', 'netloc'), RequestStatus.SUCCESS, dict(a=1), None)) self.api_explorer._process_resource = MagicMock(return_value=discovery_container) self.api_explorer.discover("url", None) self.api_explorer._process_resource.assert_called_once_with(ANY)
def test_get_resource_with_odata(self): self.api_explorer._api_caller.get_resource = MagicMock( return_value=( Link('link', 'netloc'), RequestStatus.SUCCESS, None, { '@odata.type': 'type.of.b', 'inner': [ {'@odata.id': '/redfish/a/b#inner/3'}, {'@odata.id': '/redfish/a/b#inner/4'} ] }, None)) link, status, body, _ = self.api_explorer._get_resource("/redfish/a/b#inner/3") self.assertEqual(RequestStatus.SUCCESS, status) self.assertEqual({'@odata.id': '/redfish/a/b#inner/3'}, body)
def test_get_resource(self): self.api_explorer._api_caller.get_resource = MagicMock( return_value=( Link('link', 'netloc'), RequestStatus.SUCCESS, None, { '@odata.type': 'type.of.b', 'inner': [ {'@odata.type': 'type.of.first.inner'}, {'@odata.type': 'type.of.second.inner'} ] }, None)) link, status, body, _ = self.api_explorer._get_resource("/a/b#inner/0") self.assertEqual(RequestStatus.SUCCESS, status) self.assertEqual({'@odata.type': 'type.of.first.inner'}, body)
def test_get_resource_with_odata_failed(self): self.api_explorer._api_caller.get_resource = MagicMock( return_value=( Link('link', 'netloc'), RequestStatus.SUCCESS, None, { '@odata.type': 'type.of.b', 'inner': [ {'@odata.id': '/redfish/a/b#inner/3'}, {'@odata.id': '/redfish/a/b#inner/4'} ] }, None)) with StdoutCapture() as output: link, status, body, _ = self.api_explorer._get_resource("/redfish/a/b#inner/543534634") self.assertIsNot(RequestStatus.SUCCESS, status) self.assertIn( "ERROR::Could not find /inner/543534634 from url=/redfish/a/b#inner/543534634 with search by @odata.id;", output.raw)
def test_discovery_error_if_unexpected_additional_properties(self): """ Entity type used in this test does not accept additional properties. As a result, ApiExplorer is expected to raise an error about unexpected 'additional_property' property. ApiExplorer validate """ metadata = """ <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="N"> <EntityType Name="Outer" Abstract="false"> <Property Name="@odata.id" Type="Edm.String"/> <Property Name="@odata.type" Type="Edm.String"/> <Property Name="expected" Type="Edm.Int64"/> <Annotation Term="OData.AdditionalProperties" Bool="false"/> </EntityType> </Schema> """ resource = \ { "@odata.id": "id", "@odata.type": "N.Outer", "expected": 10, "additional_property": 1 } metadata_manager = MetadataManager(["qualifier"]) metadata_container = metadata_manager.read_metadata_from_strings( "Unknown", metadata) discovery_container = DiscoveryContainer() configuration = Configuration( **dict(UseSSL='True', ApiEndpoint=API_ENDPOINT)) api_explorer = ApiExplorer(metadata_container, configuration) with mock.patch( 'cts_core.discovery.api_explorer.ApiExplorer._get_resource' ) as get_resource: get_resource.side_effect = [ (Link('link', 'netloc'), RequestStatus.SUCCESS, resource, None) ] with StdoutCapture() as output: api_explorer.discover("/outer", "N.Outer", discovery_container) self.assertEqual(1, get_resource.call_count) self.assertIn("unexpected properties: [additional_property]", '\n'.join(output))
def test_discovery_can_handle_null_navigation_property(self): metadata = """ <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="N"> <EntityType Name="Outer" Abstract="false"> <NavigationProperty Name="Link" Type="N.Referenced" ContainsTarget="true"> </NavigationProperty> </EntityType> <EntityType Name="Referenced" Abstract="false"> </EntityType> </Schema> """ resource = \ { "@odata.id": "resource.id", "@odata.type": "#N.Outer", "Link": { "@odata.id": None }, } metadata_manager = MetadataManager(["qualifier"]) metadata_container = metadata_manager.read_metadata_from_strings( "Unknown", metadata) discovery_container = DiscoveryContainer() configuration = Configuration( **dict(UseSSL='True', ApiEndpoint=API_ENDPOINT)) api_explorer = ApiExplorer(metadata_container, configuration) with mock.patch( 'cts_core.discovery.api_explorer.ApiExplorer._get_resource' ) as get_resource: get_resource.side_effect = [ (Link('link', 'netloc'), RequestStatus.SUCCESS, resource, None) ] with StdoutCapture() as output: api_explorer.discover("/outer", "N.Outer", discovery_container) self.assertEqual(1, get_resource.call_count) self.assertNotIn("ERROR::", '\n'.join(output))
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))
def test_validate_elements_of_collection_with_elements_referenced_by_out_of_range_json_pointer( self): metadata = """ <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="N"> <EntityType Name="Outer" Abstract="false"> <NavigationProperty Name="MyCollection" Type="Collection(ReferencedEntity)" ContainsTarget="true"> <Annotation Term="OData.Permissions" EnumMember="OData.Permission/Read"/> </NavigationProperty> </EntityType> <EntityType Name="ReferencedEntity" Abstract="false"> <Property Name="name" Type="Edm.String"/> </EntityType> </Schema> """ resource = \ { "@odata.id": "resource.id", "@odata.type": "N.Outer", "MyCollection": [ { "@odata.id": "/outer#MyCollection/0" }, { "@odata.id": "/outer#MyCollection/2" # pointer out of range } ] } metadata_manager = MetadataManager(["qualifier"]) metadata_container = metadata_manager.read_metadata_from_strings( "Unknown", metadata) discovery_container = DiscoveryContainer() configuration = Configuration( **dict(UseSSL='True', ApiEndpoint=API_ENDPOINT)) api_explorer = ApiExplorer(metadata_container, configuration) with mock.patch('cts_core.commons.api_caller.ApiCaller.get_resource' ) as get_resource: get_resource.side_effect = [ (Link('url1', 'netloc'), RequestStatus.SUCCESS, ReturnCodes.OK, resource, Link('url1', 'netloc')), (Link('url2', 'netloc'), RequestStatus.SUCCESS, ReturnCodes.OK, resource, Link('url2', 'netloc')), (Link('url3', 'netloc'), RequestStatus.SUCCESS, ReturnCodes.OK, resource, Link('url3', 'netloc')) ] with StdoutCapture() as output: api_explorer.discover("/outer", "N.Outer", discovery_container) self.assertEqual(3, get_resource.call_count) self.assertIn( 'ERROR::JSON pointer exception while dereferencing /MyCollection/2', ';'.join(output)) requirements = [] validator = MetadataGetValidator(metadata_container, requirements) with StdoutCapture() as output: self.assertEqual(ValidationStatus.PASSED, validator.validate(discovery_container)) self.assertEqual(0, len(re.findall('ERROR::', ';'.join(output))))
def test_handle_complex_types_with_dynamic_property_patterns(self): metadata = """ <Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="N"> <EntityType Name="Outer" Abstract="false"> <Property Name="inner" Type="N.TypeWithDynamics"/> </EntityType> <ComplexType Name="TypeWithDynamics" Abstract="false"> <Annotation Term="OData.AdditionalProperties" Bool="true"/> <Annotation Term="Redfish.DynamicPropertyPatterns"> <Collection> <Record> <PropertyValue Property="Pattern" String="[a-z0-9]+"/> <PropertyValue Property="Type" String="N.Referenced"/> </Record> <Record> <PropertyValue Property="Pattern" String="[A-Z0-9]+"/> <PropertyValue Property="Type" String="Edm.String"/> </Record> </Collection> </Annotation> </ComplexType> <ComplexType Name="Referenced" Abstract="false"> <Property Name="name" Type="Edm.String"/> </ComplexType> </Schema> """ resource = \ { "@odata.id": "resource.id", "@odata.type": "N.Outer", "inner": { "additional1": { "name": "additional#1" }, "additional2": { "name": "additional#2" }, "ADDITIONAL3": "ADDITIONAL3", "ADDITIONAL4": "ADDITIONAL4", "SiMpLeTyPeIsOk": 22, "CoMpLeXInVaLiD": {"x": 1} } } metadata_manager = MetadataManager(["qualifier"]) metadata_container = metadata_manager.read_metadata_from_strings("Unknown", metadata) discovery_container = DiscoveryContainer() configuration = Configuration( **dict(UseSSL='True', ApiEndpoint=API_ENDPOINT)) api_explorer = ApiExplorer(metadata_container, configuration) with mock.patch( 'cts_core.discovery.api_explorer.ApiExplorer._get_resource') as get_resource: get_resource.side_effect = [(Link('https://{API_ENDPOINT}/outer'.format( API_ENDPOINT=API_ENDPOINT), 'netloc'), RequestStatus.SUCCESS, resource, None)] with StdoutCapture() as output: api_explorer.discover("/outer", "N.Outer", discovery_container) self.assertEqual(1, get_resource.call_count) self.assertEqual(1, len(re.findall('ERROR::', ';'.join(output)))) self.assertIn( 'ERROR::url=https://1.2.3.4:567/outer#inner.CoMpLeXInVaLiD: @odata.type not found in complex additional property;', ';'.join(output))
def test_patch_nontrivial_boot_property(self): metadata_manager = MetadataManager(["qualifier"]) this_dir = os.path.dirname(os.path.realpath(__file__)) metadata_dir = os.path.join(this_dir, 'metadata') self.assertTrue(metadata_manager.read_metadata_from_dir(metadata_dir)) metadata_container = metadata_manager.metadata_container entity_json = { "@odata.context": "/redfish/v1/$metadata#ComputerSystem.ComputerSystem", "@odata.id": "/redfish/v1/Systems/34", "@odata.type": "#ComputerSystem.v1_3_0.ComputerSystem", "Id": "34", "Name": "Computer System", "Description": "Computer System description", "SystemType": "Physical", "AssetTag": "AZaz09[]!@#$%^&*()_+", "Manufacturer": None, "Model": None, "SKU": None, "SerialNumber": None, "PartNumber": None, "UUID": "1d7a1040-05e4-11e6-a108-54ab3a8ec80f", "HostName": None, "Status": { "State": "Enabled", "Health": "OK", "HealthRollup": "OK" }, "IndicatorLED": None, "PowerState": "Off", "BiosVersion": "F20A3A03", "Boot": { "@odata.type": "#ComputerSystem.v1_1_0.Boot", "BootSourceOverrideEnabled": "Disabled", "BootSourceOverrideTarget": "None", "*****@*****.**": ["Hdd", "Pxe", "None", "RemoteDrive"], "BootSourceOverrideMode": "Legacy", "*****@*****.**": ["Legacy", "UEFI"] }, "ProcessorSummary": { "Count": 2, "Model": "Intel(R) Xeon(R)", "Status": { "State": "Enabled", "Health": "OK", "HealthRollup": "OK" } }, "MemorySummary": { "TotalSystemMemoryGiB": 30.52, "Status": { "State": "Enabled", "Health": "OK", "HealthRollup": "OK" } }, "Processors": { "@odata.id": "/redfish/v1/Systems/34/Processors" }, "EthernetInterfaces": { "@odata.id": "/redfish/v1/Systems/34/EthernetInterfaces" }, "NetworkInterfaces": { "@odata.id": "/redfish/v1/Systems/34/NetworkInterfaces" }, "Storage": { "@odata.id": "/redfish/v1/Systems/34/Storage" }, "Memory": { "@odata.id": "/redfish/v1/Systems/34/Memory" }, "PCIeDevices": [], "PCIeFunctions": [], "TrustedModules": [], "Links": { "@odata.type": "#ComputerSystem.v1_2_0.Links", "Chassis": [{ "@odata.id": "/redfish/v1/Chassis/38" }], "Endpoints": [], "ManagedBy": [{ "@odata.id": "/redfish/v1/Managers/38" }], "Oem": {} }, "Actions": { "#ComputerSystem.Reset": { "target": "/redfish/v1/Systems/34/Actions/ComputerSystem.Reset", "*****@*****.**": [ "On", "ForceOff", "GracefulShutdown", "GracefulRestart", "ForceRestart" ] }, "Oem": { "#Intel.Oem.ChangeTPMState": { "target": "/redfish/v1/Systems/34/Actions/Oem/Intel.Oem.ChangeTPMState", "*****@*****.**": [] } } }, "Oem": { "Intel_RackScale": { "@odata.type": "#Intel.Oem.ComputerSystem", "PciDevices": [], "PCIeConnectionId": [], "ProcessorSockets": 2, "MemorySockets": 16, "DiscoveryState": "Basic", "UserModeEnabled": None, "TrustedExecutionTechnologyEnabled": None, "Metrics": { "@odata.id": "/redfish/v1/Systems/34/Metrics" } } } } self.discovery_container.add_resource( ApiResource("/redfish/v1/Systems/34", "https://localhost:8443", entity_json, "#ComputerSystem.v1_3_0.ComputerSystem")) with mock.patch('cts_core.commons.api_caller.ApiCaller.__init__' ) as api_caller_init_mock: api_caller_init_mock.return_value = None validator = MetadataPatchValidator(metadata_container, None, PatchingStrategy2_2()) # this ValidationStatus.BLOCKED should be not affect final ValidationStatus validator._verify_property = MagicMock( return_value=(ValidationStatus.PASSED, None)) validator._restore_property = MagicMock( return_value=ValidationStatus.PASSED) validator._patch_property = MagicMock( return_value=(True, True, ValidationStatus.PASSED)) with mock.patch( 'cts_core.commons.api_caller.ApiCaller.patch_resource' ) as api_caller_patch_resource: api_caller_patch_resource.return_value = ( RequestStatus.SUCCESS, 200, None, None) with mock.patch( 'cts_core.commons.api_caller.ApiCaller.get_resource' ) as api_caller_get_resource: api_caller_get_resource.return_value = (Link( "/redfish/v1/Systems/34", "https://localhost:8443"), RequestStatus.SUCCESS, 200, entity_json, None) with StdoutCapture() as out: self.assertEqual( ValidationStatus.PASSED, validator.validate(self.discovery_container)) self.assertNotIn('WARNING::Skipping non-trivial property', out.raw)