Skip to content

Commit

Permalink
Acclerate improvements
Browse files Browse the repository at this point in the history
* Added capability to support multiple keys, so clients from different
  machines can connect to a single daemon instance
* Any activity on the daemon will cause the timeout to extend, so that the
  daemon must be idle for the full number of minutes before it will auto-
  shutdown
* Various other small fixes to remove some redundancy

Fixes ansible#5171
  • Loading branch information
jimi-c committed Mar 23, 2014
1 parent f82ac9f commit 3ea5d57
Show file tree
Hide file tree
Showing 3 changed files with 340 additions and 108 deletions.
1 change: 1 addition & 0 deletions lib/ansible/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ def shell_expand_path(path):
ACCELERATE_KEYS_DIR = get_config(p, 'accelerate', 'accelerate_keys_dir', 'ACCELERATE_KEYS_DIR', '~/.fireball.keys')
ACCELERATE_KEYS_DIR_PERMS = get_config(p, 'accelerate', 'accelerate_keys_dir_perms', 'ACCELERATE_KEYS_DIR_PERMS', '700')
ACCELERATE_KEYS_FILE_PERMS = get_config(p, 'accelerate', 'accelerate_keys_file_perms', 'ACCELERATE_KEYS_FILE_PERMS', '600')
ACCELERATE_MULTI_KEY = get_config(p, 'accelerate', 'accelerate_multi_key', 'ACCELERATE_MULTI_KEY', False, boolean=True)
PARAMIKO_PTY = get_config(p, 'paramiko_connection', 'pty', 'ANSIBLE_PARAMIKO_PTY', True, boolean=True)

# characters included in auto-generated passwords
Expand Down
78 changes: 44 additions & 34 deletions lib/ansible/runner/connection_plugins/accelerate.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
import struct
import time
from ansible.callbacks import vvv, vvvv
from ansible.errors import AnsibleError, AnsibleFileNotFound
from ansible.runner.connection_plugins.ssh import Connection as SSHConnection
from ansible.runner.connection_plugins.paramiko_ssh import Connection as ParamikoConnection
from ansible import utils
from ansible import errors
from ansible import constants

# the chunk size to read and send, assuming mtu 1500 and
Expand Down Expand Up @@ -85,7 +85,9 @@ def __init__(self, runner, host, port, user, password, private_key_file, *args,
utils.AES_KEYS = self.runner.aes_keys

def _execute_accelerate_module(self):
args = "password=%s port=%s debug=%d ipv6=%s" % (base64.b64encode(self.key.__str__()), str(self.accport), int(utils.VERBOSITY), self.runner.accelerate_ipv6)
args = "password=%s port=%s minutes=%d debug=%d ipv6=%s" % (base64.b64encode(self.key.__str__()), str(self.accport), constants.ACCELERATE_TIMEOUT, int(utils.VERBOSITY), self.runner.accelerate_ipv6)
if constants.ACCELERATE_MULTI_KEY:
args += " multi_key=yes"
inject = dict(password=self.key)
if getattr(self.runner, 'accelerate_inventory_host', False):
inject = utils.combine_vars(inject, self.runner.inventory.get_variables(self.runner.accelerate_inventory_host))
Expand All @@ -109,33 +111,38 @@ def connect(self, allow_ssh=True):
while tries > 0:
try:
self.conn.connect((self.host,self.accport))
if not self.validate_user():
# the accelerated daemon was started with a
# different remote_user. The above command
# should have caused the accelerate daemon to
# shutdown, so we'll reconnect.
wrong_user = True
break
except:
vvvv("failed, retrying...")
except socket.error:
vvvv("connection to %s failed, retrying..." % self.host)
time.sleep(0.1)
tries -= 1
if tries == 0:
vvv("Could not connect via the accelerated connection, exceeded # of tries")
raise errors.AnsibleError("Failed to connect")
raise AnsibleError("FAILED")
elif wrong_user:
vvv("Restarting daemon with a different remote_user")
raise errors.AnsibleError("Wrong user")
raise AnsibleError("WRONG_USER")

self.conn.settimeout(constants.ACCELERATE_TIMEOUT)
except:
if not self.validate_user():
# the accelerated daemon was started with a
# different remote_user. The above command
# should have caused the accelerate daemon to
# shutdown, so we'll reconnect.
wrong_user = True

except AnsibleError, e:
if allow_ssh:
if "WRONG_USER" in e:
vvv("Switching users, waiting for the daemon on %s to shutdown completely..." % self.host)
time.sleep(5)
vvv("Falling back to ssh to startup accelerated mode")
res = self._execute_accelerate_module()
if not res.is_successful():
raise errors.AnsibleError("Failed to launch the accelerated daemon on %s (reason: %s)" % (self.host,res.result.get('msg')))
raise AnsibleError("Failed to launch the accelerated daemon on %s (reason: %s)" % (self.host,res.result.get('msg')))
return self.connect(allow_ssh=False)
else:
raise errors.AnsibleError("Failed to connect to %s:%s" % (self.host,self.accport))
raise AnsibleError("Failed to connect to %s:%s" % (self.host,self.accport))
self.is_connected = True
return self

Expand Down Expand Up @@ -163,11 +170,12 @@ def recv_data(self):
if not d:
vvvv("%s: received nothing, bailing out" % self.host)
return None
vvvv("%s: received %d bytes" % (self.host, len(d)))
data += d
vvvv("%s: received all of the data, returning" % self.host)
return data
except socket.timeout:
raise errors.AnsibleError("timed out while waiting to receive data")
raise AnsibleError("timed out while waiting to receive data")

def validate_user(self):
'''
Expand All @@ -176,45 +184,47 @@ def validate_user(self):
daemon to exit if they don't match
'''

vvvv("%s: sending request for validate_user" % self.host)
data = dict(
mode='validate_user',
username=self.user,
)
data = utils.jsonify(data)
data = utils.encrypt(self.key, data)
if self.send_data(data):
raise errors.AnsibleError("Failed to send command to %s" % self.host)
raise AnsibleError("Failed to send command to %s" % self.host)

vvvv("%s: waiting for validate_user response" % self.host)
while True:
# we loop here while waiting for the response, because a
# long running command may cause us to receive keepalive packets
# ({"pong":"true"}) rather than the response we want.
response = self.recv_data()
if not response:
raise errors.AnsibleError("Failed to get a response from %s" % self.host)
raise AnsibleError("Failed to get a response from %s" % self.host)
response = utils.decrypt(self.key, response)
response = utils.parse_json(response)
if "pong" in response:
# it's a keepalive, go back to waiting
vvvv("%s: received a keepalive packet" % self.host)
continue
else:
vvvv("%s: received the response" % self.host)
vvvv("%s: received the validate_user response: %s" % (self.host, response))
break

if response.get('failed'):
raise errors.AnsibleError("Error while validating user: %s" % response.get("msg"))
return False
else:
return response.get('rc') == 0

def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None):
''' run a command on the remote host '''

if su or su_user:
raise errors.AnsibleError("Internal Error: this module does not support running commands via su")
raise AnsibleError("Internal Error: this module does not support running commands via su")

if in_data:
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
raise AnsibleError("Internal Error: this module does not support optimized module pipelining")

if executable == "":
executable = constants.DEFAULT_EXECUTABLE
Expand All @@ -233,15 +243,15 @@ def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable
data = utils.jsonify(data)
data = utils.encrypt(self.key, data)
if self.send_data(data):
raise errors.AnsibleError("Failed to send command to %s" % self.host)
raise AnsibleError("Failed to send command to %s" % self.host)

while True:
# we loop here while waiting for the response, because a
# long running command may cause us to receive keepalive packets
# ({"pong":"true"}) rather than the response we want.
response = self.recv_data()
if not response:
raise errors.AnsibleError("Failed to get a response from %s" % self.host)
raise AnsibleError("Failed to get a response from %s" % self.host)
response = utils.decrypt(self.key, response)
response = utils.parse_json(response)
if "pong" in response:
Expand All @@ -260,7 +270,7 @@ def put_file(self, in_path, out_path):
vvv("PUT %s TO %s" % (in_path, out_path), host=self.host)

if not os.path.exists(in_path):
raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path)
raise AnsibleFileNotFound("file or module does not exist: %s" % in_path)

fd = file(in_path, 'rb')
fstat = os.stat(in_path)
Expand All @@ -279,27 +289,27 @@ def put_file(self, in_path, out_path):
data = utils.encrypt(self.key, data)

if self.send_data(data):
raise errors.AnsibleError("failed to send the file to %s" % self.host)
raise AnsibleError("failed to send the file to %s" % self.host)

response = self.recv_data()
if not response:
raise errors.AnsibleError("Failed to get a response from %s" % self.host)
raise AnsibleError("Failed to get a response from %s" % self.host)
response = utils.decrypt(self.key, response)
response = utils.parse_json(response)

if response.get('failed',False):
raise errors.AnsibleError("failed to put the file in the requested location")
raise AnsibleError("failed to put the file in the requested location")
finally:
fd.close()
vvvv("waiting for final response after PUT")
response = self.recv_data()
if not response:
raise errors.AnsibleError("Failed to get a response from %s" % self.host)
raise AnsibleError("Failed to get a response from %s" % self.host)
response = utils.decrypt(self.key, response)
response = utils.parse_json(response)

if response.get('failed',False):
raise errors.AnsibleError("failed to put the file in the requested location")
raise AnsibleError("failed to put the file in the requested location")

def fetch_file(self, in_path, out_path):
''' save a remote file to the specified path '''
Expand All @@ -309,19 +319,19 @@ def fetch_file(self, in_path, out_path):
data = utils.jsonify(data)
data = utils.encrypt(self.key, data)
if self.send_data(data):
raise errors.AnsibleError("failed to initiate the file fetch with %s" % self.host)
raise AnsibleError("failed to initiate the file fetch with %s" % self.host)

fh = open(out_path, "w")
try:
bytes = 0
while True:
response = self.recv_data()
if not response:
raise errors.AnsibleError("Failed to get a response from %s" % self.host)
raise AnsibleError("Failed to get a response from %s" % self.host)
response = utils.decrypt(self.key, response)
response = utils.parse_json(response)
if response.get('failed', False):
raise errors.AnsibleError("Error during file fetch, aborting")
raise AnsibleError("Error during file fetch, aborting")
out = base64.b64decode(response['data'])
fh.write(out)
bytes += len(out)
Expand All @@ -330,7 +340,7 @@ def fetch_file(self, in_path, out_path):
data = utils.jsonify(dict())
data = utils.encrypt(self.key, data)
if self.send_data(data):
raise errors.AnsibleError("failed to send ack during file fetch")
raise AnsibleError("failed to send ack during file fetch")
if response.get('last', False):
break
finally:
Expand Down
Loading

0 comments on commit 3ea5d57

Please sign in to comment.