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