示例#1
0
    def test_equality(self):
        test_cases = [
            ("1.0.0", "1.0.0", True),
            ("01.0.0", "1.0.0", True),
            ("10.0.0", "9.0.0", False),
            ("1.0.0", "1.0.1", False),
            ("1.0.1", "1.0.0", False),
            ("1.0.0", "1.1.0", False),
            ("1.1.0", "1.0.0", False),
            ("1.0.0", "2.0.0", False),
            ("1.2-alpha1", "1.2.0", False),
            ("1.2-alpha2", "1.2-alpha1", False),
            ("1.2-alpha2.1", "1.2-alpha2", False),
            ("1.2-alpha2.2", "1.2-alpha2.1", False),
            ("1.2-beta1", "1.2-alpha1", False),
            ("1.2-beta1", "1.2-alpha2.1", False),
            ("1.2-beta1", "1.2.0", False),
            ("1.2.1", "1.2.0", False),
            ("2.0.0", "1.0.0", False),
            ("2.0.0.0", "2.0.0", True),
            ("2.0.0.0", "2.0.0.0", True),
            ("2.0.0.1", "2.0.0.0", False),
            ("2.0.1.10", "2.0.0.0", False),
        ]

        for a, b, expected in test_cases:
            self.assertEqual(JujuVersion(a) == JujuVersion(b), expected)
            self.assertEqual(JujuVersion(a) == b, expected)
示例#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
示例#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()
示例#4
0
    def test_parsing(self):
        test_cases = [("0.0.0", 0, 0, '', 0, 0), ("0.0.2", 0, 0, '', 2, 0),
                      ("0.1.0", 0, 1, '', 0, 0), ("0.2.3", 0, 2, '', 3, 0),
                      ("10.234.3456", 10, 234, '', 3456, 0),
                      ("10.234.3456.1", 10, 234, '', 3456, 1),
                      ("1.21-alpha12", 1, 21, 'alpha', 12, 0),
                      ("1.21-alpha1.34", 1, 21, 'alpha', 1, 34),
                      ("2.7", 2, 7, '', 0, 0)]

        for vs, major, minor, tag, patch, build in test_cases:
            v = JujuVersion(vs)
            self.assertEqual(v.major, major)
            self.assertEqual(v.minor, minor)
            self.assertEqual(v.tag, tag)
            self.assertEqual(v.patch, patch)
            self.assertEqual(v.build, build)
示例#5
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'))
示例#6
0
 def test_parsing_errors(self):
     invalid_versions = [
         "xyz",
         "foo.bar",
         "foo.bar.baz",
         "dead.beef.ca.fe",
         "1234567890.2.1",  # The major version is too long.
         "0.2..1",  # Two periods next to each other.
         "1.21.alpha1",  # Tag comes after period.
         "1.21-alpha",  # No patch number but a tag is present.
         "1.21-alpha1beta",  # Non-numeric string after the patch number.
         "1.21-alpha-dev",  # Tag duplication.
         "1.21-alpha_dev3",  # Underscore in a tag.
         "1.21-alpha123dev3",  # Non-numeric string after the patch number.
     ]
     for v in invalid_versions:
         with self.assertRaises(RuntimeError):
             JujuVersion(v)
示例#7
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
示例#8
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()
示例#9
0
 def test_has_controller_storage(self):
     self.assertTrue(JujuVersion('2.8.0').has_controller_storage())
     self.assertFalse(JujuVersion('2.7.9').has_controller_storage())
示例#10
0
 def test_is_dispatch_aware(self):
     self.assertTrue(JujuVersion('2.8.0').is_dispatch_aware())
     self.assertFalse(JujuVersion('2.7.9').is_dispatch_aware())
示例#11
0
 def test_has_app_data(self):
     self.assertTrue(JujuVersion('2.8.0').has_app_data())
     self.assertTrue(JujuVersion('2.7.0').has_app_data())
     self.assertFalse(JujuVersion('2.6.9').has_app_data())
示例#12
0
    def test_comparison(self):
        test_cases = [
            ("1.0.0", "1.0.0", False, True),
            ("01.0.0", "1.0.0", False, True),
            ("10.0.0", "9.0.0", False, False),
            ("1.0.0", "1.0.1", True, True),
            ("1.0.1", "1.0.0", False, False),
            ("1.0.0", "1.1.0", True, True),
            ("1.1.0", "1.0.0", False, False),
            ("1.0.0", "2.0.0", True, True),
            ("1.2-alpha1", "1.2.0", True, True),
            ("1.2-alpha2", "1.2-alpha1", False, False),
            ("1.2-alpha2.1", "1.2-alpha2", False, False),
            ("1.2-alpha2.2", "1.2-alpha2.1", False, False),
            ("1.2-beta1", "1.2-alpha1", False, False),
            ("1.2-beta1", "1.2-alpha2.1", False, False),
            ("1.2-beta1", "1.2.0", True, True),
            ("1.2.1", "1.2.0", False, False),
            ("2.0.0", "1.0.0", False, False),
            ("2.0.0.0", "2.0.0", False, True),
            ("2.0.0.0", "2.0.0.0", False, True),
            ("2.0.0.1", "2.0.0.0", False, False),
            ("2.0.1.10", "2.0.0.0", False, False),
            ("2.10.0", "2.8.0", False, False),
        ]

        for a, b, expected_strict, expected_weak in test_cases:
            with self.subTest(a=a, b=b):
                self.assertEqual(
                    JujuVersion(a) < JujuVersion(b), expected_strict)
                self.assertEqual(
                    JujuVersion(a) <= JujuVersion(b), expected_weak)
                self.assertEqual(
                    JujuVersion(b) > JujuVersion(a), expected_strict)
                self.assertEqual(
                    JujuVersion(b) >= JujuVersion(a), expected_weak)
                # Implicit conversion.
                self.assertEqual(JujuVersion(a) < b, expected_strict)
                self.assertEqual(JujuVersion(a) <= b, expected_weak)
                self.assertEqual(b > JujuVersion(a), expected_strict)
                self.assertEqual(b >= JujuVersion(a), expected_weak)