def get_regions(cloud): """ List available regions for cloud Arguments: cloud: Cloud to list regions for Returns: Dictionary of all known regions for cloud """ sh = run('juju list-regions {} --format yaml'.format(cloud), shell=True, stdout=PIPE, stderr=PIPE) stdout = sh.stdout.decode('utf8') stderr = sh.stderr.decode('utf8') if sh.returncode > 0: raise Exception("Unable to list regions: {}".format(stderr)) if 'no regions' in stdout: return {} result = yaml.safe_load(stdout) if not isinstance(result, dict): msg = 'Unexpected response from regions: {}'.format(result) app.log.error(msg) utils.sentry_report(msg, level=logging.ERROR) result = {} return result
def handle_exception(loop, context): exc = context.get('exception') if exc is None or isinstance(exc, CancelledError): return # not an error, cleanup message if isinstance(exc, ExitMainLoop): Shutdown.set() # use previously stored exit code return if Error.is_set(): return # already reporting an error Error.set() exc_info = (type(exc), exc, exc.__traceback__) if any(pred(exc) for pred in NOTRACK_EXCEPTIONS): app.log.debug('Would not track exception: {}'.format(exc)) if not (app.noreport or any(pred(exc) for pred in NOTRACK_EXCEPTIONS)): track_exception(str(exc)) utils.sentry_report(exc_info=exc_info) msg = 'Unhandled exception' if 'future' in context: msg += ' in {}'.format(context['future']) app.log.exception(msg, exc_info=exc) if app.headless: msg = str(exc) utils.error(msg) Shutdown.set(1) else: app.exit_code = 1 # store exit code for later app.ui.show_exception_message(exc) # eventually raises ExitMainLoop
def test_sentry_report(self, app, lxd_version, juju_version): # test task schedule flag = asyncio.Event() with patch.object(utils, '_sentry_report', lambda *a, **kw: flag.set()): with test_loop() as loop: app.loop = loop utils.sentry_report('m') loop.run_until_complete(asyncio.wait_for(flag.wait(), 30)) # test implementation app.config = {'spell': 'spell'} app.current_cloud_type = 'type' app.current_region = 'region' app.is_jaas = False app.headless = False juju_version.return_value = '2.j' lxd_version.return_value = '2.l' app.noreport = True utils._sentry_report('message', tags={'foo': 'bar'}) assert not app.sentry.capture.called app.noreport = False utils._sentry_report('message', tags={'foo': 'bar'}) app.sentry.capture.assert_called_once_with('raven.events.Message', message='message', level=logging.WARNING, tags={ 'spell': 'spell', 'cloud_type': 'type', 'region': 'region', 'jaas': False, 'headless': False, 'juju_version': '2.j', 'lxd_version': '2.l', 'foo': 'bar', }) app.sentry.capture.reset_mock() utils._sentry_report('message', 'exc_info') app.sentry.capture.assert_called_once_with('raven.events.Exception', level=logging.ERROR, exc_info='exc_info', tags={ 'spell': 'spell', 'cloud_type': 'type', 'region': 'region', 'jaas': False, 'headless': False, 'juju_version': '2.j', 'lxd_version': '2.l', })
async def run(self, msg_cb, event_name=None): # Define STEP_NAME for use in determining where to store # our step results, # state set "conjure-up.$SPELL_NAME.$STEP_NAME.result" "val" app.env['CONJURE_UP_STEP'] = self.name step_path = Path(app.config['spell-dir']) / 'steps' / self.filename if not step_path.is_file(): return step_path = str(step_path) msg = "Running step: {}.".format(self.name) app.log.info(msg) msg_cb(msg) if event_name is not None: track_event(event_name, "Started", "") if not os.access(step_path, os.X_OK): raise Exception("Step {} not executable".format(self.title)) if is_linux() and self.needs_sudo and not await can_sudo(): raise SudoError('Step "{}" requires sudo: {}'.format( self.title, 'password failed' if app.sudo_pass else 'passwordless sudo required', )) cloud_types = juju.get_cloud_types_by_name() provider_type = cloud_types[app.provider.cloud] app.env['JUJU_PROVIDERTYPE'] = provider_type # not all providers have a credential, e.g., localhost app.env['JUJU_CREDENTIAL'] = app.provider.credential or '' app.env['JUJU_CONTROLLER'] = app.provider.controller app.env['JUJU_MODEL'] = app.provider.model app.env['JUJU_REGION'] = app.provider.region or '' app.env['CONJURE_UP_SPELLSDIR'] = app.argv.spells_dir if provider_type == "maas": app.env['MAAS_ENDPOINT'] = app.maas.endpoint app.env['MAAS_APIKEY'] = app.maas.api_key for step_name, step_data in app.steps_data.items(): for key, value in step_data.items(): app.env[key.upper()] = step_data[key] for key, value in app.env.items(): if value is None: app.log.warning('Env {} is None; ' 'replacing with empty string'.format(key)) app.env[key] = '' app.log.debug("Storing environment") async with aiofiles.open(step_path + ".env", 'w') as outf: for k, v in app.env.items(): if 'JUJU' in k or 'MAAS' in k or 'CONJURE' in k: await outf.write("{}=\"{}\" ".format(k.upper(), v)) app.log.debug("Executing script: {}".format(step_path)) async with aiofiles.open(step_path + ".out", 'w') as outf: async with aiofiles.open(step_path + ".err", 'w') as errf: proc = await asyncio.create_subprocess_exec(step_path, env=app.env, stdout=outf, stderr=errf) async with aiofiles.open(step_path + '.out', 'r') as f: while proc.returncode is None: async for line in f: msg_cb(line) await asyncio.sleep(0.01) out_log = Path(step_path + '.out').read_text() err_log = Path(step_path + '.err').read_text() if proc.returncode != 0: app.sentry.context.merge({ 'extra': { 'out_log_tail': out_log[-400:], 'err_log_tail': err_log[-400:], } }) raise Exception("Failure in step {}".format(self.filename)) # special case for 00_deploy-done to report masked # charm hook failures that were retried automatically if not app.noreport: failed_apps = set() # only report each charm once for line in err_log.splitlines(): if 'hook failure, will retry' in line: log_leader = line.split()[0] unit_name = log_leader.split(':')[-1] app_name = unit_name.split('/')[0] failed_apps.add(app_name) for app_name in failed_apps: # report each individually so that Sentry will give us a # breakdown of failures per-charm in addition to per-spell sentry_report('Retried hook failure', tags={ 'app_name': app_name, }) if event_name is not None: track_event(event_name, "Done", "") result_key = "conjure-up.{}.{}.result".format(app.config['spell'], self.name) result = app.state.get(result_key) return (result or '')
async def run(self, phase, msg_cb, event_name=None): # Define STEP_NAME for use in determining where to store # our step results, # state set "conjure-up.$SPELL_NAME.$STEP_NAME.result" "val" app.env['CONJURE_UP_STEP'] = self.name app.env['CONJURE_UP_PHASE'] = phase.value step_path = self._build_phase_path(phase) if not step_path.is_file(): return if not os.access(str(step_path), os.X_OK): app.log.error( 'Unable to run {} step {} {}, it is not executable'.format( self.source, step_path.stem, phase.value)) return step_path = str(step_path) msg = "Running {} step: {} {}.".format(self.source, self.name, phase.value) app.log.info(msg) msg_cb(msg) if event_name is not None: track_event(event_name, "Started", "") if is_linux() and self.needs_sudo and not await can_sudo(): raise SudoError('The "{}" step "{}" requires sudo: {}'.format( self.source, self.title, 'password failed' if app.sudo_pass else 'passwordless sudo required', )) app.env['CONJURE_UP_SPELLSDIR'] = app.conjurefile['spells-dir'] app.env['CONJURE_UP_SESSION_ID'] = app.session_id if app.metadata.spell_type == spell_types.JUJU: cloud_types = juju.get_cloud_types_by_name() provider_type = cloud_types[app.provider.cloud] app.env['JUJU_PROVIDERTYPE'] = provider_type # not all providers have a credential, e.g., localhost app.env['JUJU_CREDENTIAL'] = app.provider.credential or '' app.env['JUJU_CONTROLLER'] = app.provider.controller app.env['JUJU_MODEL'] = app.provider.model app.env['JUJU_REGION'] = app.provider.region or '' if provider_type == "maas": app.env['MAAS_ENDPOINT'] = app.maas.endpoint app.env['MAAS_APIKEY'] = app.maas.api_key for step_name, step_data in app.steps_data.items(): for key, value in step_data.items(): app.env[key.upper()] = str(step_data[key]) for key, value in app.env.items(): if value is None: app.log.warning('Env {} is None; ' 'replacing with empty string'.format(key)) app.env[key] = '' app.log.debug("Storing environment") async with aiofiles.open(step_path + ".env", 'w') as outf: for k, v in app.env.items(): if 'JUJU' in k or 'MAAS' in k or 'CONJURE' in k: await outf.write("{}=\"{}\" ".format(k.upper(), v)) app.log.debug("Executing script: {}".format(step_path)) out_path = step_path + '.out' err_path = step_path + '.err' ret, out_log, err_log = await arun([step_path], stdout=out_path, stderr=err_path, cb_stdout=msg_cb) if ret != 0: app.sentry.context.merge({ 'extra': { 'out_log_tail': out_log[-400:], 'err_log_tail': err_log[-400:], } }) raise Exception("Failure in step {} {}".format( self.name, phase.value)) # special case for 00_deploy-done to report masked # charm hook failures that were retried automatically if not app.no_report and app.metadata.spell_type == spell_types.JUJU: failed_apps = set() # only report each charm once for line in err_log.splitlines(): if 'hook failure, will retry' in line: log_leader = line.split()[0] unit_name = log_leader.split(':')[-1] app_name = unit_name.split('/')[0] failed_apps.add(app_name) for app_name in failed_apps: # report each individually so that Sentry will give us a # breakdown of failures per-charm in addition to per-spell sentry_report('Retried hook failure', tags={ 'app_name': app_name, }) if event_name is not None: track_event(event_name, "Done", "") return self.get_state('result', phase)