Merge "Add proxy related parameters to agent driver"
This commit is contained in:
commit
a444ee3564
@ -16,6 +16,7 @@ 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 six.moves.urllib_parse as urlparse
|
||||||
|
|
||||||
from ironic.common import dhcp_factory
|
from ironic.common import dhcp_factory
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
@ -29,6 +30,7 @@ from ironic.common import images
|
|||||||
from ironic.common import paths
|
from ironic.common import paths
|
||||||
from ironic.common import raid
|
from ironic.common import raid
|
||||||
from ironic.common import states
|
from ironic.common import states
|
||||||
|
from ironic.common import utils
|
||||||
from ironic.conductor import task_manager
|
from ironic.conductor import task_manager
|
||||||
from ironic.conductor import utils as manager_utils
|
from ironic.conductor import utils as manager_utils
|
||||||
from ironic.drivers import base
|
from ironic.drivers import base
|
||||||
@ -92,7 +94,22 @@ REQUIRED_PROPERTIES = {
|
|||||||
'deploy_ramdisk': _('UUID (from Glance) of the ramdisk with agent that is '
|
'deploy_ramdisk': _('UUID (from Glance) of the ramdisk with agent that is '
|
||||||
'used at deploy time. Required.'),
|
'used at deploy time. Required.'),
|
||||||
}
|
}
|
||||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES
|
|
||||||
|
OPTIONAL_PROPERTIES = {
|
||||||
|
'image_http_proxy': _('URL of a proxy server for HTTP connections. '
|
||||||
|
'Optional.'),
|
||||||
|
'image_https_proxy': _('URL of a proxy server for HTTPS connections. '
|
||||||
|
'Optional.'),
|
||||||
|
'image_no_proxy': _('A comma-separated list of host names, IP addresses '
|
||||||
|
'and domain names (with optional :port) that will be '
|
||||||
|
'excluded from proxying. To denote a doman name, use '
|
||||||
|
'a dot to prefix the domain name. This value will be '
|
||||||
|
'ignored if ``image_http_proxy`` and '
|
||||||
|
'``image_https_proxy`` are not specified. Optional.'),
|
||||||
|
}
|
||||||
|
|
||||||
|
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
||||||
|
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
||||||
|
|
||||||
|
|
||||||
def build_instance_info_for_deploy(task):
|
def build_instance_info_for_deploy(task):
|
||||||
@ -171,6 +188,42 @@ def check_image_size(task, image_source):
|
|||||||
raise exception.InvalidParameterValue(msg)
|
raise exception.InvalidParameterValue(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_image_proxies(node):
|
||||||
|
"""Check that the provided proxy parameters are valid.
|
||||||
|
|
||||||
|
:param node: an Ironic node.
|
||||||
|
:raises: InvalidParameterValue if any of the provided proxy parameters are
|
||||||
|
incorrect.
|
||||||
|
"""
|
||||||
|
invalid_proxies = {}
|
||||||
|
for scheme in ('http', 'https'):
|
||||||
|
proxy_param = 'image_%s_proxy' % scheme
|
||||||
|
proxy = node.driver_info.get(proxy_param)
|
||||||
|
if proxy:
|
||||||
|
chunks = urlparse.urlparse(proxy)
|
||||||
|
# NOTE(vdrok) If no scheme specified, this is still a valid
|
||||||
|
# proxy address. It is also possible for a proxy to have a
|
||||||
|
# scheme different from the one specified in the image URL,
|
||||||
|
# e.g. it is possible to use https:// proxy for downloading
|
||||||
|
# http:// image.
|
||||||
|
if chunks.scheme not in ('', 'http', 'https'):
|
||||||
|
invalid_proxies[proxy_param] = proxy
|
||||||
|
msg = ''
|
||||||
|
if invalid_proxies:
|
||||||
|
msg += _("Proxy URL should either have HTTP(S) scheme "
|
||||||
|
"or no scheme at all, the following URLs are "
|
||||||
|
"invalid: %s.") % invalid_proxies
|
||||||
|
no_proxy = node.driver_info.get('image_no_proxy')
|
||||||
|
if no_proxy is not None and not utils.is_valid_no_proxy(no_proxy):
|
||||||
|
msg += _(
|
||||||
|
"image_no_proxy should be a list of host names, IP addresses "
|
||||||
|
"or domain names to exclude from proxying, the specified list "
|
||||||
|
"%s is incorrect. To denote a domain name, prefix it with a dot "
|
||||||
|
"(instead of e.g. '.*').") % no_proxy
|
||||||
|
if msg:
|
||||||
|
raise exception.InvalidParameterValue(msg)
|
||||||
|
|
||||||
|
|
||||||
class AgentDeploy(base.DeployInterface):
|
class AgentDeploy(base.DeployInterface):
|
||||||
"""Interface for deploy-related actions."""
|
"""Interface for deploy-related actions."""
|
||||||
|
|
||||||
@ -227,6 +280,8 @@ class AgentDeploy(base.DeployInterface):
|
|||||||
# Validate node capabilities
|
# Validate node capabilities
|
||||||
deploy_utils.validate_capabilities(node)
|
deploy_utils.validate_capabilities(node)
|
||||||
|
|
||||||
|
validate_image_proxies(node)
|
||||||
|
|
||||||
@task_manager.require_exclusive_lock
|
@task_manager.require_exclusive_lock
|
||||||
def deploy(self, task):
|
def deploy(self, task):
|
||||||
"""Perform a deployment to a node.
|
"""Perform a deployment to a node.
|
||||||
@ -397,6 +452,18 @@ class AgentVendorInterface(agent_base_vendor.BaseAgentVendor):
|
|||||||
'stream_raw_images': CONF.agent.stream_raw_images,
|
'stream_raw_images': CONF.agent.stream_raw_images,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proxies = {}
|
||||||
|
for scheme in ('http', 'https'):
|
||||||
|
proxy_param = 'image_%s_proxy' % scheme
|
||||||
|
proxy = node.driver_info.get(proxy_param)
|
||||||
|
if proxy:
|
||||||
|
proxies[scheme] = proxy
|
||||||
|
if proxies:
|
||||||
|
image_info['proxies'] = proxies
|
||||||
|
no_proxy = node.driver_info.get('image_no_proxy')
|
||||||
|
if no_proxy is not None:
|
||||||
|
image_info['no_proxy'] = no_proxy
|
||||||
|
|
||||||
# Tell the client to download and write the image with the given args
|
# Tell the client to download and write the image with the given args
|
||||||
self._client.prepare_image(node, image_info)
|
self._client.prepare_image(node, image_info)
|
||||||
|
|
||||||
|
@ -313,6 +313,22 @@ class TestAgentDeploy(db_base.DbTestCase):
|
|||||||
task.driver.boot, task)
|
task.driver.boot, task)
|
||||||
show_mock.assert_called_once_with(self.context, 'fake-image')
|
show_mock.assert_called_once_with(self.context, 'fake-image')
|
||||||
|
|
||||||
|
@mock.patch.object(images, 'image_show', autospec=True)
|
||||||
|
@mock.patch.object(pxe.PXEBoot, 'validate', autospec=True)
|
||||||
|
def test_validate_invalid_proxies(self, pxe_boot_validate_mock, show_mock):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.node.driver_info.update({
|
||||||
|
'image_https_proxy': 'git://spam.ni',
|
||||||
|
'image_http_proxy': 'http://spam.ni',
|
||||||
|
'image_no_proxy': '1' * 500})
|
||||||
|
self.assertRaisesRegexp(exception.InvalidParameterValue,
|
||||||
|
'image_https_proxy.*image_no_proxy',
|
||||||
|
task.driver.deploy.validate, task)
|
||||||
|
pxe_boot_validate_mock.assert_called_once_with(
|
||||||
|
task.driver.boot, task)
|
||||||
|
show_mock.assert_called_once_with(self.context, 'fake-image')
|
||||||
|
|
||||||
@mock.patch('ironic.conductor.utils.node_power_action', autospec=True)
|
@mock.patch('ironic.conductor.utils.node_power_action', autospec=True)
|
||||||
def test_deploy(self, power_mock):
|
def test_deploy(self, power_mock):
|
||||||
with task_manager.acquire(
|
with task_manager.acquire(
|
||||||
@ -495,10 +511,13 @@ class TestAgentVendor(db_base.DbTestCase):
|
|||||||
}
|
}
|
||||||
self.node = object_utils.create_test_node(self.context, **n)
|
self.node = object_utils.create_test_node(self.context, **n)
|
||||||
|
|
||||||
def test_continue_deploy(self):
|
def _test_continue_deploy(self, additional_driver_info=None,
|
||||||
CONF.set_override('stream_raw_images', False, 'agent')
|
additional_expected_image_info=None):
|
||||||
self.node.provision_state = states.DEPLOYWAIT
|
self.node.provision_state = states.DEPLOYWAIT
|
||||||
self.node.target_provision_state = states.ACTIVE
|
self.node.target_provision_state = states.ACTIVE
|
||||||
|
driver_info = self.node.driver_info
|
||||||
|
driver_info.update(additional_driver_info or {})
|
||||||
|
self.node.driver_info = driver_info
|
||||||
self.node.save()
|
self.node.save()
|
||||||
test_temp_url = 'http://image'
|
test_temp_url = 'http://image'
|
||||||
expected_image_info = {
|
expected_image_info = {
|
||||||
@ -507,8 +526,9 @@ class TestAgentVendor(db_base.DbTestCase):
|
|||||||
'checksum': 'checksum',
|
'checksum': 'checksum',
|
||||||
'disk_format': 'qcow2',
|
'disk_format': 'qcow2',
|
||||||
'container_format': 'bare',
|
'container_format': 'bare',
|
||||||
'stream_raw_images': False,
|
'stream_raw_images': CONF.agent.stream_raw_images,
|
||||||
}
|
}
|
||||||
|
expected_image_info.update(additional_expected_image_info or {})
|
||||||
|
|
||||||
client_mock = mock.MagicMock(spec_set=['prepare_image'])
|
client_mock = mock.MagicMock(spec_set=['prepare_image'])
|
||||||
self.passthru._client = client_mock
|
self.passthru._client = client_mock
|
||||||
@ -523,32 +543,34 @@ class TestAgentVendor(db_base.DbTestCase):
|
|||||||
self.assertEqual(states.ACTIVE,
|
self.assertEqual(states.ACTIVE,
|
||||||
task.node.target_provision_state)
|
task.node.target_provision_state)
|
||||||
|
|
||||||
|
def test_continue_deploy(self):
|
||||||
|
self._test_continue_deploy()
|
||||||
|
|
||||||
|
def test_continue_deploy_with_proxies(self):
|
||||||
|
self._test_continue_deploy(
|
||||||
|
additional_driver_info={'image_https_proxy': 'https://spam.ni',
|
||||||
|
'image_http_proxy': 'spam.ni',
|
||||||
|
'image_no_proxy': '.eggs.com'},
|
||||||
|
additional_expected_image_info={
|
||||||
|
'proxies': {'https': 'https://spam.ni',
|
||||||
|
'http': 'spam.ni'},
|
||||||
|
'no_proxy': '.eggs.com'}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_continue_deploy_with_no_proxy_without_proxies(self):
|
||||||
|
self._test_continue_deploy(
|
||||||
|
additional_driver_info={'image_no_proxy': '.eggs.com'}
|
||||||
|
)
|
||||||
|
|
||||||
def test_continue_deploy_image_source_is_url(self):
|
def test_continue_deploy_image_source_is_url(self):
|
||||||
self.node.provision_state = states.DEPLOYWAIT
|
instance_info = self.node.instance_info
|
||||||
self.node.target_provision_state = states.ACTIVE
|
instance_info['image_source'] = 'http://example.com/woof.img'
|
||||||
self.node.save()
|
self.node.instance_info = instance_info
|
||||||
test_temp_url = 'http://image'
|
self._test_continue_deploy(
|
||||||
expected_image_info = {
|
additional_expected_image_info={
|
||||||
'urls': [test_temp_url],
|
'id': 'woof.img'
|
||||||
'id': self.node.instance_info['image_source'],
|
}
|
||||||
'checksum': 'checksum',
|
)
|
||||||
'disk_format': 'qcow2',
|
|
||||||
'container_format': 'bare',
|
|
||||||
'stream_raw_images': True,
|
|
||||||
}
|
|
||||||
|
|
||||||
client_mock = mock.MagicMock(spec_set=['prepare_image'])
|
|
||||||
self.passthru._client = client_mock
|
|
||||||
|
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
|
||||||
shared=False) as task:
|
|
||||||
self.passthru.continue_deploy(task)
|
|
||||||
|
|
||||||
client_mock.prepare_image.assert_called_with(task.node,
|
|
||||||
expected_image_info)
|
|
||||||
self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
|
|
||||||
self.assertEqual(states.ACTIVE,
|
|
||||||
task.node.target_provision_state)
|
|
||||||
|
|
||||||
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
|
||||||
@mock.patch.object(fake.FakePower, 'get_power_state',
|
@mock.patch.object(fake.FakePower, 'get_power_state',
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Pass proxy information from agent driver to IPA ramdisk, so that images
|
||||||
|
can be cached on the proxy server.
|
||||||
|
issues:
|
||||||
|
- When using caching proxy with ``agent_*`` drivers, caching the image on the
|
||||||
|
proxy server might involve increasing [glance]swift_temp_url_duration
|
||||||
|
config option value. This way, the cached entry will be valid for a period
|
||||||
|
of time long enough to see the benefits of caching. Large temporary URL
|
||||||
|
duration might become a security issue in some cases.
|
||||||
|
upgrade:
|
||||||
|
- Adds a [glance]swift_temp_url_cache_enabled configuration option to enable
|
||||||
|
Swift temporary URL caching. It is only useful if the caching proxy is
|
||||||
|
used. Also adds [glance]swift_temp_url_expected_download_start_delay, which
|
||||||
|
is used to check if the Swift temporary URL duration is long enough to let
|
||||||
|
the image download to start, and, if temporary URL caching is enabled, to
|
||||||
|
determine if a cached entry will be still valid when download starts. The
|
||||||
|
value of [glance]swift_temp_url_expected_download_start_delay must be less
|
||||||
|
than the value for the [glance]swift_temp_url_duration configuration
|
||||||
|
option.
|
Loading…
x
Reference in New Issue
Block a user