#!/opt/tasvmruntime-py/bin/python3

# Initconf hook script to apply RA configuration, and activate the RA and service.
# This script needs to be more careful than the build hook script,
# as it will be run multiple times during a VM's lifecycle,
# and hence has to be prepared for changes it might apply to have already been made.

import os
from pathlib import Path
import re
import subprocess
import sys
import yaml

# Location of rhino-console.
RHINO_CONSOLE = Path.home() / "rhino" / "client" / "bin" / "rhino-console"

# Location of the keystore for HTTPS.
# As per the HTTP RA example documentation, the location is important
# as it must match configured Rhino permissions.
KEYSTORE = Path.home() / "rhino" / "http-ra.ks"

# Password for the keystore.
# Note: hardcoding a password like this is very insecure.
# Prefer instead to make it configurable through your configuration file.
KEYSTORE_PASSWORD = "changeit"

# Name of the RA entity.
HTTP_RA_ENTITY = "http"


def run_rhino_console(cmd: list[str]) -> str:
    """
    Runs a rhino-console command.

    :param cmd: The command to run and its arguments, as separate list elements.
    :return: Output from rhino-console.
    """
    return subprocess.check_output([RHINO_CONSOLE] + cmd, text=True, stderr=subprocess.STDOUT)


def main() -> None:
    """
    Main routine.
    """
    # Load the custom-config-data.yaml file.
    # The first CLI argument given to this script will be the directory
    # where that file can be found.
    config_dir = Path(sys.argv[1])
    custom_config_data = config_dir / "custom-config-data.yaml"

    config_file_contents = yaml.safe_load(custom_config_data.read_text())
    config = config_file_contents["deployment-config:custom-data"]["custom-config"]
    listen_port = config.get("listen-port", 8000)
    secure_listen_port = config.get("secure-listen-port", 8002)

    # Load SDF.
    sdf = config_dir / "sdf-rvt.yaml"
    sdf_contents = yaml.safe_load(sdf.read_text())

    # Determine this VM's signaling IP address.
    # Start by locating our site and VNFC.
    our_hostname = os.uname().nodename
    for vnfc in [
        vnfc for site in sdf_contents["msw-deployment:deployment"]["sites"]
        for vnfc in site["vnfcs"]
    ]:
        instance_hostnames = [
            instance["name"] for instance in vnfc["cluster-configuration"]["instances"]
        ]
        if our_hostname in instance_hostnames:
            this_vnfc = vnfc
            this_vm_index = instance_hostnames.index(our_hostname)
            break
    else:
        raise ValueError("Couldn't find our VNFC in the SDF")

    # Find the signaling network (which carries the HTTP traffic type).
    # Within that, the IP address will be at the same index as above in its list of IPs.
    for network in this_vnfc["networks"]:
        if "http" in network["traffic-types"]:
            this_vm_sig_ip = network["ip-addresses"]["ip"][this_vm_index]
            break
    else:
        raise ValueError("Couldn't find the signaling network in the SDF")

    # Generate a keystore, if we don't have one already.
    if not KEYSTORE.exists():
        subprocess.check_call(
            [
                "keytool",
                "-keystore", os.fspath(KEYSTORE),
                "-storepass", KEYSTORE_PASSWORD,
                "-genkeypair",
                "-dname", "O=Metaswitch,OU=Rhino,CN=HTTP Example Server,C=NZ"
            ]
        )

    # Configure the RA entity with some properties.
    run_rhino_console(
        [
            "updateraentityconfigproperties",
            "http",
            "ListenAddress", this_vm_sig_ip,
            "ListenPort", str(listen_port),
            "SecureListenPort", str(secure_listen_port),
            "KeyStore", os.fspath(KEYSTORE),
            "KeyStorePassword", KEYSTORE_PASSWORD,
        ]
    )

    # A restart of the RA is necessary to pick up configuration changes.
    # Deactivate the RA and wait until it has stopped.
    # If this is the first time the script runs, the RA entity will be inactive
    # and so this code doesn't need to take any action.
    if HTTP_RA_ENTITY in run_rhino_console(["listraentitiesbystate", "Active"]):
        run_rhino_console(["deactivateraentity", "http"])
        run_rhino_console(["waittilraentityisinactive", "http"])

    # Now activate the RA again.
    run_rhino_console(["activateraentity", "http"])

    # Determine the service ID, and whether the service is active.
    # The output will look like:
    #
    # Services in Inactive state on node 101:
    #  ServiceID[name=HTTP Ping Service,vendor=OpenCloud,version=1.1]
    #
    # where the part within the square brackets is the service ID.
    #
    # If the service is not found in the list of Inactive services,
    # we can assume that it is already active.
    output = run_rhino_console(["listservicesbystate", "Inactive"])
    for line in output.splitlines():
        if matches := re.search(r"\[(name=HTTP Ping Service[^\]]+)\]", line):
            # Activate the service.
            service_id = matches.group(1)
            run_rhino_console(["activateservice", service_id])
            break


if __name__ == "__main__":
    main()
