diff --git a/gso/migrations/versions/2023-04-21_d3616a686094_add_iptrunk_create_workflow.py b/gso/migrations/versions/2023-04-21_d3616a686094_add_iptrunk_create_workflow.py new file mode 100644 index 0000000000000000000000000000000000000000..0380996f37ec80ff32dc8ff855640ca3fb7bcec1 --- /dev/null +++ b/gso/migrations/versions/2023-04-21_d3616a686094_add_iptrunk_create_workflow.py @@ -0,0 +1,39 @@ +"""add Iptrunk create workflow. + +Revision ID: d3616a686094 +Revises: 42d52099c7da +Create Date: 2023-04-21 12:22:18.243625 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = 'd3616a686094' +down_revision = '42d52099c7da' +branch_labels = None +depends_on = None + + +from orchestrator.migrations.helpers import create_workflow, delete_workflow + +new_workflows = [ + { + "name": "create_iptrunk", + "target": "CREATE", + "description": "Create Iptrunk", + "product_type": "Iptrunk" + } +] + + +def upgrade() -> None: + conn = op.get_bind() + for workflow in new_workflows: + create_workflow(conn, workflow) + + +def downgrade() -> None: + conn = op.get_bind() + for workflow in new_workflows: + delete_workflow(conn, workflow["name"]) diff --git a/gso/workflows/__init__.py b/gso/workflows/__init__.py index d93b338a9c01be9a2e5c3fee0c46f341ef5c98ae..b768502bfcfc93ae3473581f3349368c99503b53 100644 --- a/gso/workflows/__init__.py +++ b/gso/workflows/__init__.py @@ -3,3 +3,4 @@ from orchestrator.workflows import LazyWorkflowInstance LazyWorkflowInstance("workflows.device.create_device", "create_device") LazyWorkflowInstance("workflows.device.terminate_device", "terminate_device") LazyWorkflowInstance("workflows.device.get_facts", "get_facts") +LazyWorkflowInstance("workflows.iptrunk.create_iptrunk", "create_iptrunk") diff --git a/gso/workflows/iptrunk/create_iptrunk.py b/gso/workflows/iptrunk/create_iptrunk.py new file mode 100644 index 0000000000000000000000000000000000000000..72be7cb7481ab5be36452edc29e916d4719a2d79 --- /dev/null +++ b/gso/workflows/iptrunk/create_iptrunk.py @@ -0,0 +1,196 @@ +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 iptrunk +from gso.services import ipam, provisioning_proxy + + +def initial_input_form_generator(product_name: str) -> FormGenerator: + class CreateIptrunkForm(FormPage): + class Config: + title = product_name + + fqdn: str + + user_input = yield CreateIptrunkForm + + return user_input.dict() + + +@step("Create subscription") +def create_subscription(product: UUIDstr) -> State: + subscription = iptrunk.IptrunkInactive.from_product_id(product, uuid4()) + + return { + "subscription": subscription, + "subscription_id": subscription.subscription_id, + } + + +@step("Get information from IPAM ") +def get_info_from_ipam(subscription: iptrunk.IptrunkInactive) -> State: + lo = ipam.new_iptrunk_lo_address() + subscription.iptrunk.lo_ipv4_address = lo.v4 + subscription.iptrunk.lo_ipv6_address = lo.v6 + # TODO: get info about how these should be generated + subscription.iptrunk.lo_iso_address = "49.51e5.0001.0620.4009.6047.00" + subscription.iptrunk.si_ipv4_network = "192.168.0.0/31" + subscription.iptrunk.ias_lt_ipv4_network = "192.168.1.0/31" + subscription.iptrunk.ias_lt_ipv6_network = "fc00:798:1::150/126" + return {"subscription": subscription} + + +@step("get information about SNMP") +def get_snmp_info(subscription: iptrunk.IptrunkInactive) -> State: + country = 'Spain' + city = 'Barcelona' + country_code = 'ES' + latitude = '41.3743' + longitude = '2.1328' + subscription.iptrunk.site_country = country + subscription.iptrunk.site_city = city + subscription.iptrunk.site_country_code = country_code + subscription.iptrunk.site_latitude = latitude + subscription.iptrunk.site_longitude = longitude + subscription.iptrunk.snmp_location = ( + f'{city.upper()},{country.upper()}[{latitude},{longitude}]' + ) + + return {"subscription": subscription} + + +@step("Initialize subscription") +def initialize_subscription( + subscription: iptrunk.IptrunkInactive, + fqdn: str, + ts_address: ipaddress.IPv4Address, + ts_port: str +) -> State: + subscription.iptrunk.fqdn = fqdn + subscription.iptrunk.ts_address = str(ts_address) + subscription.iptrunk.ts_port = str(ts_port) + subscription.iptrunk_vendor = iptrunk_vendor + subscription.description = f"Iptrunk {fqdn} type \ + ({subscription.iptrunk_type})" + subscription = iptrunk.IptrunkProvisioning.from_other_lifecycle( + subscription, SubscriptionLifecycle.PROVISIONING + ) + + return {"subscription": subscription} + + +@step("Provision iptrunk [DRY RUN]") +def provision_iptrunk_dry( + subscription: iptrunk.IptrunkProvisioning, + fqdn: str, + ts_address: str, + ts_port: str +) -> State: + # import ansible_runner + # + # r = ansible_runner.run( + # private_data_dir="/opt/geant-gap-ansible", + # playbook="base_config.yaml", + # inventory=subscription.iptrunk.fqdn, + # extravars={ + # "lo_ipv4_address": str(subscription.iptrunk.lo_ipv4_address), + # "lo_ipv6_address": str(subscription.iptrunk.lo_ipv6_address), + # "lo_iso_address": subscription.iptrunk.lo_iso_address, + # "snmp_location": subscription.iptrunk.snmp_location, + # "si_ipv4_network": str(subscription.iptrunk.si_ipv4_network), + # "lt_ipv4_network": str(subscription.iptrunk.ias_lt_ipv4_network), + # "lt_ipv6_network": str(subscription.iptrunk.ias_lt_ipv6_network), + # "site_country_code": subscription.iptrunk.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 iptrunk [FOR REAL]") +def provision_iptrunk_real( + subscription: iptrunk.IptrunkProvisioning, + fqdn: str, + ts_address: str, + ts_port: str +) -> State: + # import ansible_runner + # + # r = ansible_runner.run( + # private_data_dir="/opt/geant-gap-ansible", + # playbook="base_config.yaml", + # inventory=subscription.iptrunk.fqdn, + # extravars={ + # "lo_ipv4_address": str(subscription.iptrunk.lo_ipv4_address), + # "lo_ipv6_address": str(subscription.iptrunk.lo_ipv6_address), + # "lo_iso_address": subscription.iptrunk.lo_iso_address, + # "snmp_location": subscription.iptrunk.snmp_location, + # "si_ipv4_network": str(subscription.iptrunk.si_ipv4_network), + # "lt_ipv4_network": str(subscription.iptrunk.ias_lt_ipv4_network), + # "lt_ipv6_network": str(subscription.iptrunk.ias_lt_ipv6_network), + # "site_country_code": subscription.iptrunk.site_country_code, + # "verb": "deploy", + # }, + # ) + # out = r.stdout.read() + # out_splitted = out.splitlines() + # + # return {"real_run_output": out_splitted, "return_code": r.rc} + provisioning_proxy.provision_node( + node_subscription_params=subscription) + # TODO: figure out what to return when we are suspending & waiting + # for the provisioning-proxy to call back + return {"return_code": 0} + + +@workflow( + "Create Iptrunk", + initial_input_form=wrap_create_initial_input_form( + initial_input_form_generator), + target=Target.CREATE, +) +def create_iptrunk(): + return ( + init + >> create_subscription + >> store_process_subscription(Target.CREATE) + >> get_info_from_ipam + >> get_snmp_info + >> initialize_subscription + >> provision_iptrunk_dry + >> confirm_step + >> provision_iptrunk_real + >> set_status(SubscriptionLifecycle.ACTIVE) + >> resync + >> done + )