#!/bin/bash

# set -x
. functions.sh

# Storage type
storage_format="raw"

# Models for nic and storage
nic_model="virtio"
stg_bus="scsi"

# Time between building VMs
build_fanout=60

maas_assign_networks()
{
    maas_auto_assign_networks $1
}

# Attempts to auto assign all the networks for a host
# Note: This only works straight after a commissioning of a machine
maas_auto_assign_networks()
{
    system_id=$1

    # Grabs all the interfaces that are attached to the system
    node_interfaces=$(maas ${maas_profile} interfaces read ${system_id} \
        | jq ".[] | {id:.id, name:.name, mode:.links[].mode, subnet:.links[].subnet.id, vlan:.vlan.vid }" --compact-output)

    # This for loop will go through all the interfaces and enable Auto-Assign
    # on all ports
    for interface in ${node_interfaces}
    do
        int_id=$(echo $interface | jq ".id" | sed s/\"//g)
        subnet_id=$(echo $interface | jq ".subnet" | sed s/\"//g)
        mode=$(echo $interface | jq ".mode" | sed s/\"//g)
        vlan=$(echo $interface | jq ".vlan" | sed s/\"//g)

        # Although the vlan would have been set the discovered vlan wouldn't,
        # and therefore link[] and discovery[] list won't exist. So we grab
        # the subnet details from subnets that have the vlan assigned/discovered
        # at commissioning stage
        if [[ $subnet_id == null ]] ; then
            subnet_line=$(maas admin subnets read | jq ".[] | {subnet_id:.id, vlan:.vlan.vid, vlan_id:.vlan.id}" --compact-output | grep "vlan\":$vlan,")
            subnet_id=$(echo $subnet_line | jq .subnet_id | sed s/\"//g)
        fi
        # If vlan is the external network, then we want to grab IP via DHCP
        # from the external network. Other networks would be auto mode
        if [[ $vlan -eq $external_vlan ]] && [[ $mode != "dhcp" ]]; then
            new_mode="DHCP"
        elif [[ $mode != "auto" ]] && [[ $mode != "dhcp" ]] ; then
            new_mode="AUTO"
        fi

        # Then finally set link details for all the interfaces that haven't
        # been configured already
        if [[ $new_mode != "AUTO" ]] || [[ $new_mode != "DHCP" ]]; then
            assign_network=$(maas ${maas_profile} interface link-subnet ${system_id} ${int_id} mode=${new_mode} subnet=${subnet_id})
        fi
    done
}

# Calls the 3 functions that creates the VMs
create_vms() {
    install_deps
    maas_login
    create_storage
    build_vms
}

# Calls the 3 functions that creates the VMs
create_juju() {
    install_deps
    maas_login
    create_storage "juju"
    build_vms "juju"
}

# Calls the functions that destroys and cleans up all the VMs
wipe_vms() {
    install_deps
    maas_login
    destroy_vms
}

# Fixes all the networks on all the VMs
network_auto()
{
    install_deps
    maas_login

    for ((virt="$node_start"; virt<=node_count; virt++)); do
        printf -v virt_node %s-%02d "$compute" "$virt"
        system_id=$(maas_system_id ${virt_node})

        maas_auto_assign_networks ${system_id} &
    done
    wait
}

commission_vm()
{
    system_id=$1

    commission_machine=$(maas ${maas_profile} machine commission ${system_id})

    # Ensure that the machine is in ready state before the next step
    ensure_machine_in_state ${system_id} "Ready"

    maas_auto_assign_networks ${system_id}
}

recommission_vms()
{
    install_deps
    maas_login

    for ((virt="$node_start"; virt<=node_count; virt++)); do
        printf -v virt_node %s-%02d "$compute" "$virt"
        system_id=$(maas_system_id ${virt_node})

        commission_vm ${system_id} &

        sleep ${build_fanout}
    done
    wait
}

# Creates the disks for all the nodes
create_storage() {
    # To keep a track of how many juju VMs we have created
    only_juju="false"
    node_count_bak=$node_count
    if [[ $1 == "juju" ]] ; then
        node_count=0
        if [[ $juju_count -lt 1 ]] ; then
            echo "WARNING: requested only create juju, but juju_count = ${juju_count}"
            return 0
        fi
    fi
    for ((virt="$node_start"; virt<=node_count; virt++)); do
        printf -v virt_node %s-%02d "$compute" "$virt"

        # Create th directory where the storage files will be located
        mkdir -p "$storage_path/$virt_node"

        # For all the disks that are defined in the array, create a disk
        for ((disk=0;disk<${#disks[@]};disk++)); do
            file_name="$storage_path/$virt_node/$virt_node-d$((${disk} + 1)).img"

            if [[ ! -f $file_name ]] ; then
                /usr/bin/qemu-img create -f "$storage_format" "${file_name}" "${disks[$disk]}"G &
            fi
        done
    done
    for ((juju=1; juju<=juju_count; juju++)); do
        printf -v virt_node %s-%02d "$hypervisor_name-juju" "$juju"

        # Create th directory where the storage files will be located
        mkdir -p "$storage_path/$virt_node"

        file_name="$storage_path/$virt_node/$virt_node.img"

        if [[ ! -f $file_name ]] ; then
            /usr/bin/qemu-img create -f "$storage_format" ${file_name} "${juju_disk}"G &
        fi
    done
    node_count=$node_count_bak
    wait
}

# The purpose of this function is to stop, release the nodes and wipe the disks
# to save space, and then so that the machines in MAAS can be re-used
wipe_disks() {
    juju_total=1
    doing_juju="false"
    for ((virt="$node_start"; virt<=node_count; virt++)); do
        if [[ $juju_total -le $juju_count ]] ; then
            printf -v virt_node %s-%02d "$hypervisor_name-juju" "$juju_total"
            doing_juju="true"
            (( virt-- ))
            (( juju_total++ ))
        else
            printf -v virt_node %s-%02d "$compute" "$virt"
            doing_juju="false"
        fi

        system_id=$(maas_system_id ${virt_node})

        # Release the machine in MAAS
        release_machine=$(maas ${maas_profile} machine release ${system_id})

        # Ensure that the machine is in ready state before the next step
        ensure_machine_in_state ${system_id} "Ready"

        # Stop the machine if it is running
        # It's probably stopped anyway as per the release above
        virsh --connect qemu:///system shutdown "$virt_node"

        # Remove the disks
        if [[ $doing_juju == "true" ]] ; then
            rm -rf "$storage_path/$virt_node/$virt_node.img"
        else
            for ((disk=0;disk<${#disks[@]};disk++)); do
                rm -rf "$storage_path/$virt_node/$virt_node-d$((${disk} + 1)).img" &
            done
        fi
    done
    # Re-create the storage again from scratch
    create_storage
    wait
}

machine_exists()
{
    node_name=$1

    virsh_machine=$(virsh list --all --name | grep ${node_name})

    if [[ $virsh_machine != "" ]] ; then
        macaddr=$(virsh domiflist ${node_name} | grep br0 | awk '{print $5}')

        echo $macaddr
    else
        echo "false"
    fi

}

# Builds the VMs from scratch, and then adds them to MAAS
build_vms() {
    # To keep a track of how many juju VMs we have created
    juju_total=1
    only_juju="false"
    if [[ $1 == "juju" ]] ; then
        only_juju="true"
        if [[ $juju_count -lt 1 ]] ; then
            echo "WARNING: requested only create juju, but juju_count = ${juju_count}"
            return 0
        fi
    fi

    for ((virt="$node_start"; virt<=node_count; virt++)); do

        # Based on the bridges array, it will generate these amount of MAC
        # addresses and then create the network definitions to add to
        # virt-install
        macaddr=()
        network_spec=""

        # Based on the type of network we are using we will assign variables
        # such that this can be either bridge or network type
        if [[ $network_type == "bridge" ]] ; then
            net_prefix="bridge"
            net_type=(${bridges[@]})
        elif [[ $network_type == "network" ]] ; then
            net_prefix="network"
            net_type=(${networks[@]})
        fi

        # Now define the network definition
        for ((mac=0;mac<${#net_type[@]};mac++)); do
            macaddr+=($(printf '52:54:00:%02x:%02x:%02x\n' "$((RANDOM%256))" "$((RANDOM%256))" "$((RANDOM%256))"))
            network_spec+=" --network=$net_prefix="${net_type[$mac]}",mac="${macaddr[$mac]}",model=$nic_model"
        done

        if [[ $juju_total -le $juju_count ]] ; then
            printf -v virt_node %s-%02d "$hypervisor_name-juju" "$juju_total"

            ram="$juju_ram"
            vcpus="$juju_cpus"
            node_type="juju"

            network_spec="--network=$net_prefix="${net_type[0]}",mac="${macaddr[0]}",model=$nic_model"

            disk_spec="--disk path=$storage_path/$virt_node/$virt_node.img"
            disk_spec+=",format=$storage_format,size=${juju_disk},bus=$stg_bus,io=native,cache=directsync"

            # So that we have the right amount of VMs
            (( virt-- ))
            (( juju_total++ ))
            # This will ensure that we only create the juju VMs
            [[ $only_juju == "true" ]] && [[ $juju_total -gt $juju_count ]] && virt=$(( $node_count + 1 ))
        else
            printf -v virt_node %s-%02d "$compute" "$virt"
            # Based on the variables in hypervisor.config, we define the variables
            # for ram and cpus. This also allows a number of control nodes that
            # can be defined as part of full set of nodes.
            ram="$node_ram"
            vcpus="$node_cpus"
            node_type="compute"
            if [[ $virt -le $control_count ]] ; then
                ram="$control_ram"
                vcpus="$control_cpus"
                node_type="control"
            fi

            # Based on the disks array, it will create a definition to add these
            # disks to the VM
            disk_spec=""
            for ((disk=0;disk<${#disks[@]};disk++)); do
                disk_spec+=" --disk path=$storage_path/$virt_node/$virt_node-d$((${disk} + 1)).img"
                disk_spec+=",format=$storage_format,size=${disks[$disk]},bus=$stg_bus,io=native,cache=directsync"
            done
        fi

        # Check to see if the libvirt machine already exists. If it exists
        # then just use the same one again and commission in MAAS
        check_machine=$(machine_exists ${virt_node})
        if [[ $check_machine != "false" ]] ; then
            macaddr=$check_machine

            maas_add_node ${virt_node} ${macaddr} ${node_type} &

            sleep ${build_fanout}
            continue
        fi

        # Creates the VM with all the attributes given
        virt-install -v --noautoconsole   \
            --print-xml                   \
            --autostart                   \
            --boot network,hd,menu=on     \
            --video qxl,vram=256          \
            --channel spicevmc            \
            --name "$virt_node"           \
            --ram "$ram"                  \
            --vcpus "$vcpus"              \
            --console pty,target_type=serial \
            --graphics spice,clipboard_copypaste=no,mouse_mode=client,filetransfer_enable=off \
            --cpu host-passthrough,cache.mode=passthrough  \
            --controller "$stg_bus",model=virtio-scsi,index=0  \
            $disk_spec \
            $network_spec > "$virt_node.xml" &&

        # Create the Vm based on the XML file defined in the above command
        virsh define "$virt_node.xml"

        # Start the VM
        virsh start "$virt_node" &

        # Call the maas_add_node function, this will add the node to MAAS
        maas_add_node ${virt_node} ${macaddr[0]} ${node_type} &

        # Wait some time before building the next, this helps with a lot of DHCP requests
        # and ensures that all VMs are commissioned and deployed.
        sleep ${build_fanout}

    done
    wait
}

destroy_vms() {
    juju_total=1
    doing_juju="false"
    for ((virt="$node_start"; virt<=node_count; virt++)); do
        if [[ $juju_total -le $juju_count ]] ; then
            printf -v virt_node %s-%02d "$hypervisor_name-juju" "$juju_total"

            doing_juju="true"
            (( virt-- ))
            (( juju_total++ ))
        else
            printf -v virt_node %s-%02d "$compute" "$virt"
            doing_juju="false"
        fi

        # If the domain is running, this will complete, else throw a warning
        virsh --connect qemu:///system destroy "$virt_node"

        # Actually remove the VM
        virsh --connect qemu:///system undefine "$virt_node"

        # Remove the three storage volumes from disk
        if [[ $doing_juju = "true" ]] ; then
            virsh vol-delete --pool "$virt_node" "$virt_node.img"
        else
            for ((disk=0;disk<${#disks[@]};disk++)); do
                virsh vol-delete --pool "$virt_node" "$virt_node-d$((${disk} + 1)).img"
            done
        fi

        # Remove the folder storage is located
        rm -rf "$storage_path/$virt_node/"
        sync

        # Remove the XML definitions for the VM
        rm -f "$virt_node.xml" \
            "/etc/libvirt/qemu/$virt_node.xml"    \
            "/etc/libvirt/storage/$virt_node.xml" \
            "/etc/libvirt/storage/autostart/$virt_node.xml"

        # Now remove the VM from MAAS
        system_id=$(maas_system_id ${virt_node})
        delete_machine=$(maas ${maas_profile} machine delete ${system_id})
    done
}

show_help() {
  echo "

  -c    Creates everything
  -w    Removes everything
  -d    Releases VMs, Clears Disk
  -n    Updates all the networks on all VMs
  -r    Recommission all VMs
  -j    Only create juju VM
  "
}

# Initialise the configs
read_config

while getopts ":cwjdnr" opt; do
  case $opt in
    c)
        create_vms
        ;;
    w)
        wipe_vms
        ;;
    d)
        wipe_disks
        ;;
    n)
        network_auto
        ;;
    r)
        recommission_vms
        ;;
    j)
        create_juju
        ;;
    \?)
        printf "Unrecognized option: -%s. Valid options are:" "$OPTARG" >&2
        show_help
        exit 1
        ;;
  esac
done