files: # Licensed to the Apache Software Foundation (ASF) under one or more
  # contributor license agreements.  See the NOTICE file distributed with
  # this work for additional information regarding copyright ownership.
  # The ASF licenses this file to You 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.
  
  """
  Provides generic deployment steps for machines post boot.
  """
  
  from __future__ import with_statement
  
  from typing import Union
  from typing import Optional
  from typing import List
  from typing import IO
  from typing import cast
  
  import os
  import binascii
  
  from libcloud.utils.py3 import basestring, PY3
  from libcloud.compute.base import Node
  from libcloud.compute.ssh import BaseSSHClient
  
  
  class Deployment(object):
      """
      Base class for deployment tasks.
      """
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Runs this deployment task on node using the client provided.
  
          :type node: :class:`Node`
          :keyword node: Node to operate one
  
          :type client: :class:`BaseSSHClient`
          :keyword client: Connected SSH client to use.
  
          :return: :class:`Node`
          """
          raise NotImplementedError(
              'run not implemented for this deployment')
  
      def _get_string_value(self, argument_name, argument_value):
          if not isinstance(argument_value, basestring) and \
             not hasattr(argument_value, 'read'):
              raise TypeError('%s argument must be a string or a file-like '
                              'object' % (argument_name))
  
          if hasattr(argument_value, 'read'):
              argument_value = argument_value.read()
  
          return argument_value
  
  
  class SSHKeyDeployment(Deployment):
      """
      Installs a public SSH Key onto a server.
      """
  
      def __init__(self, key):
          # type: (Union[str, IO]) -> None
          """
          :type key: ``str`` or :class:`File` object
          :keyword key: Contents of the public key write or a file object which
                        can be read.
          """
          self.key = self._get_string_value(argument_name='key',
                                            argument_value=key)
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Installs SSH key into ``.ssh/authorized_keys``
  
          See also :class:`Deployment.run`
          """
          client.put(".ssh/authorized_keys", contents=self.key, mode='a')
          return node
  
  
  class FileDeployment(Deployment):
      """
      Installs a file on the server.
      """
  
      def __init__(self, source, target):
          # type: (str, str) -> None
          """
          :type source: ``str``
          :keyword source: Local path of file to be installed
  
          :type target: ``str``
          :keyword target: Path to install file on node
          """
          self.source = source
          self.target = target
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Upload the file, retaining permissions.
  
          See also :class:`Deployment.run`
          """
          perms = int(oct(os.stat(self.source).st_mode)[4:], 8)
  
          with open(self.source, 'rb') as fp:
              content = fp.read()
  
          client.put(path=self.target, chmod=perms,
                     contents=content)
          return node
  
  
  class ScriptDeployment(Deployment):
      """
      Runs an arbitrary shell script on the server.
  
      This step works by first writing the content of the shell script (script
      argument) in a *.sh file on a remote server and then running that file.
  
      If you are running a non-shell script, make sure to put the appropriate
      shebang to the top of the script. You are also advised to do that even if
      you are running a plan shell script.
      """
  
      def __init__(self,
                   script,  # type: str
                   args=None,  # type: Optional[List[str]]
                   name=None,  # type: Optional[str]
                   delete=False,  # type bool
                   timeout=None  # type: Optional[float]
                   ):
          # type: (...) -> None
          """
          :type script: ``str``
          :keyword script: Contents of the script to run.
  
          :type args: ``list``
          :keyword args: Optional command line arguments which get passed to the
                         deployment script file.
  
          :type name: ``str``
          :keyword name: Name of the script to upload it as, if not specified,
                         a random name will be chosen.
  
          :type delete: ``bool``
          :keyword delete: Whether to delete the script on completion.
  
          :param timeout: Optional run timeout for this command.
          :type timeout: ``float``
          """
          script = self._get_string_value(argument_name='script',
                                          argument_value=script)
  
          self.script = script
          self.args = args or []
          self.stdout = None  # type: Optional[str]
          self.stderr = None  # type: Optional[str]
          self.exit_status = None  # type: Optional[int]
          self.delete = delete
          self.timeout = timeout
          self.name = name  # type: Optional[str]
  
          if self.name is None:
              # File is put under user's home directory
              # (~/libcloud_deployment_<random_string>.sh)
              random_string = ''  # type: Union[str, bytes]
              random_string = binascii.hexlify(os.urandom(4))
              random_string = cast(bytes, random_string)
              random_string = random_string.decode('ascii')
              self.name = 'libcloud_deployment_%s.sh' % (random_string)
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Uploads the shell script and then executes it.
  
          See also :class:`Deployment.run`
          """
          self.name = cast(str, self.name)
          file_path = client.put(path=self.name, chmod=int('755', 8),
                                 contents=self.script)
  
          # Pre-pend cwd if user specified a relative path
          if self.name and self.name[0] != '/':
              base_path = os.path.dirname(file_path)
              name = os.path.join(base_path, self.name)
          else:
              self.name = cast(str, self.name)
              name = self.name
  
          cmd = name
  
          if self.args:
              # Append arguments to the command
              cmd = '%s %s' % (name, ' '.join(self.args))
          else:
              cmd = name
  
          self.stdout, self.stderr, self.exit_status = \
              client.run(cmd, timeout=self.timeout)
  
          if self.delete:
              client.delete(self.name)
  
          return node
  
      def __str__(self):
          return self.__repr__()
  
      def __repr__(self):
          script = self.script[:15] + '...'
          exit_status = self.exit_status
  
          if exit_status is not None:
              stdout = self.stdout[:30] + '...'
              stderr = self.stderr[:30] + '...'
          else:
              exit_status = 'script didn\'t run yet'
              stdout = None
              stderr = None
          return ("<ScriptDeployment script=%s, exit_status=%s, stdout=%s, "
                  "stderr=%s>" % (script, exit_status, stdout, stderr))
  
  
  class ScriptFileDeployment(ScriptDeployment):
      """
      Runs an arbitrary shell script from a local file on the server. Same as
      ScriptDeployment, except that you can pass in a path to the file instead of
      the script content.
      """
  
      def __init__(self,
                   script_file,  # type: str
                   args=None,  # type: Optional[List[str]]
                   name=None,  # type: Optional[str]
                   delete=False,  # type bool
                   timeout=None  # type: Optional[float]
                   ):
          # type: (...) -> None
          """
          :type script_file: ``str``
          :keyword script_file: Path to a file containing the script to run.
  
          :type args: ``list``
          :keyword args: Optional command line arguments which get passed to the
                         deployment script file.
  
  
          :type name: ``str``
          :keyword name: Name of the script to upload it as, if not specified,
                         a random name will be chosen.
  
          :type delete: ``bool``
          :keyword delete: Whether to delete the script on completion.
  
          :param timeout: Optional run timeout for this command.
          :type timeout: ``float``
          """
          with open(script_file, 'rb') as fp:
              content = fp.read()  # type: Union[bytes, str]
  
          if PY3:
              content = cast(bytes, content)
              content = content.decode('utf-8')
  
          super(ScriptFileDeployment, self).__init__(script=content,
                                                     args=args,
                                                     name=name,
                                                     delete=delete,
                                                     timeout=timeout)
  
  
  class MultiStepDeployment(Deployment):
      """
      Runs a chain of Deployment steps.
      """
      def __init__(self, add=None):
          # type: (Optional[Union[Deployment, List[Deployment]]]) -> None
          """
          :type add: ``list``
          :keyword add: Deployment steps to add.
          """
          self.steps = []  # type: list
  
          if add:
              self.add(add)
  
      def add(self, add):
          # type: (Union[Deployment, List[Deployment]]) -> None
          """
          Add a deployment to this chain.
  
          :type add: Single :class:`Deployment` or a ``list`` of
                     :class:`Deployment`
          :keyword add: Adds this deployment to the others already in this
                        object.
          """
          if add is not None:
              add = add if isinstance(add, (list, tuple)) else [add]
              self.steps.extend(add)
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Run each deployment that has been added.
  
          See also :class:`Deployment.run`
          """
          for s in self.steps:
              node = s.run(node, client)
          return node
  
files: libcloud/compute/deployment.py
files: # Licensed to the Apache Software Foundation (ASF) under one or more
  # contributor license agreements.  See the NOTICE file distributed with
  # this work for additional information regarding copyright ownership.
  # The ASF licenses this file to You 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.
  
  """
  Provides generic deployment steps for machines post boot.
  """
  
  from __future__ import with_statement
  
  from typing import Union
  from typing import Optional
  from typing import List
  from typing import IO
  from typing import cast
  
  import os
  import binascii
  
  from libcloud.utils.py3 import basestring, PY3
  from libcloud.compute.base import Node
  from libcloud.compute.ssh import BaseSSHClient
  
  
  class Deployment(object):
      """
      Base class for deployment tasks.
      """
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Runs this deployment task on node using the client provided.
  
          :type node: :class:`Node`
          :keyword node: Node to operate one
  
          :type client: :class:`BaseSSHClient`
          :keyword client: Connected SSH client to use.
  
          :return: :class:`Node`
          """
          raise NotImplementedError(
              'run not implemented for this deployment')
  
      def _get_string_value(self, argument_name, argument_value):
          if not isinstance(argument_value, basestring) and \
             not hasattr(argument_value, 'read'):
              raise TypeError('%s argument must be a string or a file-like '
                              'object' % (argument_name))
  
          if hasattr(argument_value, 'read'):
              argument_value = argument_value.read()
  
          return argument_value
  
  
  class SSHKeyDeployment(Deployment):
      """
      Installs a public SSH Key onto a server.
      """
  
      def __init__(self, key):
          # type: (Union[str, IO]) -> None
          """
          :type key: ``str`` or :class:`File` object
          :keyword key: Contents of the public key write or a file object which
                        can be read.
          """
          self.key = self._get_string_value(argument_name='key',
                                            argument_value=key)
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Installs SSH key into ``.ssh/authorized_keys``
  
          See also :class:`Deployment.run`
          """
          client.put(".ssh/authorized_keys", contents=self.key, mode='a')
          return node
  
  
  class FileDeployment(Deployment):
      """
      Installs a file on the server.
      """
  
      def __init__(self, source, target):
          # type: (str, str) -> None
          """
          :type source: ``str``
          :keyword source: Local path of file to be installed
  
          :type target: ``str``
          :keyword target: Path to install file on node
          """
          self.source = source
          self.target = target
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Upload the file, retaining permissions.
  
          See also :class:`Deployment.run`
          """
          perms = int(oct(os.stat(self.source).st_mode)[4:], 8)
  
          with open(self.source, 'rb') as fp:
              content = fp.read()
  
          client.put(path=self.target, chmod=perms,
                     contents=content)
          return node
  
  
  class ScriptDeployment(Deployment):
      """
      Runs an arbitrary shell script on the server.
  
      This step works by first writing the content of the shell script (script
      argument) in a *.sh file on a remote server and then running that file.
  
      If you are running a non-shell script, make sure to put the appropriate
      shebang to the top of the script. You are also advised to do that even if
      you are running a plan shell script.
      """
  
      def __init__(self, script, args=None, name=None, delete=False):
          # type: (str, Optional[List[str]], Optional[str], bool) -> None
          """
          :type script: ``str``
          :keyword script: Contents of the script to run.
  
          :type args: ``list``
          :keyword args: Optional command line arguments which get passed to the
                         deployment script file.
  
          :type name: ``str``
          :keyword name: Name of the script to upload it as, if not specified,
                         a random name will be chosen.
  
          :type delete: ``bool``
          :keyword delete: Whether to delete the script on completion.
          """
          script = self._get_string_value(argument_name='script',
                                          argument_value=script)
  
          self.script = script
          self.args = args or []
          self.stdout = None  # type: Optional[str]
          self.stderr = None  # type: Optional[str]
          self.exit_status = None  # type: Optional[int]
          self.delete = delete
          self.name = name  # type: Optional[str]
  
          if self.name is None:
              # File is put under user's home directory
              # (~/libcloud_deployment_<random_string>.sh)
              random_string = ''  # type: Union[str, bytes]
              random_string = binascii.hexlify(os.urandom(4))
              random_string = cast(bytes, random_string)
              random_string = random_string.decode('ascii')
              self.name = 'libcloud_deployment_%s.sh' % (random_string)
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Uploads the shell script and then executes it.
  
          See also :class:`Deployment.run`
          """
          self.name = cast(str, self.name)
          file_path = client.put(path=self.name, chmod=int('755', 8),
                                 contents=self.script)
  
          # Pre-pend cwd if user specified a relative path
          if self.name and self.name[0] != '/':
              base_path = os.path.dirname(file_path)
              name = os.path.join(base_path, self.name)
          else:
              self.name = cast(str, self.name)
              name = self.name
  
          cmd = name
  
          if self.args:
              # Append arguments to the command
              cmd = '%s %s' % (name, ' '.join(self.args))
          else:
              cmd = name
  
          self.stdout, self.stderr, self.exit_status = client.run(cmd)
  
          if self.delete:
              client.delete(self.name)
  
          return node
  
      def __str__(self):
          return self.__repr__()
  
      def __repr__(self):
          script = self.script[:15] + '...'
          exit_status = self.exit_status
  
          if exit_status is not None:
              stdout = self.stdout[:30] + '...'
              stderr = self.stderr[:30] + '...'
          else:
              exit_status = 'script didn\'t run yet'
              stdout = None
              stderr = None
          return ("<ScriptDeployment script=%s, exit_status=%s, stdout=%s, "
                  "stderr=%s>" % (script, exit_status, stdout, stderr))
  
  
  class ScriptFileDeployment(ScriptDeployment):
      """
      Runs an arbitrary shell script from a local file on the server. Same as
      ScriptDeployment, except that you can pass in a path to the file instead of
      the script content.
      """
  
      def __init__(self, script_file, args=None, name=None, delete=False):
          # type: (str, Optional[List[str]], Optional[str], bool) -> None
          """
          :type script_file: ``str``
          :keyword script_file: Path to a file containing the script to run.
  
          :type args: ``list``
          :keyword args: Optional command line arguments which get passed to the
                         deployment script file.
  
  
          :type name: ``str``
          :keyword name: Name of the script to upload it as, if not specified,
                         a random name will be chosen.
  
          :type delete: ``bool``
          :keyword delete: Whether to delete the script on completion.
          """
          with open(script_file, 'rb') as fp:
              content = fp.read()  # type: Union[bytes, str]
  
          if PY3:
              content = cast(bytes, content)
              content = content.decode('utf-8')
  
          super(ScriptFileDeployment, self).__init__(script=content,
                                                     args=args,
                                                     name=name,
                                                     delete=delete)
  
  
  class MultiStepDeployment(Deployment):
      """
      Runs a chain of Deployment steps.
      """
      def __init__(self, add=None):
          # type: (Optional[Union[Deployment, List[Deployment]]]) -> None
          """
          :type add: ``list``
          :keyword add: Deployment steps to add.
          """
          self.steps = []  # type: list
  
          if add:
              self.add(add)
  
      def add(self, add):
          # type: (Union[Deployment, List[Deployment]]) -> None
          """
          Add a deployment to this chain.
  
          :type add: Single :class:`Deployment` or a ``list`` of
                     :class:`Deployment`
          :keyword add: Adds this deployment to the others already in this
                        object.
          """
          if add is not None:
              add = add if isinstance(add, (list, tuple)) else [add]
              self.steps.extend(add)
  
      def run(self, node, client):
          # type: (Node, BaseSSHClient) -> Node
          """
          Run each deployment that has been added.
  
          See also :class:`Deployment.run`
          """
          for s in self.steps:
              node = s.run(node, client)
          return node
  
files: libcloud/compute/deployment.py
files: # Licensed to the Apache Software Foundation (ASF) under one or more
  # contributor license agreements.  See the NOTICE file distributed with
  # this work for additional information regarding copyright ownership.
  # The ASF licenses this file to You 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.
  
  """
  Wraps multiple ways to communicate over SSH.
  """
  
  from typing import Type
  from typing import Optional
  from typing import Tuple
  from typing import List
  from typing import Union
  from typing import cast
  
  have_paramiko = False
  
  try:
      import paramiko
      have_paramiko = True
  except ImportError:
      pass
  
  # Depending on your version of Paramiko, it may cause a deprecation
  # warning on Python 2.6.
  # Ref: https://bugs.launchpad.net/paramiko/+bug/392973
  
  import os
  import time
  import subprocess
  import logging
  import warnings
  
  from os.path import split as psplit
  from os.path import join as pjoin
  
  from libcloud.utils.logging import ExtraLogFormatter
  from libcloud.utils.py3 import StringIO
  from libcloud.utils.py3 import b
  
  __all__ = [
      'BaseSSHClient',
      'ParamikoSSHClient',
      'ShellOutSSHClient',
  
      'SSHCommandTimeoutError'
  ]
  
  SUPPORTED_KEY_TYPES_URL = 'https://libcloud.readthedocs.io/en/latest/compute/deployment.html#supported-private-ssh-key-types' # NOQA
  
  
  class SSHCommandTimeoutError(Exception):
      """
      Exception which is raised when an SSH command times out.
      """
      def __init__(self, cmd, timeout):
          # type: (str, float) -> None
          self.cmd = cmd
          self.timeout = timeout
          self.message = 'Command didn\'t finish in %s seconds' % (timeout)
          super(SSHCommandTimeoutError, self).__init__(self.message)
  
      def __repr__(self):
          return ('<SSHCommandTimeoutError: cmd="%s",timeout=%s)>' %
                  (self.cmd, self.timeout))
  
      def __str__(self):
          return self.__repr__()
  
  
  class BaseSSHClient(object):
      """
      Base class representing a connection over SSH/SCP to a remote node.
      """
  
      def __init__(self,
                   hostname,  # type: str
                   port=22,  # type: int
                   username='root',  # type: str
                   password=None,  # type: Optional[str]
                   key=None,  # type: Optional[str]
                   key_files=None,  # type: Optional[Union[str, List[str]]]
                   timeout=None  # type: Optional[float]
                   ):
          """
          :type hostname: ``str``
          :keyword hostname: Hostname or IP address to connect to.
  
          :type port: ``int``
          :keyword port: TCP port to communicate on, defaults to 22.
  
          :type username: ``str``
          :keyword username: Username to use, defaults to root.
  
          :type password: ``str``
          :keyword password: Password to authenticate with or a password used
                             to unlock a private key if a password protected key
                             is used.
  
          :param key: Deprecated in favor of ``key_files`` argument.
  
          :type key_files: ``str`` or ``list``
          :keyword key_files: A list of paths to the private key files to use.
          """
          if key is not None:
              message = ('You are using deprecated "key" argument which has '
                         'been replaced with "key_files" argument')
              warnings.warn(message, DeprecationWarning)
  
              # key_files has precedent
              key_files = key if not key_files else key_files
  
          self.hostname = hostname
          self.port = port
          self.username = username
          self.password = password
          self.key_files = key_files
          self.timeout = timeout
  
      def connect(self):
          # type: () -> bool
          """
          Connect to the remote node over SSH.
  
          :return: True if the connection has been successfully established,
                   False otherwise.
          :rtype: ``bool``
          """
          raise NotImplementedError(
              'connect not implemented for this ssh client')
  
      def put(self, path, contents=None, chmod=None, mode='w'):
          # type: (str, Optional[Union[str, bytes]], Optional[int], str) -> str
          """
          Upload a file to the remote node.
  
          :type path: ``str``
          :keyword path: File path on the remote node.
  
          :type contents: ``str``
          :keyword contents: File Contents.
  
          :type chmod: ``int``
          :keyword chmod: chmod file to this after creation.
  
          :type mode: ``str``
          :keyword mode: Mode in which the file is opened.
  
          :return: Full path to the location where a file has been saved.
          :rtype: ``str``
          """
          raise NotImplementedError(
              'put not implemented for this ssh client')
  
      def delete(self, path):
          # type: (str) -> bool
          """
          Delete/Unlink a file on the remote node.
  
          :type path: ``str``
          :keyword path: File path on the remote node.
  
          :return: True if the file has been successfully deleted, False
                   otherwise.
          :rtype: ``bool``
          """
          raise NotImplementedError(
              'delete not implemented for this ssh client')
  
      def run(self, cmd, timeout=None):
          # type: (str, Optional[float]) -> Tuple[str, str, int]
          """
          Run a command on a remote node.
  
          :type cmd: ``str``
          :keyword cmd: Command to run.
  
          :return ``list`` of [stdout, stderr, exit_status]
          """
          raise NotImplementedError(
              'run not implemented for this ssh client')
  
      def close(self):
          # type: () -> bool
          """
          Shutdown connection to the remote node.
  
          :return: True if the connection has been successfully closed, False
                   otherwise.
          :rtype: ``bool``
          """
          raise NotImplementedError(
              'close not implemented for this ssh client')
  
      def _get_and_setup_logger(self):
          # type: () -> logging.Logger
          logger = logging.getLogger('libcloud.compute.ssh')
          path = os.getenv('LIBCLOUD_DEBUG')
  
          if path:
              handler = logging.FileHandler(path)
              handler.setFormatter(ExtraLogFormatter())
              logger.addHandler(handler)
              logger.setLevel(logging.DEBUG)
  
          return logger
  
  
  class ParamikoSSHClient(BaseSSHClient):
      """
      A SSH Client powered by Paramiko.
      """
  
      # Maximum number of bytes to read at once from a socket
      CHUNK_SIZE = 4096
  
      # How long to sleep while waiting for command to finish (to prevent busy
      # waiting)
      SLEEP_DELAY = 0.2
  
      def __init__(self,
                   hostname,  # type: str
                   port=22,  # type: int
                   username='root',  # type: str
                   password=None,  # type: Optional[str]
                   key=None,  # type: Optional[str]
                   key_files=None,  # type: Optional[Union[str, List[str]]]
                   key_material=None,  # type: Optional[str]
                   timeout=None  # type: Optional[float]
                   ):
          """
          Authentication is always attempted in the following order:
  
          - The key passed in (if key is provided)
          - Any key we can find through an SSH agent (only if no password and
            key is provided)
          - Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (only if no
            password and key is provided)
          - Plain username/password auth, if a password was given (if password is
            provided)
          """
          if key_files and key_material:
              raise ValueError(('key_files and key_material arguments are '
                                'mutually exclusive'))
  
          super(ParamikoSSHClient, self).__init__(hostname=hostname, port=port,
                                                  username=username,
                                                  password=password,
                                                  key=key,
                                                  key_files=key_files,
                                                  timeout=timeout)
  
          self.key_material = key_material
  
          self.client = paramiko.SSHClient()
          self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
          self.logger = self._get_and_setup_logger()
  
      def connect(self):
          conninfo = {'hostname': self.hostname,
                      'port': self.port,
                      'username': self.username,
                      'allow_agent': False,
                      'look_for_keys': False}
  
          if self.password:
              conninfo['password'] = self.password
  
          if self.key_files:
              conninfo['key_filename'] = self.key_files
  
          if self.key_material:
              conninfo['pkey'] = self._get_pkey_object(
                  key=self.key_material,
                  passpharse=self.password)
  
          if not self.password and not (self.key_files or self.key_material):
              conninfo['allow_agent'] = True
              conninfo['look_for_keys'] = True
  
          if self.timeout:
              conninfo['timeout'] = self.timeout
  
          # This is a workaround for paramiko only supporting key files in
          # format staring with "BEGIN RSA PRIVATE KEY".
          # If key_files are provided and a key looks like a PEM formatted key
          # we try to convert it into a format supported by paramiko
          if (self.key_files and not isinstance(self.key_files, (list, tuple))
                  and os.path.isfile(self.key_files)):
              with open(self.key_files, 'r') as fp:
                  key_material = fp.read()
  
              try:
                  pkey = self._get_pkey_object(key=key_material,
                                               passpharse=self.password)
              except paramiko.ssh_exception.PasswordRequiredException as e:
                  raise e
              except Exception:
                  pass
              else:
                  # It appears key is valid, but it was passed in in an invalid
                  # format. Try to use the converted key directly
                  del conninfo['key_filename']
                  conninfo['pkey'] = pkey
  
          extra = {'_hostname': self.hostname, '_port': self.port,
                   '_username': self.username, '_timeout': self.timeout}
  
          if self.password:
              extra['_auth_method'] = 'password'
          else:
              extra['_auth_method'] = 'key_file'
  
              if self.key_files:
                  extra['_key_file'] = self.key_files
  
          self.logger.debug('Connecting to server', extra=extra)
  
          self.client.connect(**conninfo)
          return True
  
      def put(self, path, contents=None, chmod=None, mode='w'):
          extra = {'_path': path, '_mode': mode, '_chmod': chmod}
          self.logger.debug('Uploading file', extra=extra)
  
          sftp = self.client.open_sftp()
          # less than ideal, but we need to mkdir stuff otherwise file() fails
          head, tail = psplit(path)
  
          if path[0] == "/":
              sftp.chdir("/")
          else:
              # Relative path - start from a home directory (~)
              sftp.chdir('.')
  
          for part in head.split("/"):
              if part != "":
                  try:
                      sftp.mkdir(part)
                  except IOError:
                      # so, there doesn't seem to be a way to
                      # catch EEXIST consistently *sigh*
                      pass
                  sftp.chdir(part)
  
          cwd = sftp.getcwd()
  
          ak = sftp.file(tail, mode=mode)
          ak.write(contents)
          if chmod is not None:
              ak.chmod(chmod)
          ak.close()
          sftp.close()
  
          if path[0] == '/':
              file_path = path
          else:
              file_path = pjoin(cwd, path)
  
          return file_path
  
      def delete(self, path):
          extra = {'_path': path}
          self.logger.debug('Deleting file', extra=extra)
  
          sftp = self.client.open_sftp()
          sftp.unlink(path)
          sftp.close()
          return True
  
      def run(self, cmd, timeout=None):
          # type: (str, Optional[float]) -> Tuple[str, str, int]
          """
          Note: This function is based on paramiko's exec_command()
          method.
  
          :param timeout: How long to wait (in seconds) for the command to
                          finish (optional).
          :type timeout: ``float``
          """
          extra1 = {'_cmd': cmd}
          self.logger.debug('Executing command', extra=extra1)
  
          # Use the system default buffer size
          bufsize = -1
  
          transport = self.client.get_transport()
          chan = transport.open_session()
  
          start_time = time.time()
          chan.exec_command(cmd)
  
          stdout = StringIO()
          stderr = StringIO()
  
          # Create a stdin file and immediately close it to prevent any
          # interactive script from hanging the process.
          stdin = chan.makefile('wb', bufsize)
          stdin.close()
  
          # Receive all the output
          # Note #1: This is used instead of chan.makefile approach to prevent
          # buffering issues and hanging if the executed command produces a lot
          # of output.
          #
          # Note #2: If you are going to remove "ready" checks inside the loop
          # you are going to have a bad time. Trying to consume from a channel
          # which is not ready will block for indefinitely.
          exit_status_ready = chan.exit_status_ready()
  
          if exit_status_ready:
              # It's possible that some data is already available when exit
              # status is ready
              stdout.write(self._consume_stdout(chan).getvalue())
              stderr.write(self._consume_stderr(chan).getvalue())
  
          while not exit_status_ready:
              current_time = time.time()
              elapsed_time = (current_time - start_time)
  
              if timeout and (elapsed_time > timeout):
                  # TODO: Is this the right way to clean up?
                  chan.close()
  
                  raise SSHCommandTimeoutError(cmd=cmd, timeout=timeout)
  
              stdout.write(self._consume_stdout(chan).getvalue())
              stderr.write(self._consume_stderr(chan).getvalue())
  
              # We need to check the exist status here, because the command could
              # print some output and exit during this sleep below.
              exit_status_ready = chan.exit_status_ready()
  
              if exit_status_ready:
                  break
  
              # Short sleep to prevent busy waiting
              time.sleep(self.SLEEP_DELAY)
  
          # Receive the exit status code of the command we ran.
          status = chan.recv_exit_status()  # type: int
  
          stdout_str = stdout.getvalue()  # type: str
          stderr_str = stderr.getvalue()  # type: str
  
          extra2 = {'_status': status, '_stdout': stdout_str,
                    '_stderr': stderr_str}
          self.logger.debug('Command finished', extra=extra2)
  
          result = (stdout_str, stderr_str, status)  # type: Tuple[str, str, int]
          return result
  
      def close(self):
          self.logger.debug('Closing server connection')
  
          self.client.close()
          return True
  
      def _consume_stdout(self, chan):
          """
          Try to consume stdout data from chan if it's receive ready.
          """
          stdout = self._consume_data_from_channel(
              chan=chan,
              recv_method=chan.recv,
              recv_ready_method=chan.recv_ready)
          return stdout
  
      def _consume_stderr(self, chan):
          """
          Try to consume stderr data from chan if it's receive ready.
          """
          stderr = self._consume_data_from_channel(
              chan=chan,
              recv_method=chan.recv_stderr,
              recv_ready_method=chan.recv_stderr_ready)
          return stderr
  
      def _consume_data_from_channel(self, chan, recv_method, recv_ready_method):
          """
          Try to consume data from the provided channel.
  
          Keep in mind that data is only consumed if the channel is receive
          ready.
          """
          result = StringIO()
          result_bytes = bytearray()
  
          if recv_ready_method():
              data = recv_method(self.CHUNK_SIZE)
              result_bytes += b(data)
  
              while data:
                  ready = recv_ready_method()
  
                  if not ready:
                      break
  
                  data = recv_method(self.CHUNK_SIZE)
                  result_bytes += b(data)
  
          # We only decode data at the end because a single chunk could contain
          # a part of multi byte UTF-8 character (whole multi bytes character
          # could be split over two chunks)
          result.write(result_bytes.decode('utf-8'))
          return result
  
      def _get_pkey_object(self, key, passpharse=None):
          """
          Try to detect private key type and return paramiko.PKey object.
  
          # NOTE: Paramiko only supports key in PKCS#1 PEM format.
          """
          key_types = [
              (paramiko.RSAKey, 'RSA'),
              (paramiko.DSSKey, 'DSA'),
              (paramiko.ECDSAKey, 'EC')
          ]
  
          paramiko_version = getattr(paramiko, '__version__', '0.0.0')
          paramiko_version = tuple([int(c) for c in paramiko_version.split('.')])
  
          if paramiko_version >= (2, 2, 0):
              # Ed25519 is only supported in paramiko >= 2.2.0
              key_types.append((paramiko.ed25519key.Ed25519Key, 'Ed25519'))
  
          for cls, key_type in key_types:
              # Work around for paramiko not recognizing keys which start with
              # "----BEGIN PRIVATE KEY-----"
              # Since key is already in PEM format, we just try changing the
              # header and footer
              key_split = key.strip().splitlines()
              if (key_split[0] == '-----BEGIN PRIVATE KEY-----' and
                      key_split[-1] == '-----END PRIVATE KEY-----'):
                  key_split[0] = '-----BEGIN %s PRIVATE KEY-----' % (key_type)
                  key_split[-1] = '-----END %s PRIVATE KEY-----' % (key_type)
  
                  key_value = '\n'.join(key_split)
              else:
                  # Already a valid key, us it as is
                  key_value = key
  
              try:
                  key = cls.from_private_key(StringIO(key_value), passpharse)
              except paramiko.ssh_exception.PasswordRequiredException as e:
                  raise e
              except (paramiko.ssh_exception.SSHException, AssertionError) as e:
                  if 'private key file checkints do not match' in str(e).lower():
                      msg = ('Invalid passpharse provided for encrypted key. '
                             'Original error: %s' % (str(e)))
                      # Indicates invalid password for password protected keys
                      raise paramiko.ssh_exception.SSHException(msg)
  
                  # Invalid key, try other key type
                  pass
              else:
                  return key
  
          msg = ('Invalid or unsupported key type (only RSA, DSS, ECDSA and'
                 ' Ed25519 keys'
                 ' in PEM format are supported). For more information on '
                 ' supported key file types, see %s' % (SUPPORTED_KEY_TYPES_URL))
          raise paramiko.ssh_exception.SSHException(msg)
  
  
  class ShellOutSSHClient(BaseSSHClient):
      """
      This client shells out to "ssh" binary to run commands on the remote
      server.
  
      Note: This client should not be used in production.
      """
  
      def __init__(self,
                   hostname,  # type: str
                   port=22,  # type: int
                   username='root',  # type: str
                   password=None,  # type: Optional[str]
                   key=None,  # type: Optional[str]
                   key_files=None,  # type: Optional[str]
                   timeout=None  # type: Optional[float]
                   ):
          super(ShellOutSSHClient, self).__init__(hostname=hostname,
                                                  port=port, username=username,
                                                  password=password,
                                                  key=key,
                                                  key_files=key_files,
                                                  timeout=timeout)
          if self.password:
              raise ValueError('ShellOutSSHClient only supports key auth')
  
          child = subprocess.Popen(['ssh'], stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
          child.communicate()
  
          if child.returncode == 127:
              raise ValueError('ssh client is not available')
  
          self.logger = self._get_and_setup_logger()
  
      def connect(self):
          """
          This client doesn't support persistent connections establish a new
          connection every time "run" method is called.
          """
          return True
  
      def run(self, cmd, timeout=None):
          return self._run_remote_shell_command([cmd])
  
      def put(self, path, contents=None, chmod=None, mode='w'):
          if mode == 'w':
              redirect = '>'
          elif mode == 'a':
              redirect = '>>'
          else:
              raise ValueError('Invalid mode: ' + mode)
  
          cmd = ['echo "%s" %s %s' % (contents, redirect, path)]
          self._run_remote_shell_command(cmd)
          return path
  
      def delete(self, path):
          cmd = ['rm', '-rf', path]
          self._run_remote_shell_command(cmd)
          return True
  
      def close(self):
          return True
  
      def _get_base_ssh_command(self):
          # type: () -> List[str]
          cmd = ['ssh']
  
          if self.key_files:
              self.key_files = cast(str, self.key_files)
              cmd += ['-i', self.key_files]
  
          if self.timeout:
              cmd += ['-oConnectTimeout=%s' % (self.timeout)]
  
          cmd += ['%s@%s' % (self.username, self.hostname)]
  
          return cmd
  
      def _run_remote_shell_command(self, cmd):
          # type: (List[str]) -> Tuple[str, str, int]
          """
          Run a command on a remote server.
  
          :param      cmd: Command to run.
          :type       cmd: ``list`` of ``str``
  
          :return: Command stdout, stderr and status code.
          :rtype: ``tuple``
          """
          base_cmd = self._get_base_ssh_command()
          full_cmd = base_cmd + [' '.join(cmd)]
  
          self.logger.debug('Executing command: "%s"' % (' '.join(full_cmd)))
  
          child = subprocess.Popen(full_cmd, stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
          stdout, stderr = child.communicate()
  
          stdout_str = cast(str, stdout)
          stderr_str = cast(str, stdout)
  
          return (stdout_str, stderr_str, child.returncode)
  
  
  class MockSSHClient(BaseSSHClient):
      pass
  
  
  SSHClient = ParamikoSSHClient  # type: Type[BaseSSHClient]
  if not have_paramiko:
      SSHClient = MockSSHClient  # type: ignore
  
files: libcloud/compute/ssh.py
files: # Licensed to the Apache Software Foundation (ASF) under one or more
  # contributor license agreements.  See the NOTICE file distributed with
  # this work for additional information regarding copyright ownership.
  # The ASF licenses this file to You 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.
  
  """
  Wraps multiple ways to communicate over SSH.
  """
  
  from typing import Type
  from typing import Optional
  from typing import Tuple
  from typing import List
  from typing import Union
  from typing import cast
  
  have_paramiko = False
  
  try:
      import paramiko
      have_paramiko = True
  except ImportError:
      pass
  
  # Depending on your version of Paramiko, it may cause a deprecation
  # warning on Python 2.6.
  # Ref: https://bugs.launchpad.net/paramiko/+bug/392973
  
  import os
  import time
  import subprocess
  import logging
  import warnings
  
  from os.path import split as psplit
  from os.path import join as pjoin
  
  from libcloud.utils.logging import ExtraLogFormatter
  from libcloud.utils.py3 import StringIO
  from libcloud.utils.py3 import b
  
  __all__ = [
      'BaseSSHClient',
      'ParamikoSSHClient',
      'ShellOutSSHClient',
  
      'SSHCommandTimeoutError'
  ]
  
  SUPPORTED_KEY_TYPES_URL = 'https://libcloud.readthedocs.io/en/latest/compute/deployment.html#supported-private-ssh-key-types' # NOQA
  
  
  class SSHCommandTimeoutError(Exception):
      """
      Exception which is raised when an SSH command times out.
      """
      def __init__(self, cmd, timeout):
          # type: (str, float) -> None
          self.cmd = cmd
          self.timeout = timeout
          self.message = 'Command didn\'t finish in %s seconds' % (timeout)
          super(SSHCommandTimeoutError, self).__init__(self.message)
  
      def __repr__(self):
          return ('<SSHCommandTimeoutError: cmd="%s",timeout=%s)>' %
                  (self.cmd, self.timeout))
  
      def __str__(self):
          return self.__repr__()
  
  
  class BaseSSHClient(object):
      """
      Base class representing a connection over SSH/SCP to a remote node.
      """
  
      def __init__(self,
                   hostname,  # type: str
                   port=22,  # type: int
                   username='root',  # type: str
                   password=None,  # type: Optional[str]
                   key=None,  # type: Optional[str]
                   key_files=None,  # type: Optional[Union[str, List[str]]]
                   timeout=None  # type: Optional[float]
                   ):
          """
          :type hostname: ``str``
          :keyword hostname: Hostname or IP address to connect to.
  
          :type port: ``int``
          :keyword port: TCP port to communicate on, defaults to 22.
  
          :type username: ``str``
          :keyword username: Username to use, defaults to root.
  
          :type password: ``str``
          :keyword password: Password to authenticate with or a password used
                             to unlock a private key if a password protected key
                             is used.
  
          :param key: Deprecated in favor of ``key_files`` argument.
  
          :type key_files: ``str`` or ``list``
          :keyword key_files: A list of paths to the private key files to use.
          """
          if key is not None:
              message = ('You are using deprecated "key" argument which has '
                         'been replaced with "key_files" argument')
              warnings.warn(message, DeprecationWarning)
  
              # key_files has precedent
              key_files = key if not key_files else key_files
  
          self.hostname = hostname
          self.port = port
          self.username = username
          self.password = password
          self.key_files = key_files
          self.timeout = timeout
  
      def connect(self):
          # type: () -> bool
          """
          Connect to the remote node over SSH.
  
          :return: True if the connection has been successfully established,
                   False otherwise.
          :rtype: ``bool``
          """
          raise NotImplementedError(
              'connect not implemented for this ssh client')
  
      def put(self, path, contents=None, chmod=None, mode='w'):
          # type: (str, Optional[Union[str, bytes]], Optional[int], str) -> str
          """
          Upload a file to the remote node.
  
          :type path: ``str``
          :keyword path: File path on the remote node.
  
          :type contents: ``str``
          :keyword contents: File Contents.
  
          :type chmod: ``int``
          :keyword chmod: chmod file to this after creation.
  
          :type mode: ``str``
          :keyword mode: Mode in which the file is opened.
  
          :return: Full path to the location where a file has been saved.
          :rtype: ``str``
          """
          raise NotImplementedError(
              'put not implemented for this ssh client')
  
      def delete(self, path):
          # type: (str) -> bool
          """
          Delete/Unlink a file on the remote node.
  
          :type path: ``str``
          :keyword path: File path on the remote node.
  
          :return: True if the file has been successfully deleted, False
                   otherwise.
          :rtype: ``bool``
          """
          raise NotImplementedError(
              'delete not implemented for this ssh client')
  
      def run(self, cmd):
          # type: (str) -> Tuple[str, str, int]
          """
          Run a command on a remote node.
  
          :type cmd: ``str``
          :keyword cmd: Command to run.
  
          :return ``list`` of [stdout, stderr, exit_status]
          """
          raise NotImplementedError(
              'run not implemented for this ssh client')
  
      def close(self):
          # type: () -> bool
          """
          Shutdown connection to the remote node.
  
          :return: True if the connection has been successfully closed, False
                   otherwise.
          :rtype: ``bool``
          """
          raise NotImplementedError(
              'close not implemented for this ssh client')
  
      def _get_and_setup_logger(self):
          # type: () -> logging.Logger
          logger = logging.getLogger('libcloud.compute.ssh')
          path = os.getenv('LIBCLOUD_DEBUG')
  
          if path:
              handler = logging.FileHandler(path)
              handler.setFormatter(ExtraLogFormatter())
              logger.addHandler(handler)
              logger.setLevel(logging.DEBUG)
  
          return logger
  
  
  class ParamikoSSHClient(BaseSSHClient):
      """
      A SSH Client powered by Paramiko.
      """
  
      # Maximum number of bytes to read at once from a socket
      CHUNK_SIZE = 4096
  
      # How long to sleep while waiting for command to finish (to prevent busy
      # waiting)
      SLEEP_DELAY = 0.2
  
      def __init__(self,
                   hostname,  # type: str
                   port=22,  # type: int
                   username='root',  # type: str
                   password=None,  # type: Optional[str]
                   key=None,  # type: Optional[str]
                   key_files=None,  # type: Optional[Union[str, List[str]]]
                   key_material=None,  # type: Optional[str]
                   timeout=None  # type: Optional[float]
                   ):
          """
          Authentication is always attempted in the following order:
  
          - The key passed in (if key is provided)
          - Any key we can find through an SSH agent (only if no password and
            key is provided)
          - Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (only if no
            password and key is provided)
          - Plain username/password auth, if a password was given (if password is
            provided)
          """
          if key_files and key_material:
              raise ValueError(('key_files and key_material arguments are '
                                'mutually exclusive'))
  
          super(ParamikoSSHClient, self).__init__(hostname=hostname, port=port,
                                                  username=username,
                                                  password=password,
                                                  key=key,
                                                  key_files=key_files,
                                                  timeout=timeout)
  
          self.key_material = key_material
  
          self.client = paramiko.SSHClient()
          self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
          self.logger = self._get_and_setup_logger()
  
      def connect(self):
          conninfo = {'hostname': self.hostname,
                      'port': self.port,
                      'username': self.username,
                      'allow_agent': False,
                      'look_for_keys': False}
  
          if self.password:
              conninfo['password'] = self.password
  
          if self.key_files:
              conninfo['key_filename'] = self.key_files
  
          if self.key_material:
              conninfo['pkey'] = self._get_pkey_object(
                  key=self.key_material,
                  passpharse=self.password)
  
          if not self.password and not (self.key_files or self.key_material):
              conninfo['allow_agent'] = True
              conninfo['look_for_keys'] = True
  
          if self.timeout:
              conninfo['timeout'] = self.timeout
  
          # This is a workaround for paramiko only supporting key files in
          # format staring with "BEGIN RSA PRIVATE KEY".
          # If key_files are provided and a key looks like a PEM formatted key
          # we try to convert it into a format supported by paramiko
          if (self.key_files and not isinstance(self.key_files, (list, tuple))
                  and os.path.isfile(self.key_files)):
              with open(self.key_files, 'r') as fp:
                  key_material = fp.read()
  
              try:
                  pkey = self._get_pkey_object(key=key_material,
                                               passpharse=self.password)
              except paramiko.ssh_exception.PasswordRequiredException as e:
                  raise e
              except Exception:
                  pass
              else:
                  # It appears key is valid, but it was passed in in an invalid
                  # format. Try to use the converted key directly
                  del conninfo['key_filename']
                  conninfo['pkey'] = pkey
  
          extra = {'_hostname': self.hostname, '_port': self.port,
                   '_username': self.username, '_timeout': self.timeout}
  
          if self.password:
              extra['_auth_method'] = 'password'
          else:
              extra['_auth_method'] = 'key_file'
  
              if self.key_files:
                  extra['_key_file'] = self.key_files
  
          self.logger.debug('Connecting to server', extra=extra)
  
          self.client.connect(**conninfo)
          return True
  
      def put(self, path, contents=None, chmod=None, mode='w'):
          extra = {'_path': path, '_mode': mode, '_chmod': chmod}
          self.logger.debug('Uploading file', extra=extra)
  
          sftp = self.client.open_sftp()
          # less than ideal, but we need to mkdir stuff otherwise file() fails
          head, tail = psplit(path)
  
          if path[0] == "/":
              sftp.chdir("/")
          else:
              # Relative path - start from a home directory (~)
              sftp.chdir('.')
  
          for part in head.split("/"):
              if part != "":
                  try:
                      sftp.mkdir(part)
                  except IOError:
                      # so, there doesn't seem to be a way to
                      # catch EEXIST consistently *sigh*
                      pass
                  sftp.chdir(part)
  
          cwd = sftp.getcwd()
  
          ak = sftp.file(tail, mode=mode)
          ak.write(contents)
          if chmod is not None:
              ak.chmod(chmod)
          ak.close()
          sftp.close()
  
          if path[0] == '/':
              file_path = path
          else:
              file_path = pjoin(cwd, path)
  
          return file_path
  
      def delete(self, path):
          extra = {'_path': path}
          self.logger.debug('Deleting file', extra=extra)
  
          sftp = self.client.open_sftp()
          sftp.unlink(path)
          sftp.close()
          return True
  
      def run(self, cmd, timeout=None):
          # type: (str, Optional[float]) -> Tuple[str, str, int]
          """
          Note: This function is based on paramiko's exec_command()
          method.
  
          :param timeout: How long to wait (in seconds) for the command to
                          finish (optional).
          :type timeout: ``float``
          """
          extra1 = {'_cmd': cmd}
          self.logger.debug('Executing command', extra=extra1)
  
          # Use the system default buffer size
          bufsize = -1
  
          transport = self.client.get_transport()
          chan = transport.open_session()
  
          start_time = time.time()
          chan.exec_command(cmd)
  
          stdout = StringIO()
          stderr = StringIO()
  
          # Create a stdin file and immediately close it to prevent any
          # interactive script from hanging the process.
          stdin = chan.makefile('wb', bufsize)
          stdin.close()
  
          # Receive all the output
          # Note #1: This is used instead of chan.makefile approach to prevent
          # buffering issues and hanging if the executed command produces a lot
          # of output.
          #
          # Note #2: If you are going to remove "ready" checks inside the loop
          # you are going to have a bad time. Trying to consume from a channel
          # which is not ready will block for indefinitely.
          exit_status_ready = chan.exit_status_ready()
  
          if exit_status_ready:
              # It's possible that some data is already available when exit
              # status is ready
              stdout.write(self._consume_stdout(chan).getvalue())
              stderr.write(self._consume_stderr(chan).getvalue())
  
          while not exit_status_ready:
              current_time = time.time()
              elapsed_time = (current_time - start_time)
  
              if timeout and (elapsed_time > timeout):
                  # TODO: Is this the right way to clean up?
                  chan.close()
  
                  raise SSHCommandTimeoutError(cmd=cmd, timeout=timeout)
  
              stdout.write(self._consume_stdout(chan).getvalue())
              stderr.write(self._consume_stderr(chan).getvalue())
  
              # We need to check the exist status here, because the command could
              # print some output and exit during this sleep below.
              exit_status_ready = chan.exit_status_ready()
  
              if exit_status_ready:
                  break
  
              # Short sleep to prevent busy waiting
              time.sleep(self.SLEEP_DELAY)
  
          # Receive the exit status code of the command we ran.
          status = chan.recv_exit_status()  # type: int
  
          stdout_str = stdout.getvalue()  # type: str
          stderr_str = stderr.getvalue()  # type: str
  
          extra2 = {'_status': status, '_stdout': stdout_str,
                    '_stderr': stderr_str}
          self.logger.debug('Command finished', extra=extra2)
  
          result = (stdout_str, stderr_str, status)  # type: Tuple[str, str, int]
          return result
  
      def close(self):
          self.logger.debug('Closing server connection')
  
          self.client.close()
          return True
  
      def _consume_stdout(self, chan):
          """
          Try to consume stdout data from chan if it's receive ready.
          """
          stdout = self._consume_data_from_channel(
              chan=chan,
              recv_method=chan.recv,
              recv_ready_method=chan.recv_ready)
          return stdout
  
      def _consume_stderr(self, chan):
          """
          Try to consume stderr data from chan if it's receive ready.
          """
          stderr = self._consume_data_from_channel(
              chan=chan,
              recv_method=chan.recv_stderr,
              recv_ready_method=chan.recv_stderr_ready)
          return stderr
  
      def _consume_data_from_channel(self, chan, recv_method, recv_ready_method):
          """
          Try to consume data from the provided channel.
  
          Keep in mind that data is only consumed if the channel is receive
          ready.
          """
          result = StringIO()
          result_bytes = bytearray()
  
          if recv_ready_method():
              data = recv_method(self.CHUNK_SIZE)
              result_bytes += b(data)
  
              while data:
                  ready = recv_ready_method()
  
                  if not ready:
                      break
  
                  data = recv_method(self.CHUNK_SIZE)
                  result_bytes += b(data)
  
          # We only decode data at the end because a single chunk could contain
          # a part of multi byte UTF-8 character (whole multi bytes character
          # could be split over two chunks)
          result.write(result_bytes.decode('utf-8'))
          return result
  
      def _get_pkey_object(self, key, passpharse=None):
          """
          Try to detect private key type and return paramiko.PKey object.
  
          # NOTE: Paramiko only supports key in PKCS#1 PEM format.
          """
          key_types = [
              (paramiko.RSAKey, 'RSA'),
              (paramiko.DSSKey, 'DSA'),
              (paramiko.ECDSAKey, 'EC')
          ]
  
          paramiko_version = getattr(paramiko, '__version__', '0.0.0')
          paramiko_version = tuple([int(c) for c in paramiko_version.split('.')])
  
          if paramiko_version >= (2, 2, 0):
              # Ed25519 is only supported in paramiko >= 2.2.0
              key_types.append((paramiko.ed25519key.Ed25519Key, 'Ed25519'))
  
          for cls, key_type in key_types:
              # Work around for paramiko not recognizing keys which start with
              # "----BEGIN PRIVATE KEY-----"
              # Since key is already in PEM format, we just try changing the
              # header and footer
              key_split = key.strip().splitlines()
              if (key_split[0] == '-----BEGIN PRIVATE KEY-----' and
                      key_split[-1] == '-----END PRIVATE KEY-----'):
                  key_split[0] = '-----BEGIN %s PRIVATE KEY-----' % (key_type)
                  key_split[-1] = '-----END %s PRIVATE KEY-----' % (key_type)
  
                  key_value = '\n'.join(key_split)
              else:
                  # Already a valid key, us it as is
                  key_value = key
  
              try:
                  key = cls.from_private_key(StringIO(key_value), passpharse)
              except paramiko.ssh_exception.PasswordRequiredException as e:
                  raise e
              except (paramiko.ssh_exception.SSHException, AssertionError) as e:
                  if 'private key file checkints do not match' in str(e).lower():
                      msg = ('Invalid passpharse provided for encrypted key. '
                             'Original error: %s' % (str(e)))
                      # Indicates invalid password for password protected keys
                      raise paramiko.ssh_exception.SSHException(msg)
  
                  # Invalid key, try other key type
                  pass
              else:
                  return key
  
          msg = ('Invalid or unsupported key type (only RSA, DSS, ECDSA and'
                 ' Ed25519 keys'
                 ' in PEM format are supported). For more information on '
                 ' supported key file types, see %s' % (SUPPORTED_KEY_TYPES_URL))
          raise paramiko.ssh_exception.SSHException(msg)
  
  
  class ShellOutSSHClient(BaseSSHClient):
      """
      This client shells out to "ssh" binary to run commands on the remote
      server.
  
      Note: This client should not be used in production.
      """
  
      def __init__(self,
                   hostname,  # type: str
                   port=22,  # type: int
                   username='root',  # type: str
                   password=None,  # type: Optional[str]
                   key=None,  # type: Optional[str]
                   key_files=None,  # type: Optional[str]
                   timeout=None  # type: Optional[float]
                   ):
          super(ShellOutSSHClient, self).__init__(hostname=hostname,
                                                  port=port, username=username,
                                                  password=password,
                                                  key=key,
                                                  key_files=key_files,
                                                  timeout=timeout)
          if self.password:
              raise ValueError('ShellOutSSHClient only supports key auth')
  
          child = subprocess.Popen(['ssh'], stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
          child.communicate()
  
          if child.returncode == 127:
              raise ValueError('ssh client is not available')
  
          self.logger = self._get_and_setup_logger()
  
      def connect(self):
          """
          This client doesn't support persistent connections establish a new
          connection every time "run" method is called.
          """
          return True
  
      def run(self, cmd):
          return self._run_remote_shell_command([cmd])
  
      def put(self, path, contents=None, chmod=None, mode='w'):
          if mode == 'w':
              redirect = '>'
          elif mode == 'a':
              redirect = '>>'
          else:
              raise ValueError('Invalid mode: ' + mode)
  
          cmd = ['echo "%s" %s %s' % (contents, redirect, path)]
          self._run_remote_shell_command(cmd)
          return path
  
      def delete(self, path):
          cmd = ['rm', '-rf', path]
          self._run_remote_shell_command(cmd)
          return True
  
      def close(self):
          return True
  
      def _get_base_ssh_command(self):
          # type: () -> List[str]
          cmd = ['ssh']
  
          if self.key_files:
              self.key_files = cast(str, self.key_files)
              cmd += ['-i', self.key_files]
  
          if self.timeout:
              cmd += ['-oConnectTimeout=%s' % (self.timeout)]
  
          cmd += ['%s@%s' % (self.username, self.hostname)]
  
          return cmd
  
      def _run_remote_shell_command(self, cmd):
          # type: (List[str]) -> Tuple[str, str, int]
          """
          Run a command on a remote server.
  
          :param      cmd: Command to run.
          :type       cmd: ``list`` of ``str``
  
          :return: Command stdout, stderr and status code.
          :rtype: ``tuple``
          """
          base_cmd = self._get_base_ssh_command()
          full_cmd = base_cmd + [' '.join(cmd)]
  
          self.logger.debug('Executing command: "%s"' % (' '.join(full_cmd)))
  
          child = subprocess.Popen(full_cmd, stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
          stdout, stderr = child.communicate()
  
          stdout_str = cast(str, stdout)
          stderr_str = cast(str, stdout)
  
          return (stdout_str, stderr_str, child.returncode)
  
  
  class MockSSHClient(BaseSSHClient):
      pass
  
  
  SSHClient = ParamikoSSHClient  # type: Type[BaseSSHClient]
  if not have_paramiko:
      SSHClient = MockSSHClient  # type: ignore
  
files: libcloud/compute/ssh.py
files: # -*- coding: utf-8 -*-
  # Licensed to the Apache Software Foundation (ASF) under one or moreÂ§
  # contributor license agreements.  See the NOTICE file distributed with
  # this work for additional information regarding copyright ownership.
  # The ASF licenses this file to You 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.
  
  from __future__ import with_statement
  
  import os
  import sys
  import time
  import unittest
  
  from libcloud.utils.py3 import httplib
  from libcloud.utils.py3 import u
  from libcloud.utils.py3 import PY3
  from libcloud.utils.py3 import assertRaisesRegex
  
  from libcloud.compute.deployment import MultiStepDeployment, Deployment
  from libcloud.compute.deployment import SSHKeyDeployment, ScriptDeployment
  from libcloud.compute.deployment import ScriptFileDeployment, FileDeployment
  from libcloud.compute.base import Node
  from libcloud.compute.base import NodeAuthPassword
  from libcloud.compute.types import NodeState, DeploymentError, LibcloudError
  from libcloud.compute.ssh import BaseSSHClient
  from libcloud.compute.ssh import have_paramiko
  from libcloud.compute.ssh import SSHCommandTimeoutError
  from libcloud.compute.drivers.rackspace import RackspaceFirstGenNodeDriver as Rackspace
  
  from libcloud.test import MockHttp, XML_HEADERS
  from libcloud.test.file_fixtures import ComputeFileFixtures
  from mock import Mock, patch
  
  from libcloud.test.secrets import RACKSPACE_PARAMS
  
  # Keyword arguments which are specific to deploy_node() method, but not
  # create_node()
  DEPLOY_NODE_KWARGS = ['deploy', 'ssh_username', 'ssh_alternate_usernames',
                        'ssh_port', 'ssh_timeout', 'ssh_key', 'timeout',
                        'max_tries', 'ssh_interface']
  
  FILE_PATH = '{0}home{0}ubuntu{0}relative.sh'.format(os.path.sep)
  
  
  class MockDeployment(Deployment):
  
      def run(self, node, client):
          return node
  
  
  class MockClient(BaseSSHClient):
  
      def __init__(self, throw_on_timeout=False, *args, **kwargs):
          self.stdout = ''
          self.stderr = ''
          self.exit_status = 0
          self.throw_on_timeout = throw_on_timeout
  
      def put(self, path, contents, chmod=755, mode='w'):
          return contents
  
      def run(self, cmd, timeout=None):
          if self.throw_on_timeout and timeout is not None:
              raise ValueError("timeout")
  
          return self.stdout, self.stderr, self.exit_status
  
      def delete(self, name):
          return True
  
  
  class DeploymentTests(unittest.TestCase):
  
      def setUp(self):
          Rackspace.connectionCls.conn_class = RackspaceMockHttp
          RackspaceMockHttp.type = None
          self.driver = Rackspace(*RACKSPACE_PARAMS)
          # normally authentication happens lazily, but we force it here
          self.driver.connection._populate_hosts_and_request_paths()
          self.driver.features = {'create_node': ['generates_password']}
          self.node = Node(id=12345, name='test', state=NodeState.RUNNING,
                           public_ips=['1.2.3.4'], private_ips=['1.2.3.5'],
                           driver=Rackspace)
          self.node2 = Node(id=123456, name='test', state=NodeState.RUNNING,
                            public_ips=['1.2.3.4'], private_ips=['1.2.3.5'],
                            driver=Rackspace)
  
      def test_multi_step_deployment(self):
          msd = MultiStepDeployment()
          self.assertEqual(len(msd.steps), 0)
  
          msd.add(MockDeployment())
          self.assertEqual(len(msd.steps), 1)
  
          self.assertEqual(self.node, msd.run(node=self.node, client=None))
  
      def test_ssh_key_deployment(self):
          sshd = SSHKeyDeployment(key='1234')
  
          self.assertEqual(self.node, sshd.run(node=self.node,
                                               client=MockClient(hostname='localhost')))
  
      def test_file_deployment(self):
          # use this file (__file__) for obtaining permissions
          target = os.path.join('/tmp', os.path.basename(__file__))
          fd = FileDeployment(__file__, target)
          self.assertEqual(target, fd.target)
          self.assertEqual(__file__, fd.source)
          self.assertEqual(self.node, fd.run(
              node=self.node, client=MockClient(hostname='localhost')))
  
      def test_script_deployment(self):
          sd1 = ScriptDeployment(script='foobar', delete=True)
          sd2 = ScriptDeployment(script='foobar', delete=False)
          sd3 = ScriptDeployment(
              script='foobar', delete=False, name='foobarname')
          sd4 = ScriptDeployment(
              script='foobar', delete=False, name='foobarname', timeout=10)
  
          self.assertTrue(sd1.name.find('deployment') != '1')
          self.assertEqual(sd3.name, 'foobarname')
          self.assertEqual(sd3.timeout, None)
          self.assertEqual(sd4.timeout, 10)
  
          self.assertEqual(self.node, sd1.run(node=self.node,
                                              client=MockClient(hostname='localhost')))
          self.assertEqual(self.node, sd2.run(node=self.node,
                                              client=MockClient(hostname='localhost')))
          self.assertEqual(self.node, sd3.run(node=self.node,
                                              client=MockClient(hostname='localhost')))
  
          assertRaisesRegex(self, ValueError, 'timeout', sd4.run,
                            node=self.node,
                            client=MockClient(hostname='localhost',
                                              throw_on_timeout=True))
  
      def test_script_file_deployment(self):
          file_path = os.path.abspath(__file__)
          with open(file_path, 'rb') as fp:
              content = fp.read()
  
          if PY3:
              content = content.decode('utf-8')
  
          sfd1 = ScriptFileDeployment(script_file=file_path)
          self.assertEqual(sfd1.script, content)
          self.assertEqual(sfd1.timeout, None)
  
          sfd2 = ScriptFileDeployment(script_file=file_path, timeout=20)
          self.assertEqual(sfd2.timeout, 20)
  
      def test_script_deployment_relative_path(self):
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          sd = ScriptDeployment(script='echo "foo"', name='relative.sh')
          sd.run(self.node, client)
  
          client.run.assert_called_once_with(FILE_PATH, timeout=None)
  
      def test_script_deployment_absolute_path(self):
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          file_path = '{0}root{0}relative.sh'.format(os.path.sep)
  
          sd = ScriptDeployment(script='echo "foo"', name=file_path)
          sd.run(self.node, client)
  
          client.run.assert_called_once_with(file_path, timeout=None)
  
      def test_script_deployment_with_arguments(self):
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          file_path = '{0}root{0}relative.sh'.format(os.path.sep)
  
          args = ['arg1', 'arg2', '--option1=test']
          sd = ScriptDeployment(script='echo "foo"', args=args,
                                name=file_path)
          sd.run(self.node, client)
  
          expected = '%s arg1 arg2 --option1=test' % (file_path)
          client.run.assert_called_once_with(expected, timeout=None)
  
          client.reset_mock()
  
          args = []
          sd = ScriptDeployment(script='echo "foo"', args=args,
                                name=file_path)
          sd.run(self.node, client)
  
          expected = file_path
          client.run.assert_called_once_with(expected, timeout=None)
  
      def test_script_file_deployment_with_arguments(self):
          file_path = os.path.abspath(__file__)
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          args = ['arg1', 'arg2', '--option1=test', 'option2']
          sfd = ScriptFileDeployment(script_file=file_path, args=args,
                                     name=file_path)
  
          sfd.run(self.node, client)
  
          expected = '%s arg1 arg2 --option1=test option2' % (file_path)
          client.run.assert_called_once_with(expected, timeout=None)
  
      def test_script_deployment_and_sshkey_deployment_argument_types(self):
          class FileObject(object):
  
              def __init__(self, name):
                  self.name = name
  
              def read(self):
                  return 'bar'
  
          ScriptDeployment(script='foobar')
          ScriptDeployment(script=u('foobar'))
          ScriptDeployment(script=FileObject('test'))
  
          SSHKeyDeployment(key='foobar')
          SSHKeyDeployment(key=u('foobar'))
          SSHKeyDeployment(key=FileObject('test'))
  
          try:
              ScriptDeployment(script=[])
          except TypeError:
              pass
          else:
              self.fail('TypeError was not thrown')
  
          try:
              SSHKeyDeployment(key={})
          except TypeError:
              pass
          else:
              self.fail('TypeError was not thrown')
  
      def test_wait_until_running_running_instantly(self):
          node2, ips = self.driver.wait_until_running(
              nodes=[self.node], wait_period=0.1,
              timeout=0.5)[0]
          self.assertEqual(self.node.uuid, node2.uuid)
          self.assertEqual(['67.23.21.33'], ips)
  
      def test_wait_until_running_without_ip(self):
          RackspaceMockHttp.type = 'NO_IP'
  
          try:
              node2, ips = self.driver.wait_until_running(
                  nodes=[self.node], wait_period=0.1,
                  timeout=0.2)[0]
          except LibcloudError as e:
              self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_with_only_ipv6(self):
          RackspaceMockHttp.type = 'IPV6'
  
          try:
              node2, ips = self.driver.wait_until_running(
                  nodes=[self.node], wait_period=0.1,
                  timeout=0.2)[0]
          except LibcloudError as e:
              self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_with_ipv6_ok(self):
          RackspaceMockHttp.type = 'IPV6'
          node2, ips = self.driver.wait_until_running(
              nodes=[self.node], wait_period=1, force_ipv4=False,
              timeout=0.5)[0]
          self.assertEqual(self.node.uuid, node2.uuid)
          self.assertEqual(['2001:DB8::1'], ips)
  
      def test_wait_until_running_running_after_0_2_second(self):
          RackspaceMockHttp.type = '05_SECOND_DELAY'
          node2, ips = self.driver.wait_until_running(
              nodes=[self.node], wait_period=0.2,
              timeout=0.1)[0]
          self.assertEqual(self.node.uuid, node2.uuid)
          self.assertEqual(['67.23.21.33'], ips)
  
      def test_wait_until_running_running_after_0_2_second_private_ips(self):
          RackspaceMockHttp.type = '05_SECOND_DELAY'
          node2, ips = self.driver.wait_until_running(
              nodes=[self.node], wait_period=0.2,
              timeout=0.1, ssh_interface='private_ips')[0]
          self.assertEqual(self.node.uuid, node2.uuid)
          self.assertEqual(['10.176.168.218'], ips)
  
      def test_wait_until_running_invalid_ssh_interface_argument(self):
          try:
              self.driver.wait_until_running(nodes=[self.node], wait_period=1,
                                             ssh_interface='invalid')
          except ValueError:
              pass
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_timeout(self):
          RackspaceMockHttp.type = 'TIMEOUT'
  
          try:
              self.driver.wait_until_running(nodes=[self.node], wait_period=0.1,
                                             timeout=0.2)
          except LibcloudError as e:
              self.assertTrue(e.value.find('Timed out') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_running_node_missing_from_list_nodes(self):
          RackspaceMockHttp.type = 'MISSING'
  
          try:
              self.driver.wait_until_running(nodes=[self.node], wait_period=0.1,
                                             timeout=0.2)
          except LibcloudError as e:
              self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_running_multiple_nodes_have_same_uuid(self):
          RackspaceMockHttp.type = 'SAME_UUID'
  
          try:
              self.driver.wait_until_running(nodes=[self.node], wait_period=0.1,
                                             timeout=0.2)
          except LibcloudError as e:
              self.assertTrue(
                  e.value.find('Unable to match specified uuids') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_running_wait_for_multiple_nodes(self):
          RackspaceMockHttp.type = 'MULTIPLE_NODES'
  
          nodes = self.driver.wait_until_running(
              nodes=[self.node, self.node2], wait_period=0.1,
              timeout=0.5)
          self.assertEqual(self.node.uuid, nodes[0][0].uuid)
          self.assertEqual(self.node2.uuid, nodes[1][0].uuid)
          self.assertEqual(['67.23.21.33'], nodes[0][1])
          self.assertEqual(['67.23.21.34'], nodes[1][1])
  
      def test_ssh_client_connect_success(self):
          mock_ssh_client = Mock()
          mock_ssh_client.return_value = None
  
          ssh_client = self.driver._ssh_client_connect(
              ssh_client=mock_ssh_client,
              timeout=0.5)
          self.assertEqual(mock_ssh_client, ssh_client)
  
      def test_ssh_client_connect_timeout(self):
          mock_ssh_client = Mock()
          mock_ssh_client.connect = Mock()
          mock_ssh_client.connect.side_effect = IOError('bam')
  
          try:
              self.driver._ssh_client_connect(ssh_client=mock_ssh_client,
                                              wait_period=0.1,
                                              timeout=0.2)
          except LibcloudError as e:
              self.assertTrue(e.value.find('Giving up') != -1)
          else:
              self.fail('Exception was not thrown')
  
      @unittest.skipIf(not have_paramiko, 'Skipping because paramiko is not available')
      def test_ssh_client_connect_immediately_throws_on_fatal_execption(self):
          # Verify that fatal exceptions are immediately propagated and ensure
          # we don't try to retry on them
          from paramiko.ssh_exception import SSHException
          from paramiko.ssh_exception import PasswordRequiredException
  
          mock_ssh_client = Mock()
          mock_ssh_client.connect = Mock()
          mock_ssh_client.connect.side_effect = IOError('bam')
  
          mock_exceptions = [
              SSHException('Invalid or unsupported key type'),
              PasswordRequiredException('private key file is encrypted'),
              SSHException('OpenSSH private key file checkints do not match')
          ]
  
          for mock_exception in mock_exceptions:
              mock_ssh_client.connect = Mock(side_effect=mock_exception)
              assertRaisesRegex(self, mock_exception.__class__, str(mock_exception),
                                self.driver._ssh_client_connect,
                                ssh_client=mock_ssh_client,
                                wait_period=0.1,
                                timeout=0.2)
  
      def test_run_deployment_script_success(self):
          task = Mock()
          ssh_client = Mock()
  
          ssh_client2 = self.driver._run_deployment_script(task=task,
                                                           node=self.node,
                                                           ssh_client=ssh_client,
                                                           max_tries=2)
          self.assertTrue(isinstance(ssh_client2, Mock))
  
      def test_run_deployment_script_exception(self):
          task = Mock()
          task.run = Mock()
          task.run.side_effect = Exception('bar')
          ssh_client = Mock()
  
          try:
              self.driver._run_deployment_script(task=task,
                                                 node=self.node,
                                                 ssh_client=ssh_client,
                                                 max_tries=2)
          except LibcloudError as e:
              self.assertTrue(e.value.find('Failed after 2 tries') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_run_deployment_script_ssh_command_timeout_fatal_exception(self):
          # We shouldn't retry on SSHCommandTimeoutError error since it's fatal
          task = Mock()
          task.run = Mock()
          task.run.side_effect = SSHCommandTimeoutError('ls -la', 10)
          ssh_client = Mock()
  
          try:
              self.driver._run_deployment_script(task=task,
                                                 node=self.node,
                                                 ssh_client=ssh_client,
                                                 max_tries=5)
          except SSHCommandTimeoutError as e:
              self.assertTrue(e.message.find('Command didn\'t finish') != -1)
          else:
              self.fail('Exception was not thrown')
  
      @patch('libcloud.compute.base.SSHClient')
      @patch('libcloud.compute.ssh')
      def test_deploy_node_success(self, mock_ssh_module, _):
          self.driver.create_node = Mock()
          self.driver.create_node.return_value = self.node
          mock_ssh_module.have_paramiko = True
  
          deploy = Mock()
  
          node = self.driver.deploy_node(deploy=deploy)
          self.assertEqual(self.node.id, node.id)
  
      @patch('libcloud.compute.base.SSHClient')
      @patch('libcloud.compute.ssh')
      def test_deploy_node_deploy_node_kwargs_except_auth_are_not_propagated_on(self, mock_ssh_module, _):
          # Verify that keyword arguments which are specific to deploy_node()
          # are not propagated to create_node()
          mock_ssh_module.have_paramiko = True
          self.driver.create_node = Mock()
          self.driver.create_node.return_value = self.node
          self.driver._connect_and_run_deployment_script = Mock()
          self.driver._wait_until_running = Mock()
  
          kwargs = {}
          for key in DEPLOY_NODE_KWARGS:
              kwargs[key] = key
  
          kwargs['ssh_interface'] = 'public_ips'
          kwargs['ssh_alternate_usernames'] = ['foo', 'bar']
          kwargs['timeout'] = 10
  
          auth = NodeAuthPassword('P@$$w0rd')
  
          node = self.driver.deploy_node(name='name', image='image', size='size',
                                         auth=auth, ex_foo='ex_foo', **kwargs)
          self.assertEqual(self.node.id, node.id)
          self.assertEqual(self.driver.create_node.call_count, 1)
  
          call_kwargs = self.driver.create_node.call_args_list[0][1]
          expected_call_kwargs = {
              'name': 'name',
              'image': 'image',
              'size': 'size',
              'auth': auth,
              'ex_foo': 'ex_foo'
          }
          self.assertEqual(expected_call_kwargs, call_kwargs)
  
          # If driver throws an exception it should fall back to passing in all
          # the arguments
          global call_count
          call_count = 0
  
          def create_node(name, image, size, ex_custom_arg_1, ex_custom_arg_2,
                          ex_foo=None, auth=None, **kwargs):
              global call_count
  
              call_count += 1
              if call_count == 1:
                  msg = 'create_node() takes at least 5 arguments (7 given)'
                  raise TypeError(msg)
              return self.node
  
          self.driver.create_node = create_node
  
          node = self.driver.deploy_node(name='name', image='image', size='size',
                                         auth=auth, ex_foo='ex_foo', ex_custom_arg_1='a',
                                         ex_custom_arg_2='b', **kwargs)
          self.assertEqual(self.node.id, node.id)
          self.assertEqual(call_count, 2)
  
          call_count = 0
  
          def create_node(name, image, size, ex_custom_arg_1, ex_custom_arg_2,
                          ex_foo=None, auth=None, **kwargs):
              global call_count
  
              call_count += 1
              if call_count == 1:
                  msg = 'create_node() missing 3 required positional arguments'
                  raise TypeError(msg)
              return self.node
  
          self.driver.create_node = create_node
  
          node = self.driver.deploy_node(name='name', image='image', size='size',
                                         auth=auth, ex_foo='ex_foo', ex_custom_arg_1='a',
                                         ex_custom_arg_2='b', **kwargs)
          self.assertEqual(self.node.id, node.id)
          self.assertEqual(call_count, 2)
  
      @patch('libcloud.compute.base.SSHClient')
      @patch('libcloud.compute.ssh')
      def test_deploy_node_exception_run_deployment_script(self, mock_ssh_module,
                                                           _):
          self.driver.create_node = Mock()
          self.driver.create_node.return_value = self.node
          mock_ssh_module.have_paramiko = True
  
          deploy = Mock()
          deploy.run = Mock()
          deploy.run.side_effect = Exception('foo')
  
          try:
              self.driver.deploy_node(deploy=deploy)
          except DeploymentError as e:
              self.assertTrue(e.node.id, self.node.id)
          else:
              self.fail('Exception was not thrown')
  
      @patch('libcloud.compute.base.SSHClient')
      @patch('libcloud.compute.ssh')
      def test_deploy_node_exception_ssh_client_connect(self, mock_ssh_module,
                                                        ssh_client):
          self.driver.create_node = Mock()
          self.driver.create_node.return_value = self.node
  
          mock_ssh_module.have_paramiko = True
  
          deploy = Mock()
          ssh_client.side_effect = IOError('bar')
  
          try:
              self.driver.deploy_node(deploy=deploy)
          except DeploymentError as e:
              self.assertTrue(e.node.id, self.node.id)
          else:
              self.fail('Exception was not thrown')
  
      @patch('libcloud.compute.ssh')
      def test_deploy_node_depoy_node_not_implemented(self, mock_ssh_module):
          self.driver.features = {'create_node': []}
          mock_ssh_module.have_paramiko = True
  
          try:
              self.driver.deploy_node(deploy=Mock())
          except NotImplementedError:
              pass
          else:
              self.fail('Exception was not thrown')
  
          self.driver.features = {}
  
          try:
              self.driver.deploy_node(deploy=Mock())
          except NotImplementedError:
              pass
          else:
              self.fail('Exception was not thrown')
  
      @patch('libcloud.compute.base.SSHClient')
      @patch('libcloud.compute.ssh')
      def test_deploy_node_password_auth(self, mock_ssh_module, _):
          self.driver.features = {'create_node': ['password']}
          mock_ssh_module.have_paramiko = True
  
          self.driver.create_node = Mock()
          self.driver.create_node.return_value = self.node
  
          node = self.driver.deploy_node(deploy=Mock())
          self.assertEqual(self.node.id, node.id)
  
      @patch('libcloud.compute.base.SSHClient')
      @patch('libcloud.compute.ssh')
      def test_exception_is_thrown_is_paramiko_is_not_available(self,
                                                                mock_ssh_module,
                                                                _):
          self.driver.features = {'create_node': ['password']}
          self.driver.create_node = Mock()
          self.driver.create_node.return_value = self.node
  
          mock_ssh_module.have_paramiko = False
  
          try:
              self.driver.deploy_node(deploy=Mock())
          except RuntimeError as e:
              self.assertTrue(str(e).find('paramiko is not installed') != -1)
          else:
              self.fail('Exception was not thrown')
  
          mock_ssh_module.have_paramiko = True
          node = self.driver.deploy_node(deploy=Mock())
          self.assertEqual(self.node.id, node.id)
  
  
  class RackspaceMockHttp(MockHttp):
      fixtures = ComputeFileFixtures('openstack')
  
      def _v2_0_tokens(self, method, url, body, headers):
          body = self.fixtures.load('_v2_0__auth_deployment.json')
          headers = {
              'content-type': 'application/json'
          }
          return (httplib.OK, body, headers,
                  httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail(self, method, url, body, headers):
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_success.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail_05_SECOND_DELAY(self, method, url, body, headers):
          time.sleep(0.5)
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_success.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail_TIMEOUT(self, method, url, body, headers):
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_pending.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail_MISSING(self, method, url, body, headers):
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_missing.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail_SAME_UUID(self, method, url, body, headers):
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_same_uuid.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail_MULTIPLE_NODES(self, method, url, body, headers):
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_multiple_nodes.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail_IPV6(self, method, url, body, headers):
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_ipv6.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail_NO_IP(self, method, url, body, headers):
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_no_ip.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
  
  if __name__ == '__main__':
      sys.exit(unittest.main())
  
files: libcloud/test/compute/test_deployment.py
files: # -*- coding: utf-8 -*-
  # Licensed to the Apache Software Foundation (ASF) under one or moreÂ§
  # contributor license agreements.  See the NOTICE file distributed with
  # this work for additional information regarding copyright ownership.
  # The ASF licenses this file to You 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.
  
  from __future__ import with_statement
  
  import os
  import sys
  import time
  import unittest
  
  from libcloud.utils.py3 import httplib
  from libcloud.utils.py3 import u
  from libcloud.utils.py3 import PY3
  from libcloud.utils.py3 import assertRaisesRegex
  
  from libcloud.compute.deployment import MultiStepDeployment, Deployment
  from libcloud.compute.deployment import SSHKeyDeployment, ScriptDeployment
  from libcloud.compute.deployment import ScriptFileDeployment, FileDeployment
  from libcloud.compute.base import Node
  from libcloud.compute.base import NodeAuthPassword
  from libcloud.compute.types import NodeState, DeploymentError, LibcloudError
  from libcloud.compute.ssh import BaseSSHClient
  from libcloud.compute.ssh import have_paramiko
  from libcloud.compute.ssh import SSHCommandTimeoutError
  from libcloud.compute.drivers.rackspace import RackspaceFirstGenNodeDriver as Rackspace
  
  from libcloud.test import MockHttp, XML_HEADERS
  from libcloud.test.file_fixtures import ComputeFileFixtures
  from mock import Mock, patch
  
  from libcloud.test.secrets import RACKSPACE_PARAMS
  
  # Keyword arguments which are specific to deploy_node() method, but not
  # create_node()
  DEPLOY_NODE_KWARGS = ['deploy', 'ssh_username', 'ssh_alternate_usernames',
                        'ssh_port', 'ssh_timeout', 'ssh_key', 'timeout',
                        'max_tries', 'ssh_interface']
  
  FILE_PATH = '{0}home{0}ubuntu{0}relative.sh'.format(os.path.sep)
  
  
  class MockDeployment(Deployment):
  
      def run(self, node, client):
          return node
  
  
  class MockClient(BaseSSHClient):
  
      def __init__(self, *args, **kwargs):
          self.stdout = ''
          self.stderr = ''
          self.exit_status = 0
  
      def put(self, path, contents, chmod=755, mode='w'):
          return contents
  
      def run(self, name):
          return self.stdout, self.stderr, self.exit_status
  
      def delete(self, name):
          return True
  
  
  class DeploymentTests(unittest.TestCase):
  
      def setUp(self):
          Rackspace.connectionCls.conn_class = RackspaceMockHttp
          RackspaceMockHttp.type = None
          self.driver = Rackspace(*RACKSPACE_PARAMS)
          # normally authentication happens lazily, but we force it here
          self.driver.connection._populate_hosts_and_request_paths()
          self.driver.features = {'create_node': ['generates_password']}
          self.node = Node(id=12345, name='test', state=NodeState.RUNNING,
                           public_ips=['1.2.3.4'], private_ips=['1.2.3.5'],
                           driver=Rackspace)
          self.node2 = Node(id=123456, name='test', state=NodeState.RUNNING,
                            public_ips=['1.2.3.4'], private_ips=['1.2.3.5'],
                            driver=Rackspace)
  
      def test_multi_step_deployment(self):
          msd = MultiStepDeployment()
          self.assertEqual(len(msd.steps), 0)
  
          msd.add(MockDeployment())
          self.assertEqual(len(msd.steps), 1)
  
          self.assertEqual(self.node, msd.run(node=self.node, client=None))
  
      def test_ssh_key_deployment(self):
          sshd = SSHKeyDeployment(key='1234')
  
          self.assertEqual(self.node, sshd.run(node=self.node,
                                               client=MockClient(hostname='localhost')))
  
      def test_file_deployment(self):
          # use this file (__file__) for obtaining permissions
          target = os.path.join('/tmp', os.path.basename(__file__))
          fd = FileDeployment(__file__, target)
          self.assertEqual(target, fd.target)
          self.assertEqual(__file__, fd.source)
          self.assertEqual(self.node, fd.run(
              node=self.node, client=MockClient(hostname='localhost')))
  
      def test_script_deployment(self):
          sd1 = ScriptDeployment(script='foobar', delete=True)
          sd2 = ScriptDeployment(script='foobar', delete=False)
          sd3 = ScriptDeployment(
              script='foobar', delete=False, name='foobarname')
  
          self.assertTrue(sd1.name.find('deployment') != '1')
          self.assertEqual(sd3.name, 'foobarname')
  
          self.assertEqual(self.node, sd1.run(node=self.node,
                                              client=MockClient(hostname='localhost')))
          self.assertEqual(self.node, sd2.run(node=self.node,
                                              client=MockClient(hostname='localhost')))
  
      def test_script_file_deployment(self):
          file_path = os.path.abspath(__file__)
          with open(file_path, 'rb') as fp:
              content = fp.read()
  
          if PY3:
              content = content.decode('utf-8')
  
          sfd1 = ScriptFileDeployment(script_file=file_path)
          self.assertEqual(sfd1.script, content)
  
      def test_script_deployment_relative_path(self):
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          sd = ScriptDeployment(script='echo "foo"', name='relative.sh')
          sd.run(self.node, client)
  
          client.run.assert_called_once_with(FILE_PATH)
  
      def test_script_deployment_absolute_path(self):
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          file_path = '{0}root{0}relative.sh'.format(os.path.sep)
  
          sd = ScriptDeployment(script='echo "foo"', name=file_path)
          sd.run(self.node, client)
  
          client.run.assert_called_once_with(file_path)
  
      def test_script_deployment_with_arguments(self):
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          file_path = '{0}root{0}relative.sh'.format(os.path.sep)
  
          args = ['arg1', 'arg2', '--option1=test']
          sd = ScriptDeployment(script='echo "foo"', args=args,
                                name=file_path)
          sd.run(self.node, client)
  
          expected = '%s arg1 arg2 --option1=test' % (file_path)
          client.run.assert_called_once_with(expected)
  
          client.reset_mock()
  
          args = []
          sd = ScriptDeployment(script='echo "foo"', args=args,
                                name=file_path)
          sd.run(self.node, client)
  
          expected = file_path
          client.run.assert_called_once_with(expected)
  
      def test_script_file_deployment_with_arguments(self):
          file_path = os.path.abspath(__file__)
          client = Mock()
          client.put.return_value = FILE_PATH
          client.run.return_value = ('', '', 0)
  
          args = ['arg1', 'arg2', '--option1=test', 'option2']
          sfd = ScriptFileDeployment(script_file=file_path, args=args,
                                     name=file_path)
  
          sfd.run(self.node, client)
  
          expected = '%s arg1 arg2 --option1=test option2' % (file_path)
          client.run.assert_called_once_with(expected)
  
      def test_script_deployment_and_sshkey_deployment_argument_types(self):
          class FileObject(object):
  
              def __init__(self, name):
                  self.name = name
  
              def read(self):
                  return 'bar'
  
          ScriptDeployment(script='foobar')
          ScriptDeployment(script=u('foobar'))
          ScriptDeployment(script=FileObject('test'))
  
          SSHKeyDeployment(key='foobar')
          SSHKeyDeployment(key=u('foobar'))
          SSHKeyDeployment(key=FileObject('test'))
  
          try:
              ScriptDeployment(script=[])
          except TypeError:
              pass
          else:
              self.fail('TypeError was not thrown')
  
          try:
              SSHKeyDeployment(key={})
          except TypeError:
              pass
          else:
              self.fail('TypeError was not thrown')
  
      def test_wait_until_running_running_instantly(self):
          node2, ips = self.driver.wait_until_running(
              nodes=[self.node], wait_period=0.1,
              timeout=0.5)[0]
          self.assertEqual(self.node.uuid, node2.uuid)
          self.assertEqual(['67.23.21.33'], ips)
  
      def test_wait_until_running_without_ip(self):
          RackspaceMockHttp.type = 'NO_IP'
  
          try:
              node2, ips = self.driver.wait_until_running(
                  nodes=[self.node], wait_period=0.1,
                  timeout=0.2)[0]
          except LibcloudError as e:
              self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_with_only_ipv6(self):
          RackspaceMockHttp.type = 'IPV6'
  
          try:
              node2, ips = self.driver.wait_until_running(
                  nodes=[self.node], wait_period=0.1,
                  timeout=0.2)[0]
          except LibcloudError as e:
              self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_with_ipv6_ok(self):
          RackspaceMockHttp.type = 'IPV6'
          node2, ips = self.driver.wait_until_running(
              nodes=[self.node], wait_period=1, force_ipv4=False,
              timeout=0.5)[0]
          self.assertEqual(self.node.uuid, node2.uuid)
          self.assertEqual(['2001:DB8::1'], ips)
  
      def test_wait_until_running_running_after_0_2_second(self):
          RackspaceMockHttp.type = '05_SECOND_DELAY'
          node2, ips = self.driver.wait_until_running(
              nodes=[self.node], wait_period=0.2,
              timeout=0.1)[0]
          self.assertEqual(self.node.uuid, node2.uuid)
          self.assertEqual(['67.23.21.33'], ips)
  
      def test_wait_until_running_running_after_0_2_second_private_ips(self):
          RackspaceMockHttp.type = '05_SECOND_DELAY'
          node2, ips = self.driver.wait_until_running(
              nodes=[self.node], wait_period=0.2,
              timeout=0.1, ssh_interface='private_ips')[0]
          self.assertEqual(self.node.uuid, node2.uuid)
          self.assertEqual(['10.176.168.218'], ips)
  
      def test_wait_until_running_invalid_ssh_interface_argument(self):
          try:
              self.driver.wait_until_running(nodes=[self.node], wait_period=1,
                                             ssh_interface='invalid')
          except ValueError:
              pass
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_timeout(self):
          RackspaceMockHttp.type = 'TIMEOUT'
  
          try:
              self.driver.wait_until_running(nodes=[self.node], wait_period=0.1,
                                             timeout=0.2)
          except LibcloudError as e:
              self.assertTrue(e.value.find('Timed out') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_running_node_missing_from_list_nodes(self):
          RackspaceMockHttp.type = 'MISSING'
  
          try:
              self.driver.wait_until_running(nodes=[self.node], wait_period=0.1,
                                             timeout=0.2)
          except LibcloudError as e:
              self.assertTrue(e.value.find('Timed out after 0.2 second') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_running_multiple_nodes_have_same_uuid(self):
          RackspaceMockHttp.type = 'SAME_UUID'
  
          try:
              self.driver.wait_until_running(nodes=[self.node], wait_period=0.1,
                                             timeout=0.2)
          except LibcloudError as e:
              self.assertTrue(
                  e.value.find('Unable to match specified uuids') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_wait_until_running_running_wait_for_multiple_nodes(self):
          RackspaceMockHttp.type = 'MULTIPLE_NODES'
  
          nodes = self.driver.wait_until_running(
              nodes=[self.node, self.node2], wait_period=0.1,
              timeout=0.5)
          self.assertEqual(self.node.uuid, nodes[0][0].uuid)
          self.assertEqual(self.node2.uuid, nodes[1][0].uuid)
          self.assertEqual(['67.23.21.33'], nodes[0][1])
          self.assertEqual(['67.23.21.34'], nodes[1][1])
  
      def test_ssh_client_connect_success(self):
          mock_ssh_client = Mock()
          mock_ssh_client.return_value = None
  
          ssh_client = self.driver._ssh_client_connect(
              ssh_client=mock_ssh_client,
              timeout=0.5)
          self.assertEqual(mock_ssh_client, ssh_client)
  
      def test_ssh_client_connect_timeout(self):
          mock_ssh_client = Mock()
          mock_ssh_client.connect = Mock()
          mock_ssh_client.connect.side_effect = IOError('bam')
  
          try:
              self.driver._ssh_client_connect(ssh_client=mock_ssh_client,
                                              wait_period=0.1,
                                              timeout=0.2)
          except LibcloudError as e:
              self.assertTrue(e.value.find('Giving up') != -1)
          else:
              self.fail('Exception was not thrown')
  
      @unittest.skipIf(not have_paramiko, 'Skipping because paramiko is not available')
      def test_ssh_client_connect_immediately_throws_on_fatal_execption(self):
          # Verify that fatal exceptions are immediately propagated and ensure
          # we don't try to retry on them
          from paramiko.ssh_exception import SSHException
          from paramiko.ssh_exception import PasswordRequiredException
  
          mock_ssh_client = Mock()
          mock_ssh_client.connect = Mock()
          mock_ssh_client.connect.side_effect = IOError('bam')
  
          mock_exceptions = [
              SSHException('Invalid or unsupported key type'),
              PasswordRequiredException('private key file is encrypted'),
              SSHException('OpenSSH private key file checkints do not match')
          ]
  
          for mock_exception in mock_exceptions:
              mock_ssh_client.connect = Mock(side_effect=mock_exception)
              assertRaisesRegex(self, mock_exception.__class__, str(mock_exception),
                                self.driver._ssh_client_connect,
                                ssh_client=mock_ssh_client,
                                wait_period=0.1,
                                timeout=0.2)
  
      def test_run_deployment_script_success(self):
          task = Mock()
          ssh_client = Mock()
  
          ssh_client2 = self.driver._run_deployment_script(task=task,
                                                           node=self.node,
                                                           ssh_client=ssh_client,
                                                           max_tries=2)
          self.assertTrue(isinstance(ssh_client2, Mock))
  
      def test_run_deployment_script_exception(self):
          task = Mock()
          task.run = Mock()
          task.run.side_effect = Exception('bar')
          ssh_client = Mock()
  
          try:
              self.driver._run_deployment_script(task=task,
                                                 node=self.node,
                                                 ssh_client=ssh_client,
                                                 max_tries=2)
          except LibcloudError as e:
              self.assertTrue(e.value.find('Failed after 2 tries') != -1)
          else:
              self.fail('Exception was not thrown')
  
      def test_run_deployment_script_ssh_command_timeout_fatal_exception(self):
          # We shouldn't retry on SSHCommandTimeoutError error since it's fatal
          task = Mock()
          task.run = Mock()
          task.run.side_effect = SSHCommandTimeoutError('ls -la', 10)
          ssh_client = Mock()
  
          try:
              self.driver._run_deployment_script(task=task,
                                                 node=self.node,
                                                 ssh_client=ssh_client,
                                                 max_tries=5)
          except SSHCommandTimeoutError as e:
              self.assertTrue(e.message.find('Command didn\'t finish') != -1)
          else:
              self.fail('Exception was not thrown')
  
      @patch('libcloud.compute.base.SSHClient')
      @patch('libcloud.compute.ssh')
      def test_deploy_node_success(self, mock_ssh_module, _):
          self.driver.create_node = Mock()
          self.driver.create_node.return_value = self.node
          mock_ssh_module.have_paramiko = True
  
          deploy = Mock()
  
          node = self.driver.deploy_node(deploy=deploy)
          self.assertEqual(self.node.id, node.id)
  
      @patch('libcloud.compute.base.SSHClient')
      @patch('libcloud.compute.ssh')
      def test_deploy_node_deploy_node_kwargs_except_auth_are_not_propagated_on(self, mock_ssh_module, _):
          # Verify that keyword arguments which are specific to deploy_node()
          # are not propagated to create_node()
          mock_ssh_module.have_paramiko = True
          self.driver.create_node = Mock()
          self.driver.create_node.return_value = self.node
          self.driver._connect_and_run_deployment_script = Mock()
          self.driver._wait_until_running = Mock()
  
          kwargs = {}
          for key in DEPLOY_NODE_KWARGS:
              kwargs[key] = key
  
          kwargs['ssh_interface'] = 'public_ips'
          kwargs['ssh_alternate_usernames'] = ['foo', 'bar']
          kwargs['timeout'] = 10
  
          auth = NodeAuthPassword('P@$$w0rd')
  
          node = self.driver.deploy_node(name='name', image='image', size='size',
                                         auth=auth, ex_foo='ex_foo', **kwargs)
          self.assertEqual(self.node.id, node.id)
          self.assertEqual(self.driver.create_node.call_count, 1)
  
          call_kwargs = self.driver.create_node.call_args_list[0][1]
          expected_call_kwargs = {
              'name': 'name',
              'image': 'image',
              'size': 'size',
              'auth': auth,
              'ex_foo': 'ex_foo'
          }
          self.assertEqual(expected_call_kwargs, call_kwargs)
  
          # If driver throws an exception it should fall back to passing in all
          # the arguments
          global call_count
          call_count = 0
          def create_node(name, image, size, ex_custom_arg_1, ex_custom_arg_2,
                          ex_foo=None, auth=None, **kwargs):
              global call_count
  
              call_count += 1
              if call_count == 1:
                  msg = 'create_node() takes at least 5 arguments (7 given)'
                  raise TypeError(msg)
              return self.node
  
          self.driver.create_node = create_node
  
          node = self.driver.deploy_node(name='name', image='image', size='size',
                                         auth=auth, ex_foo='ex_foo', ex_custom_arg_1='a',
                                         ex_custom_arg_2='b', **kwargs)
          self.assertEqual(self.node.id, node.id)
          self.assertEqual(call_count, 2)
  
          call_count = 0
  
          def create_node(name, image, size, ex_custom_arg_1, ex_custom_arg_2,
                          ex_foo=None, auth=None, **kwargs):
              global call_count
  
              call_count += 1
              if call_count == 1:
                  msg = 'create_node() missing 3 required positional arguments'
                  raise TypeError(msg)
              return self.node
  
          self.driver.create_node = create_node
  
          node = self.driver.deploy_node(name='name', image='image', size='size',
                                         auth=auth, ex_foo='ex_foo', ex_custom_arg_1='a',
                                         ex_custom_arg_2='b', **kwargs)
          self.assertEqual(self.node.id, node.id)
          self.assertEqual(call_count, 2)
  
      @patch('libcloud.compute.base.SSHClient')
      @patch('libcloud.compute.ssh')
      def test_deploy_node_exception_run_deployment_script(self, mock_ssh_module,
                                                           _):
          self.driver.create_node = Mock()
          self.driver.create_node.return_value = self.node
          mock_ssh_module.have_paramiko = True
  
          deploy = Mock()
          deploy.run = Mock()
          deploy.run.side_effect = Exception('foo')
  
          try:
              self.driver.deploy_node(deploy=deploy)
          except DeploymentError as e:
              self.assertTrue(e.node.id, self.node.id)
          else:
              self.fail('Exception was not thrown')
  
      @patch('libcloud.compute.base.SSHClient')
      @patch('libcloud.compute.ssh')
      def test_deploy_node_exception_ssh_client_connect(self, mock_ssh_module,
                                                        ssh_client):
          self.driver.create_node = Mock()
          self.driver.create_node.return_value = self.node
  
          mock_ssh_module.have_paramiko = True
  
          deploy = Mock()
          ssh_client.side_effect = IOError('bar')
  
          try:
              self.driver.deploy_node(deploy=deploy)
          except DeploymentError as e:
              self.assertTrue(e.node.id, self.node.id)
          else:
              self.fail('Exception was not thrown')
  
      @patch('libcloud.compute.ssh')
      def test_deploy_node_depoy_node_not_implemented(self, mock_ssh_module):
          self.driver.features = {'create_node': []}
          mock_ssh_module.have_paramiko = True
  
          try:
              self.driver.deploy_node(deploy=Mock())
          except NotImplementedError:
              pass
          else:
              self.fail('Exception was not thrown')
  
          self.driver.features = {}
  
          try:
              self.driver.deploy_node(deploy=Mock())
          except NotImplementedError:
              pass
          else:
              self.fail('Exception was not thrown')
  
      @patch('libcloud.compute.base.SSHClient')
      @patch('libcloud.compute.ssh')
      def test_deploy_node_password_auth(self, mock_ssh_module, _):
          self.driver.features = {'create_node': ['password']}
          mock_ssh_module.have_paramiko = True
  
          self.driver.create_node = Mock()
          self.driver.create_node.return_value = self.node
  
          node = self.driver.deploy_node(deploy=Mock())
          self.assertEqual(self.node.id, node.id)
  
      @patch('libcloud.compute.base.SSHClient')
      @patch('libcloud.compute.ssh')
      def test_exception_is_thrown_is_paramiko_is_not_available(self,
                                                                mock_ssh_module,
                                                                _):
          self.driver.features = {'create_node': ['password']}
          self.driver.create_node = Mock()
          self.driver.create_node.return_value = self.node
  
          mock_ssh_module.have_paramiko = False
  
          try:
              self.driver.deploy_node(deploy=Mock())
          except RuntimeError as e:
              self.assertTrue(str(e).find('paramiko is not installed') != -1)
          else:
              self.fail('Exception was not thrown')
  
          mock_ssh_module.have_paramiko = True
          node = self.driver.deploy_node(deploy=Mock())
          self.assertEqual(self.node.id, node.id)
  
  
  class RackspaceMockHttp(MockHttp):
      fixtures = ComputeFileFixtures('openstack')
  
      def _v2_0_tokens(self, method, url, body, headers):
          body = self.fixtures.load('_v2_0__auth_deployment.json')
          headers = {
              'content-type': 'application/json'
          }
          return (httplib.OK, body, headers,
                  httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail(self, method, url, body, headers):
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_success.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail_05_SECOND_DELAY(self, method, url, body, headers):
          time.sleep(0.5)
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_success.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail_TIMEOUT(self, method, url, body, headers):
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_pending.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail_MISSING(self, method, url, body, headers):
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_missing.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail_SAME_UUID(self, method, url, body, headers):
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_same_uuid.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail_MULTIPLE_NODES(self, method, url, body, headers):
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_multiple_nodes.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail_IPV6(self, method, url, body, headers):
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_ipv6.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
      def _v1_0_slug_servers_detail_NO_IP(self, method, url, body, headers):
          body = self.fixtures.load(
              'v1_slug_servers_detail_deployment_no_ip.xml')
          return (httplib.OK, body, XML_HEADERS, httplib.responses[httplib.OK])
  
  
  if __name__ == '__main__':
      sys.exit(unittest.main())
  
files: libcloud/test/compute/test_deployment.py