Exemple #1
0
    def test_from_environ(self):
        # JUJU_VERSION is not set
        v = JujuVersion.from_environ()
        self.assertEqual(v, JujuVersion('0.0.0'))

        os.environ['JUJU_VERSION'] = 'no'
        with self.assertRaisesRegex(RuntimeError, 'not a valid Juju version'):
            JujuVersion.from_environ()

        os.environ['JUJU_VERSION'] = '2.8.0'
        v = JujuVersion.from_environ()
        self.assertEqual(v, JujuVersion('2.8.0'))
Exemple #2
0
    def relation_set(self, relation_id, key, value, is_app):
        if not isinstance(is_app, bool):
            raise TypeError(
                'is_app parameter to relation_set must be a boolean')

        if is_app:
            version = JujuVersion.from_environ()
            if not version.has_app_data():
                raise RuntimeError(
                    'setting application data is not supported on Juju version {}'
                    .format(version))

        args = [
            'relation-set', '-r',
            str(relation_id), '{}={}'.format(key, value)
        ]
        if is_app:
            args.append('--app')

        try:
            return self._run(*args)
        except ModelError as e:
            if 'relation not found' in str(e):
                raise RelationNotFoundError() from e
            raise
Exemple #3
0
    def __init__(self, charm_dir: Path):
        self._charm_dir = charm_dir
        self._exec_path = Path(os.environ.get('JUJU_DISPATCH_PATH', sys.argv[0]))

        dispatch = charm_dir / 'dispatch'
        if JujuVersion.from_environ().is_dispatch_aware() and _exe_path(dispatch) is not None:
            self._init_dispatch()
        else:
            self._init_legacy()
Exemple #4
0
def _should_use_controller_storage(db_path: Path, meta: ops.charm.CharmMeta) -> bool:
    """Figure out whether we want to use controller storage or not."""
    # if you've previously used local state, carry on using that
    if db_path.exists():
        logger.debug("Using local storage: %s already exists", db_path)
        return False

    # if you're not in k8s you don't need controller storage
    if 'kubernetes' not in meta.series:
        logger.debug("Using local storage: not a kubernetes charm")
        return False

    # are we in a new enough Juju?
    cur_version = JujuVersion.from_environ()

    if cur_version.has_controller_storage():
        logger.debug("Using controller storage: JUJU_VERSION=%s", cur_version)
        return True
    else:
        logger.debug("Using local storage: JUJU_VERSION=%s", cur_version)
        return False
Exemple #5
0
def main(charm_class: ops.charm.CharmBase, use_juju_for_storage: bool = None):
    """Setup the charm and dispatch the observed event.

    The event name is based on the way this executable was called (argv[0]).

    Args:
        charm_class: your charm class.
        use_juju_for_storage: whether to use controller-side storage. If not specified
            then kubernetes charms that haven't previously used local storage and that
            are running on a new enough Juju default to controller-side storage,
            otherwise local storage is used.
    """
    charm_dir = _get_charm_dir()

    model_backend = ops.model._ModelBackend()
    debug = ('JUJU_DEBUG' in os.environ)
    setup_root_logging(model_backend, debug=debug)
    logger.debug("Operator Framework %s up and running.", ops.__version__)

    dispatcher = _Dispatcher(charm_dir)
    dispatcher.run_any_legacy_hook()

    metadata = (charm_dir / 'metadata.yaml').read_text()
    actions_meta = charm_dir / 'actions.yaml'
    if actions_meta.exists():
        actions_metadata = actions_meta.read_text()
    else:
        actions_metadata = None

    if not yaml.__with_libyaml__:
        logger.debug(
            'yaml does not have libyaml extensions, using slower pure Python yaml loader'
        )
    meta = ops.charm.CharmMeta.from_yaml(metadata, actions_metadata)
    model = ops.model.Model(meta, model_backend)

    charm_state_path = charm_dir / CHARM_STATE_FILE

    if use_juju_for_storage and not ops.storage.juju_backend_available():
        # raise an exception; the charm is broken and needs fixing.
        msg = 'charm set use_juju_for_storage=True, but Juju version {} does not support it'
        raise RuntimeError(msg.format(JujuVersion.from_environ()))

    if use_juju_for_storage is None:
        use_juju_for_storage = _should_use_controller_storage(
            charm_state_path, meta)

    if use_juju_for_storage:
        if dispatcher.is_restricted_context():
            # TODO: jam 2020-06-30 This unconditionally avoids running a collect metrics event
            #  Though we eventually expect that juju will run collect-metrics in a
            #  non-restricted context. Once we can determine that we are running collect-metrics
            #  in a non-restricted context, we should fire the event as normal.
            logger.debug(
                '"%s" is not supported when using Juju for storage\n'
                'see: https://github.com/canonical/operator/issues/348',
                dispatcher.event_name)
            # Note that we don't exit nonzero, because that would cause Juju to rerun the hook
            return
        store = ops.storage.JujuStorage()
    else:
        store = ops.storage.SQLiteStorage(charm_state_path)
    framework = ops.framework.Framework(store, charm_dir, meta, model)
    framework.set_breakpointhook()
    try:
        sig = inspect.signature(charm_class)
        try:
            sig.bind(framework)
        except TypeError:
            msg = (
                "the second argument, 'key', has been deprecated and will be "
                "removed after the 0.7 release")
            warnings.warn(msg, DeprecationWarning)
            charm = charm_class(framework, None)
        else:
            charm = charm_class(framework)
        dispatcher.ensure_event_links(charm)

        # TODO: Remove the collect_metrics check below as soon as the relevant
        #       Juju changes are made. Also adjust the docstring on
        #       EventBase.defer().
        #
        # Skip reemission of deferred events for collect-metrics events because
        # they do not have the full access to all hook tools.
        if not dispatcher.is_restricted_context():
            framework.reemit()

        _emit_charm_event(charm, dispatcher.event_name)

        framework.commit()
    finally:
        framework.close()