Allow proxy_command to optionally use internal IP

New config option allows clusters to be provisioned with floating IPs
present, but still with communication over internal IP.

Related-Blueprint: refactor-use-floating-ip
Change-Id: Ieec4da7d852371bc9eabe6756de859b6b8189b8e
This commit is contained in:
Jeremy Freudberg 2017-07-03 14:05:53 +00:00
parent ea072718ed
commit ef51c7a004
5 changed files with 54 additions and 9 deletions

View File

@ -24,6 +24,12 @@ port. The ``{host}`` and ``{port}`` keywords should be used to describe the
destination, they will be substituted at runtime. Other keywords that
can be used are: ``{tenant_id}``, ``{network_id}`` and ``{router_id}``.
Additionally, if ``proxy_command_use_internal_ip`` is set to ``True``,
then the internal IP will be subsituted for ``{host}`` in the command.
Otherwise (if ``False``, by default) the management IP will be used: this
corresponds to floating IP if present in the relevant node group, else the
internal IP. The option is ignored if ``proxy_command`` is not also set.
For example, the following parameter in the sahara configuration file
would be used if instances are accessed through a relay machine:

View File

@ -19,6 +19,7 @@ import datetime
import string
from novaclient import exceptions as nova_exceptions
from oslo_config import cfg
from oslo_log import log as logging
import six
@ -39,6 +40,7 @@ from sahara.utils import remote
LOG = logging.getLogger(__name__)
conductor = c.API
CONF = cfg.CONF
@six.add_metaclass(abc.ABCMeta)
@ -125,9 +127,11 @@ class Engine(object):
LOG.debug('Instance is accessible')
return True
except Exception as ex:
LOG.debug("Can't login to node, IP: {mgmt_ip}, "
"reason {reason}".format(mgmt_ip=instance.management_ip,
reason=ex))
ip_used = "internal_ip" if CONF.proxy_command and \
CONF.proxy_command_use_internal_ip else "management_ip"
LOG.debug("Can't login to node, IP: {ip}, reason {reason}"
.format(ip=getattr(instance, ip_used),
reason=ex))
return False
return False

View File

@ -291,6 +291,29 @@ class TestInstanceInteropHelper(base.SaharaTestCase):
p_simple_exec_func.assert_any_call(
shlex.split('ssh fakerelay nc 10.0.0.3 8080'))
@mock.patch('sahara.utils.ssh_remote._simple_exec_func')
@mock.patch('sahara.utils.ssh_remote.ProxiedHTTPAdapter')
def test_proxy_command_internal_ip(self, p_adapter, p_simple_exec_func):
self.override_config('proxy_command', 'ssh fakerelay nc {host} {port}')
self.override_config('proxy_command_use_internal_ip', True)
instance = FakeInstance('inst3', '123', '10.0.0.3', '10.0.0.4',
'user3', 'key3')
remote = ssh_remote.InstanceInteropHelper(instance)
# Test SSH
remote.execute_command('/bin/true')
self.run_in_subprocess.assert_any_call(
42, ssh_remote._connect,
('10.0.0.4', 'user3', 'key3', 'ssh fakerelay nc 10.0.0.4 22',
None, None))
# Test HTTP
remote.get_http_client(8080)
p_adapter.assert_called_once_with(
p_simple_exec_func(), '10.0.0.4', 8080)
p_simple_exec_func.assert_any_call(
shlex.split('ssh fakerelay nc 10.0.0.4 8080'))
def test_proxy_command_bad(self):
self.override_config('proxy_command', '{bad_kw} nc {host} {port}')

View File

@ -38,6 +38,10 @@ ssh_opts = [
'SSH and HTTP connections. Use {host} and {port} to describe '
'the destination. Other available keywords: {tenant_id}, '
'{network_id}, {router_id}.'),
cfg.BoolOpt('proxy_command_use_internal_ip', default=False,
help='Force proxy_command usage to be consuming internal IP '
'always, instead of management IP. Ignored if proxy_command '
'is not set.')
]

View File

@ -94,6 +94,12 @@ SSH_TIMEOUTS_MAPPING = {
_global_remote_semaphore = None
def _get_access_ip(instance):
if CONF.proxy_command and CONF.proxy_command_use_internal_ip:
return instance.internal_ip
return instance.management_ip
def _default_timeout(func):
timeout = SSH_TIMEOUTS_MAPPING.get(func.__name__, 'ssh_timeout_files')
return getattr(CONF, timeout, CONF.ssh_timeout_common)
@ -639,7 +645,7 @@ class InstanceInteropHelper(remote.Remote):
ctx = context.current()
neutron_info['token'] = context.get_auth_token()
neutron_info['tenant'] = ctx.tenant_name
neutron_info['host'] = instance.management_ip
neutron_info['host'] = _get_access_ip(instance)
log_info = copy.deepcopy(neutron_info)
del log_info['token']
@ -664,7 +670,7 @@ class InstanceInteropHelper(remote.Remote):
info['tenant'], auth=auth)
keywords['router_id'] = client.get_router()
keywords['host'] = instance.management_ip
keywords['host'] = _get_access_ip(instance)
keywords['port'] = port
try:
@ -728,7 +734,9 @@ class InstanceInteropHelper(remote.Remote):
proxy_command, instance=access_instance, port=22,
info=None, rootwrap_command=rootwrap)
return (self.instance.management_ip,
host_ip = _get_access_ip(self.instance)
return (host_ip,
host_ng.image_username,
cluster.management_private_key,
proxy_command,
@ -771,7 +779,7 @@ class InstanceInteropHelper(remote.Remote):
def get_http_client(self, port, info=None):
self._log_command('Retrieving HTTP session for {0}:{1}'.format(
self.instance.management_ip, port))
_get_access_ip(self.instance), port))
host_ng = self.instance.node_group
cluster = host_ng.cluster
@ -818,7 +826,7 @@ class InstanceInteropHelper(remote.Remote):
proxy_command, instance=access_instance, port=access_port,
info=info, rootwrap_command=rootwrap)
return _get_http_client(self.instance.management_ip, port,
return _get_http_client(_get_access_ip(self.instance), port,
proxy_command, gateway_host,
gateway_username,
gateway_private_key)
@ -826,7 +834,7 @@ class InstanceInteropHelper(remote.Remote):
def close_http_session(self, port):
global _sessions
host = self.instance.management_ip
host = _get_access_ip(self.instance)
self._log_command(_("Closing HTTP session for %(host)s:%(port)s") % {
'host': host, 'port': port})