Respect global parameters when downloading a configdrive
* Use the same TLS parameters as everything else * Respect image_download_connection_timeout * Do not ignore HTTP errors Change-Id: I84f8021f731186d82e44ac3d4ef2d12df13f830a
This commit is contained in:
		| @@ -29,13 +29,17 @@ from ironic_lib import disk_utils | |||||||
| from ironic_lib import exception | from ironic_lib import exception | ||||||
| from ironic_lib import utils | from ironic_lib import utils | ||||||
| from oslo_concurrency import processutils | from oslo_concurrency import processutils | ||||||
|  | from oslo_config import cfg | ||||||
| from oslo_log import log | from oslo_log import log | ||||||
| from oslo_utils import excutils | from oslo_utils import excutils | ||||||
| from oslo_utils import units | from oslo_utils import units | ||||||
| import requests | import requests | ||||||
|  |  | ||||||
|  | from ironic_python_agent import utils as ipa_utils | ||||||
|  |  | ||||||
|  |  | ||||||
| LOG = log.getLogger() | LOG = log.getLogger() | ||||||
|  | CONF = cfg.CONF | ||||||
|  |  | ||||||
| MAX_CONFIG_DRIVE_SIZE_MB = 64 | MAX_CONFIG_DRIVE_SIZE_MB = 64 | ||||||
|  |  | ||||||
| @@ -59,13 +63,27 @@ def get_configdrive(configdrive, node_uuid, tempdir=None): | |||||||
|     # Check if the configdrive option is a HTTP URL or the content directly |     # Check if the configdrive option is a HTTP URL or the content directly | ||||||
|     is_url = utils.is_http_url(configdrive) |     is_url = utils.is_http_url(configdrive) | ||||||
|     if is_url: |     if is_url: | ||||||
|  |         verify, cert = ipa_utils.get_ssl_client_options(CONF) | ||||||
|  |         timeout = CONF.image_download_connection_timeout | ||||||
|  |         # TODO(dtantsur): support proxy parameters from instance_info | ||||||
|         try: |         try: | ||||||
|             data = requests.get(configdrive).content |             resp = requests.get(configdrive, verify=verify, cert=cert, | ||||||
|  |                                 timeout=timeout) | ||||||
|         except requests.exceptions.RequestException as e: |         except requests.exceptions.RequestException as e: | ||||||
|             raise exception.InstanceDeployFailure( |             raise exception.InstanceDeployFailure( | ||||||
|                 "Can't download the configdrive content for node %(node)s " |                 "Can't download the configdrive content for node %(node)s " | ||||||
|                 "from '%(url)s'. Reason: %(reason)s" % |                 "from '%(url)s'. Reason: %(reason)s" % | ||||||
|                 {'node': node_uuid, 'url': configdrive, 'reason': e}) |                 {'node': node_uuid, 'url': configdrive, 'reason': e}) | ||||||
|  |  | ||||||
|  |         if resp.status_code >= 400: | ||||||
|  |             raise exception.InstanceDeployFailure( | ||||||
|  |                 "Can't download the configdrive content for node %(node)s " | ||||||
|  |                 "from '%(url)s'. Got status code %(code)s, response " | ||||||
|  |                 "body %(body)s" % | ||||||
|  |                 {'node': node_uuid, 'url': configdrive, | ||||||
|  |                  'code': resp.status_code, 'body': resp.text}) | ||||||
|  |  | ||||||
|  |         data = resp.content | ||||||
|     else: |     else: | ||||||
|         data = configdrive |         data = configdrive | ||||||
|  |  | ||||||
|   | |||||||
| @@ -32,26 +32,68 @@ class GetConfigdriveTestCase(base.IronicAgentTest): | |||||||
|  |  | ||||||
|     @mock.patch.object(gzip, 'GzipFile', autospec=True) |     @mock.patch.object(gzip, 'GzipFile', autospec=True) | ||||||
|     def test_get_configdrive(self, mock_gzip, mock_requests, mock_copy): |     def test_get_configdrive(self, mock_gzip, mock_requests, mock_copy): | ||||||
|         mock_requests.return_value = mock.MagicMock(content='Zm9vYmFy') |         mock_requests.return_value = mock.MagicMock(content='Zm9vYmFy', | ||||||
|  |                                                     status_code=200) | ||||||
|         tempdir = tempfile.mkdtemp() |         tempdir = tempfile.mkdtemp() | ||||||
|         (size, path) = partition_utils.get_configdrive('http://1.2.3.4/cd', |         (size, path) = partition_utils.get_configdrive('http://1.2.3.4/cd', | ||||||
|                                                        'fake-node-uuid', |                                                        'fake-node-uuid', | ||||||
|                                                        tempdir=tempdir) |                                                        tempdir=tempdir) | ||||||
|         self.assertTrue(path.startswith(tempdir)) |         self.assertTrue(path.startswith(tempdir)) | ||||||
|         mock_requests.assert_called_once_with('http://1.2.3.4/cd') |         mock_requests.assert_called_once_with('http://1.2.3.4/cd', | ||||||
|  |                                               verify=True, cert=None, | ||||||
|  |                                               timeout=60) | ||||||
|  |         mock_gzip.assert_called_once_with('configdrive', 'rb', | ||||||
|  |                                           fileobj=mock.ANY) | ||||||
|  |         mock_copy.assert_called_once_with(mock.ANY, mock.ANY) | ||||||
|  |  | ||||||
|  |     @mock.patch.object(gzip, 'GzipFile', autospec=True) | ||||||
|  |     def test_get_configdrive_insecure(self, mock_gzip, mock_requests, | ||||||
|  |                                       mock_copy): | ||||||
|  |         self.config(insecure=True) | ||||||
|  |         mock_requests.return_value = mock.MagicMock(content='Zm9vYmFy', | ||||||
|  |                                                     status_code=200) | ||||||
|  |         tempdir = tempfile.mkdtemp() | ||||||
|  |         (size, path) = partition_utils.get_configdrive('http://1.2.3.4/cd', | ||||||
|  |                                                        'fake-node-uuid', | ||||||
|  |                                                        tempdir=tempdir) | ||||||
|  |         self.assertTrue(path.startswith(tempdir)) | ||||||
|  |         mock_requests.assert_called_once_with('http://1.2.3.4/cd', | ||||||
|  |                                               verify=False, cert=None, | ||||||
|  |                                               timeout=60) | ||||||
|  |         mock_gzip.assert_called_once_with('configdrive', 'rb', | ||||||
|  |                                           fileobj=mock.ANY) | ||||||
|  |         mock_copy.assert_called_once_with(mock.ANY, mock.ANY) | ||||||
|  |  | ||||||
|  |     @mock.patch.object(gzip, 'GzipFile', autospec=True) | ||||||
|  |     def test_get_configdrive_ssl(self, mock_gzip, mock_requests, mock_copy): | ||||||
|  |         self.config(cafile='cafile', keyfile='keyfile', certfile='certfile') | ||||||
|  |         mock_requests.return_value = mock.MagicMock(content='Zm9vYmFy', | ||||||
|  |                                                     status_code=200) | ||||||
|  |         tempdir = tempfile.mkdtemp() | ||||||
|  |         (size, path) = partition_utils.get_configdrive('http://1.2.3.4/cd', | ||||||
|  |                                                        'fake-node-uuid', | ||||||
|  |                                                        tempdir=tempdir) | ||||||
|  |         self.assertTrue(path.startswith(tempdir)) | ||||||
|  |         mock_requests.assert_called_once_with('http://1.2.3.4/cd', | ||||||
|  |                                               verify='cafile', | ||||||
|  |                                               cert=('certfile', 'keyfile'), | ||||||
|  |                                               timeout=60) | ||||||
|         mock_gzip.assert_called_once_with('configdrive', 'rb', |         mock_gzip.assert_called_once_with('configdrive', 'rb', | ||||||
|                                           fileobj=mock.ANY) |                                           fileobj=mock.ANY) | ||||||
|         mock_copy.assert_called_once_with(mock.ANY, mock.ANY) |         mock_copy.assert_called_once_with(mock.ANY, mock.ANY) | ||||||
|  |  | ||||||
|     def test_get_configdrive_binary(self, mock_requests, mock_copy): |     def test_get_configdrive_binary(self, mock_requests, mock_copy): | ||||||
|         mock_requests.return_value = mock.MagicMock(content=b'content') |         mock_requests.return_value = mock.MagicMock(content=b'content', | ||||||
|  |                                                     status_code=200) | ||||||
|         tempdir = tempfile.mkdtemp() |         tempdir = tempfile.mkdtemp() | ||||||
|         (size, path) = partition_utils.get_configdrive('http://1.2.3.4/cd', |         (size, path) = partition_utils.get_configdrive('http://1.2.3.4/cd', | ||||||
|                                                        'fake-node-uuid', |                                                        'fake-node-uuid', | ||||||
|                                                        tempdir=tempdir) |                                                        tempdir=tempdir) | ||||||
|         self.assertTrue(path.startswith(tempdir)) |         self.assertTrue(path.startswith(tempdir)) | ||||||
|         self.assertEqual(b'content', open(path, 'rb').read()) |         self.assertEqual(b'content', open(path, 'rb').read()) | ||||||
|         mock_requests.assert_called_once_with('http://1.2.3.4/cd') |         mock_requests.assert_called_once_with('http://1.2.3.4/cd', | ||||||
|  |                                               verify=True, cert=None, | ||||||
|  |                                               timeout=60) | ||||||
|         self.assertFalse(mock_copy.called) |         self.assertFalse(mock_copy.called) | ||||||
|  |  | ||||||
|     @mock.patch.object(gzip, 'GzipFile', autospec=True) |     @mock.patch.object(gzip, 'GzipFile', autospec=True) | ||||||
| @@ -70,6 +112,14 @@ class GetConfigdriveTestCase(base.IronicAgentTest): | |||||||
|                           'http://1.2.3.4/cd', 'fake-node-uuid') |                           'http://1.2.3.4/cd', 'fake-node-uuid') | ||||||
|         self.assertFalse(mock_copy.called) |         self.assertFalse(mock_copy.called) | ||||||
|  |  | ||||||
|  |     def test_get_configdrive_bad_status_code(self, mock_requests, mock_copy): | ||||||
|  |         mock_requests.return_value = mock.MagicMock(text='Not found', | ||||||
|  |                                                     status_code=404) | ||||||
|  |         self.assertRaises(exception.InstanceDeployFailure, | ||||||
|  |                           partition_utils.get_configdrive, | ||||||
|  |                           'http://1.2.3.4/cd', 'fake-node-uuid') | ||||||
|  |         self.assertFalse(mock_copy.called) | ||||||
|  |  | ||||||
|     def test_get_configdrive_base64_error(self, mock_requests, mock_copy): |     def test_get_configdrive_base64_error(self, mock_requests, mock_copy): | ||||||
|         self.assertRaises(exception.InstanceDeployFailure, |         self.assertRaises(exception.InstanceDeployFailure, | ||||||
|                           partition_utils.get_configdrive, |                           partition_utils.get_configdrive, | ||||||
| @@ -79,12 +129,15 @@ class GetConfigdriveTestCase(base.IronicAgentTest): | |||||||
|     @mock.patch.object(gzip, 'GzipFile', autospec=True) |     @mock.patch.object(gzip, 'GzipFile', autospec=True) | ||||||
|     def test_get_configdrive_gzip_error(self, mock_gzip, mock_requests, |     def test_get_configdrive_gzip_error(self, mock_gzip, mock_requests, | ||||||
|                                         mock_copy): |                                         mock_copy): | ||||||
|         mock_requests.return_value = mock.MagicMock(content='Zm9vYmFy') |         mock_requests.return_value = mock.MagicMock(content='Zm9vYmFy', | ||||||
|  |                                                     status_code=200) | ||||||
|         mock_copy.side_effect = IOError |         mock_copy.side_effect = IOError | ||||||
|         self.assertRaises(exception.InstanceDeployFailure, |         self.assertRaises(exception.InstanceDeployFailure, | ||||||
|                           partition_utils.get_configdrive, |                           partition_utils.get_configdrive, | ||||||
|                           'http://1.2.3.4/cd', 'fake-node-uuid') |                           'http://1.2.3.4/cd', 'fake-node-uuid') | ||||||
|         mock_requests.assert_called_once_with('http://1.2.3.4/cd') |         mock_requests.assert_called_once_with('http://1.2.3.4/cd', | ||||||
|  |                                               verify=True, cert=None, | ||||||
|  |                                               timeout=60) | ||||||
|         mock_gzip.assert_called_once_with('configdrive', 'rb', |         mock_gzip.assert_called_once_with('configdrive', 'rb', | ||||||
|                                           fileobj=mock.ANY) |                                           fileobj=mock.ANY) | ||||||
|         mock_copy.assert_called_once_with(mock.ANY, mock.ANY) |         mock_copy.assert_called_once_with(mock.ANY, mock.ANY) | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								releasenotes/notes/configdrive-ssl-02b069948dfef814.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								releasenotes/notes/configdrive-ssl-02b069948dfef814.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | --- | ||||||
|  | fixes: | ||||||
|  |   - | | ||||||
|  |     No longer ignores global TLS configuration options (``ipa-insecure``, etc) | ||||||
|  |     when downloading a configdrive via a URL. | ||||||
|  |   - | | ||||||
|  |     No longer ignores error status codes from the server when downloading | ||||||
|  |     a configdrive via a URL. | ||||||
|  |   - | | ||||||
|  |     The configdrive downloading code now respects the | ||||||
|  |     ``ipa-image-download-connection-timeout`` option and will no longer hang | ||||||
|  |     for a long time if the server does not respond. | ||||||
		Reference in New Issue
	
	Block a user
	 Dmitry Tantsur
					Dmitry Tantsur