Example #1
0
class SlurmctldCharm(CharmBase):
    slurm_instance_manager_cls = SlurmSnapInstanceManager

    def __init__(self, *args):
        super().__init__(*args)

        self.dbd_requires = HostPortRequires(self, "slurmdbd-host-port")
        self.fw_adapter = FrameworkAdapter(self.framework)
        self.slurm_snap = self.slurm_instance_manager_cls(self, "slurmdctld")
        self.munge = MungeProvides(self, "munge")

        event_handler_bindings = {
            self.on.install:
            self._on_install,
            self.dbd_requires.on.host_port_available:
            self._on_dbd_host_port_available,
        }
        for event, handler in event_handler_bindings.items():
            self.fw_adapter.observe(event, handler)

    def _on_install(self, event):
        handle_install(
            event,
            self.fw_adapter,
            self.slurm_snap,
        )

    def _on_dbd_host_port_available(self, event):
        handle_dbd_host_port_available(
            event,
            self.fw_adapter,
        )
    def __init__(self, charm, relation_name):
        super().__init__(charm, relation_name)

        self.fw_adapter = FrameworkAdapter(self.framework)
        self.relation_name = relation_name

        self.fw_adapter.observe(charm.on[relation_name].relation_changed,
                                self.on_relation_changed)
Example #3
0
class SlurmdbdCharm(CharmBase):
    _state = StoredState() 
    slurm_instance_manager_cls = SlurmSnapInstanceManager

    def __init__(self, *args):
        super().__init__(*args)

        self._state.set_default(db_info=None)
        self._state.set_default(munge_key=None)
        
        # provides host port to slurmctld
        self.dbd_provides = HostPortProvides(self, "slurmdbd-host-port", f'{socket.gethostname()}', '6819')
        self.slurm_snap = self.slurm_instance_manager_cls(self, "slurmdbd")
        self.fw_adapter = FrameworkAdapter(self.framework) 
        self.db = MySQLClient(self, "db")
        self.munge = MungeRequires(self, "munge")

        event_handler_bindings = {
            self.db.on.database_available: self._on_database_available,
            self.munge.on.munge_available: self._on_munge_available,
            self.on.install: self._on_install,
            self.on.start: self._on_start,
        }
        for event, handler in event_handler_bindings.items():
            self.fw_adapter.observe(event, handler)
        
    def _on_install(self, event):
        handle_install(
            event,
            self.fw_adapter,
            self.slurm_snap,
            self.dbd_provides,
        )

    def _on_start(self, event):
        handle_start(
            event,
            self.fw_adapter,
            self.slurm_snap,
            self._state,
        )

    def _on_database_available(self, event):
        handle_database_available(
            event,
            self.fw_adapter,
            self._state,
        )

    def _on_munge_available(self, event):
        handle_munge_available(
            event,
            self.fw_adapter,
            self.slurm_snap,
            self._state,
        )
    def test__am_i_leader__returns_true_if_unit_is_leader(self):
        # Setup
        mock_framework = create_autospec(self.create_framework(),
                                         spec_set=True)
        mock_framework.model.unit.is_leader.return_value = True

        # Exercise
        adapter = FrameworkAdapter(mock_framework)
        is_leader = adapter.am_i_leader()

        # Assert
        assert is_leader
Example #5
0
class Charm(CharmBase):
    _stored = StoredState()

    def __init__(self, *args):
        super().__init__(*args)

        # Abstract out framework and friends so that this object is not
        # too tightly coupled with the underlying framework's implementation.
        # From this point forward, our Charm object will only interact with the
        # adapter and not directly with the framework.
        self.fw_adapter = FrameworkAdapter(self.framework)
        self.prometheus = PrometheusInterface(self, 'http-api')
        self.alertmanager = AlertManagerInterface(self, 'alertmanager')
        # Bind event handlers to events
        event_handler_bindings = {
            self.on.start: self.on_start,
            self.on.config_changed: self.on_config_changed,
            self.on.upgrade_charm: self.on_upgrade,
            self.on.stop: self.on_stop,
            self.alertmanager.on.new_relation:
            self.on_new_alertmanager_relation
        }
        for event, handler in event_handler_bindings.items():
            self.fw_adapter.observe(event, handler)

        self._stored.set_default(is_started=False)

    # DELEGATORS

    # These delegators exist to decouple the actual handlers from the
    # underlying framework which has some very specific requirements that
    # do not always apply to every event. For instance if we were to add
    # an interface in our initializer, we would be forced to write unit
    # tests that mock out that object even for handlers that do not need
    # it. This hard coupling results in verbose tests that contain unused
    # mocks. These tests tend to be hard to follow. To counter that, the
    # logic is moved away from this class.

    def on_config_changed(self, event):
        on_config_changed_handler(event, self.fw_adapter, self._stored)

    def on_new_alertmanager_relation(self, event):
        on_new_alertmanager_relation_handler(event, self.fw_adapter)

    def on_start(self, event):
        on_start_handler(event, self.fw_adapter)

    def on_upgrade(self, event):
        on_upgrade_handler(event, self.fw_adapter)

    def on_stop(self, event):
        on_stop_handler(event, self.fw_adapter)
Example #6
0
    def test__get_app_name__returns_the_name_of_the_app(self):
        # Setup
        mock_framework = create_autospec(self.create_framework(),
                                         spec_set=True)
        mock_app_name = str(uuid4())
        mock_framework.model.app.name = mock_app_name

        # Exercise
        adapter = FrameworkAdapter(mock_framework)
        app_name = adapter.get_app_name()

        # Assert
        assert app_name == mock_app_name
Example #7
0
    def test__get_config__returns_a_value_given_a_key(self):
        # Setup
        mock_framework = create_autospec(self.create_framework(),
                                         spec_set=True)
        mock_key = str(uuid4())
        mock_value = str(uuid4())
        mock_framework.model.config = {mock_key: mock_value}

        # Exercise
        adapter = FrameworkAdapter(mock_framework)
        value = adapter.get_config(mock_key)

        # Assert
        assert value == mock_value
class PrometheusInterface(Object):
    on = PrometheusEvents()

    def __init__(self, charm, relation_name):
        super().__init__(charm, relation_name)

        self.fw_adapter = FrameworkAdapter(self.framework)
        self.relation_name = relation_name

        self.fw_adapter.observe(charm.on[relation_name].relation_changed,
                                self.on_relation_changed)

    def on_relation_changed(self, event):
        logger.debug("Emitting new_prom_rel event")
        self.on.new_prom_rel.emit()
        logger.debug("Done emitting new_prom_rel_event")
Example #9
0
    def __init__(self, charm, key):
        super().__init__(charm, key)
        self.snap_mode = key
        self.fw_adapter = FrameworkAdapter(self.framework)

        # Set the template and config file paths based on the snap.mode.
        # Throw an exception if initialized with an unsupported snap.mode.
        if self.snap_mode == "slurmdbd":
            self.slurm_config_yaml = self.SLURM_CONFIGURATOR_TEMPLATES_DIR / 'slurmdbd.yaml'
            self.slurm_config_template = self.TEMPLATE_DIR / 'slurmdbd.yaml.tmpl'
        elif self.snap_mode in ["slurmd", "slurmrestd", "slurmctld"]:
            self.slurm_config_yaml = self.SLURM_CONFIGURATOR_TEMPLATES_DIR / 'slurm.yaml'
            self.slurm_config_template = self.TEMPLATE_DIR / 'slurm.yaml.tmpl'
        else:
            self.logger.error(
                f"Slurm component not supported: {self.snap_mode}",
                exc_info=True)
Example #10
0
class AlertManagerInterface(Object):
    on = AlertManagerEvents()

    def __init__(self, charm, relation_name):
        super().__init__(charm, relation_name)

        self.fw_adapter = FrameworkAdapter(self.framework)
        self.relation_name = relation_name

        self.fw_adapter.observe(charm.on[relation_name].relation_changed,
                                self.on_relation_changed)

    def on_relation_changed(self, event):
        remote_data = event.relation.data[event.unit]
        logging.debug("Received remote_data: {}".format(dict(remote_data)))

        logger.debug("Emitting new_relation event")
        self.on.new_relation.emit(remote_data)
Example #11
0
    def test__get_config__returns_the_config_dict_when_no_key_given(self):
        # Setup
        mock_framework = create_autospec(self.create_framework(),
                                         spec_set=True)
        mock_config = {
            str(uuid4()): str(uuid4()),
            str(uuid4()): str(uuid4()),
            str(uuid4()): str(uuid4()),
            str(uuid4()): str(uuid4()),
        }
        mock_framework.model.config = mock_config

        # Exercise
        adapter = FrameworkAdapter(mock_framework)
        config = adapter.get_config()

        # Assert
        assert config == mock_config
Example #12
0
    def test__get_image_meta__returns_an_image_meta_object(
            self, mock_fetch_image_meta_func):
        # Setup
        framework = self.create_framework()
        mock_framework = create_autospec(framework, spec_set=True)
        mock_framework.model = create_autospec(framework.model, spec_set=True)

        image_name = uuid4()

        # Exercise
        adapter = FrameworkAdapter(mock_framework)
        image_meta = adapter.get_image_meta(image_name)

        # Assert
        assert mock_fetch_image_meta_func.call_count == 1
        assert mock_fetch_image_meta_func.call_args == \
            call(image_name, mock_framework.model.resources)

        assert image_meta == mock_fetch_image_meta_func.return_value
Example #13
0
class PrometheusInterface(Object):
    on = PrometheusEvents()

    def __init__(self, charm, relation_name):
        super().__init__(charm, relation_name)
        self.fw = FrameworkAdapter(self.framework)
        self.relation_name = relation_name
        self.fw.observe(charm.on[relation_name].relation_joined,
                        self.on_relation_joined)

    def render_relation_data(self):
        logging.debug('render-relation-data in')
        for relation in self.model.relations[self.relation_name]:
            relation.data[self.model.unit]['prometheus-port'] = \
                str(PROMETHEUS_ADVERTISED_PORT)
        logging.debug('render-relation-data out')

    def on_relation_joined(self, event):
        logging.debug("on-joined; emit new-client")
        self.on.new_client.emit(event.relation)
        self.render_relation_data()
Example #14
0
    def __init__(self, *args):
        super().__init__(*args)

        # Abstract out framework and friends so that this object is not
        # too tightly coupled with the underlying framework's implementation.
        # From this point forward, our Charm object will only interact with the
        # adapter and not directly with the framework.
        self.fw_adapter = FrameworkAdapter(self.framework)
        self.prometheus = PrometheusInterface(self, 'http-api')
        self.alertmanager = AlertManagerInterface(self, 'alertmanager')
        # Bind event handlers to events
        event_handler_bindings = {
            self.on.start: self.on_start,
            self.on.config_changed: self.on_config_changed,
            self.on.upgrade_charm: self.on_upgrade,
            self.on.stop: self.on_stop,
            self.alertmanager.on.new_relation:
            self.on_new_alertmanager_relation
        }
        for event, handler in event_handler_bindings.items():
            self.fw_adapter.observe(event, handler)

        self._stored.set_default(is_started=False)
    def __init__(self, *args):
        super().__init__(*args)

        # Abstract out framework and friends so that this object is not
        # too tightly coupled with the underlying framework's implementation.
        # From this point forward, our Charm object will only interact with the
        # adapter and not directly with the framework.
        self.fw_adapter = FrameworkAdapter(self.framework)

        self.prom_relation_name = 'prometheus'
        self.prom_interface = \
            PrometheusInterface(self, self.prom_relation_name)

        # Bind event handlers to events
        event_handler_bindings = {
            self.on.start: self.on_start,
            self.on.config_changed: self.on_config_changed,
            self.on.upgrade_charm: self.on_upgrade,
            self.on.stop: self.on_stop,
            self.prom_interface.on.new_prom_rel: self.on_new_prom_rel
        }
        for event, handler in event_handler_bindings.items():
            self.fw_adapter.observe(event, handler)
Example #16
0
class SlurmSnapInstanceManager(Object):
    """
    responsible for installing the slurm_snap, connecting to network, and
    setting the snap mode
    """

    _stored = StoredState()

    on = SlurmSnapInstanceManagerEvents()

    logger = logging.getLogger()

    MUNGE_KEY_PATH = Path("/var/snap/slurm/common/etc/munge/munge.key")
    SLURM_CONFIGURATOR_TEMPLATES_DIR = Path(
        "/var/snap/slurm/common/etc/slurm-configurator")
    TEMPLATE_DIR = Path(f"{os.getcwd()}/templates")

    def __init__(self, charm, key):
        super().__init__(charm, key)
        self.snap_mode = key
        self.fw_adapter = FrameworkAdapter(self.framework)

        # Set the template and config file paths based on the snap.mode.
        # Throw an exception if initialized with an unsupported snap.mode.
        if self.snap_mode == "slurmdbd":
            self.slurm_config_yaml = self.SLURM_CONFIGURATOR_TEMPLATES_DIR / 'slurmdbd.yaml'
            self.slurm_config_template = self.TEMPLATE_DIR / 'slurmdbd.yaml.tmpl'
        elif self.snap_mode in ["slurmd", "slurmrestd", "slurmctld"]:
            self.slurm_config_yaml = self.SLURM_CONFIGURATOR_TEMPLATES_DIR / 'slurm.yaml'
            self.slurm_config_template = self.TEMPLATE_DIR / 'slurm.yaml.tmpl'
        else:
            self.logger.error(
                f"Slurm component not supported: {self.snap_mode}",
                exc_info=True)

    @property
    def _hostname(self):
        return socket.gethostname().split(".")[0]

    def set_snap_mode(self):
        """Set the snap mode, thorw an exception if it fails.
        """
        try:
            subprocess.call([
                "snap",
                "set",
                "slurm",
                f"snap.mode={self.snap_mode}",
            ])
        except subprocess.CalledProcessError as e:
            self.logger.error(
                f"Setting the snap.mode failed. snap.mode={self.snap_mode} - {e}",
                exc_info=True)

    def install(self):
        self._install_snap()
        self._snap_connect()

    def _snap_connect(self, slot=None):
        connect_commands = [
            ["snap", "connect", "slurm:network-control"],
            ["snap", "connect", "slurm:system-observe"],
            ["snap", "connect", "slurm:hardware-observe"],
        ]

        for connect_command in connect_commands:
            if slot:
                connect_command.append(slot)
            try:
                subprocess.call(connect_command)
            except subprocess.CalledProcessError as e:
                self.logger.error(
                    f"Could not connect snap interface: {e}",
                    exc_info=True,
                )

    def _install_snap(self):
        snap_install_cmd = ["snap", "install"]
        resource_path = None
        try:
            resource_path = self.model.resources.fetch('slurm')
        except ModelError as e:
            logger.error(
                f"Resource could not be found when executing: {e}",
                exc_info=True,
            )
        if resource_path:
            snap_install_cmd.append(resource_path)
            snap_install_cmd.append("--dangerous")
        else:
            snap_store_channel = self.fw_adapter.get_config(
                "snap-store-channel")
            snap_install_cmd.append("slurm")
            snap_install_cmd.append(f"--{snap_store_channel}")
        try:
            subprocess.call(snap_install_cmd)
        except subprocess.CalledProcessError as e:
            logger.error(
                f"Could not install the slurm snap using the command: {e}",
                exc_info=True)

    def write_munge_key(self, munge_key):
        munge = b64decode(munge_key.encode())
        self.MUNGE_KEY_PATH.write_bytes(munge)

    def write_config(self, context):

        ctxt = {}
        source = self.slurm_config_template
        target = self.slurm_config_yaml

        if not type(context) == dict:
            self.framework.set_unit_status(
                MaintenanceStatus("context not of type dict"))
            return
        else:
            ctxt = {**{"hostname": self._hostname}, **context}
        if not source.exists():
            # raise Exception(f"Source config {source} does not exist - Please debug.")
            self.framework.set_unit_status(
                MaintenanceStatus("source doesn't exist"))
        if target.exists():
            target.unlink()

        target.write_text(source.read_text().format(**ctxt))