Skip to content
Snippets Groups Projects
Commit ffbd0a2b authored by Karel van Klink's avatar Karel van Klink :smiley_cat:
Browse files

Merge branch 'feature/new_device_model_and_sites_and_orgs' into 'develop'

Add sites, and add integration of IPtrunks and routers with LSO deployment

See merge request !12
parents 5893f566 0649d9ee
No related branches found
No related tags found
1 merge request!12Add sites, and add integration of IPtrunks and routers with LSO deployment
Showing
with 996 additions and 418 deletions
......@@ -10,7 +10,7 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = '857225661207'
down_revision = 'f4959f32c866'
down_revision = 'd52256e7d715'
branch_labels = None
depends_on = None
......
"""add IPtrunk create workflows.
"""add Site create workflow.
Revision ID: 95cd21cb2b05
Revises: 6b8483b46d06
Create Date: 2023-04-28 12:37:36.801782
Revision ID: 21e7bb0e5cad
Revises: 60d340427471
Create Date: 2023-05-04 09:00:36.433715
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = '95cd21cb2b05'
down_revision = '6b8483b46d06'
revision = '21e7bb0e5cad'
down_revision = '60d340427471'
branch_labels = None
depends_on = None
......@@ -19,10 +19,10 @@ from orchestrator.migrations.helpers import create_workflow, delete_workflow
new_workflows = [
{
"name": "create_iptrunk",
"name": "create_site",
"target": "CREATE",
"description": "Create Iptrunk",
"product_type": "Iptrunk"
"description": "Create Site",
"product_type": "Site"
}
]
......
"""add Terminate device workflow.
Revision ID: 61f8e90581c5
Revises: 21e7bb0e5cad
Create Date: 2023-05-08 10:48:21.655880
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = '61f8e90581c5'
down_revision = '21e7bb0e5cad'
branch_labels = None
depends_on = None
from orchestrator.migrations.helpers import create_workflow, delete_workflow
new_workflows = [
{
"name": "terminate_device",
"target": "TERMINATE",
"description": "Terminate 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"])
"""add Terminate Iptrunk workflow.
Revision ID: 647e066bc99e
Revises: 61f8e90581c5
Create Date: 2023-05-08 18:59:01.309425
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = '647e066bc99e'
down_revision = '61f8e90581c5'
branch_labels = None
depends_on = None
from orchestrator.migrations.helpers import create_workflow, delete_workflow
new_workflows = [
{
"name": "terminate_iptrunk",
"target": "TERMINATE",
"description": "Terminate 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"])
......@@ -2,9 +2,11 @@ from orchestrator.domain import SUBSCRIPTION_MODEL_REGISTRY
from gso.products.product_types.device import Device
from gso.products.product_types.iptrunk import Iptrunk
from gso.products.product_types.site import Site
SUBSCRIPTION_MODEL_REGISTRY.update(
{
"Site": Site,
"Router": Device,
"Switch": Device,
"Iptrunk": Iptrunk,
......
from typing import Optional
from orchestrator.domain.base import ProductBlockModel
from orchestrator.types import SubscriptionLifecycle
from orchestrator.types import SubscriptionLifecycle, strEnum
from gso.products.product_blocks.site \
import SiteBlock, SiteBlockInactive, SiteBlockProvisioning
import ipaddress
class DeviceVendor(strEnum):
juniper = "juniper"
nokia = "nokia"
class DeviceRole(strEnum):
p = "p"
pe = "pe"
amt = "amt"
class DeviceBlockInactive(ProductBlockModel,
lifecycle=[SubscriptionLifecycle.INITIAL],
product_block_name="DeviceBlock"):
fqdn: Optional[str] = None
ts_address: Optional[str] = None
ts_port: Optional[int] = None
lo_ipv4_address: Optional[ipaddress.IPv4Address] = None
lo_ipv6_address: Optional[ipaddress.IPv6Address] = None
lo_iso_address: Optional[str] = None
si_ipv4_network: Optional[ipaddress.IPv4Network] = None
ias_lt_ipv4_network: Optional[ipaddress.IPv4Network] = None
ias_lt_ipv6_network: Optional[ipaddress.IPv6Network] = None
site_city: Optional[str] = None
site_country: Optional[str] = None
site_country_code: Optional[str] = None
site_latitude: Optional[str] = None
site_longitude: Optional[str] = None
snmp_location: Optional[str] = None
device_fqdn: Optional[str] = None
device_ts_address: Optional[str] = None
device_ts_port: Optional[int] = None
device_lo_ipv4_address: Optional[ipaddress.IPv4Address] = None
device_lo_ipv6_address: Optional[ipaddress.IPv6Address] = None
device_lo_iso_address: Optional[str] = None
device_si_ipv4_network: Optional[ipaddress.IPv4Network] = None
device_ias_lt_ipv4_network: Optional[ipaddress.IPv4Network] = None
device_ias_lt_ipv6_network: Optional[ipaddress.IPv6Network] = None
device_vendor: Optional[DeviceVendor] = None
device_role: Optional[DeviceRole] = None
device_site: Optional[SiteBlockInactive]
class DeviceBlockProvisioning(DeviceBlockInactive,
lifecycle=[SubscriptionLifecycle.PROVISIONING]):
fqdn: str
ts_address: str
ts_port: str
lo_ipv4_address: Optional[ipaddress.IPv4Address] = None
lo_ipv6_address: Optional[ipaddress.IPv6Address] = None
lo_iso_address: Optional[str] = None
si_ipv4_network: Optional[ipaddress.IPv4Network] = None
ias_lt_ipv4_network: Optional[ipaddress.IPv4Network] = None
ias_lt_ipv6_network: Optional[ipaddress.IPv6Network] = None
site_city: Optional[str] = None
site_country: Optional[str] = None
site_country_code: Optional[str] = None
site_latitude: Optional[str] = None
site_longitude: Optional[str] = None
snmp_location: Optional[str] = None
device_fqdn: str
device_ts_address: str
device_ts_port: str
device_lo_ipv4_address: Optional[ipaddress.IPv4Address] = None
device_lo_ipv6_address: Optional[ipaddress.IPv6Address] = None
device_lo_iso_address: Optional[str] = None
device_si_ipv4_network: Optional[ipaddress.IPv4Network] = None
device_ias_lt_ipv4_network: Optional[ipaddress.IPv4Network] = None
device_ias_lt_ipv6_network: Optional[ipaddress.IPv6Network] = None
device_vendor: Optional[DeviceVendor] = None
device_role: Optional[DeviceRole] = None
device_site: Optional[SiteBlockProvisioning]
class DeviceBlock(DeviceBlockProvisioning,
lifecycle=[SubscriptionLifecycle.ACTIVE]):
fqdn: str
ts_address: str
ts_port: str
lo_ipv4_address: ipaddress.IPv4Address
lo_ipv6_address: ipaddress.IPv6Address
lo_iso_address: str
si_ipv4_network: ipaddress.IPv4Network
ias_lt_ipv4_network: ipaddress.IPv4Network
ias_lt_ipv6_network: ipaddress.IPv6Network
site_city: str
site_country: str
site_country_code: str
site_latitude: str
site_longitude: str
snmp_location: str
device_fqdn: str
device_ts_address: str
device_ts_port: str
device_lo_ipv4_address: ipaddress.IPv4Address
device_lo_ipv6_address: ipaddress.IPv6Address
device_lo_iso_address: str
device_si_ipv4_network: ipaddress.IPv4Network
device_ias_lt_ipv4_network: ipaddress.IPv4Network
device_ias_lt_ipv6_network: ipaddress.IPv6Network
device_vendor: DeviceVendor
device_role: DeviceRole
device_site: SiteBlock
......@@ -27,14 +27,16 @@ class IptrunkBlockInactive(ProductBlockModel,
iptrunk_sideA_node: DeviceBlockInactive
iptrunk_sideA_ae_iface: Optional[str] = None
iptrunk_sideA_ae_geant_a_sid: Optional[str] = None
iptrunk_sideA_ae_members: Optional[list] = None
iptrunk_sideA_ae_members_description: Optional[list] = None
iptrunk_sideA_ae_members: list[str] = Field(default_factory=list)
iptrunk_sideA_ae_members_description: list[str] \
= Field(default_factory=list)
#
iptrunk_sideB_node: DeviceBlockInactive
iptrunk_sideB_ae_iface: Optional[str] = None
iptrunk_sideB_ae_geant_a_sid: Optional[str] = None
iptrunk_sideB_ae_members: Optional[list] = None
iptrunk_sideB_ae_members_description: Optional[list] = None
iptrunk_sideB_ae_members: list[str] = Field(default_factory=list)
iptrunk_sideB_ae_members_description: list[str] \
= Field(default_factory=list)
class IptrunkBlockProvisioning(IptrunkBlockInactive,
......
from typing import Optional
from orchestrator.domain.base import ProductBlockModel
from orchestrator.types import SubscriptionLifecycle, strEnum
class SiteTier(strEnum):
tier1 = 1
tier2 = 2
tier3 = 3
tier4 = 4
class SiteBlockInactive(ProductBlockModel,
lifecycle=[SubscriptionLifecycle.INITIAL],
product_block_name="SiteBlock"):
site_name: Optional[str] = None
site_city: Optional[str] = None
site_country: Optional[str] = None
site_country_code: Optional[str] = None
site_latitude: Optional[float] = None
site_longitude: Optional[float] = None
site_internal_id: Optional[int] = None
site_bgp_community_id: Optional[int] = None
site_tier: Optional[SiteTier] = None
class SiteBlockProvisioning(SiteBlockInactive,
lifecycle=[SubscriptionLifecycle.PROVISIONING]):
site_name: Optional[str] = None
site_city: Optional[str] = None
site_country: Optional[str] = None
site_country_code: Optional[str] = None
site_latitude: Optional[float] = None
site_longitude: Optional[float] = None
site_internal_id: Optional[int] = None
site_bgp_community_id: Optional[int] = None
site_tier: Optional[SiteTier] = None
class SiteBlock(SiteBlockProvisioning,
lifecycle=[SubscriptionLifecycle.ACTIVE]):
site_name: str
site_city: str
site_country: str
site_country_code: str
site_latitude: float
site_longitude: float
site_internal_id: int
site_bgp_community_id: int
site_tier: SiteTier
......@@ -10,25 +10,17 @@ class DeviceType(strEnum):
switch = "switch"
class DeviceVendor(strEnum):
Juniper = "Juniper"
Newvendor = "Newvendor"
class DeviceInactive(SubscriptionModel, is_base=True):
device_type: DeviceType
device_vendor: DeviceVendor
device: DeviceBlockInactive
class DeviceProvisioning(DeviceInactive,
lifecycle=[SubscriptionLifecycle.PROVISIONING]):
device_type: DeviceType
device_vendor: DeviceVendor
device: DeviceBlockProvisioning
class Device(DeviceProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
device_type: DeviceType
device_vendor: DeviceVendor
device: DeviceBlock
from orchestrator.domain.base import SubscriptionModel
from orchestrator.types import SubscriptionLifecycle
from gso.products.product_blocks.site \
import SiteBlock, SiteBlockInactive, SiteBlockProvisioning
class SiteInactive(SubscriptionModel, is_base=True):
site: SiteBlockInactive
class SiteProvisioning(SiteInactive,
lifecycle=[SubscriptionLifecycle.PROVISIONING]):
site: SiteBlockProvisioning
class Site(SiteProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
site: SiteBlock
import json
import logging
import requests
from orchestrator import inputstep
from orchestrator.config.assignee import Assignee
# noinspection PyProtectedMember
from orchestrator.forms import FormPage, ReadOnlyField
from orchestrator.forms.validators import Accept, Label, LongText
from orchestrator.types import UUIDstr, State
from orchestrator.types import UUIDstr, State, strEnum
from orchestrator.utils.json import json_dumps
from gso import settings
from gso.products.product_types.device import DeviceProvisioning
from gso.products.product_types.iptrunk import IptrunkProvisioning, Iptrunk
logger = logging.getLogger(__name__)
def provision_node(
node_subscription_params: DeviceProvisioning,
process_id: UUIDstr,
dry_run: bool = True):
class CUDOperation(strEnum):
"""
Enum for different C(R)UD operations that the provisioning proxy supports.
Read is not applicable, hence these become CUD and not CRUD operations.
"""
#: Creation is done with a POST request
POST = 'POST'
#: Updating is done with a PUT request
PUT = 'PUT'
#: Removal is done with a DELETE request
DELETE = 'DELETE'
def _send_request(endpoint: str, parameters: dict, process_id: UUIDstr,
operation: CUDOperation):
"""
Internal function for sending a request to LSO. The callback address is
derived using the process ID provided.
:param str endpoint: The LSO-specific endpoint to call, depending on the
type of service object that is acted upon.
:param dict parameters: JSON body for the request, which will almost always
at least consist of a subscription object, and a boolean value to
indicate a dry run.
:param UUIDstr process_id: The process ID that this request is a part of,
used to call back to when the execution of the playbook is completed.
:param :class:`CUDOperation` operation: The specific operation that is
performed with the request.
"""
oss = settings.load_oss_params()
pp_params = oss.PROVISIONING_PROXY
assert pp_params
device_params = node_subscription_params.device
callback_url = f'{settings.load_oss_params().GENERAL.public_hostname}' \
f'/api/processes/{process_id}/resume'
logger.debug(f'[disabled] provisioning node {device_params}')
logger.debug(f'[provisioning proxy] provisioning for process {process_id}')
parameters.update({'callback': callback_url})
url = f'{pp_params.scheme}://{pp_params.api_base}/api/{endpoint}'
request = None
if operation == CUDOperation.POST:
request = requests.post(url, json=parameters)
elif operation == CUDOperation.PUT:
request = requests.put(url, json=parameters)
elif operation == CUDOperation.DELETE:
request = requests.delete(url, json=parameters)
if request.status_code != 200:
raise AssertionError(request.text)
def provision_device(
subscription: DeviceProvisioning,
process_id: UUIDstr,
dry_run: bool = True):
"""
Function that provisions a new device using LSO.
:param :class:`DeviceProvisioning` subscription: The subscription object
that is to be provisioned.
:param UUIDstr process_id: The related process ID, used for callback.
:param bool dry_run: A boolean indicating whether this should be a dry run
or not, defaults to ``True``.
"""
parameters = {
'dry_run': dry_run,
'subscription': json.loads(json_dumps(subscription))
}
_send_request('device', parameters, process_id, CUDOperation.POST)
def provision_ip_trunk(subscription: IptrunkProvisioning,
process_id: UUIDstr,
config_object: str,
dry_run: bool = True):
"""
Function that provisions an IP trunk service using LSO.
:param :class:`IptrunkProvisioning` subscription: The subscription object
that is to be provisioned.
:param UUIDstr process_id: The related process ID, used for callback.
:param str config_object: The type of object that is deployed
:param bool dry_run: A boolean indicating whether this should be a dry run
or not, defaults to ``True``.
"""
parameters = {
'subscription': json.loads(json_dumps(subscription)),
'dry_run': dry_run,
'verb': "deploy",
'object': config_object
}
_send_request('ip_trunk', parameters, process_id, CUDOperation.POST)
# def modify_ip_trunk(old_subscription: Iptrunk,
# new_subscription: Iptrunk,
# process_id: UUIDstr,
# dry_run: bool = True):
# """
# Function that modifies an existing IP trunk subscription using LSO.
#
# :param :class:`Iptrunk` old_subscription: The subscription object, before
# its modification.
# :param :class:`Iptrunk` new_subscription: The subscription object, after
# modifications have been made to it.
# :param UUIDstr process_id: The related process ID, used for callback.
# :param bool dry_run: A boolean indicating whether this should be a dry ryn
# or not, defaults to ``True``.
# """
# parameters = {
# 'dry_run': dry_run,
# 'old_subscription': old_subscription,
# 'subscription': new_subscription
# # FIXME missing parameters
# }
#
# _send_request('ip_trunk', parameters, process_id, CUDOperation.PUT)
def deprovision_ip_trunk(subscription: Iptrunk,
process_id: UUIDstr,
dry_run: bool = True):
"""
Function that provisions an IP trunk service using LSO.
:param :class:`IptrunkProvisioning` subscription: The subscription object
that is to be provisioned.
:param UUIDstr process_id: The related process ID, used for callback.
:param bool dry_run: A boolean indicating whether this should be a dry run
or not, defaults to ``True``.
"""
parameters = {
'callback': callback_url,
'subscription': json.loads(json_dumps(subscription)),
'dry_run': dry_run,
'device': {
'fqdn': device_params.fqdn,
'lo_address': {
'v4': str(device_params.lo_ipv4_address),
'v6': str(device_params.lo_ipv6_address)
},
'lo_iso_address': device_params.lo_iso_address,
'si_ipv4_network': str(device_params.si_ipv4_network),
'ias_lt_network': {
'v4': str(device_params.ias_lt_ipv4_network),
'v6': str(device_params.ias_lt_ipv6_network)
},
'site_country_code': device_params.site_country_code,
'site_city': device_params.site_city,
'site_latitude': device_params.site_latitude,
'site_longitude': device_params.site_longitude,
'snmp_location': device_params.snmp_location,
'device_type': node_subscription_params.device_type,
'device_vendor': node_subscription_params.device_vendor,
'ts_address': device_params.ts_address,
'ts_port': device_params.ts_port
}
'verb': "remove"
}
post_request = requests.post(
f'{pp_params.scheme}://{pp_params.api_base}'
f'/api/device',
json=parameters)
post_request.raise_for_status()
_send_request('ip_trunk', parameters, process_id, CUDOperation.DELETE)
@inputstep('Await provisioning proxy results', assignee=Assignee('SYSTEM'))
......
......@@ -5,3 +5,6 @@ LazyWorkflowInstance("gso.workflows.device.terminate_device",
"terminate_device")
LazyWorkflowInstance("gso.workflows.device.get_facts", "get_facts")
LazyWorkflowInstance("gso.workflows.iptrunk.create_iptrunk", "create_iptrunk")
LazyWorkflowInstance("gso.workflows.iptrunk.terminate_iptrunk",
"terminate_iptrunk")
LazyWorkflowInstance("gso.workflows.site.create_site", "create_site")
import ipaddress
import re
from uuid import uuid4
from orchestrator.db.models import ProductTable, SubscriptionTable
# noinspection PyProtectedMember
from orchestrator.forms import FormPage
from orchestrator.forms.validators import Choice, choice_list
from orchestrator.targets import Target
from orchestrator.types import FormGenerator, State
from orchestrator.types import SubscriptionLifecycle, UUIDstr
......@@ -10,11 +14,38 @@ 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.device import DeviceVendor, DeviceInactive, \
from gso.products.product_blocks import device as device_pb
from gso.products.product_types import device
from gso.products.product_types.device import DeviceInactive, \
DeviceProvisioning
from gso.products.product_types.site import Site
from gso.services import provisioning_proxy
from gso.services.provisioning_proxy import confirm_pp_results, \
await_pp_results
from gso.services.provisioning_proxy import await_pp_results, \
confirm_pp_results
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
# noinspection PyTypeChecker
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:
......@@ -22,10 +53,12 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
class Config:
title = product_name
fqdn: str
device_site: site_selector()
hostname: str
ts_address: ipaddress.IPv4Address
ts_port: int
device_vendor: DeviceVendor
device_vendor: device_pb.DeviceVendor
device_role: device_pb.DeviceRole
user_input = yield CreateDeviceForm
......@@ -42,69 +75,66 @@ def create_subscription(product: UUIDstr) -> State:
}
def iso_from_ipv4(ipv4_address):
padded_octets = [f'{x:>03}' for x in ipv4_address.split('.')]
joined_octets = ''.join(padded_octets)
re_split = '.'.join(re.findall('....', joined_octets))
result = '.'.join(['49.51e5.0001', re_split, '00'])
return result
@step('Get information from IPAM')
def get_info_from_ipam(subscription: 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_ipv4_address = '10.10.10.10'
subscription.device.lo_ipv6_address = 'fc00:798:10::10'
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: 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}]'
)
subscription.device.device_lo_ipv4_address = \
ipaddress.ip_address('10.10.10.20')
subscription.device.device_lo_ipv6_address = \
ipaddress.ip_address('fc00:798:10::20')
subscription.device.device_lo_iso_address \
= iso_from_ipv4(str(subscription.device.device_lo_ipv4_address))
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: DeviceInactive,
fqdn: str,
subscription: device.DeviceInactive,
hostname: str,
ts_address: ipaddress.IPv4Address,
ts_port: str,
device_vendor: DeviceVendor
device_vendor: device_pb.DeviceVendor,
device_site: str,
device_role: device_pb.DeviceRole
) -> 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} ' \
f'({subscription.device_type})'
subscription = DeviceProvisioning.from_other_lifecycle(
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}
return {'subscription': subscription, 'fqdn': fqdn}
@step('Provision device [DRY RUN]')
def provision_device_dry(subscription: DeviceProvisioning,
process_id: UUIDstr) -> State:
provisioning_proxy.provision_node(
subscription,
process_id
)
provisioning_proxy.provision_device(subscription, process_id)
return {'subscription': subscription}
......@@ -112,11 +142,7 @@ def provision_device_dry(subscription: DeviceProvisioning,
@step('Provision device [FOR REAL]')
def provision_device_real(subscription: DeviceProvisioning,
process_id: UUIDstr) -> State:
provisioning_proxy.provision_node(
subscription,
process_id,
False # No dry run this time, run it for real
)
provisioning_proxy.provision_device(subscription, process_id, False)
return {'subscription': subscription}
......@@ -133,7 +159,6 @@ def create_device():
>> create_subscription
>> store_process_subscription(Target.CREATE)
>> get_info_from_ipam
>> get_snmp_info
>> initialize_subscription
>> provision_device_dry
>> await_pp_results
......
......@@ -19,8 +19,8 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> InputForm:
class TerminateForm(FormPage):
are_you_sure: Label = (
f"Are you sure you want to remove {subscription.description}?"
) # type:ignore
f'Are you sure you want to remove {subscription.description}?'
)
return TerminateForm
......@@ -41,7 +41,7 @@ def deprovision_user(subscription: Device) -> None:
initial_input_form_generator),
target=Target.TERMINATE,
)
def terminate_user():
def terminate_device():
return (
init
>> store_process_subscription(Target.TERMINATE)
......
# import ipaddress
import ipaddress
from uuid import uuid4
from orchestrator.db.models import ProductTable, SubscriptionTable
# noinspection PyProtectedMember
from orchestrator.forms import FormPage
from orchestrator.forms.validators import Choice, choice_list
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.products.product_blocks import iptrunk as iptrunk_pb
from gso.products.product_types.device import Device
# from gso.services import ipam, provisioning_proxy
# from gso.products.product_types import device
from orchestrator.db.models import ProductTable, SubscriptionTable
from orchestrator.forms.validators import Choice, choice_list
from gso.products.product_blocks.iptrunk import IptrunkType
from gso.products.product_types.device import Device
from gso.products.product_types.iptrunk import IptrunkInactive, \
IptrunkProvisioning
from gso.services import provisioning_proxy
from gso.services.provisioning_proxy import confirm_pp_results, \
await_pp_results
def device_selector(choice_value: str) -> list:
device_subscriptions = {}
for device_id, device_description in (
SubscriptionTable.query.join(ProductTable)
.filter(
ProductTable.product_type == "Device",
SubscriptionTable.status == "active",
)
.with_entities(SubscriptionTable.subscription_id,
SubscriptionTable.description)
.all()
SubscriptionTable.query.join(ProductTable)
.filter(
ProductTable.product_type == 'Device',
SubscriptionTable.status == 'active',
)
.with_entities(SubscriptionTable.subscription_id,
SubscriptionTable.description)
.all()
):
device_subscriptions[str(device_id)] = device_description
# noinspection PyTypeChecker
return choice_list(
Choice(choice_value, zip(device_subscriptions.keys(),
device_subscriptions.items())), # type:ignore
......@@ -50,7 +52,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
geant_s_sid: str
iptrunk_description: str
iptrunk_type: iptrunk_pb.IptrunkType
iptrunk_type: IptrunkType
iptrunk_speed: str # This should be an enum: 1/10/100/400
iptrunk_minimum_links: int
......@@ -68,52 +70,54 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
iptrunk_sideB_ae_members: list[str]
iptrunk_sideB_ae_members_descriptions: list[str]
# TODO: we need additional validation:
# sideA fqdn must be different from sideB fqdn
# the lenght of iptrunk_sideA_ae_members should
# be the same as iptrunk_sideA_ae_members
# interface names must be validated
# * sideA fqdn must be different from sideB fqdn
# * the length of iptrunk_sideA_ae_members should
# be the same as iptrunk_sideB_ae_members
# * interface names must be validated
user_input = yield CreateIptrunkForm
return user_input.dict()
@step("Create subscription")
@step('Create subscription')
def create_subscription(product: UUIDstr) -> State:
subscription = iptrunk.IptrunkInactive.from_product_id(product, uuid4())
subscription = IptrunkInactive.from_product_id(product, uuid4())
return {
"subscription": subscription,
"subscription_id": subscription.subscription_id,
'subscription': subscription,
'subscription_id': subscription.subscription_id,
}
@step("Get information from IPAM ")
def get_info_from_ipam(subscription: iptrunk.IptrunkInactive) -> State:
@step('Get information from IPAM')
def get_info_from_ipam(subscription: IptrunkInactive) -> State:
# TODO: get info about how these should be generated
subscription.iptrunk.iptrunk_ipv4_network = "192.168.1.0/31"
subscription.iptrunk.iptrunk_ipv6_network = "fc00:798:1::150/126"
return {"subscription": subscription}
subscription.iptrunk.iptrunk_ipv4_network \
= ipaddress.ip_network('192.168.255.0/31')
subscription.iptrunk.iptrunk_ipv6_network \
= ipaddress.ip_network('fc00:798:255::150/126')
return {'subscription': subscription}
@step("Initialize subscription")
@step('Initialize subscription')
def initialize_subscription(
subscription: iptrunk.IptrunkInactive,
geant_s_sid: str,
iptrunk_type: iptrunk_pb.IptrunkType,
iptrunk_description: str,
iptrunk_speed: str,
iptrunk_minimum_links: int,
iptrunk_sideA_node_id: str,
iptrunk_sideA_ae_iface: str,
iptrunk_sideA_ae_geant_a_sid: str,
iptrunk_sideA_ae_members: list[str],
iptrunk_sideA_ae_members_descriptions: list[str],
iptrunk_sideB_node_id: str,
iptrunk_sideB_ae_iface: str,
iptrunk_sideB_ae_geant_a_sid: str,
iptrunk_sideB_ae_members: list[str],
iptrunk_sideB_ae_members_descriptions: list[str]
subscription: IptrunkInactive,
geant_s_sid: str,
iptrunk_type: IptrunkType,
iptrunk_description: str,
iptrunk_speed: str,
iptrunk_minimum_links: int,
iptrunk_sideA_node_id: str,
iptrunk_sideA_ae_iface: str,
iptrunk_sideA_ae_geant_a_sid: str,
iptrunk_sideA_ae_members: list[str],
iptrunk_sideA_ae_members_descriptions: list[str],
iptrunk_sideB_node_id: str,
iptrunk_sideB_ae_iface: str,
iptrunk_sideB_ae_geant_a_sid: str,
iptrunk_sideB_ae_members: list[str],
iptrunk_sideB_ae_members_descriptions: list[str]
) -> State:
subscription.iptrunk.geant_s_sid = geant_s_sid
subscription.iptrunk.iptrunk_description = iptrunk_description
......@@ -122,7 +126,7 @@ def initialize_subscription(
subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links
subscription.iptrunk.iptrunk_sideA_node = Device.from_subscription(
iptrunk_sideA_node_id[0]).device
iptrunk_sideA_node_id[0]).device
subscription.iptrunk.iptrunk_sideA_ae_iface = iptrunk_sideA_ae_iface
subscription.iptrunk.iptrunk_sideA_ae_geant_a_sid \
= iptrunk_sideA_ae_geant_a_sid
......@@ -131,7 +135,7 @@ def initialize_subscription(
= iptrunk_sideA_ae_members_descriptions
subscription.iptrunk.iptrunk_sideB_node = Device.from_subscription(
iptrunk_sideB_node_id[0]).device
iptrunk_sideB_node_id[0]).device
subscription.iptrunk.iptrunk_sideB_ae_iface \
= iptrunk_sideB_ae_iface
subscription.iptrunk.iptrunk_sideB_ae_geant_a_sid \
......@@ -140,110 +144,124 @@ def initialize_subscription(
subscription.iptrunk.iptrunk_sideB_ae_members_description \
= iptrunk_sideB_ae_members_descriptions
subscription.description = f"Iptrunk {geant_s_sid}: \
(blablabla)"
subscription = iptrunk.IptrunkProvisioning.from_other_lifecycle(
subscription.description = f'IP trunk, geant_s_sid:{geant_s_sid}'
subscription = IptrunkProvisioning.from_other_lifecycle(
subscription, SubscriptionLifecycle.PROVISIONING
)
return {"subscription": subscription}
return {'subscription': subscription}
@step("Provision iptrunk [DRY RUN]")
def provision_iptrunk_dry(
subscription: iptrunk.IptrunkProvisioning,
) -> 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,
) -> 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}
@step('Provision IP trunk interface [DRY RUN]')
def provision_ip_trunk_iface_dry(subscription: IptrunkProvisioning,
process_id: UUIDstr) -> State:
provisioning_proxy.provision_ip_trunk(subscription, process_id,
'trunk_interface')
return {'subscription': subscription}
@step('Provision IP trunk interface [FOR REAL]')
def provision_ip_trunk_iface_real(subscription: IptrunkProvisioning,
process_id: UUIDstr) -> State:
provisioning_proxy.provision_ip_trunk(subscription, process_id,
'trunk_interface', False)
return {'subscription': subscription}
@step('Provision IP trunk ISIS interface [DRY RUN]')
def provision_ip_trunk_isis_iface_dry(subscription: IptrunkProvisioning,
process_id: UUIDstr) -> State:
provisioning_proxy.provision_ip_trunk(subscription, process_id,
'isis_interface')
return {'subscription': subscription}
@step('Provision IP trunk ISIS interface [FOR REAL]')
def provision_ip_trunk_isis_iface_real(subscription: IptrunkProvisioning,
process_id: UUIDstr) -> State:
provisioning_proxy.provision_ip_trunk(subscription, process_id,
'isis_interface', False)
return {'subscription': subscription}
@step('Provision IP trunk LDP interface [DRY RUN]')
def provision_ip_trunk_ldp_iface_dry(subscription: IptrunkProvisioning,
process_id: UUIDstr) -> State:
provisioning_proxy.provision_ip_trunk(subscription, process_id,
'ldp_interface')
return {'subscription': subscription}
@step('Provision IP trunk LDP interface [FOR REAL]')
def provision_ip_trunk_ldp_iface_real(subscription: IptrunkProvisioning,
process_id: UUIDstr) -> State:
provisioning_proxy.provision_ip_trunk(subscription, process_id,
'ldp_interface', False)
return {'subscription': subscription}
@step('Provision IP trunk LLDP interface [DRY RUN]')
def provision_ip_trunk_lldp_iface_dry(subscription: IptrunkProvisioning,
process_id: UUIDstr) -> State:
provisioning_proxy.provision_ip_trunk(subscription, process_id,
'lldp_interface')
return {'subscription': subscription}
@step('Provision IP trunk LLDP interface [FOR REAL]')
def provision_ip_trunk_lldp_iface_real(subscription: IptrunkProvisioning,
process_id: UUIDstr) -> State:
provisioning_proxy.provision_ip_trunk(subscription, process_id,
'lldp_interface', False)
return {'subscription': subscription}
@workflow(
"Create Iptrunk",
'Create IP trunk',
initial_input_form=wrap_create_initial_input_form(
initial_input_form_generator),
initial_input_form_generator),
target=Target.CREATE,
)
def create_iptrunk():
return (
init
>> create_subscription
>> store_process_subscription(Target.CREATE)
>> get_info_from_ipam
>> initialize_subscription
>> provision_iptrunk_dry
>> confirm_step
>> provision_iptrunk_real
>> set_status(SubscriptionLifecycle.ACTIVE)
>> resync
>> done
init
>> create_subscription
>> store_process_subscription(Target.CREATE)
>> get_info_from_ipam
>> initialize_subscription
>> provision_ip_trunk_iface_dry
>> await_pp_results
>> confirm_pp_results
>> provision_ip_trunk_iface_real
>> await_pp_results
>> confirm_pp_results
>> provision_ip_trunk_isis_iface_dry
>> await_pp_results
>> confirm_pp_results
>> provision_ip_trunk_isis_iface_real
>> await_pp_results
>> confirm_pp_results
>> provision_ip_trunk_ldp_iface_dry
>> await_pp_results
>> confirm_pp_results
>> provision_ip_trunk_ldp_iface_real
>> await_pp_results
>> confirm_pp_results
>> provision_ip_trunk_lldp_iface_dry
>> await_pp_results
>> confirm_pp_results
>> provision_ip_trunk_lldp_iface_real
>> await_pp_results
>> confirm_pp_results
>> set_status(SubscriptionLifecycle.ACTIVE)
>> resync
>> done
)
# noinspection PyProtectedMember
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.types import State
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 gso.products.product_types.iptrunk import Iptrunk
from gso.services import provisioning_proxy
from gso.services.provisioning_proxy import confirm_pp_results, \
await_pp_results
def initial_input_form_generator(subscription_id: UUIDstr) -> InputForm:
subscription = Iptrunk.from_subscription(subscription_id)
class TerminateForm(FormPage):
are_you_sure: Label = (
f'Are you sure you want to remove {subscription.description}?'
)
return TerminateForm
@step('Deprovision IP trunk [DRY RUN]')
def deprovision_ip_trunk_dry(subscription: Iptrunk,
process_id: UUIDstr) -> State:
provisioning_proxy.deprovision_ip_trunk(subscription, process_id)
return {'subscription': subscription}
@step('Deprovision IP trunk [FOR REAL]')
def deprovision_ip_trunk_real(subscription: Iptrunk,
process_id: UUIDstr) -> State:
provisioning_proxy.deprovision_ip_trunk(subscription, process_id, False)
return {'subscription': subscription}
@workflow(
'Terminate IPtrunk',
initial_input_form=wrap_modify_initial_input_form(
initial_input_form_generator),
target=Target.TERMINATE,
)
def terminate_iptrunk():
return (
init
>> store_process_subscription(Target.TERMINATE)
>> unsync
>> deprovision_ip_trunk_dry
>> await_pp_results
>> confirm_pp_results
>> deprovision_ip_trunk_real
>> await_pp_results
>> confirm_pp_results
>> set_status(SubscriptionLifecycle.TERMINATED)
>> resync
>> done
)
from uuid import uuid4
from orchestrator.forms import FormPage
from orchestrator.targets import Target
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_blocks import site as site_pb
from gso.products.product_types import site
def initial_input_form_generator(product_name: str) -> FormGenerator:
class CreateSiteForm(FormPage):
class Config:
title = product_name
site_name: str
site_city: str
site_country: str
site_country_code: str
site_latitude: float
site_longitude: float
site_bgp_community_id: int
site_internal_id: int
site_tier: site_pb.SiteTier
user_input = yield CreateSiteForm
return user_input.dict()
@step("Create subscription")
def create_subscription(product: UUIDstr) -> State:
subscription = site.SiteInactive.from_product_id(product, uuid4())
return {
"subscription": subscription,
"subscription_id": subscription.subscription_id,
}
@step("Initialize subscription")
def initialize_subscription(
subscription: site.SiteInactive,
site_name: str,
site_city: str,
site_country: str,
site_country_code: str,
site_latitude: float,
site_longitude: float,
site_bgp_community_id: int,
site_internal_id: int,
site_tier: site_pb.SiteTier
) -> State:
subscription.site.site_name = site_name
subscription.site.site_city = site_city
subscription.site.site_country = site_country
subscription.site.site_country_code = site_country_code
subscription.site.site_latitude = site_longitude
subscription.site.site_longitude = site_latitude
subscription.site.site_bgp_community_id = site_bgp_community_id
subscription.site.site_internal_id = site_internal_id
subscription.site.site_tier = site_tier
subscription.description = f"Site {site_name}"
subscription = site.SiteProvisioning.from_other_lifecycle(
subscription, SubscriptionLifecycle.PROVISIONING
)
return {"subscription": subscription}
@workflow(
"Create Site",
initial_input_form=wrap_create_initial_input_form(
initial_input_form_generator),
target=Target.CREATE,
)
def create_site():
return (
init
>> create_subscription
>> store_process_subscription(Target.CREATE)
>> initialize_subscription
>> set_status(SubscriptionLifecycle.ACTIVE)
>> 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