Commit 84f49688 authored by lavado's avatar lavado
Browse files

Merge branch 'improve-vyos-fix-magmagw' into 'master'

Improve vyos-charm and fix some edge cases in magmagw-charm

See merge request !87
parents 4f652622 092fe2ec
Loading
Loading
Loading
Loading
+0 −12
Original line number Diff line number Diff line
@@ -9,18 +9,6 @@ configure-remote:
  required:
    - magmaIP

# Standard OSM functions
start:
  description: "Stop the service on the VNF."
stop:
  description: "Stop the service on the VNF."
restart:
  description: "Stop the service on the VNF."
reboot:
  description: "Reboot the VNF virtual machine."
upgrade:
  description: "Upgrade the software on the VNF."

# Required by charms.osm.sshproxy
run:
  description: "Run an arbitrary command"
+13 −209
Original line number Diff line number Diff line
@@ -30,145 +30,48 @@ from ops.model import (
import os
import subprocess
import traceback
from proxy_cluster import ProxyCluster

from charms.osm.sshproxy import SSHProxy
from charms.osm.sshproxy import SSHProxyCharm
from charms.osm import libansible


class SSHKeysInitialized(EventBase):
    def __init__(self, handle, ssh_public_key, ssh_private_key):
        super().__init__(handle)
        self.ssh_public_key = ssh_public_key
        self.ssh_private_key = ssh_private_key

    def snapshot(self):
        return {
            "ssh_public_key": self.ssh_public_key,
            "ssh_private_key": self.ssh_private_key,
        }

    def restore(self, snapshot):
        self.ssh_public_key = snapshot["ssh_public_key"]
        self.ssh_private_key = snapshot["ssh_private_key"]


class ProxyClusterEvents(CharmEvents):
    ssh_keys_initialized = EventSource(SSHKeysInitialized)


class SimpleHAProxyCharm(CharmBase):

    state = StoredState()
    on = ProxyClusterEvents()

class VyosCharm(SSHProxyCharm):
    def __init__(self, framework, key):
        super().__init__(framework, key)

        # An example of setting charm state
        # that's persistent across events
        self.state.set_default(is_started=False)

        self.peers = ProxyCluster(self, "proxypeer")

        if not self.state.is_started:
            self.state.is_started = True

        # Register all of the events we want to observe
        self.framework.observe(self.on.config_changed, self.on_config_changed)
        self.framework.observe(self.on.install, self.on_install)
        self.framework.observe(self.on.start, self.on_start)
        self.framework.observe(self.on.upgrade_charm, self.on_upgrade_charm)
        # Charm actions (primitives)
        self.framework.observe(self.on.configure_remote_action, self.on_configure_remote_action)
        # OSM actions (primitives)
        self.framework.observe(self.on.start_action, self.on_start_action)
        self.framework.observe(self.on.stop_action, self.on_stop_action)
        self.framework.observe(self.on.restart_action, self.on_restart_action)
        self.framework.observe(self.on.reboot_action, self.on_reboot_action)
        self.framework.observe(self.on.upgrade_action, self.on_upgrade_action)
        # SSH Proxy actions (primitives)
        self.framework.observe(self.on.generate_ssh_key_action, self.on_generate_ssh_key_action)
        self.framework.observe(self.on.get_ssh_public_key_action, self.on_get_ssh_public_key_action)
        self.framework.observe(self.on.run_action, self.on_run_action)
        self.framework.observe(self.on.verify_ssh_credentials_action, self.on_verify_ssh_credentials_action)

        self.framework.observe(self.on.proxypeer_relation_changed, self.on_proxypeer_relation_changed)

    def get_ssh_proxy(self):
        """Get the SSHProxy instance"""
        proxy = SSHProxy(
            hostname=self.model.config["ssh-hostname"],
            username=self.model.config["ssh-username"],
            password=self.model.config["ssh-password"],
        self.framework.observe(
            self.on.configure_remote_action, self.on_configure_remote_action
        )
        return proxy

    def on_proxypeer_relation_changed(self, event):
        if self.peers.is_cluster_initialized:
            pubkey = self.peers.ssh_public_key
            privkey = self.peers.ssh_private_key
            SSHProxy.write_ssh_keys(public=pubkey, private=privkey)
            self.on_config_changed(event)
        else:
            event.defer()

    def on_config_changed(self, event):
        """Handle changes in configuration"""
        unit = self.model.unit

        # Unit should go into a waiting state until verify_ssh_credentials is successful
        unit.status = WaitingStatus("Waiting for SSH credentials")
        proxy = self.get_ssh_proxy()

        verified = proxy.verify_credentials()
        if verified:
            unit.status = ActiveStatus()
        else:
            unit.status = BlockedStatus("Invalid SSH credentials.")
        super().on_config_changed(event)

    def on_install(self, event):
        unit = self.model.unit
        unit.status = MaintenanceStatus("Installing Ansible")
        """Called when the charm is being installed"""
        super().on_install(event)
        self.unit.status = MaintenanceStatus("Installing Ansible")
        libansible.install_ansible_support()
        unit.status = ActiveStatus()
        self.unit.status = ActiveStatus()

    def on_start(self, event):
        """Called when the charm is being installed"""
        if not self.peers.is_joined:
            event.defer()
            return

        unit = self.model.unit

        if not SSHProxy.has_ssh_key():
            unit.status = MaintenanceStatus("Generating SSH keys...")
            pubkey = None
            privkey = None
            if self.is_leader:
                if self.peers.is_cluster_initialized:
                    SSHProxy.write_ssh_keys(
                        public=self.peers.ssh_public_key,
                        private=self.peers.ssh_private_key,
                    )
                else:
                    SSHProxy.generate_ssh_key()
                    self.on.ssh_keys_initialized.emit(
                        SSHProxy.get_ssh_public_key(), SSHProxy.get_ssh_private_key()
                    )
                unit.status = ActiveStatus()
            else:
                unit.status = WaitingStatus("Waiting for leader to populate the keys")
        """Called when the charm is being started"""
        super().on_start(event)

    def on_configure_remote_action(self, event):
        """Configure remote."""

        if self.is_leader:
        if self.unit.is_leader():
            try:
                config = self.model.config
                magmaIP = event.params["magmaIP"]
                dict_vars = {"MAGMA_AGW_IP": magmaIP}
                proxy = self.get_ssh_proxy()
                result = libansible.execute_playbook(
                    "configure-remote.yaml",
                    config["ssh-hostname"],
@@ -188,106 +91,7 @@ class SimpleHAProxyCharm(CharmBase):

    def on_upgrade_charm(self, event):
        """Upgrade the charm."""
        unit = self.model.unit

        # Mark the unit as under Maintenance.
        unit.status = MaintenanceStatus("Upgrading charm")

        self.on_install(event)

        # When maintenance is done, return to an Active state
        unit.status = ActiveStatus()

    ###############
    # OSM methods #
    ###############
    def on_start_action(self, event):
        """Start the VNF service on the VM."""
        pass

    def on_stop_action(self, event):
        """Stop the VNF service on the VM."""
        pass

    def on_restart_action(self, event):
        """Restart the VNF service on the VM."""
        pass

    def on_reboot_action(self, event):
        """Reboot the VM."""
        if self.is_leader:
            proxy = self.get_ssh_proxy()
            stdout, stderr = proxy.run("sudo reboot")
            if len(stderr):
                event.fail(stderr)
        else:
            event.fail("Unit is not leader")
            return

    def on_upgrade_action(self, event):
        """Upgrade the VNF service on the VM."""
        pass

    #####################
    # SSH Proxy methods #
    #####################
    def on_generate_ssh_key_action(self, event):
        """Generate a new SSH keypair for this unit."""
        if self.is_leader:
            if not SSHProxy.generate_ssh_key():
                event.fail("Unable to generate ssh key")
        else:
            event.fail("Unit is not leader")
            return

    def on_get_ssh_public_key_action(self, event):
        """Get the SSH public key for this unit."""
        if self.is_leader:
            pubkey = SSHProxy.get_ssh_public_key()
            event.set_results({"pubkey": SSHProxy.get_ssh_public_key()})
        else:
            event.fail("Unit is not leader")
            return

    def on_run_action(self, event):
        """Run an arbitrary command on the remote host."""
        if self.is_leader:
            cmd = event.params["command"]
            proxy = self.get_ssh_proxy()
            stdout, stderr = proxy.run(cmd)
            event.set_results({"output": stdout})
            if len(stderr):
                event.fail(stderr)
        else:
            event.fail("Unit is not leader")
            return

    def on_verify_ssh_credentials_action(self, event):
        """Verify the SSH credentials for this unit."""
        if self.is_leader:
            proxy = self.get_ssh_proxy()

            verified = proxy.verify_credentials()
            if verified:
                print("Verified!")
                event.set_results({"verified": True})
            else:
                print("Verification failed!")
                event.set_results({"verified": False})
        else:
            event.fail("Unit is not leader")
            return

    @property
    def is_leader(self):
        # update the framework to include self.unit.is_leader()
        return self.model.unit.is_leader()


class LeadershipError(ModelError):
    def __init__(self):
        super().__init__("not leader")


if __name__ == "__main__":
    main(SimpleHAProxyCharm)
    main(VyosCharm)
+0 −71
Original line number Diff line number Diff line
# Copyright 2020 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.

from ops.framework import Object, StoredState


class ProxyCluster(Object):

    state = StoredState()

    def __init__(self, charm, relation_name):
        super().__init__(charm, relation_name)
        self._relation_name = relation_name
        self._relation = self.framework.model.get_relation(self._relation_name)

        self.framework.observe(charm.on.ssh_keys_initialized, self.on_ssh_keys_initialized)

        self.state.set_default(ssh_public_key=None)
        self.state.set_default(ssh_private_key=None)

    def on_ssh_keys_initialized(self, event):
        if not self.framework.model.unit.is_leader():
            raise RuntimeError("The initial unit of a cluster must also be a leader.")

        self.state.ssh_public_key = event.ssh_public_key
        self.state.ssh_private_key = event.ssh_private_key
        if not self.is_joined:
            event.defer()
            return

        self._relation.data[self.model.app][
            "ssh_public_key"
        ] = self.state.ssh_public_key
        self._relation.data[self.model.app][
            "ssh_private_key"
        ] = self.state.ssh_private_key

    @property
    def is_joined(self):
        return self._relation is not None

    @property
    def ssh_public_key(self):
        if self.is_joined:
            return self._relation.data[self.model.app].get("ssh_public_key")

    @property
    def ssh_private_key(self):
        if self.is_joined:
            return self._relation.data[self.model.app].get("ssh_private_key")

    @property
    def is_cluster_initialized(self):
        return (
            True
            if self.is_joined
            and self._relation.data[self.model.app].get("ssh_public_key")
            and self._relation.data[self.model.app].get("ssh_private_key")
            else False
        )
+0 −12
Original line number Diff line number Diff line
@@ -67,18 +67,6 @@ add-test-subscriber:
      type: "string"
      default: ""

# Standard OSM functions
start:
  description: "Stop the service on the VNF."
stop:
  description: "Stop the service on the VNF."
restart:
  description: "Stop the service on the VNF."
reboot:
  description: "Reboot the VNF virtual machine."
upgrade:
  description: "Upgrade the software on the VNF."

# Required by charms.osm.sshproxy
run:
  description: "Run an arbitrary command"
+10 −1
Original line number Diff line number Diff line
@@ -146,7 +146,16 @@ class MagmaAGWProxyCharm(SSHProxyCharm):
        """Resets the hardware ID"""
        if self.unit.is_leader():
            proxy = self.get_ssh_proxy()
            attempt = 0
            while attempt < 50:
                try:
                    stdout, stderr = proxy.run("sudo snowflake --force-new-key")
                    break
                except subprocess.CalledProcessError:
                    attempt += 1
                    import time

                    time.sleep(5)
            event.set_results({"output": stdout, "stderr": stderr})
        else:
            event.fail("Unit is not leader")