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