Move SSHPool into ssh_utils.py
utils.py is loaded by almost everything, but SSH code and imports (paramiko) are only needed by certain drivers. Split this into a separate file to reduce overhead (and things that can break) for commands like cinder-manage. Partial-Bug: #1348787 Change-Id: I46896f2bd1fd0de2aedde8e87e255398e5bc3171
This commit is contained in:
parent
078f3ea1bf
commit
75ef446fef
|
@ -0,0 +1,120 @@
|
|||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# Copyright 2011 Justin Santa Barbara
|
||||
# Copyright 2014 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Utilities related to SSH connection management."""
|
||||
|
||||
import os.path
|
||||
|
||||
from eventlet import pools
|
||||
import paramiko
|
||||
|
||||
from cinder import exception
|
||||
from cinder.openstack.common.gettextutils import _
|
||||
from cinder.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SSHPool(pools.Pool):
|
||||
"""A simple eventlet pool to hold ssh connections."""
|
||||
|
||||
def __init__(self, ip, port, conn_timeout, login, password=None,
|
||||
privatekey=None, *args, **kwargs):
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.login = login
|
||||
self.password = password
|
||||
self.conn_timeout = conn_timeout if conn_timeout else None
|
||||
self.privatekey = privatekey
|
||||
if 'missing_key_policy' in kwargs.keys():
|
||||
self.missing_key_policy = kwargs.pop('missing_key_policy')
|
||||
else:
|
||||
self.missing_key_policy = paramiko.AutoAddPolicy()
|
||||
if 'hosts_key_file' in kwargs.keys():
|
||||
self.hosts_key_file = kwargs.pop('hosts_key_file')
|
||||
else:
|
||||
self.hosts_key_file = None
|
||||
super(SSHPool, self).__init__(*args, **kwargs)
|
||||
|
||||
def create(self):
|
||||
try:
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(self.missing_key_policy)
|
||||
if not self.hosts_key_file:
|
||||
ssh.load_system_host_keys()
|
||||
else:
|
||||
ssh.load_host_keys(self.hosts_key_file)
|
||||
if self.password:
|
||||
ssh.connect(self.ip,
|
||||
port=self.port,
|
||||
username=self.login,
|
||||
password=self.password,
|
||||
timeout=self.conn_timeout)
|
||||
elif self.privatekey:
|
||||
pkfile = os.path.expanduser(self.privatekey)
|
||||
privatekey = paramiko.RSAKey.from_private_key_file(pkfile)
|
||||
ssh.connect(self.ip,
|
||||
port=self.port,
|
||||
username=self.login,
|
||||
pkey=privatekey,
|
||||
timeout=self.conn_timeout)
|
||||
else:
|
||||
msg = _("Specify a password or private_key")
|
||||
raise exception.CinderException(msg)
|
||||
|
||||
# Paramiko by default sets the socket timeout to 0.1 seconds,
|
||||
# ignoring what we set through the sshclient. This doesn't help for
|
||||
# keeping long lived connections. Hence we have to bypass it, by
|
||||
# overriding it after the transport is initialized. We are setting
|
||||
# the sockettimeout to None and setting a keepalive packet so that,
|
||||
# the server will keep the connection open. All that does is send
|
||||
# a keepalive packet every ssh_conn_timeout seconds.
|
||||
if self.conn_timeout:
|
||||
transport = ssh.get_transport()
|
||||
transport.sock.settimeout(None)
|
||||
transport.set_keepalive(self.conn_timeout)
|
||||
return ssh
|
||||
except Exception as e:
|
||||
msg = _("Error connecting via ssh: %s") % e
|
||||
LOG.error(msg)
|
||||
raise paramiko.SSHException(msg)
|
||||
|
||||
def get(self):
|
||||
"""Return an item from the pool, when one is available.
|
||||
|
||||
This may cause the calling greenthread to block. Check if a
|
||||
connection is active before returning it.
|
||||
|
||||
For dead connections create and return a new connection.
|
||||
"""
|
||||
conn = super(SSHPool, self).get()
|
||||
if conn:
|
||||
if conn.get_transport().is_active():
|
||||
return conn
|
||||
else:
|
||||
conn.close()
|
||||
return self.create()
|
||||
|
||||
def remove(self, ssh):
|
||||
"""Close an ssh client and remove it from free_items."""
|
||||
ssh.close()
|
||||
ssh = None
|
||||
if ssh in self.free_items:
|
||||
self.free_items.pop(ssh)
|
||||
if self.current_size > 0:
|
||||
self.current_size -= 1
|
|
@ -30,8 +30,8 @@ from xml.etree import ElementTree as ET
|
|||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder import ssh_utils
|
||||
from cinder import test
|
||||
from cinder import utils
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume.drivers.huawei import huawei_utils
|
||||
from cinder.volume.drivers.huawei import HuaweiVolumeDriver
|
||||
|
@ -1079,7 +1079,7 @@ class HuaweiTISCSIDriverTestCase(test.TestCase):
|
|||
self.configuration.append_config_values(mox.IgnoreArg())
|
||||
|
||||
self.stubs.Set(time, 'sleep', Fake_sleep)
|
||||
self.stubs.Set(utils, 'SSHPool', FakeSSHPool)
|
||||
self.stubs.Set(ssh_utils, 'SSHPool', FakeSSHPool)
|
||||
self.stubs.Set(ssh_common.TseriesCommon, '_change_file_mode',
|
||||
Fake_change_file_mode)
|
||||
self._init_driver()
|
||||
|
@ -1473,7 +1473,7 @@ class HuaweiTFCDriverTestCase(test.TestCase):
|
|||
self.configuration.append_config_values(mox.IgnoreArg())
|
||||
|
||||
self.stubs.Set(time, 'sleep', Fake_sleep)
|
||||
self.stubs.Set(utils, 'SSHPool', FakeSSHPool)
|
||||
self.stubs.Set(ssh_utils, 'SSHPool', FakeSSHPool)
|
||||
self.stubs.Set(ssh_common.TseriesCommon, '_change_file_mode',
|
||||
Fake_change_file_mode)
|
||||
self._init_driver()
|
||||
|
@ -1705,7 +1705,7 @@ class SSHMethodTestCase(test.TestCase):
|
|||
self.configuration.append_config_values(mox.IgnoreArg())
|
||||
|
||||
self.stubs.Set(time, 'sleep', Fake_sleep)
|
||||
self.stubs.Set(utils, 'SSHPool', FakeSSHPool)
|
||||
self.stubs.Set(ssh_utils, 'SSHPool', FakeSSHPool)
|
||||
self.stubs.Set(ssh_common.TseriesCommon, '_change_file_mode',
|
||||
Fake_change_file_mode)
|
||||
Curr_test[0] = 'T'
|
||||
|
|
|
@ -31,6 +31,7 @@ from cinder.brick.initiator import linuxfc
|
|||
from cinder import exception
|
||||
from cinder.openstack.common import processutils as putils
|
||||
from cinder.openstack.common import timeutils
|
||||
from cinder import ssh_utils
|
||||
from cinder import test
|
||||
from cinder import utils
|
||||
|
||||
|
@ -870,24 +871,24 @@ class SSHPoolTestCase(test.TestCase):
|
|||
mock_sshclient.return_value = FakeSSHClient()
|
||||
|
||||
# create with customized setting
|
||||
sshpool = utils.SSHPool("127.0.0.1", 22, 10,
|
||||
"test",
|
||||
password="test",
|
||||
min_size=1,
|
||||
max_size=1,
|
||||
missing_key_policy=paramiko.RejectPolicy(),
|
||||
hosts_key_file='dummy_host_keyfile')
|
||||
sshpool = ssh_utils.SSHPool("127.0.0.1", 22, 10,
|
||||
"test",
|
||||
password="test",
|
||||
min_size=1,
|
||||
max_size=1,
|
||||
missing_key_policy=paramiko.RejectPolicy(),
|
||||
hosts_key_file='dummy_host_keyfile')
|
||||
with sshpool.item() as ssh:
|
||||
self.assertTrue(isinstance(ssh.get_policy(),
|
||||
paramiko.RejectPolicy))
|
||||
self.assertEqual(ssh.hosts_key_file, 'dummy_host_keyfile')
|
||||
|
||||
# create with default setting
|
||||
sshpool = utils.SSHPool("127.0.0.1", 22, 10,
|
||||
"test",
|
||||
password="test",
|
||||
min_size=1,
|
||||
max_size=1)
|
||||
sshpool = ssh_utils.SSHPool("127.0.0.1", 22, 10,
|
||||
"test",
|
||||
password="test",
|
||||
min_size=1,
|
||||
max_size=1)
|
||||
with sshpool.item() as ssh:
|
||||
self.assertTrue(isinstance(ssh.get_policy(),
|
||||
paramiko.AutoAddPolicy))
|
||||
|
@ -899,11 +900,11 @@ class SSHPoolTestCase(test.TestCase):
|
|||
mock_sshclient.return_value = FakeSSHClient()
|
||||
|
||||
# create with password
|
||||
sshpool = utils.SSHPool("127.0.0.1", 22, 10,
|
||||
"test",
|
||||
password="test",
|
||||
min_size=1,
|
||||
max_size=1)
|
||||
sshpool = ssh_utils.SSHPool("127.0.0.1", 22, 10,
|
||||
"test",
|
||||
password="test",
|
||||
min_size=1,
|
||||
max_size=1)
|
||||
with sshpool.item() as ssh:
|
||||
first_id = ssh.id
|
||||
|
||||
|
@ -914,16 +915,16 @@ class SSHPoolTestCase(test.TestCase):
|
|||
mock_sshclient.connect.assert_called_once()
|
||||
|
||||
# create with private key
|
||||
sshpool = utils.SSHPool("127.0.0.1", 22, 10,
|
||||
"test",
|
||||
privatekey="test",
|
||||
min_size=1,
|
||||
max_size=1)
|
||||
sshpool = ssh_utils.SSHPool("127.0.0.1", 22, 10,
|
||||
"test",
|
||||
privatekey="test",
|
||||
min_size=1,
|
||||
max_size=1)
|
||||
mock_sshclient.connect.assert_called_once()
|
||||
|
||||
# attempt to create with no password or private key
|
||||
self.assertRaises(paramiko.SSHException,
|
||||
utils.SSHPool,
|
||||
ssh_utils.SSHPool,
|
||||
"127.0.0.1", 22, 10,
|
||||
"test",
|
||||
min_size=1,
|
||||
|
@ -932,11 +933,11 @@ class SSHPoolTestCase(test.TestCase):
|
|||
@mock.patch('paramiko.SSHClient')
|
||||
def test_closed_reopend_ssh_connections(self, mock_sshclient):
|
||||
mock_sshclient.return_value = eval('FakeSSHClient')()
|
||||
sshpool = utils.SSHPool("127.0.0.1", 22, 10,
|
||||
"test",
|
||||
password="test",
|
||||
min_size=1,
|
||||
max_size=4)
|
||||
sshpool = ssh_utils.SSHPool("127.0.0.1", 22, 10,
|
||||
"test",
|
||||
password="test",
|
||||
min_size=1,
|
||||
max_size=4)
|
||||
with sshpool.item() as ssh:
|
||||
mock_sshclient.reset_mock()
|
||||
first_id = ssh.id
|
||||
|
|
|
@ -31,9 +31,7 @@ import sys
|
|||
import tempfile
|
||||
|
||||
from Crypto.Random import random
|
||||
from eventlet import pools
|
||||
from oslo.config import cfg
|
||||
import paramiko
|
||||
import six
|
||||
from xml.dom import minidom
|
||||
from xml.parsers import expat
|
||||
|
@ -179,96 +177,6 @@ def create_channel(client, width, height):
|
|||
return channel
|
||||
|
||||
|
||||
class SSHPool(pools.Pool):
|
||||
"""A simple eventlet pool to hold ssh connections."""
|
||||
|
||||
def __init__(self, ip, port, conn_timeout, login, password=None,
|
||||
privatekey=None, *args, **kwargs):
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.login = login
|
||||
self.password = password
|
||||
self.conn_timeout = conn_timeout if conn_timeout else None
|
||||
self.privatekey = privatekey
|
||||
if 'missing_key_policy' in kwargs.keys():
|
||||
self.missing_key_policy = kwargs.pop('missing_key_policy')
|
||||
else:
|
||||
self.missing_key_policy = paramiko.AutoAddPolicy()
|
||||
if 'hosts_key_file' in kwargs.keys():
|
||||
self.hosts_key_file = kwargs.pop('hosts_key_file')
|
||||
else:
|
||||
self.hosts_key_file = None
|
||||
super(SSHPool, self).__init__(*args, **kwargs)
|
||||
|
||||
def create(self):
|
||||
try:
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(self.missing_key_policy)
|
||||
if not self.hosts_key_file:
|
||||
ssh.load_system_host_keys()
|
||||
else:
|
||||
ssh.load_host_keys(self.hosts_key_file)
|
||||
if self.password:
|
||||
ssh.connect(self.ip,
|
||||
port=self.port,
|
||||
username=self.login,
|
||||
password=self.password,
|
||||
timeout=self.conn_timeout)
|
||||
elif self.privatekey:
|
||||
pkfile = os.path.expanduser(self.privatekey)
|
||||
privatekey = paramiko.RSAKey.from_private_key_file(pkfile)
|
||||
ssh.connect(self.ip,
|
||||
port=self.port,
|
||||
username=self.login,
|
||||
pkey=privatekey,
|
||||
timeout=self.conn_timeout)
|
||||
else:
|
||||
msg = _("Specify a password or private_key")
|
||||
raise exception.CinderException(msg)
|
||||
|
||||
# Paramiko by default sets the socket timeout to 0.1 seconds,
|
||||
# ignoring what we set through the sshclient. This doesn't help for
|
||||
# keeping long lived connections. Hence we have to bypass it, by
|
||||
# overriding it after the transport is initialized. We are setting
|
||||
# the sockettimeout to None and setting a keepalive packet so that,
|
||||
# the server will keep the connection open. All that does is send
|
||||
# a keepalive packet every ssh_conn_timeout seconds.
|
||||
if self.conn_timeout:
|
||||
transport = ssh.get_transport()
|
||||
transport.sock.settimeout(None)
|
||||
transport.set_keepalive(self.conn_timeout)
|
||||
return ssh
|
||||
except Exception as e:
|
||||
msg = _("Error connecting via ssh: %s") % e
|
||||
LOG.error(msg)
|
||||
raise paramiko.SSHException(msg)
|
||||
|
||||
def get(self):
|
||||
"""Return an item from the pool, when one is available.
|
||||
|
||||
This may cause the calling greenthread to block. Check if a
|
||||
connection is active before returning it.
|
||||
|
||||
For dead connections create and return a new connection.
|
||||
"""
|
||||
conn = super(SSHPool, self).get()
|
||||
if conn:
|
||||
if conn.get_transport().is_active():
|
||||
return conn
|
||||
else:
|
||||
conn.close()
|
||||
return self.create()
|
||||
|
||||
def remove(self, ssh):
|
||||
"""Close an ssh client and remove it from free_items."""
|
||||
ssh.close()
|
||||
ssh = None
|
||||
if ssh in self.free_items:
|
||||
self.free_items.pop(ssh)
|
||||
if self.current_size > 0:
|
||||
self.current_size -= 1
|
||||
|
||||
|
||||
def cinderdir():
|
||||
import cinder
|
||||
return os.path.abspath(cinder.__file__).split('cinder/__init__.py')[0]
|
||||
|
|
|
@ -28,6 +28,7 @@ from cinder.openstack.common import excutils
|
|||
from cinder.openstack.common.gettextutils import _
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.openstack.common import processutils
|
||||
from cinder import ssh_utils
|
||||
from cinder import utils
|
||||
from cinder.volume.drivers.san import SanISCSIDriver
|
||||
|
||||
|
@ -183,14 +184,15 @@ class DellEQLSanISCSIDriver(SanISCSIDriver):
|
|||
privatekey = self.configuration.san_private_key
|
||||
min_size = self.configuration.ssh_min_pool_conn
|
||||
max_size = self.configuration.ssh_max_pool_conn
|
||||
self.sshpool = utils.SSHPool(self.configuration.san_ip,
|
||||
self.configuration.san_ssh_port,
|
||||
self.configuration.ssh_conn_timeout,
|
||||
self.configuration.san_login,
|
||||
password=password,
|
||||
privatekey=privatekey,
|
||||
min_size=min_size,
|
||||
max_size=max_size)
|
||||
self.sshpool = ssh_utils.SSHPool(
|
||||
self.configuration.san_ip,
|
||||
self.configuration.san_ssh_port,
|
||||
self.configuration.ssh_conn_timeout,
|
||||
self.configuration.san_login,
|
||||
password=password,
|
||||
privatekey=privatekey,
|
||||
min_size=min_size,
|
||||
max_size=max_size)
|
||||
try:
|
||||
total_attempts = attempts
|
||||
with self.sshpool.item() as ssh:
|
||||
|
|
|
@ -32,6 +32,7 @@ from cinder import exception
|
|||
from cinder.openstack.common import excutils
|
||||
from cinder.openstack.common.gettextutils import _
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder import ssh_utils
|
||||
from cinder import utils
|
||||
from cinder.volume.drivers.huawei import huawei_utils
|
||||
from cinder.volume import volume_types
|
||||
|
@ -439,7 +440,8 @@ class TseriesCommon():
|
|||
user = self.login_info['UserName']
|
||||
pwd = self.login_info['UserPassword']
|
||||
if not self.ssh_pool:
|
||||
self.ssh_pool = utils.SSHPool(ip0, 22, 30, user, pwd, max_size=2)
|
||||
self.ssh_pool = ssh_utils.SSHPool(ip0, 22, 30, user, pwd,
|
||||
max_size=2)
|
||||
ssh_client = None
|
||||
while True:
|
||||
try:
|
||||
|
|
|
@ -29,6 +29,7 @@ from cinder.openstack.common import excutils
|
|||
from cinder.openstack.common.gettextutils import _
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.openstack.common import processutils
|
||||
from cinder import ssh_utils
|
||||
from cinder import utils
|
||||
from cinder.volume import driver
|
||||
|
||||
|
@ -109,14 +110,15 @@ class SanDriver(driver.VolumeDriver):
|
|||
privatekey = self.configuration.san_private_key
|
||||
min_size = self.configuration.ssh_min_pool_conn
|
||||
max_size = self.configuration.ssh_max_pool_conn
|
||||
self.sshpool = utils.SSHPool(self.configuration.san_ip,
|
||||
self.configuration.san_ssh_port,
|
||||
self.configuration.ssh_conn_timeout,
|
||||
self.configuration.san_login,
|
||||
password=password,
|
||||
privatekey=privatekey,
|
||||
min_size=min_size,
|
||||
max_size=max_size)
|
||||
self.sshpool = ssh_utils.SSHPool(
|
||||
self.configuration.san_ip,
|
||||
self.configuration.san_ssh_port,
|
||||
self.configuration.ssh_conn_timeout,
|
||||
self.configuration.san_login,
|
||||
password=password,
|
||||
privatekey=privatekey,
|
||||
min_size=min_size,
|
||||
max_size=max_size)
|
||||
last_exception = None
|
||||
try:
|
||||
with self.sshpool.item() as ssh:
|
||||
|
|
|
@ -31,6 +31,7 @@ from cinder.openstack.common import excutils
|
|||
from cinder.openstack.common.gettextutils import _
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder.openstack.common import processutils
|
||||
from cinder import ssh_utils
|
||||
from cinder import utils
|
||||
import cinder.zonemanager.drivers.brocade.fc_zone_constants as ZoneConstant
|
||||
|
||||
|
@ -378,13 +379,13 @@ class BrcdFCZoneClientCLI(object):
|
|||
command = ' '. join(cmd_list)
|
||||
|
||||
if not self.sshpool:
|
||||
self.sshpool = utils.SSHPool(self.switch_ip,
|
||||
self.switch_port,
|
||||
None,
|
||||
self.switch_user,
|
||||
self.switch_pwd,
|
||||
min_size=1,
|
||||
max_size=5)
|
||||
self.sshpool = ssh_utils.SSHPool(self.switch_ip,
|
||||
self.switch_port,
|
||||
None,
|
||||
self.switch_user,
|
||||
self.switch_pwd,
|
||||
min_size=1,
|
||||
max_size=5)
|
||||
last_exception = None
|
||||
try:
|
||||
with self.sshpool.item() as ssh:
|
||||
|
@ -424,13 +425,13 @@ class BrcdFCZoneClientCLI(object):
|
|||
command = ' '. join(cmd_list)
|
||||
|
||||
if not self.sshpool:
|
||||
self.sshpool = utils.SSHPool(self.switch_ip,
|
||||
self.switch_port,
|
||||
None,
|
||||
self.switch_user,
|
||||
self.switch_pwd,
|
||||
min_size=1,
|
||||
max_size=5)
|
||||
self.sshpool = ssh_utils.SSHPool(self.switch_ip,
|
||||
self.switch_port,
|
||||
None,
|
||||
self.switch_user,
|
||||
self.switch_pwd,
|
||||
min_size=1,
|
||||
max_size=5)
|
||||
stdin, stdout, stderr = None, None, None
|
||||
LOG.debug("Executing command via ssh: %s" % command)
|
||||
last_exception = None
|
||||
|
@ -499,13 +500,13 @@ class BrcdFCZoneClientCLI(object):
|
|||
command = ' '. join(cmd)
|
||||
stdout, stderr = None, None
|
||||
if not self.sshpool:
|
||||
self.sshpool = utils.SSHPool(self.switch_ip,
|
||||
self.switch_port,
|
||||
None,
|
||||
self.switch_user,
|
||||
self.switch_pwd,
|
||||
min_size=1,
|
||||
max_size=5)
|
||||
self.sshpool = ssh_utils.SSHPool(self.switch_ip,
|
||||
self.switch_port,
|
||||
None,
|
||||
self.switch_user,
|
||||
self.switch_pwd,
|
||||
min_size=1,
|
||||
max_size=5)
|
||||
with self.sshpool.item() as ssh:
|
||||
LOG.debug('Running cmd (SSH): %s' % command)
|
||||
channel = ssh.invoke_shell()
|
||||
|
|
Loading…
Reference in New Issue