def _check_version(self, version, headers=None): if headers is None: headers = {} # ensure that major version in the URL matches the header if version.major != BASE_VERSION: raise exc.HTTPNotAcceptable( _("Mutually exclusive versions requested. Version %(ver)s " "requested but not supported by this service. The supported " "version range is: [%(min)s, %(max)s].") % { 'ver': version, 'min': versions.min_version_string(), 'max': versions.max_version_string() }, headers=headers) # ensure the minor version is within the supported range if version < min_version() or version > max_version(): raise exc.HTTPNotAcceptable( _("Version %(ver)s was requested but the minor version is not " "supported by this service. The supported version range is: " "[%(min)s, %(max)s].") % { 'ver': version, 'min': versions.min_version_string(), 'max': versions.max_version_string() }, headers=headers)
def _add_version_attributes(self): v = base.Version(api.request.headers, versions.min_version_string(), versions.max_version_string()) # Always set the min and max headers api.response.headers[base.Version.min_string] = ( versions.min_version_string()) api.response.headers[base.Version.max_string] = ( versions.max_version_string()) # assert that requested version is supported self._check_version(v, api.response.headers) api.response.headers[base.Version.string] = str(v) api.request.version = v
def _test_get_root(self, headers=None, additional_expected_resources=None): if headers is None: headers = {} if additional_expected_resources is None: additional_expected_resources = [] data = self.get_json('/', headers=headers) self.assertEqual('v1', data['id']) # Check fields are not empty for f in data: self.assertNotIn(f, ['', []]) # Check if all known resources are present and there are no extra ones. not_resources = ('id', 'links', 'media_types', 'version') actual_resources = tuple(set(data) - set(not_resources)) expected_resources = (['chassis', 'drivers', 'nodes', 'ports'] + additional_expected_resources) self.assertEqual(sorted(expected_resources), sorted(actual_resources)) self.assertIn({'type': 'application/vnd.openstack.ironic.v1+json', 'base': 'application/json'}, data['media_types']) version1 = data['version'] self.assertEqual('v1', version1['id']) self.assertEqual('CURRENT', version1['status']) self.assertEqual(versions.min_version_string(), version1['min_version']) self.assertEqual(versions.max_version_string(), version1['version'])
def _test_drivers(self, use_dynamic, detail=False, latest_if=False): self.register_fake_conductors() headers = {} expected = [ { 'name': self.hw1, 'hosts': [self.h1, self.h2], 'type': 'dynamic' }, { 'name': self.hw2, 'hosts': [self.h1], 'type': 'dynamic' }, ] expected = sorted(expected, key=lambda d: d['name']) if use_dynamic: if latest_if: headers[api_base.Version.string] = \ api_versions.max_version_string() else: headers[api_base.Version.string] = '1.30' path = '/drivers' if detail: path += '?detail=True' data = self.get_json(path, headers=headers) self.assertEqual(len(expected), len(data['drivers'])) drivers = sorted(data['drivers'], key=lambda d: d['name']) for i in range(len(expected)): d = drivers[i] e = expected[i] self.assertEqual(e['name'], d['name']) self.assertEqual(sorted(e['hosts']), sorted(d['hosts'])) self.validate_link(d['links'][0]['href']) self.validate_link(d['links'][1]['href']) if use_dynamic: self.assertEqual(e['type'], d['type']) # NOTE(jroll) we don't test detail=True with use_dynamic=False # as this case can't actually happen. if detail: self.assertIn('default_deploy_interface', d) if latest_if: self.assertIn('default_rescue_interface', d) self.assertIn('enabled_rescue_interfaces', d) self.assertIn('default_storage_interface', d) self.assertIn('enabled_storage_interfaces', d) else: self.assertNotIn('default_rescue_interface', d) self.assertNotIn('enabled_rescue_interfaces', d) self.assertNotIn('default_storage_interface', d) self.assertNotIn('enabled_storage_interfaces', d) else: # ensure we don't spill these fields into driver listing # one should be enough self.assertNotIn('default_deploy_interface', d)
def default_version(): """Return a dict representing the current default version id: The ID of the (major) version, also acts as the release number links: A list containing one link that points to the current version of the API status: Status of the version, one of CURRENT, SUPPORTED, DEPRECATED min_version: The current, maximum supported (major.minor) version of API. version: Minimum supported (major.minor) version of API. """ # NOTE(dtantsur): avoid circular imports from ironic.api.controllers.v1 import versions return { 'id': ID_VERSION1, 'links': [ link.make_link('self', api.request.public_url, ID_VERSION1, '', bookmark=True) ], 'status': 'CURRENT', 'min_version': versions.min_version_string(), 'version': versions.max_version_string() }
def _test_get_root(self, headers=None, additional_expected_resources=None): if headers is None: headers = {} if additional_expected_resources is None: additional_expected_resources = [] data = self.get_json('/', headers=headers) self.assertEqual('v1', data['id']) # Check fields are not empty for f in data: self.assertNotIn(f, ['', []]) # Check if all known resources are present and there are no extra ones. not_resources = ('id', 'links', 'media_types', 'version') actual_resources = tuple(set(data) - set(not_resources)) expected_resources = (['chassis', 'drivers', 'nodes', 'ports'] + additional_expected_resources) self.assertEqual(sorted(expected_resources), sorted(actual_resources)) self.assertEqual( { 'type': 'application/vnd.openstack.ironic.v1+json', 'base': 'application/json' }, data['media_types']) version1 = data['version'] self.assertEqual('v1', version1['id']) self.assertEqual('CURRENT', version1['status']) self.assertEqual(versions.min_version_string(), version1['min_version']) self.assertEqual(versions.max_version_string(), version1['version'])
def _route(self, args, request=None): v = base.Version(pecan.request.headers, versions.min_version_string(), versions.max_version_string()) # Always set the min and max headers pecan.response.headers[base.Version.min_string] = ( versions.min_version_string()) pecan.response.headers[base.Version.max_string] = ( versions.max_version_string()) # assert that requested version is supported self._check_version(v, pecan.response.headers) pecan.response.headers[base.Version.string] = str(v) pecan.request.version = v return super(Controller, self)._route(args, request)
def convert(): root = Root() root.name = "OpenStack Ironic API" root.description = ("Ironic is an OpenStack project which aims to " "provision baremetal machines.") root.default_version = Version(ID_VERSION1, versions.min_version_string(), versions.max_version_string()) root.versions = [root.default_version] return root
def test_max_version_pinned(self, mock_release_mapping): CONF.set_override('pin_release_version', release_mappings.RELEASE_VERSIONS[-1]) mock_release_mapping.get.return_value = { 'api': '1.5', 'rpc': '1.4', 'objects': { 'MyObj': ['1.4'], } } self.assertEqual('1.5', versions.max_version_string())
def test_max_version_pinned(self, mock_release_mapping): CONF.set_override('pin_release_version', release_mappings.RELEASE_VERSIONS[-1]) mock_release_mapping.get.return_value = { 'api': '1.5', 'rpc': '1.4', 'objects': { 'MyObj': ['1.4'], } } self.assertEqual('1.5', versions.max_version_string())
def _check_version(self, version, headers=None): if headers is None: headers = {} # ensure that major version in the URL matches the header if version.major != BASE_VERSION: raise exc.HTTPNotAcceptable(_( "Mutually exclusive versions requested. Version %(ver)s " "requested but not supported by this service. The supported " "version range is: [%(min)s, %(max)s].") % {'ver': version, 'min': versions.min_version_string(), 'max': versions.max_version_string()}, headers=headers) # ensure the minor version is within the supported range if version < min_version() or version > max_version(): raise exc.HTTPNotAcceptable(_( "Version %(ver)s was requested but the minor version is not " "supported by this service. The supported version range is: " "[%(min)s, %(max)s].") % {'ver': version, 'min': versions.min_version_string(), 'max': versions.max_version_string()}, headers=headers)
def _test_drivers(self, use_dynamic, detail=False, latest_if=False): self.register_fake_conductors() headers = {} expected = [ {'name': self.hw1, 'hosts': [self.h1, self.h2], 'type': 'dynamic'}, {'name': self.hw2, 'hosts': [self.h1], 'type': 'dynamic'}, ] expected = sorted(expected, key=lambda d: d['name']) if use_dynamic: if latest_if: headers[api_base.Version.string] = \ api_versions.max_version_string() else: headers[api_base.Version.string] = '1.30' path = '/drivers' if detail: path += '?detail=True' data = self.get_json(path, headers=headers) self.assertEqual(len(expected), len(data['drivers'])) drivers = sorted(data['drivers'], key=lambda d: d['name']) for i in range(len(expected)): d = drivers[i] e = expected[i] self.assertEqual(e['name'], d['name']) self.assertEqual(sorted(e['hosts']), sorted(d['hosts'])) self.validate_link(d['links'][0]['href']) self.validate_link(d['links'][1]['href']) if use_dynamic: self.assertEqual(e['type'], d['type']) # NOTE(jroll) we don't test detail=True with use_dynamic=False # as this case can't actually happen. if detail: self.assertIn('default_deploy_interface', d) if latest_if: self.assertIn('default_rescue_interface', d) self.assertIn('enabled_rescue_interfaces', d) self.assertIn('default_storage_interface', d) self.assertIn('enabled_storage_interfaces', d) else: self.assertNotIn('default_rescue_interface', d) self.assertNotIn('enabled_rescue_interfaces', d) self.assertNotIn('default_storage_interface', d) self.assertNotIn('enabled_storage_interfaces', d) else: # ensure we don't spill these fields into driver listing # one should be enough self.assertNotIn('default_deploy_interface', d)
def test_get_root(self): response = self.get_json('/', path_prefix='') # Check fields are not empty [self.assertNotIn(f, ['', []]) for f in response] self.assertEqual('OpenStack Ironic API', response['name']) self.assertTrue(response['description']) self.assertEqual([response['default_version']], response['versions']) version1 = response['default_version'] self.assertEqual('v1', version1['id']) self.assertEqual('CURRENT', version1['status']) self.assertEqual(versions.min_version_string(), version1['min_version']) self.assertEqual(versions.max_version_string(), version1['version'])
def test_get_root(self): response = self.get_json('/', path_prefix='') # Check fields are not empty [self.assertNotIn(f, ['', []]) for f in response] self.assertEqual('OpenStack Ironic API', response['name']) self.assertTrue(response['description']) self.assertEqual([response['default_version']], response['versions']) version1 = response['default_version'] self.assertEqual('v1', version1['id']) self.assertEqual('CURRENT', version1['status']) self.assertEqual(versions.min_version_string(), version1['min_version']) self.assertEqual(versions.max_version_string(), version1['version'])
def setUp(self): super(TestPostRBAC, self).setUp() cfg.CONF.set_override('enforce_scope', True, group='oslo_policy') cfg.CONF.set_override('enforce_new_defaults', True, group='oslo_policy') cfg.CONF.set_override('auth_strategy', 'keystone') # Headers required for this to pass in system scope restricted # authentication, as our default for api tests is noauth. self.headers = { api_base.Version.string: str(versions.max_version_string()), 'X-Auth-Token': 'test-auth-token', 'X-Roles': 'admin', 'OpenStack-System-Scope': 'all', }
def _test_drivers_get_one_ok(self, use_dynamic, mock_driver_properties, latest_if=False): # get_driver_properties mock is required by validate_link() self.register_fake_conductors() if use_dynamic: driver = self.d3 driver_type = 'dynamic' hosts = [self.h1, self.h2] else: driver = self.d1 driver_type = 'classic' hosts = [self.h1] headers = {} if latest_if: headers[api_base.Version.string] = \ api_versions.max_version_string() else: headers[api_base.Version.string] = '1.30' data = self.get_json('/drivers/%s' % driver, headers=headers) self.assertEqual(driver, data['name']) self.assertEqual(sorted(hosts), sorted(data['hosts'])) self.assertIn('properties', data) self.assertEqual(driver_type, data['type']) if use_dynamic: for iface in driver_base.ALL_INTERFACES: if iface != 'bios': if latest_if or iface not in ['rescue', 'storage']: self.assertIn('default_%s_interface' % iface, data) self.assertIn('enabled_%s_interfaces' % iface, data) self.assertIsNotNone(data['default_deploy_interface']) self.assertIsNotNone(data['enabled_deploy_interfaces']) else: self.assertIsNone(data['default_deploy_interface']) self.assertIsNone(data['enabled_deploy_interfaces']) self.validate_link(data['links'][0]['href']) self.validate_link(data['links'][1]['href']) self.validate_link(data['properties'][0]['href']) self.validate_link(data['properties'][1]['href'])
def _test_drivers_get_one_ok(self, mock_driver_properties, latest_if=False): # get_driver_properties mock is required by validate_link() self.register_fake_conductors() driver = self.hw1 driver_type = 'dynamic' hosts = [self.h1, self.h2] headers = {} if latest_if: headers[api_base.Version.string] = \ api_versions.max_version_string() else: headers[api_base.Version.string] = '1.30' data = self.get_json('/drivers/%s' % driver, headers=headers) self.assertEqual(driver, data['name']) self.assertEqual(sorted(hosts), sorted(data['hosts'])) self.assertIn('properties', data) self.assertEqual(driver_type, data['type']) for iface in driver_base.ALL_INTERFACES: if iface != 'bios': if latest_if or iface not in ['rescue', 'storage']: self.assertIn('default_%s_interface' % iface, data) self.assertIn('enabled_%s_interfaces' % iface, data) self.assertIsNotNone(data['default_deploy_interface']) self.assertIsNotNone(data['enabled_deploy_interfaces']) self.validate_link(data['links'][0]['href']) self.validate_link(data['links'][1]['href']) self.validate_link(data['properties'][0]['href']) self.validate_link(data['properties'][1]['href'])
def _test_request(self, path, params=None, headers=None, method='get', body=None, assert_status=None, assert_dict_contains=None, assert_list_length=None, deprecated=None): path = path.format(**self.format_data) self.mock_auth.side_effect = self._fake_process_request # always request the latest api version version = api_versions.max_version_string() rheaders = { 'X-OpenStack-Ironic-API-Version': version } # NOTE(TheJulia): Logging the test request to aid # in troubleshooting ACL testing. This is a pattern # followed in API unit testing in ironic, and # really does help. print('API ACL Testing Path %s %s' % (method, path)) if headers: for k, v in headers.items(): rheaders[k] = v.format(**self.format_data) if method == 'get': response = self.get_json( path, headers=rheaders, expect_errors=True, extra_environ=self.environ, path_prefix='' ) elif method == 'put': response = self.put_json( path, headers=rheaders, expect_errors=True, extra_environ=self.environ, path_prefix='', params=body ) elif method == 'post': response = self.post_json( path, headers=rheaders, expect_errors=True, extra_environ=self.environ, path_prefix='', params=body ) elif method == 'patch': response = self.patch_json( path, params=body, headers=rheaders, expect_errors=True, extra_environ=self.environ, path_prefix='' ) elif method == 'delete': response = self.delete( path, headers=rheaders, expect_errors=True, extra_environ=self.environ, path_prefix='' ) else: assert False, 'Unimplemented test method: %s' % method # Once miggrated: # Items will return: # 403 - Trying to access something that is generally denied. # Example: PATCH /v1/nodes/<uuid> as a reader. # 404 - Trying to access something where we don't have permissions # in a project scope. This is particularly true where implied # permissions or assocation exists. Ports are attempted to be # accessed when the underlying node is inaccessible as owner # nor node matches. # Example: GET /v1/portgroups or /v1/nodes/<uuid>/ports # 500 - Attempting to access something such an system scoped endpoint # with a project scoped request. Example: /v1/conductors. if not (bool(deprecated) and ('404' in response.status or '500' in response.status or '403' in response.status) and cfg.CONF.oslo_policy.enforce_scope and cfg.CONF.oslo_policy.enforce_new_defaults): self.assertEqual(assert_status, response.status_int) else: self.assertTrue( ('404' in response.status or '500' in response.status or '403' in response.status)) # We can't check the contents of the response if there is no # response. return if not bool(deprecated): self.assertIsNotNone(assert_status, 'Tests must include an assert_status') if assert_dict_contains: for k, v in assert_dict_contains.items(): self.assertIn(k, response) print(k) print(v) if str(v) == "None": # Compare since the variable loaded from the # json ends up being null in json or None. self.assertIsNone(response.json[k]) elif str(v) == "{}": # Special match for signifying a dictonary. self.assertEqual({}, response.json[k]) elif isinstance(v, dict): # The value from the YAML can be a dictionary, # which cannot be formatted, so we're likely doing # direct matching. self.assertEqual(str(v), str(response.json[k])) else: self.assertEqual(v.format(**self.format_data), response.json[k]) if assert_list_length: for root, length in assert_list_length.items(): # root - object to look inside # length - number of expected elements which will be # important for owner/lessee testing. items = response.json[root] self.assertIsInstance(items, list) if not (bool(deprecated) and cfg.CONF.oslo_policy.enforce_scope): self.assertEqual(length, len(items)) else: # If we have scope enforcement, we likely have different # views, such as "other" admins being subjected to # a filtered view in these cases. self.assertEqual(0, len(items)) # NOTE(TheJulia): API tests in Ironic tend to have a pattern # to print request and response data to aid in development # and troubleshooting. As such the prints should remain, # at least until we are through primary development of the # this test suite. print('ACL Test GOT %s' % response)
def test_max_version(self): response = self.get_json('/', headers={ 'Accept': 'application/json', 'X-OpenStack-Ironic-API-Version': versions.max_version_string() }) self.assertEqual( { 'id': 'v1', 'links': [{ 'href': 'http://localhost/v1/', 'rel': 'self' }, { 'href': 'https://docs.openstack.org//ironic/latest' '/contributor//webapi.html', 'rel': 'describedby', 'type': 'text/html' }], 'media_types': { 'base': 'application/json', 'type': 'application/vnd.openstack.ironic.v1+json' }, 'version': { 'id': 'v1', 'links': [{ 'href': 'http://localhost/v1/', 'rel': 'self' }], 'status': 'CURRENT', 'min_version': versions.min_version_string(), 'version': versions.max_version_string() }, 'allocations': [{ 'href': 'http://localhost/v1/allocations/', 'rel': 'self' }, { 'href': 'http://localhost/allocations/', 'rel': 'bookmark' }], 'chassis': [{ 'href': 'http://localhost/v1/chassis/', 'rel': 'self' }, { 'href': 'http://localhost/chassis/', 'rel': 'bookmark' }], 'conductors': [{ 'href': 'http://localhost/v1/conductors/', 'rel': 'self' }, { 'href': 'http://localhost/conductors/', 'rel': 'bookmark' }], 'deploy_templates': [{ 'href': 'http://localhost/v1/deploy_templates/', 'rel': 'self' }, { 'href': 'http://localhost/deploy_templates/', 'rel': 'bookmark' }], 'drivers': [{ 'href': 'http://localhost/v1/drivers/', 'rel': 'self' }, { 'href': 'http://localhost/drivers/', 'rel': 'bookmark' }], 'events': [{ 'href': 'http://localhost/v1/events/', 'rel': 'self' }, { 'href': 'http://localhost/events/', 'rel': 'bookmark' }], 'heartbeat': [{ 'href': 'http://localhost/v1/heartbeat/', 'rel': 'self' }, { 'href': 'http://localhost/heartbeat/', 'rel': 'bookmark' }], 'lookup': [{ 'href': 'http://localhost/v1/lookup/', 'rel': 'self' }, { 'href': 'http://localhost/lookup/', 'rel': 'bookmark' }], 'nodes': [{ 'href': 'http://localhost/v1/nodes/', 'rel': 'self' }, { 'href': 'http://localhost/nodes/', 'rel': 'bookmark' }], 'portgroups': [{ 'href': 'http://localhost/v1/portgroups/', 'rel': 'self' }, { 'href': 'http://localhost/portgroups/', 'rel': 'bookmark' }], 'ports': [{ 'href': 'http://localhost/v1/ports/', 'rel': 'self' }, { 'href': 'http://localhost/ports/', 'rel': 'bookmark' }], 'volume': [{ 'href': 'http://localhost/v1/volume/', 'rel': 'self' }, { 'href': 'http://localhost/volume/', 'rel': 'bookmark' }] }, response)
def default_version(): # NOTE(dtantsur): avoid circular imports from ironic.api.controllers.v1 import versions return Version(ID_VERSION1, versions.min_version_string(), versions.max_version_string())
def test_max_version_not_pinned_in_release_mappings(self): CONF.set_override('pin_release_version', None) self.assertEqual(release_mappings.RELEASE_MAPPING['master']['api'], versions.max_version_string())
def test_max_version_not_pinned(self): CONF.set_override('pin_release_version', None) self.assertEqual(versions._MAX_VERSION_STRING, versions.max_version_string())
def max_version(): return base.Version( {base.Version.string: versions.max_version_string()}, versions.min_version_string(), versions.max_version_string())
def test_max_version_not_pinned_in_release_mappings(self): CONF.set_override('pin_release_version', None) self.assertEqual(release_mappings.RELEASE_MAPPING['master']['api'], versions.max_version_string())
def setUp(self): super(TestPost, self).setUp() self.headers = { api_base.Version.string: str(versions.max_version_string()) }
def test_max_version_not_pinned(self): CONF.set_override('pin_release_version', None) self.assertEqual(versions._MAX_VERSION_STRING, versions.max_version_string())