Skip to content
Snippets Groups Projects
create_device.py 8.05 KiB
import ipaddress
from uuid import uuid4

from orchestrator.forms import FormPage
from orchestrator.targets import Target
from orchestrator.workflow import inputstep
from orchestrator.forms.validators import Accept
from orchestrator.types import FormGenerator, State
from orchestrator.types import SubscriptionLifecycle, UUIDstr
from orchestrator.workflow import done, init, step, workflow
from orchestrator.workflows.steps import resync, set_status
from orchestrator.workflows.steps import store_process_subscription
from orchestrator.workflows.utils import wrap_create_initial_input_form
from gso.products.product_types import device
from gso.products.product_blocks import device as device_pb
from orchestrator.db.models import ProductTable, SubscriptionTable
from orchestrator.forms.validators import Choice, choice_list
from gso.products.product_types.site import Site
# from gso.services import ipam, provisioning_proxy


def site_selector() -> list:
    site_subscriptions = {}
    for site_id, site_description in (
        SubscriptionTable.query.join(ProductTable)
        .filter(
            ProductTable.product_type == "Site",
            SubscriptionTable.status == "active",
        )
        .with_entities(SubscriptionTable.subscription_id,
                       SubscriptionTable.description)
        .all()
    ):
        site_subscriptions[str(site_id)] = site_description

    return choice_list(
        Choice("site_selection",
               zip(site_subscriptions.keys(),
                   site_subscriptions.items())),  # type:ignore
        min_items=1,
        max_items=1,
    )


def initial_input_form_generator(product_name: str) -> FormGenerator:
    class CreateDeviceForm(FormPage):
        class Config:
            title = product_name

        device_site: site_selector()
        hostname: str
        ts_address: ipaddress.IPv4Address
        ts_port: int
        device_vendor: device_pb.DeviceVendor
        device_role: device_pb.DeviceRole

    user_input = yield CreateDeviceForm

    return user_input.dict()


@step("Create subscription")
def create_subscription(product: UUIDstr) -> State:
    subscription = device.DeviceInactive.from_product_id(product, uuid4())

    return {
        "subscription": subscription,
        "subscription_id": subscription.subscription_id,
    }


@step("Get information from IPAM ")
def get_info_from_ipam(subscription: device.DeviceInactive) -> State:
    # lo = ipam.new_device_lo_address()
    # subscription.device.lo_ipv4_address = lo.v4
    # subscription.device.lo_ipv6_address = lo.v6
    # TODO: get info about how these should be generated
    subscription.device.device_lo_ipv4_address = "10.10.10.10"
    subscription.device.device_lo_ipv6_address = "fc00:798:10::10"
    subscription.device.device_lo_iso_address \
        = "49.51e5.0001.0620.4009.6047.00"
    subscription.device.device_si_ipv4_network = "192.168.0.0/31"
    subscription.device.device_ias_lt_ipv4_network = "192.168.1.0/31"
    subscription.device.device_ias_lt_ipv6_network = "fc00:798:1::150/126"
    return {"subscription": subscription}


@step("Initialize subscription")
def initialize_subscription(
    subscription: device.DeviceInactive,
    hostname: str,
    ts_address: ipaddress.IPv4Address,
    ts_port: str,
    device_vendor: device_pb.DeviceVendor,
    device_site: str,
    device_role: device_pb.DeviceRole
) -> State:
    subscription.device.device_ts_address = str(ts_address)
    subscription.device.device_ts_port = str(ts_port)
    subscription.device.device_vendor = device_vendor
    subscription.device.device_site \
        = Site.from_subscription(device_site[0]).site
    fqdn = str(hostname + "." +
               subscription.device.device_site.site_name.lower() + "." +
               subscription.device.device_site.site_country_code.lower() +
               ".geant.net")
    subscription.device.device_fqdn = fqdn
    subscription.device.device_role = device_role
    subscription.description = f"Device {fqdn} type \
                                ({subscription.device_type})"
    subscription = device.DeviceProvisioning.from_other_lifecycle(
        subscription, SubscriptionLifecycle.PROVISIONING
    )

    return {"subscription": subscription, "fqdn": fqdn}


@step("Provision device [DRY RUN]")
def provision_device_dry(
    subscription: device.DeviceProvisioning
) -> State:
     import ansible_runner
    
     snmp_location = subscription.device.device_site.site_country_code 
     r = ansible_runner.run(
         private_data_dir="/opt/geant-gap-ansible",
         playbook="base_config.yaml",
         inventory=subscription.device.device_fqdn,
         extravars={
             "lo_ipv4_address": str(subscription.device.device_lo_ipv4_address),
             "lo_ipv6_address": str(subscription.device.device_lo_ipv6_address),
             "lo_iso_address": subscription.device.device_lo_iso_address,
             "snmp_location": snmp_location,
             "si_ipv4_network": str(subscription.device.device_si_ipv4_network),
             "lt_ipv4_network": str(subscription.device.device_ias_lt_ipv4_network),
             "lt_ipv6_network": str(subscription.device.device_ias_lt_ipv6_network),
             "site_country_code": subscription.device.device_site.site_country_code,
             "verb": "deploy",
         },
     )
     out = r.stdout.read()
     out_splitted = out.splitlines()
    # # if r.rc != 0:
    # #    raise ValueError("Ansible has failed")
     return {"dry_run_output": out_splitted, "return_code": r.rc}
    # provisioning_proxy.provision_node(
    #     node_subscription_params=subscription,
    #     dry_run=True)
    #  TODO: figure out what to return when we are suspending & waiting
    #        for the provisioning-proxy to call back
    #return {"return_code": 0}


@inputstep("Confirm step", assignee="CHANGES")
def confirm_step() -> FormGenerator:
    class ConfirmForm(FormPage):
        confirm: Accept

    user_input = yield ConfirmForm

    return {"confirm": user_input.confirm}


@step("Provision device [FOR REAL]")
def provision_device_real(
    subscription: device.DeviceProvisioning
) -> State:
     import ansible_runner

     snmp_location = subscription.device.device_site.site_country_code
     r = ansible_runner.run(
         private_data_dir="/opt/geant-gap-ansible",
         playbook="base_config.yaml",
         inventory=subscription.device.device_fqdn,
         extravars={
             "lo_ipv4_address": str(subscription.device.device_lo_ipv4_address),
             "lo_ipv6_address": str(subscription.device.device_lo_ipv6_address),
             "lo_iso_address": subscription.device.device_lo_iso_address,
             "snmp_location": snmp_location,
             "si_ipv4_network": str(subscription.device.device_si_ipv4_network),
             "lt_ipv4_network": str(subscription.device.device_ias_lt_ipv4_network),
             "lt_ipv6_network": str(subscription.device.device_ias_lt_ipv6_network),
             "site_country_code": subscription.device.device_site.site_country_code,
             "verb": "deploy",
             "dryrun": "False",
             "commit_comment": "Deployed with WFO and Ansible",
         },
     )
     out = r.stdout.read()
     out_splitted = out.splitlines()
    # # if r.rc != 0:
    # #    raise ValueError("Ansible has failed")
     return {"dry_run_output": out_splitted, "return_code": r.rc}
    # provisioning_proxy.provision_node(
    #     node_subscription_params=subscription,
    #     dry_run=True)
    #  TODO: figure out what to return when we are suspending & waiting
    #        for the provisioning-proxy to call back
    #return {"return_code": 0}

@workflow(
    "Create Device",
    initial_input_form=wrap_create_initial_input_form(
                       initial_input_form_generator),
    target=Target.CREATE,
)
def create_device():
    return (
        init
        >> create_subscription
        >> store_process_subscription(Target.CREATE)
        >> get_info_from_ipam
        >> initialize_subscription
        >> provision_device_dry
        >> confirm_step
        >> provision_device_real
        >> set_status(SubscriptionLifecycle.ACTIVE)
        >> resync
        >> done
    )