Merge "Validate the generated swift temp url"

This commit is contained in:
Jenkins 2017-01-19 13:50:20 +00:00 committed by Gerrit Code Review
commit 5ef20fe6c3
5 changed files with 58 additions and 19 deletions

View File

@ -95,23 +95,26 @@ class BaseImageService(object):
class HttpImageService(BaseImageService): class HttpImageService(BaseImageService):
"""Provides retrieval of disk images using HTTP.""" """Provides retrieval of disk images using HTTP."""
def validate_href(self, image_href): def validate_href(self, image_href, secret=False):
"""Validate HTTP image reference. """Validate HTTP image reference.
:param image_href: Image reference. :param image_href: Image reference.
:param secret: Specify if image_href being validated should not be
shown in exception message.
:raises: exception.ImageRefValidationFailed if HEAD request failed or :raises: exception.ImageRefValidationFailed if HEAD request failed or
returned response code not equal to 200. returned response code not equal to 200.
:returns: Response to HEAD request. :returns: Response to HEAD request.
""" """
output_url = 'secreturl' if secret else image_href
try: try:
response = requests.head(image_href) response = requests.head(image_href)
if response.status_code != http_client.OK: if response.status_code != http_client.OK:
raise exception.ImageRefValidationFailed( raise exception.ImageRefValidationFailed(
image_href=image_href, image_href=output_url,
reason=_("Got HTTP code %s instead of 200 in response to " reason=_("Got HTTP code %s instead of 200 in response to "
"HEAD request.") % response.status_code) "HEAD request.") % response.status_code)
except requests.RequestException as e: except requests.RequestException as e:
raise exception.ImageRefValidationFailed(image_href=image_href, raise exception.ImageRefValidationFailed(image_href=output_url,
reason=e) reason=e)
return response return response

View File

@ -1160,6 +1160,23 @@ def build_instance_info_for_deploy(task):
:raises: exception.ImageRefValidationFailed if image_source is not :raises: exception.ImageRefValidationFailed if image_source is not
Glance href and is not HTTP(S) URL. Glance href and is not HTTP(S) URL.
""" """
def validate_image_url(url, secret=False):
"""Validates image URL through the HEAD request.
:param url: URL to be validated
:param secret: if URL is secret (e.g. swift temp url),
it will not be shown in logs.
"""
try:
image_service.HttpImageService().validate_href(url, secret)
except exception.ImageRefValidationFailed as e:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Agent deploy supports only HTTP(S) URLs as "
"instance_info['image_source'] or swift "
"temporary URL. Either the specified URL is not "
"a valid HTTP(S) URL or is not reachable "
"for node %(node)s. Error: %(msg)s"),
{'node': node.uuid, 'msg': e})
node = task.node node = task.node
instance_info = node.instance_info instance_info = node.instance_info
iwdi = node.driver_internal_info.get('is_whole_disk_image') iwdi = node.driver_internal_info.get('is_whole_disk_image')
@ -1168,9 +1185,10 @@ def build_instance_info_for_deploy(task):
glance = image_service.GlanceImageService(version=2, glance = image_service.GlanceImageService(version=2,
context=task.context) context=task.context)
image_info = glance.show(image_source) image_info = glance.show(image_source)
swift_temp_url = glance.swift_temp_url(image_info)
LOG.debug('Got image info: %(info)s for node %(node)s.', LOG.debug('Got image info: %(info)s for node %(node)s.',
{'info': image_info, 'node': node.uuid}) {'info': image_info, 'node': node.uuid})
swift_temp_url = glance.swift_temp_url(image_info)
validate_image_url(swift_temp_url, secret=True)
instance_info['image_url'] = swift_temp_url instance_info['image_url'] = swift_temp_url
instance_info['image_checksum'] = image_info['checksum'] instance_info['image_checksum'] = image_info['checksum']
instance_info['image_disk_format'] = image_info['disk_format'] instance_info['image_disk_format'] = image_info['disk_format']
@ -1183,14 +1201,7 @@ def build_instance_info_for_deploy(task):
instance_info['kernel'] = image_info['properties']['kernel_id'] instance_info['kernel'] = image_info['properties']['kernel_id']
instance_info['ramdisk'] = image_info['properties']['ramdisk_id'] instance_info['ramdisk'] = image_info['properties']['ramdisk_id']
else: else:
try: validate_image_url(image_source)
image_service.HttpImageService().validate_href(image_source)
except exception.ImageRefValidationFailed:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Agent deploy supports only HTTP(S) URLs as "
"instance_info['image_source']. Either %s "
"is not a valid HTTP(S) URL or "
"is not reachable."), image_source)
instance_info['image_url'] = image_source instance_info['image_url'] = image_source
if not iwdi: if not iwdi:

View File

@ -67,6 +67,17 @@ class HttpImageServiceTestCase(base.TestCase):
self.service.validate_href, self.href) self.service.validate_href, self.href)
head_mock.assert_called_once_with(self.href) head_mock.assert_called_once_with(self.href)
@mock.patch.object(requests, 'head', autospec=True)
def test_validate_href_error_with_secret_parameter(self, head_mock):
head_mock.return_value.status_code = 204
e = self.assertRaises(exception.ImageRefValidationFailed,
self.service.validate_href,
self.href,
True)
self.assertIn('secreturl', six.text_type(e))
self.assertNotIn(self.href, six.text_type(e))
head_mock.assert_called_once_with(self.href)
@mock.patch.object(requests, 'head', autospec=True) @mock.patch.object(requests, 'head', autospec=True)
def _test_show(self, head_mock, mtime, mtime_date): def _test_show(self, head_mock, mtime, mtime_date):
head_mock.return_value.status_code = http_client.OK head_mock.return_value.status_code = http_client.OK

View File

@ -2115,8 +2115,11 @@ class TestBuildInstanceInfoForDeploy(db_base.DbTestCase):
self.node = obj_utils.create_test_node(self.context, self.node = obj_utils.create_test_node(self.context,
driver='fake_agent') driver='fake_agent')
@mock.patch.object(image_service.HttpImageService, 'validate_href',
autospec=True)
@mock.patch.object(image_service, 'GlanceImageService', autospec=True) @mock.patch.object(image_service, 'GlanceImageService', autospec=True)
def test_build_instance_info_for_deploy_glance_image(self, glance_mock): def test_build_instance_info_for_deploy_glance_image(self, glance_mock,
validate_mock):
i_info = self.node.instance_info i_info = self.node.instance_info
i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810' i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810'
driver_internal_info = self.node.driver_internal_info driver_internal_info = self.node.driver_internal_info
@ -2129,7 +2132,8 @@ class TestBuildInstanceInfoForDeploy(db_base.DbTestCase):
'container_format': 'bare', 'properties': {}} 'container_format': 'bare', 'properties': {}}
glance_mock.return_value.show = mock.MagicMock(spec_set=[], glance_mock.return_value.show = mock.MagicMock(spec_set=[],
return_value=image_info) return_value=image_info)
glance_mock.return_value.swift_temp_url.return_value = (
'http://temp-url')
mgr_utils.mock_the_extension_manager(driver='fake_agent') mgr_utils.mock_the_extension_manager(driver='fake_agent')
with task_manager.acquire( with task_manager.acquire(
self.context, self.node.uuid, shared=False) as task: self.context, self.node.uuid, shared=False) as task:
@ -2142,11 +2146,15 @@ class TestBuildInstanceInfoForDeploy(db_base.DbTestCase):
self.node.instance_info['image_source']) self.node.instance_info['image_source'])
glance_mock.return_value.swift_temp_url.assert_called_once_with( glance_mock.return_value.swift_temp_url.assert_called_once_with(
image_info) image_info)
validate_mock.assert_called_once_with(mock.ANY, 'http://temp-url',
secret=True)
@mock.patch.object(image_service.HttpImageService, 'validate_href',
autospec=True)
@mock.patch.object(utils, 'parse_instance_info', autospec=True) @mock.patch.object(utils, 'parse_instance_info', autospec=True)
@mock.patch.object(image_service, 'GlanceImageService', autospec=True) @mock.patch.object(image_service, 'GlanceImageService', autospec=True)
def test_build_instance_info_for_deploy_glance_partition_image( def test_build_instance_info_for_deploy_glance_partition_image(
self, glance_mock, parse_instance_info_mock): self, glance_mock, parse_instance_info_mock, validate_mock):
i_info = {} i_info = {}
i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810' i_info['image_source'] = '733d1c44-a2ea-414b-aca7-69decf20d810'
i_info['kernel'] = '13ce5a56-1de3-4916-b8b2-be778645d003' i_info['kernel'] = '13ce5a56-1de3-4916-b8b2-be778645d003'
@ -2169,7 +2177,7 @@ class TestBuildInstanceInfoForDeploy(db_base.DbTestCase):
glance_mock.return_value.show = mock.MagicMock(spec_set=[], glance_mock.return_value.show = mock.MagicMock(spec_set=[],
return_value=image_info) return_value=image_info)
glance_obj_mock = glance_mock.return_value glance_obj_mock = glance_mock.return_value
glance_obj_mock.swift_temp_url.return_value = 'temp-url' glance_obj_mock.swift_temp_url.return_value = 'http://temp-url'
parse_instance_info_mock.return_value = {'swap_mb': 4} parse_instance_info_mock.return_value = {'swap_mb': 4}
image_source = '733d1c44-a2ea-414b-aca7-69decf20d810' image_source = '733d1c44-a2ea-414b-aca7-69decf20d810'
expected_i_info = {'root_gb': 5, expected_i_info = {'root_gb': 5,
@ -2178,7 +2186,7 @@ class TestBuildInstanceInfoForDeploy(db_base.DbTestCase):
'ephemeral_format': None, 'ephemeral_format': None,
'configdrive': 'configdrive', 'configdrive': 'configdrive',
'image_source': image_source, 'image_source': image_source,
'image_url': 'temp-url', 'image_url': 'http://temp-url',
'kernel': 'kernel', 'kernel': 'kernel',
'ramdisk': 'ramdisk', 'ramdisk': 'ramdisk',
'image_type': 'partition', 'image_type': 'partition',
@ -2200,6 +2208,8 @@ class TestBuildInstanceInfoForDeploy(db_base.DbTestCase):
self.node.instance_info['image_source']) self.node.instance_info['image_source'])
glance_mock.return_value.swift_temp_url.assert_called_once_with( glance_mock.return_value.swift_temp_url.assert_called_once_with(
image_info) image_info)
validate_mock.assert_called_once_with(
mock.ANY, 'http://temp-url', secret=True)
image_type = task.node.instance_info['image_type'] image_type = task.node.instance_info['image_type']
self.assertEqual('partition', image_type) self.assertEqual('partition', image_type)
self.assertEqual('kernel', info['kernel']) self.assertEqual('kernel', info['kernel'])
@ -2231,7 +2241,7 @@ class TestBuildInstanceInfoForDeploy(db_base.DbTestCase):
self.assertEqual(self.node.instance_info['image_source'], self.assertEqual(self.node.instance_info['image_source'],
info['image_url']) info['image_url'])
validate_href_mock.assert_called_once_with( validate_href_mock.assert_called_once_with(
mock.ANY, 'http://image-ref') mock.ANY, 'http://image-ref', False)
@mock.patch.object(utils, 'parse_instance_info', autospec=True) @mock.patch.object(utils, 'parse_instance_info', autospec=True)
@mock.patch.object(image_service.HttpImageService, 'validate_href', @mock.patch.object(image_service.HttpImageService, 'validate_href',
@ -2273,7 +2283,7 @@ class TestBuildInstanceInfoForDeploy(db_base.DbTestCase):
self.assertEqual(self.node.instance_info['image_source'], self.assertEqual(self.node.instance_info['image_source'],
info['image_url']) info['image_url'])
validate_href_mock.assert_called_once_with( validate_href_mock.assert_called_once_with(
mock.ANY, 'http://image-ref') mock.ANY, 'http://image-ref', False)
self.assertEqual('partition', info['image_type']) self.assertEqual('partition', info['image_type'])
self.assertEqual(expected_i_info, info) self.assertEqual(expected_i_info, info)
parse_instance_info_mock.assert_called_once_with(task.node) parse_instance_info_mock.assert_called_once_with(task.node)

View File

@ -0,0 +1,4 @@
---
fixes:
- Ironic now validates any swift temporary URL
when preparing for deployment of nodes.