github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/acceptancetests/jujupy/utility.py (about)

     1  # This file is part of JujuPy, a library for driving the Juju CLI.
     2  # Copyright 2014-2017 Canonical Ltd.
     3  #
     4  # This program is free software: you can redistribute it and/or modify it
     5  # under the terms of the Lesser GNU General Public License version 3, as
     6  # published by the Free Software Foundation.
     7  #
     8  # This program is distributed in the hope that it will be useful, but WITHOUT
     9  # ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
    10  # SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the Lesser
    11  # GNU General Public License for more details.
    12  #
    13  # You should have received a copy of the Lesser GNU General Public License
    14  # along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  
    17  from contextlib import contextmanager
    18  from datetime import (
    19      datetime,
    20      )
    21  import errno
    22  import os
    23  import logging
    24  from shutil import rmtree
    25  import socket
    26  import sys
    27  from time import (
    28      sleep,
    29      )
    30  from tempfile import (
    31      mkdtemp,
    32      NamedTemporaryFile,
    33      )
    34  # Export shell quoting function which has moved in newer python versions
    35  try:
    36      from shlex import quote
    37  except ImportError:
    38      from pipes import quote
    39  import yaml
    40  
    41  quote
    42  
    43  
    44  log = logging.getLogger("jujupy.utility")
    45  
    46  
    47  @contextmanager
    48  def scoped_environ(new_environ=None):
    49      """Save the current environment and restore it when the context is exited.
    50  
    51      :param new_environ: If provided and not None, the key/value pairs of the
    52      iterable are used to create a new environment in the context."""
    53      old_environ = dict(os.environ)
    54      try:
    55          if new_environ is not None:
    56              os.environ.clear()
    57              os.environ.update(new_environ)
    58          yield
    59      finally:
    60          os.environ.clear()
    61          os.environ.update(old_environ)
    62  
    63  
    64  class until_timeout:
    65  
    66      """Yields remaining number of seconds.  Stops when timeout is reached.
    67  
    68      :ivar timeout: Number of seconds to wait.
    69      """
    70      def __init__(self, timeout, start=None):
    71          self.timeout = timeout
    72          if start is None:
    73              start = self.now()
    74          self.start = start
    75  
    76      def __iter__(self):
    77          return self
    78  
    79      @staticmethod
    80      def now():
    81          return datetime.now()
    82  
    83      def __next__(self):
    84          return self.next()
    85  
    86      def next(self):
    87          elapsed = self.now() - self.start
    88          remaining = self.timeout - elapsed.total_seconds()
    89          if remaining <= 0:
    90              raise StopIteration
    91          return remaining
    92  
    93  
    94  class JujuResourceTimeout(Exception):
    95      """A timeout exception for a resource not being downloaded into a unit."""
    96  
    97  
    98  def pause(seconds):
    99      print_now('Sleeping for {:d} seconds.'.format(seconds))
   100      sleep(seconds)
   101  
   102  
   103  def is_ipv6_address(address):
   104      """Returns True if address is IPv6 rather than IPv4 or a host name.
   105  
   106      Incorrectly returns False for IPv6 addresses on windows due to lack of
   107      support for socket.inet_pton there.
   108      """
   109      try:
   110          socket.inet_pton(socket.AF_INET6, address)
   111      except (AttributeError, socket.error):
   112          # IPv4 or hostname
   113          return False
   114      return True
   115  
   116  
   117  def split_address_port(address_port):
   118      """Split an ipv4 or ipv6 address and port into a tuple.
   119  
   120      ipv6 addresses must be in the literal form with a port ([::12af]:80).
   121      ipv4 addresses may be without a port, which translates to None.
   122      """
   123      if ':' not in address_port:
   124          # This is correct for ipv4.
   125          return address_port, None
   126      address, port = address_port.rsplit(':', 1)
   127      address = address.strip('[]')
   128      return address, port
   129  
   130  
   131  def get_unit_public_ip(client, unit_name):
   132      status = client.get_status()
   133      return status.get_unit(unit_name)['public-address']
   134  
   135  
   136  def print_now(string):
   137      print(string)
   138      sys.stdout.flush()
   139  
   140  
   141  @contextmanager
   142  def temp_dir(parent=None, keep=False):
   143      directory = mkdtemp(dir=parent)
   144      try:
   145          yield directory
   146      finally:
   147          if not keep:
   148              rmtree(directory)
   149  
   150  
   151  @contextmanager
   152  def skip_on_missing_file():
   153      """Skip to the end of block if a missing file exception is raised."""
   154      try:
   155          yield
   156      except (IOError, OSError) as e:
   157          if e.errno != errno.ENOENT:
   158              raise
   159  
   160  
   161  def ensure_dir(path):
   162      try:
   163          os.mkdir(path)
   164      except OSError as e:
   165          if e.errno != errno.EEXIST:
   166              raise
   167  
   168  
   169  def ensure_deleted(path):
   170      with skip_on_missing_file():
   171          os.unlink(path)
   172  
   173  
   174  @contextmanager
   175  def temp_yaml_file(yaml_dict, encoding="utf-8"):
   176      temp_file_cxt = NamedTemporaryFile(suffix='.yaml', delete=False)
   177      try:
   178          with temp_file_cxt as temp_file:
   179              yaml.safe_dump(yaml_dict, temp_file, encoding=encoding)
   180          yield temp_file.name
   181      finally:
   182          os.unlink(temp_file.name)
   183  
   184  
   185  def get_timeout_path():
   186      import jujupy.timeout
   187      return os.path.abspath(jujupy.timeout.__file__)
   188  
   189  
   190  def get_timeout_prefix(duration, timeout_path=None):
   191      """Return extra arguments to run a command with a timeout."""
   192      if timeout_path is None:
   193          timeout_path = get_timeout_path()
   194      return (sys.executable, timeout_path, '%.2f' % duration, '--')
   195  
   196  
   197  def unqualified_model_name(model_name):
   198      """Return the model name with the owner qualifier stripped if present."""
   199      return model_name.split('/', 1)[-1]
   200  
   201  
   202  def qualified_model_name(model_name, owner_name):
   203      """Return the model name qualified with the given owner name."""
   204      if model_name == '' or owner_name == '':
   205          raise ValueError(
   206              'Neither model_name nor owner_name can be blank strings')
   207  
   208      parts = model_name.split('/', 1)
   209      if len(parts) == 2 and parts[0] != owner_name:
   210          raise ValueError(
   211              'qualified model name {} with owner not matching {}'.format(
   212                  model_name, owner_name))
   213      return '{}/{}'.format(owner_name, parts[-1])
   214  
   215  
   216  def _dns_name_for_machine(status, machine):
   217      host = status.status['machines'][machine]['dns-name']
   218      if is_ipv6_address(host):
   219          log.warning("Selected IPv6 address for machine %s: %r", machine, host)
   220      return host