예제 #1
0
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
예제 #2
0
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
예제 #3
0
    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',
                                                   })
예제 #4
0
    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 '')
예제 #5
0
    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)