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 destination, they will be substituted at runtime. Other keywords that
can be used are: ``{tenant_id}``, ``{network_id}`` and ``{router_id}``. 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 For example, the following parameter in the sahara configuration file
would be used if instances are accessed through a relay machine: would be used if instances are accessed through a relay machine:

View File

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

View File

@ -291,6 +291,29 @@ class TestInstanceInteropHelper(base.SaharaTestCase):
p_simple_exec_func.assert_any_call( p_simple_exec_func.assert_any_call(
shlex.split('ssh fakerelay nc 10.0.0.3 8080')) 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): def test_proxy_command_bad(self):
self.override_config('proxy_command', '{bad_kw} nc {host} {port}') 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 ' 'SSH and HTTP connections. Use {host} and {port} to describe '
'the destination. Other available keywords: {tenant_id}, ' 'the destination. Other available keywords: {tenant_id}, '
'{network_id}, {router_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 _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): def _default_timeout(func):
timeout = SSH_TIMEOUTS_MAPPING.get(func.__name__, 'ssh_timeout_files') timeout = SSH_TIMEOUTS_MAPPING.get(func.__name__, 'ssh_timeout_files')
return getattr(CONF, timeout, CONF.ssh_timeout_common) return getattr(CONF, timeout, CONF.ssh_timeout_common)
@ -639,7 +645,7 @@ class InstanceInteropHelper(remote.Remote):
ctx = context.current() ctx = context.current()
neutron_info['token'] = context.get_auth_token() neutron_info['token'] = context.get_auth_token()
neutron_info['tenant'] = ctx.tenant_name 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) log_info = copy.deepcopy(neutron_info)
del log_info['token'] del log_info['token']
@ -664,7 +670,7 @@ class InstanceInteropHelper(remote.Remote):
info['tenant'], auth=auth) info['tenant'], auth=auth)
keywords['router_id'] = client.get_router() keywords['router_id'] = client.get_router()
keywords['host'] = instance.management_ip keywords['host'] = _get_access_ip(instance)
keywords['port'] = port keywords['port'] = port
try: try:
@ -728,7 +734,9 @@ class InstanceInteropHelper(remote.Remote):
proxy_command, instance=access_instance, port=22, proxy_command, instance=access_instance, port=22,
info=None, rootwrap_command=rootwrap) info=None, rootwrap_command=rootwrap)
return (self.instance.management_ip, host_ip = _get_access_ip(self.instance)
return (host_ip,
host_ng.image_username, host_ng.image_username,
cluster.management_private_key, cluster.management_private_key,
proxy_command, proxy_command,
@ -771,7 +779,7 @@ class InstanceInteropHelper(remote.Remote):
def get_http_client(self, port, info=None): def get_http_client(self, port, info=None):
self._log_command('Retrieving HTTP session for {0}:{1}'.format( 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 host_ng = self.instance.node_group
cluster = host_ng.cluster cluster = host_ng.cluster
@ -818,7 +826,7 @@ class InstanceInteropHelper(remote.Remote):
proxy_command, instance=access_instance, port=access_port, proxy_command, instance=access_instance, port=access_port,
info=info, rootwrap_command=rootwrap) 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, proxy_command, gateway_host,
gateway_username, gateway_username,
gateway_private_key) gateway_private_key)
@ -826,7 +834,7 @@ class InstanceInteropHelper(remote.Remote):
def close_http_session(self, port): def close_http_session(self, port):
global _sessions global _sessions
host = self.instance.management_ip host = _get_access_ip(self.instance)
self._log_command(_("Closing HTTP session for %(host)s:%(port)s") % { self._log_command(_("Closing HTTP session for %(host)s:%(port)s") % {
'host': host, 'port': port}) 'host': host, 'port': port})