Beispiel #1
0
def preflight(action):
    """
    Decorator to run a handler before the main hook phase.

    preflight hooks are used to initialize state for use by the
    main hooks (including hooks that exist in other layers). They
    can also be used to validate the environment, blocking the unit
    and aborting the hook if something this layer is responsible for
    is broken (eg. a service configuration option set to an invalid
    value). We need abort before the main reactive loop, or we
    risk failing to run handlers that rely on @when_file_changed,
    reactive.helpers.data_changed or other state tied to
    charmhelpers.core.unitdata transactions.
    """
    _id = _short_action_id(action)
    hookenv.atstart(hookenv.log, "preflight handler: {}".format(_id))
    hookenv.atstart(action)
    return action
    def __init__(self, relation_key='coordinator', peer_relation_name=None):
        '''Instatiate a Coordinator.

        Data is stored on the peer relation and in leadership storage
        under the provided relation_key.

        The peer relation is identified by peer_relation_name, and defaults
        to the first one found in metadata.yaml.
        '''
        # Most initialization is deferred, since invoking hook tools from
        # the constructor makes testing hard.
        self.key = relation_key
        self.relname = peer_relation_name
        hookenv.atstart(self.initialize)

        # Ensure that handle() is called, without placing that burden on
        # the charm author. They still need to do this manually if they
        # are not using a hook framework.
        hookenv.atstart(self.handle)
Beispiel #3
0
def preflight(action):
    '''
    Decorator to run a handler before the main hook phase.

    preflight hooks are used to initialize state for use by the
    main hooks (including hooks that exist in other layers). They
    can also be used to validate the environment, blocking the unit
    and aborting the hook if something this layer is responsible for
    is broken (eg. a service configuration option set to an invalid
    value). We need abort before the main reactive loop, or we
    risk failing to run handlers that rely on @when_file_changed,
    reactive.helpers.data_changed or other state tied to
    charmhelpers.core.unitdata transactions.
    '''
    _id = _short_action_id(action)
    hookenv.atstart(hookenv.log,
                    'preflight handler: {}'.format(_id))
    hookenv.atstart(action)
    return action
Beispiel #4
0
    def __init__(self, relation_key='coordinator', peer_relation_name=None):
        '''Instatiate a Coordinator.

        Data is stored on the peers relation and in leadership storage
        under the provided relation_key.

        The peers relation is identified by peer_relation_name, and defaults
        to the first one found in metadata.yaml.
        '''
        # Most initialization is deferred, since invoking hook tools from
        # the constructor makes testing hard.
        self.key = relation_key
        self.relname = peer_relation_name
        hookenv.atstart(self.initialize)

        # Ensure that handle() is called, without placing that burden on
        # the charm author. They still need to do this manually if they
        # are not using a hook framework.
        hookenv.atstart(self.handle)
Beispiel #5
0
    reactive.helpers.toggle_state('leadership.is_leader', is_leader)

    previous = unitdata.kv().getrange('leadership.settings.', strip=True)
    current = hookenv.leader_get()

    # Handle deletions.
    for key in set(previous.keys()) - set(current.keys()):
        current[key] = None

    any_changed = False
    for key, value in current.items():
        reactive.helpers.toggle_state('leadership.changed.{}'.format(key),
                                      value != previous.get(key))
        if value != previous.get(key):
            any_changed = True
        reactive.helpers.toggle_state('leadership.set.{}'.format(key),
                                      value is not None)
    reactive.helpers.toggle_state('leadership.changed', any_changed)

    unitdata.kv().update(current, prefix='leadership.settings.')


# Per https://github.com/juju-solutions/charms.reactive/issues/33,
# this module may be imported multiple times so ensure the
# initialization hook is only registered once. I have to piggy back
# onto the namespace of a module imported before reactive discovery
# to do this.
if not hasattr(reactive, '_leadership_registered'):
    hookenv.atstart(initialize_leadership_state)
    reactive._leadership_registered = True
Beispiel #6
0
        # there is no other way to inform the operator that they have
        # invalid configuration.
        raise SystemExit(0)

    sources = config.get('install_sources')
    keys = config.get('install_keys')
    if reactive.helpers.data_changed('apt.configure_sources', (sources, keys)):
        fetch.configure_sources(update=False,
                                sources_var='install_sources',
                                keys_var='install_keys')
        reactive.set_state('apt.needs_update')

    extra_packages = sorted(config.get('extra_packages', '').split())
    if extra_packages:
        queue_install(extra_packages)


# Per https://github.com/juju-solutions/charms.reactive/issues/33,
# this module may be imported multiple times so ensure the
# initialization hook is only registered once. I have to piggy back
# onto the namespace of a module imported before reactive discovery
# to do this.
if not hasattr(reactive, '_apt_registered'):
    # We need to register this to run every hook, not just during install
    # and config-changed, to protect against race conditions. If we don't
    # do this, then the config in the hook environment may show updates
    # to running hooks well before the config-changed hook has been invoked
    # and the intialization provided an opertunity to be run.
    hookenv.atstart(configure_sources)
    reactive._apt_registered = True
Beispiel #7
0

def queue_layer_packages():
    """Add packages listed in build-time layer options."""
    # Both basic and apt layer. basic layer will have already installed
    # its defined packages, but rescheduling it here gets the apt layer
    # state set and they will pinned as any other apt layer installed
    # package.
    opts = layer.options()
    for section in ['basic', 'apt']:
        if section in opts and 'packages' in opts[section]:
            charms.apt.queue_install(opts[section]['packages'])


# Per https://github.com/juju-solutions/charms.reactive/issues/33,
# this module may be imported multiple times so ensure the
# initialization hook is only registered once. I have to piggy back
# onto the namespace of a module imported before reactive discovery
# to do this.
if not hasattr(reactive, '_apt_registered'):
    # We need to register this to run every hook, not just during install
    # and config-changed, to protect against race conditions. If we don't
    # do this, then the config in the hook environment may show updates
    # to running hooks well before the config-changed hook has been invoked
    # and the intialization provided an opertunity to be run.
    hookenv.atstart(hookenv.log, 'Initializing Apt Layer')
    hookenv.atstart(clear_removed_package_states)
    hookenv.atstart(configure_sources)
    hookenv.atstart(queue_layer_packages)
    reactive._apt_registered = True
Beispiel #8
0

def remove_snap_proxy_conf(path):
    if os.path.exists(path):
        os.remove(path)


def ensure_path():
    # Per Bug #1662856, /snap/bin may be missing from $PATH. Fix this.
    if '/snap/bin' not in os.environ['PATH'].split(':'):
        os.environ['PATH'] += ':/snap/bin'


# Per https://github.com/juju-solutions/charms.reactive/issues/33,
# this module may be imported multiple times so ensure the
# initialization hook is only registered once. I have to piggy back
# onto the namespace of a module imported before reactive discovery
# to do this.
if not hasattr(reactive, '_snap_registered'):
    # We need to register this to run every hook, not just during install
    # and config-changed, to protect against race conditions. If we don't
    # do this, then the config in the hook environment may show updates
    # to running hooks well before the config-changed hook has been invoked
    # and the intialization provided an opertunity to be run.
    hookenv.atstart(hookenv.log, 'Initializing Snap Layer')
    hookenv.atstart(ensure_snapd)
    hookenv.atstart(ensure_path)
    hookenv.atstart(update_snap_proxy)
    hookenv.atstart(install)
    reactive._snap_registered = True
Beispiel #9
0
from charmhelpers.core import hookenv
from charms.reactive import trace

hookenv.atstart(trace.install_tracer, trace.LogTracer())
Beispiel #10
0
                if not value:
                    continue
                if key not in value['conversations']:
                    continue
                value['conversations'].remove(key)
                value['conversations'].extend(new_keys)
                set_flag(flag, value)


@cmdline.subcommand()
def relation_call(method, relation_name=None, flag=None, state=None, *args):
    """Invoke a method on the class implementing a relation via the CLI"""
    if relation_name:
        relation = relation_from_name(relation_name)
        if relation is None:
            raise ValueError('Relation not found: %s' % relation_name)
    elif flag or state:
        relation = relation_from_flag(flag or state)
        if relation is None:
            raise ValueError('Relation not found: %s' % (flag or state))
    else:
        raise ValueError('Must specify either relation_name or flag')
    result = getattr(relation, method)(*args)
    if isinstance(relation, RelationBase) and method == 'conversations':
        # special case for conversations to make them work from CLI
        result = [c.scope for c in result]
    return result


hookenv.atstart(RelationBase._startup)
Beispiel #11
0
        return
    else:
        if data_changed("layer.vault-kv.config", config):
            set_flag("layer.vault-kv.config.changed")


def manage_app_kv_flags():
    try:
        app_kv = vault_kv.VaultAppKV()
        for key in app_kv.keys():
            app_kv._manage_flags(key)
    except vault_kv.VaultNotReady:
        vault_kv.VaultAppKV._clear_all_flags()


def update_app_kv_hashes():
    try:
        app_kv = vault_kv.VaultAppKV()
        if app_kv.any_changed():
            if hookenv.is_leader():
                # force hooks to run on non-leader units
                hookenv.leader_set({"vault-kv-nonce": host.pwgen(8)})
            # Update the local unit hashes at successful exit
            app_kv.update_hashes()
    except vault_kv.VaultNotReady:
        return


hookenv.atstart(manage_app_kv_flags)
hookenv.atexit(update_app_kv_hashes)
Beispiel #12
0
 def test_manage_calls_atstart(self):
     cb = mock.MagicMock()
     hookenv.atstart(cb)
     manager = services.ServiceManager()
     manager.manage()
     self.assertTrue(cb.called)
Beispiel #13
0
    reactive.helpers.toggle_state('leadership.is_leader', is_leader)

    previous = unitdata.kv().getrange('leadership.settings.', strip=True)
    current = hookenv.leader_get()

    # Handle deletions.
    for key in set(previous.keys()) - set(current.keys()):
        current[key] = None

    any_changed = False
    for key, value in current.items():
        reactive.helpers.toggle_state('leadership.changed.{}'.format(key),
                                      value != previous.get(key))
        if value != previous.get(key):
            any_changed = True
        reactive.helpers.toggle_state('leadership.set.{}'.format(key), value
                                      is not None)
    reactive.helpers.toggle_state('leadership.changed', any_changed)

    unitdata.kv().update(current, prefix='leadership.settings.')


# Per https://github.com/juju-solutions/charms.reactive/issues/33,
# this module may be imported multiple times so ensure the
# initialization hook is only registered once. I have to piggy back
# onto the namespace of a module imported before reactive discovery
# to do this.
if not hasattr(reactive, '_leadership_registered'):
    hookenv.atstart(initialize_leadership_state)
    reactive._leadership_registered = True
Beispiel #14
0
def queue_layer_packages():
    """Add packages listed in build-time layer options."""
    # Both basic and apt layer. basic layer will have already installed
    # its defined packages, but rescheduling it here gets the apt layer
    # state set and they will pinned as any other apt layer installed
    # package.
    opts = layer.options()
    for section in ['basic', 'apt']:
        if section in opts and 'packages' in opts[section]:
            charms.apt.queue_install(opts[section]['packages'])


# Per https://github.com/juju-solutions/charms.reactive/issues/33,
# this module may be imported multiple times so ensure the
# initialization hook is only registered once. I have to piggy back
# onto the namespace of a module imported before reactive discovery
# to do this.
if not hasattr(reactive, '_apt_registered'):
    # We need to register this to run every hook, not just during install
    # and config-changed, to protect against race conditions. If we don't
    # do this, then the config in the hook environment may show updates
    # to running hooks well before the config-changed hook has been invoked
    # and the intialization provided an opertunity to be run.
    hookenv.atstart(hookenv.log, 'Initializing Apt Layer')
    hookenv.atstart(clear_removed_package_states)
    hookenv.atstart(configure_sources)
    hookenv.atstart(queue_layer_packages)
    hookenv.atstart(charms.apt.reset_application_version)
    reactive._apt_registered = True
Beispiel #15
0
        # there is no other way to inform the operator that they have
        # invalid configuration.
        raise SystemExit(0)

    sources = config.get('install_sources')
    keys = config.get('install_keys')
    if reactive.helpers.data_changed('apt.configure_sources', (sources, keys)):
        fetch.configure_sources(update=False,
                                sources_var='install_sources',
                                keys_var='install_keys')
        reactive.set_state('apt.needs_update')

    extra_packages = sorted(config.get('extra_packages', '').split())
    if extra_packages:
        queue_install(extra_packages)


# Per https://github.com/juju-solutions/charms.reactive/issues/33,
# this module may be imported multiple times so ensure the
# initialization hook is only registered once. I have to piggy back
# onto the namespace of a module imported before reactive discovery
# to do this.
if not hasattr(reactive, '_apt_registered'):
    # We need to register this to run every hook, not just during install
    # and config-changed, to protect against race conditions. If we don't
    # do this, then the config in the hook environment may show updates
    # to running hooks well before the config-changed hook has been invoked
    # and the intialization provided an opertunity to be run.
    hookenv.atstart(configure_sources)
    reactive._apt_registered = True
Beispiel #16
0

def remove_snap_proxy_conf(path):
    if os.path.exists(path):
        os.remove(path)


def ensure_path():
    # Per Bug #1662856, /snap/bin may be missing from $PATH. Fix this.
    if '/snap/bin' not in os.environ['PATH'].split(':'):
        os.environ['PATH'] += ':/snap/bin'


# Per https://github.com/juju-solutions/charms.reactive/issues/33,
# this module may be imported multiple times so ensure the
# initialization hook is only registered once. I have to piggy back
# onto the namespace of a module imported before reactive discovery
# to do this.
if not hasattr(reactive, '_snap_registered'):
    # We need to register this to run every hook, not just during install
    # and config-changed, to protect against race conditions. If we don't
    # do this, then the config in the hook environment may show updates
    # to running hooks well before the config-changed hook has been invoked
    # and the intialization provided an opertunity to be run.
    hookenv.atstart(hookenv.log, 'Initializing Snap Layer')
    hookenv.atstart(ensure_snapd)
    hookenv.atstart(ensure_path)
    hookenv.atstart(update_snap_proxy)
    hookenv.atstart(install)
    reactive._snap_registered = True
Beispiel #17
0
            universal_newlines=True,
        )
    except subprocess.CalledProcessError as e:
        raise InvalidBundleError("Proxy ID from header did not match store assertion: " + e.output)


register_trigger(when="config.changed.snapd_refresh", clear_flag="snap.refresh.set")


@when_not("snap.refresh.set")
@when("snap.installed.core")
def change_snapd_refresh():
    """Set the system refresh.timer option"""
    ensure_snapd_min_version("2.31")
    timer = hookenv.config()["snapd_refresh"]
    was_set = reactive.is_flag_set("snap.refresh.was-set")
    if timer or was_set:
        snap.set_refresh_timer(timer)
    reactive.toggle_flag("snap.refresh.was-set", timer)
    reactive.set_flag("snap.refresh.set")


# Bootstrap. We don't use standard reactive handlers to ensure that
# everything is bootstrapped before any charm handlers are run.
hookenv.atstart(hookenv.log, "Initializing Snap Layer")
hookenv.atstart(ensure_snapd)
hookenv.atstart(ensure_path)
hookenv.atstart(update_snap_proxy)
hookenv.atstart(configure_snap_store_proxy)
hookenv.atstart(install)
Beispiel #18
0
def queue_layer_packages():
    """Add packages listed in build-time layer options."""
    # Both basic and apt layer. basic layer will have already installed
    # its defined packages, but rescheduling it here gets the apt layer
    # state set and they will pinned as any other apt layer installed
    # package.
    opts = layer.options()
    for section in ['basic', 'apt']:
        if section in opts and 'packages' in opts[section]:
            charms.apt.queue_install(opts[section]['packages'])


# Per https://github.com/juju-solutions/charms.reactive/issues/33,
# this module may be imported multiple times so ensure the
# initialization hook is only registered once. I have to piggy back
# onto the namespace of a module imported before reactive discovery
# to do this.
if not hasattr(reactive, '_apt_registered'):
    # We need to register this to run every hook, not just during install
    # and config-changed, to protect against race conditions. If we don't
    # do this, then the config in the hook environment may show updates
    # to running hooks well before the config-changed hook has been invoked
    # and the intialization provided an opertunity to be run.
    hookenv.atstart(hookenv.log, 'Initializing Apt Layer')
    hookenv.atstart(clear_removed_package_states)
    hookenv.atstart(configure_sources)
    hookenv.atstart(queue_layer_packages)
    hookenv.atstart(charms.apt.reset_application_version)
    reactive._apt_registered = True
Beispiel #19
0
            check_call([
                "/usr/bin/python3", "-m", "venv", "--prompt", ENV_NAME,
                str(ENV_DIR)
            ])

    # venv.create(str(ENV_DIR), with_pip=True)
    set_flag("venv.active")


@when("venv.active", "venv.ready")
def reconfigure():
    cfg = options.get("venv")
    if "packages" in cfg:
        if helpers.data_changed("venv-packages", cfg["packages"]):
            clear_flag("venv.ready")
    if helpers.data_changed("venv-name", ENV_NAME):
        clear_flag("venv.active")


@when("venv.active")
@when_not("venv.ready")
def add_pip_to_venv():
    cfg = options.get("venv")
    for package in cfg["packages"]:
        pip_install(package)

    set_flag("venv.ready")


hookenv.atstart(init)
Beispiel #20
0
        """
        Whether this collection can be modified.
        """
        return self.raw_data.writeable

    def get(self, key, default=None):
        if key not in self.raw_data:
            return default
        return self[key]

    def __getitem__(self, key):
        value = self.raw_data[key]
        if not value:
            return value
        try:
            return json.loads(value)
        except Exception:
            # Catch json.JSONDecodeError when we drop Python 3.4 support.
            return value

    def __setitem__(self, key, value):
        self.raw_data[key] = json.dumps(value, sort_keys=True)

    def setdefault(self, key, value):
        if key not in self:
            self[key] = value
        return self[key]


hookenv.atstart(Endpoint._startup)
Beispiel #21
0
        raise InvalidBundleError(
            'Proxy ID from header did not match store assertion: ' + e.output)


register_trigger(when='config.changed.snapd_refresh',
                 clear_flag='snap.refresh.set')


@when_not('snap.refresh.set')
@when('snap.installed.core')
def change_snapd_refresh():
    """Set the system refresh.timer option"""
    ensure_snapd_min_version('2.31')
    timer = hookenv.config()['snapd_refresh']
    was_set = reactive.is_flag_set('snap.refresh.was-set')
    if timer or was_set:
        snap.set_refresh_timer(timer)
    reactive.toggle_flag('snap.refresh.was-set', timer)
    reactive.set_flag('snap.refresh.set')


# Bootstrap. We don't use standard reactive handlers to ensure that
# everything is bootstrapped before any charm handlers are run.
hookenv.atstart(hookenv.log, 'Initializing Snap Layer')
hookenv.atstart(ensure_snapd)
hookenv.atstart(ensure_path)
hookenv.atstart(update_snap_proxy)
hookenv.atstart(configure_snap_store_proxy)
hookenv.atstart(install)
hookenv.atstart(check_refresh_available)
Beispiel #22
0
    # Set reactive state for requested locks.
    for lock in requested:
        log('Requested {} lock'.format(lock), hookenv.DEBUG)
        charms.reactive.set_state('coordinator.requested.{}'.format(lock))

    # Set reactive state for locks that have been granted.
    for lock in granted:
        log('Granted {} lock'.format(lock), hookenv.DEBUG)
        charms.reactive.set_state('coordinator.granted.{}'.format(lock))

    # Remove reactive state for locks that have been released.
    for lock in (previously_granted - granted):
        log('Dropped {} lock'.format(lock), hookenv.DEBUG)
        charms.reactive.remove_state('coordinator.granted.{}'.format(lock))

    # Remove requested state for locks no longer requested and not granted.
    for lock in (previously_requested - requested - granted):
        log('Request for {} lock was dropped'.format(lock), hookenv.DEBUG)
        charms.reactive.remove_state('coordinator.requested.{}'.format(lock))


# Per https://github.com/juju-solutions/charms.reactive/issues/33,
# this module may be imported multiple times so ensure the
# initialization hook is only registered once. I have to piggy back
# onto the namespace of a module imported before reactive discovery
# to do this.
if not hasattr(charms.reactive, '_coordinator_registered'):
    hookenv.atstart(initialize_coordinator_state)
    charms.reactive._coordinator_registered = True
Beispiel #23
0
        self._flush()

        self._active_handlers = next_handlers


_tracer = None


def install_tracer(tracer):
    global _tracer
    _tracer = tracer
    # Disable tracing when we hit atexit, to avoid spam from layers
    # such as when the base layer tears down its automatic flags.
    hookenv.atexit(install_tracer, NullTracer())


def tracer():
    global _tracer
    return _tracer


# On import, the NullTracer is installed to avoid unit tests having
# to mock hookenv.log.
install_tracer(NullTracer())

# Install the LogTracer by default, after discovery, before running
# handlers. If this is too noisy for some, we can make it optional
# via layer.yaml. Using hookenv.atstart, because the tests already
# mock it.
hookenv.atstart(install_tracer, LogTracer())