Skip to content
Snippets Groups Projects
Commit e492f9ca authored by Erik Reid's avatar Erik Reid
Browse files

Merge branch 'device_workflows' into 'develop'

Device workflows

See merge request !5
parents 8ca21277 b0844547
No related branches found
No related tags found
1 merge request!5Device workflows
......@@ -2,4 +2,5 @@ __pycache__/
*.egg-info
.coverage
coverage.xml
.tox/
.tox/device_vendor
.vscode
\ No newline at end of file
......@@ -2,7 +2,7 @@ from orchestrator import OrchestratorCore
from orchestrator.cli.main import app as core_cli
from orchestrator.settings import AppSettings
import geant_service_orchestrator.products # noqa: F401
# import workflows
import geant_service_orchestrator.workflows # noqa: F401
app = OrchestratorCore(base_settings=AppSettings())
......
"""add Device workflows.
Revision ID: 857225661207
Revises: f4959f32c866
Create Date: 2023-04-05 09:16:03.725750
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = '857225661207'
down_revision = 'f4959f32c866'
branch_labels = None
depends_on = None
from orchestrator.migrations.helpers import create_workflow, delete_workflow
new_workflows = [
{
"name": "create_device",
"target": "CREATE",
"description": "Create Device",
"product_type": "Device"
}
]
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"])
......@@ -11,8 +11,8 @@ class DeviceType(strEnum):
class DeviceVendor(strEnum):
Juniper = "juniper"
Newvendor = "newvendor"
Juniper = "Juniper"
Newvendor = "Newvendor"
class DeviceInactive(SubscriptionModel, is_base=True):
......
......@@ -14,8 +14,9 @@ class HostAddresses(BaseSettings):
def new_service_networks(
ipam: settings.IPAMParams,
service_params: settings.ServiceNetworkParams) -> ServiceNetworks:
oss = settings.load_oss_params()
assert oss.IPAM.INFOBLOX
# TODO: load from ipam
# cf. https://gitlab.geant.org/goat/gap-jenkins/-/blob/development/service-editor/gap_service_editor/ipam.py#L35-66 # noqa: E501
return ServiceNetworks(
......@@ -23,8 +24,10 @@ def new_service_networks(
v6=ipaddress.IPv6Network('dead:beef::/120'))
def new_host_address(fqdn: str, networks: ServiceNetworks) -> HostAddresses:
def new_device_lo_address() -> HostAddresses:
oss = settings.load_oss_params()
assert oss.IPAM.INFOBLOX
# TODO: load from ipam
return HostAddresses(
v4=ipaddress.IPv4Address('10.0.0.1'),
v6=ipaddress.IPv6Address('dead:beef::1'))
v4=ipaddress.IPv4Address('10.10.10.10'),
v6=ipaddress.IPv6Address('fc00:798:aa:1::10'))
......@@ -6,7 +6,10 @@ import requests
def provision_node(
node_subscription_params: DeviceBlock,
pp_params=settings.OSSParams.PROVISIONING_PROXY):
dry_run: bool = False):
oss = settings.load_oss_params()
pp_params = oss.PROVISIONING_PROXY
assert pp_params
r = requests.get(
f'https://{pp_params.api_base}'
f'/api/version',
......
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")
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 geant_service_orchestrator.products.product_types import device
from geant_service_orchestrator.services import ipam, provisioning_proxy
def initial_input_form_generator(product_name: str) -> FormGenerator:
class CreateDeviceForm(FormPage):
class Config:
title = product_name
fqdn: str
ts_address: ipaddress.IPv4Address
ts_port: int
device_vendor: device.DeviceVendor
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.lo_iso_address = "49.51e5.0001.0620.4009.6047.00"
subscription.device.si_ipv4_network = "192.168.0.0/31"
subscription.device.ias_lt_ipv4_network = "192.168.1.0/31"
subscription.device.ias_lt_ipv6_network = "fc00:798:1::150/126"
return {"subscription": subscription}
@step("get information about SNMP")
def get_snmp_info(subscription: device.DeviceInactive) -> State:
country = 'Spain'
city = 'Barcelona'
country_code = 'ES'
latitude = '41.3743'
longitude = '2.1328'
subscription.device.site_country = country
subscription.device.site_city = city
subscription.device.site_country_code = country_code
subscription.device.site_latitude = latitude
subscription.device.site_longitude = longitude
subscription.device.snmp_location = (
f'{city.upper()},{country.upper()}[{latitude},{longitude}]'
)
return {"subscription": subscription}
@step("Initialize subscription")
def initialize_subscription(
subscription: device.DeviceInactive,
fqdn: str,
ts_address: ipaddress.IPv4Address,
ts_port: str,
device_vendor: device.DeviceVendor
) -> State:
subscription.device.fqdn = fqdn
subscription.device.ts_address = str(ts_address)
subscription.device.ts_port = str(ts_port)
subscription.device_vendor = device_vendor
subscription.description = f"Device {fqdn} type \
({subscription.device_type})"
subscription = device.DeviceProvisioning.from_other_lifecycle(
subscription, SubscriptionLifecycle.PROVISIONING
)
return {"subscription": subscription}
@step("Provision device [DRY RUN]")
def provision_device_dry(
subscription: device.DeviceProvisioning,
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.device.fqdn,
# extravars={
# "lo_ipv4_address": str(subscription.device.lo_ipv4_address),
# "lo_ipv6_address": str(subscription.device.lo_ipv6_address),
# "lo_iso_address": subscription.device.lo_iso_address,
# "snmp_location": subscription.device.snmp_location,
# "si_ipv4_network": str(subscription.device.si_ipv4_network),
# "lt_ipv4_network": str(subscription.device.ias_lt_ipv4_network),
# "lt_ipv6_network": str(subscription.device.ias_lt_ipv6_network),
# "site_country_code": subscription.device.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,
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.device.fqdn,
# extravars={
# "lo_ipv4_address": str(subscription.device.lo_ipv4_address),
# "lo_ipv6_address": str(subscription.device.lo_ipv6_address),
# "lo_iso_address": subscription.device.lo_iso_address,
# "snmp_location": subscription.device.snmp_location,
# "si_ipv4_network": str(subscription.device.si_ipv4_network),
# "lt_ipv4_network": str(subscription.device.ias_lt_ipv4_network),
# "lt_ipv6_network": str(subscription.device.ias_lt_ipv6_network),
# "site_country_code": subscription.device.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 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
>> get_snmp_info
>> initialize_subscription
>> provision_device_dry
>> confirm_step
>> provision_device_real
>> set_status(SubscriptionLifecycle.ACTIVE)
>> resync
>> done
)
from orchestrator.forms import FormPage
from orchestrator.forms.validators import Label
from orchestrator.targets import Target
# from orchestrator.types import SubscriptionLifecycle
from orchestrator.types import InputForm, UUIDstr
from orchestrator.workflow import done, init, step, workflow
# from orchestrator.workflows.steps import (
# resync,
# set_status,
# store_process_subscription,
# unsync,
# )
from orchestrator.workflows.utils import wrap_modify_initial_input_form
from products import Device
def initial_input_form_generator(
subscription_id: UUIDstr, organisation: UUIDstr
) -> InputForm:
subscription = Device.from_subscription(subscription_id)
class TerminateForm(FormPage):
are_you_sure: Label = (
f"Are you sure you want to get facts from \
{subscription.description}?"
)
return TerminateForm
@step("Get facts")
def get_facts(subscription_id) -> None:
subscription = Device.from_subscription(subscription_id)
import ansible_runner
r = ansible_runner.run(
private_data_dir="/opt",
playbook="get_facts.yaml",
inventory=subscription.device.fqdn,
)
out = r.stdout.read()
out_splitted = out.splitlines()
return {"output": out_splitted}
@workflow(
"Get Facts from Device",
initial_input_form=wrap_modify_initial_input_form(
initial_input_form_generator),
target=Target.SYSTEM,
)
def get_facts_from_device():
return (
init
>> get_facts
# >> resync
>> done
)
from orchestrator.forms import FormPage
from orchestrator.forms.validators import Label
from orchestrator.targets import Target
from orchestrator.types import InputForm, SubscriptionLifecycle, UUIDstr
from orchestrator.workflow import done, init, step, workflow
from orchestrator.workflows.steps import (
resync,
set_status,
store_process_subscription,
unsync,
)
from orchestrator.workflows.utils import wrap_modify_initial_input_form
from geant_service_orchestrator.products.product_types.device import Device
def initial_input_form_generator(subscription_id: UUIDstr) -> InputForm:
subscription = Device.from_subscription(subscription_id)
class TerminateForm(FormPage):
are_you_sure: Label = (
f"Are you sure you want to remove {subscription.description}?"
) # type:ignore
return TerminateForm
def _deprovision_in_user_management_system(fqdn: str) -> str:
pass
@step("Deprovision device")
def deprovision_user(subscription: Device) -> None:
# _deprovision_in_user_management_system(subscription.user.user_id)
pass
@workflow(
"Terminate device",
initial_input_form=wrap_modify_initial_input_form(
initial_input_form_generator),
target=Target.TERMINATE,
)
def terminate_user():
return (
init
>> store_process_subscription(Target.TERMINATE)
>> unsync
>> deprovision_user
>> set_status(SubscriptionLifecycle.TERMINATED)
>> resync
>> done
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment