1 ##############################################################################
2 # Copyright (c) 2016 Tim Rozet (trozet@redhat.com) and others.
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
25 from apex.common import exceptions as exc
29 if isinstance(var, bool):
32 return var.lower() in ("true", "yes")
35 def parse_yaml(yaml_file):
36 with open(yaml_file) as f:
37 parsed_dict = yaml.safe_load(f)
41 def dump_yaml(data, file):
43 Dumps data to a file as yaml
44 :param data: yaml to be written to file
45 :param file: filename to write to
48 logging.debug("Writing file {} with "
49 "yaml data:\n{}".format(file, yaml.safe_dump(data)))
50 with open(file, "w") as fh:
51 yaml.safe_dump(data, fh, default_flow_style=False)
54 def dict_objects_to_str(dictionary):
55 if isinstance(dictionary, list):
57 for element in dictionary:
58 if isinstance(element, dict):
59 tmp_list.append(dict_objects_to_str(element))
61 tmp_list.append(str(element))
63 elif not isinstance(dictionary, dict):
64 if not isinstance(dictionary, bool):
65 return str(dictionary)
68 return dict((k, dict_objects_to_str(v)) for
69 k, v in dictionary.items())
72 def run_ansible(ansible_vars, playbook, host='localhost', user='root',
73 tmp_dir=None, dry_run=False):
75 Executes ansible playbook and checks for errors
76 :param ansible_vars: dictionary of variables to inject into ansible run
77 :param playbook: playbook to execute
78 :param host: inventory file or string of target hosts
79 :param user: remote user to run ansible tasks
80 :param tmp_dir: temp directory to store ansible command
81 :param dry_run: Do not actually apply changes
84 logging.info("Executing ansible playbook: {}".format(playbook))
85 if not os.path.isfile(host):
86 inv_host = "{},".format(host)
89 if host == 'localhost':
93 ansible_command = ['ansible-playbook', '--become', '-i', inv_host,
94 '-u', user, '-c', conn_type, '-T', '30',
97 ansible_command.append('--check')
99 if isinstance(ansible_vars, dict) and ansible_vars:
100 logging.debug("Ansible variables to be set:\n{}".format(
101 pprint.pformat(ansible_vars)))
102 ansible_command.append('--extra-vars')
103 ansible_command.append(json.dumps(ansible_vars))
105 ansible_tmp = os.path.join(tmp_dir,
106 os.path.basename(playbook) + '.rerun')
107 # FIXME(trozet): extra vars are printed without single quotes
108 # so a dev has to add them manually to the command to rerun
109 # the playbook. Need to test if we can just add the single quotes
110 # to the json dumps to the ansible command and see if that works
111 with open(ansible_tmp, 'w') as fh:
112 fh.write("ANSIBLE_HOST_KEY_CHECKING=FALSE {}".format(
113 ' '.join(ansible_command)))
115 my_env = os.environ.copy()
116 my_env['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
117 logging.info("Executing playbook...this may take some time")
118 p = subprocess.Popen(ansible_command,
119 stdin=subprocess.PIPE,
120 stdout=subprocess.PIPE,
123 universal_newlines=True)
125 x = p.stdout.readline()
129 # append lines to task
131 # log the line and read another
132 x = p.stdout.readline()
133 # deliver the task to info when we get a blank line
136 logging.info(task.replace('\\n', '\n'))
138 x = p.stdout.readline()
139 # clean up and get return code
144 e = "Ansible playbook failed. See Ansible logs for details."
149 def get_url_modified_date(url):
151 Returns the last modified date for an Tripleo image artifact
152 :param url: URL to examine
153 :return: datetime object of when artifact was last modified
156 u = urllib.request.urlopen(url)
157 except urllib.error.URLError as e:
158 logging.error("Failed to fetch target url. Error: {}".format(
163 headers = metadata.items()
164 for header in headers:
165 if isinstance(header, tuple) and len(header) == 2:
166 if header[0] == 'Last-Modified':
167 return datetime.datetime.strptime(header[1],
168 "%a, %d %b %Y %X GMT")
171 def fetch_upstream_and_unpack(dest, url, targets, fetch=True):
173 Fetches targets from a url destination and downloads them if they are
174 newer. Also unpacks tar files in dest dir.
175 :param dest: Directory to download and unpack files to
176 :param url: URL where target files are located
177 :param targets: List of target files to download
178 :param fetch: Whether or not to fetch latest from internet (boolean)
181 os.makedirs(dest, exist_ok=True)
182 assert isinstance(targets, list)
183 for target in targets:
184 target_url = urllib.parse.urljoin(url, target)
185 target_dest = os.path.join(dest, target)
186 target_exists = os.path.isfile(target_dest)
188 download_target = True
189 elif not target_exists:
190 logging.warning("no-fetch requested but target: {} is not "
191 "cached, will download".format(target_dest))
192 download_target = True
194 logging.info("no-fetch requested and previous cache exists for "
195 "target: {}. Will skip download".format(target_dest))
196 download_target = False
199 logging.debug("Fetching and comparing upstream"
200 " target: \n{}".format(target_url))
201 # Check if previous file and fetch we need to compare files to
202 # determine if download is necessary
203 if target_exists and download_target:
204 logging.debug("Previous file found: {}".format(target_dest))
205 target_url_date = get_url_modified_date(target_url)
206 if target_url_date is not None:
207 target_dest_mtime = os.path.getmtime(target_dest)
208 target_url_mtime = time.mktime(target_url_date.timetuple())
209 if target_url_mtime > target_dest_mtime:
210 logging.debug('URL target is newer than disk...will '
213 logging.info('URL target does not need to be downloaded')
214 download_target = False
216 logging.debug('Unable to find last modified url date')
219 urllib.request.urlretrieve(target_url, filename=target_dest)
220 logging.info("Target downloaded: {}".format(target))
221 if target.endswith(('.tar', 'tar.gz', 'tgz')):
222 logging.info('Unpacking tar file')
223 tar = tarfile.open(target_dest)
224 tar.extractall(path=dest)
228 def install_ansible():
229 # we only install for CentOS/Fedora for now
233 elif 'fedora' in dist:
238 # yum python module only exists for 2.x, so use subprocess
240 subprocess.check_call([pkg_mgr, '-y', 'install', 'ansible'])
241 except subprocess.CalledProcessError:
242 logging.warning('Unable to install Ansible')
245 def internet_connectivity():
247 urllib.request.urlopen('http://opnfv.org', timeout=3)
249 except (urllib.request.URLError, socket.timeout):
250 logging.debug('No internet connectivity detected')
254 def open_webpage(url, timeout=5):
256 response = urllib.request.urlopen(url, timeout=timeout)
257 return response.read()
258 except (urllib.request.URLError, socket.timeout) as e:
259 logging.error("Unable to open URL: {}".format(url))
260 raise exc.FetchException('Unable to open URL') from e
263 def edit_tht_env(env_file, section, settings):
264 assert isinstance(settings, dict)
265 with open(env_file) as fh:
266 data = yaml.safe_load(fh)
268 if section not in data.keys():
270 for setting, value in settings.items():
271 data[section][setting] = value
272 with open(env_file, 'w') as fh:
273 yaml.safe_dump(data, fh, default_flow_style=False)
274 logging.debug("Data written to env file {}:\n{}".format(env_file, data))
277 def unique(tmp_list):
278 assert isinstance(tmp_list, list)
281 if x not in uniq_list:
286 def bash_settings_to_dict(data):
288 Parses bash settings x=y and returns dict of key, values
289 :param data: bash settings data in x=y format
290 :return: dict of keys and values
292 return dict(item.split('=') for item in data.splitlines())
295 def fetch_properties(url):
297 Downloads OPNFV properties and returns a dictionary of the key, values
298 :param url: URL of properties file
299 :return: dict of k,v for each properties
301 if bool(urllib.parse.urlparse(url).scheme):
302 logging.debug('Fetching properties from internet: {}'.format(url))
303 return bash_settings_to_dict(open_webpage(url).decode('utf-8'))
304 elif os.path.isfile(url):
305 logging.debug('Fetching properties from file: {}'.format(url))
306 with open(url, 'r') as fh:
308 return bash_settings_to_dict(data)
310 logging.warning('Unable to fetch properties for: {}'.format(url))
311 raise exc.FetchException('Unable determine properties location: '