Skip to content
Snippets Groups Projects
Commit ae916874 authored by Simone Spinelli's avatar Simone Spinelli
Browse files

Merge branch 'feature/new_device_model_and_sites_and_orgs' into 'feature/test-provisioning-proxy'

# Conflicts:
#   gso/workflows/device/create_device.py
parents 9875c289 b754fd1f
Branches
Tags
2 merge requests!12Add sites, and add integration of IPtrunks and routers with LSO deployment,!11Feature/test provisioning proxy
Showing
with 910 additions and 164 deletions
...@@ -10,7 +10,7 @@ from alembic import op ...@@ -10,7 +10,7 @@ from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = '857225661207' revision = '857225661207'
down_revision = 'f4959f32c866' down_revision = 'd52256e7d715'
branch_labels = None branch_labels = None
depends_on = None depends_on = None
......
"""add IPtrunk create workflows. """add Site create workflow.
Revision ID: 95cd21cb2b05 Revision ID: 21e7bb0e5cad
Revises: 6b8483b46d06 Revises: 60d340427471
Create Date: 2023-04-28 12:37:36.801782 Create Date: 2023-05-04 09:00:36.433715
""" """
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = '95cd21cb2b05' revision = '21e7bb0e5cad'
down_revision = '6b8483b46d06' down_revision = '60d340427471'
branch_labels = None branch_labels = None
depends_on = None depends_on = None
...@@ -19,10 +19,10 @@ from orchestrator.migrations.helpers import create_workflow, delete_workflow ...@@ -19,10 +19,10 @@ from orchestrator.migrations.helpers import create_workflow, delete_workflow
new_workflows = [ new_workflows = [
{ {
"name": "create_iptrunk", "name": "create_site",
"target": "CREATE", "target": "CREATE",
"description": "Create Iptrunk", "description": "Create Site",
"product_type": "Iptrunk" "product_type": "Site"
} }
] ]
......
"""Add site model.
Revision ID: 4d850df522f2
Revises:
Create Date: 2023-05-04 08:27:31.942548
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = '4d850df522f2'
down_revision = None
branch_labels = ('data',)
depends_on = 'e05bb1967eff'
def upgrade() -> None:
conn = op.get_bind()
conn.execute("""
INSERT INTO products (name, description, product_type, tag, status) VALUES ('Site', 'A Geant POP', 'Site', 'SITE', 'active') RETURNING products.product_id
""")
conn.execute("""
INSERT INTO product_blocks (name, description, tag, status) VALUES ('SiteBlock', 'A Geant POP', 'SITE', 'active') RETURNING product_blocks.product_block_id
""")
conn.execute("""
INSERT INTO resource_types (resource_type, description) VALUES ('site_internal_id', 'Site internal ID') RETURNING resource_types.resource_type_id
""")
conn.execute("""
INSERT INTO resource_types (resource_type, description) VALUES ('site_country', 'Site country') RETURNING resource_types.resource_type_id
""")
conn.execute("""
INSERT INTO resource_types (resource_type, description) VALUES ('site_longitude', 'Site Longitude') RETURNING resource_types.resource_type_id
""")
conn.execute("""
INSERT INTO resource_types (resource_type, description) VALUES ('site_country_code', 'Site country code') RETURNING resource_types.resource_type_id
""")
conn.execute("""
INSERT INTO resource_types (resource_type, description) VALUES ('site_tier', 'Site Tier') RETURNING resource_types.resource_type_id
""")
conn.execute("""
INSERT INTO resource_types (resource_type, description) VALUES ('site_city', 'Site city') RETURNING resource_types.resource_type_id
""")
conn.execute("""
INSERT INTO resource_types (resource_type, description) VALUES ('site_latitude', 'Site latitude') RETURNING resource_types.resource_type_id
""")
conn.execute("""
INSERT INTO resource_types (resource_type, description) VALUES ('site_name', 'Site name') RETURNING resource_types.resource_type_id
""")
conn.execute("""
INSERT INTO resource_types (resource_type, description) VALUES ('site_bgp_community_id', 'Site BGP community ID') RETURNING resource_types.resource_type_id
""")
conn.execute("""
INSERT INTO product_product_blocks (product_id, product_block_id) VALUES ((SELECT products.product_id FROM products WHERE products.name IN ('Site')), (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')))
""")
conn.execute("""
INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_name')))
""")
conn.execute("""
INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_city')))
""")
conn.execute("""
INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_country')))
""")
conn.execute("""
INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_country_code')))
""")
conn.execute("""
INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_latitude')))
""")
conn.execute("""
INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_longitude')))
""")
conn.execute("""
INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_internal_id')))
""")
conn.execute("""
INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_bgp_community_id')))
""")
conn.execute("""
INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_tier')))
""")
def downgrade() -> None:
conn = op.get_bind()
conn.execute("""
DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_name'))
""")
conn.execute("""
DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_name'))
""")
conn.execute("""
DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_city'))
""")
conn.execute("""
DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_city'))
""")
conn.execute("""
DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_country'))
""")
conn.execute("""
DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_country'))
""")
conn.execute("""
DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_country_code'))
""")
conn.execute("""
DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_country_code'))
""")
conn.execute("""
DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_latitude'))
""")
conn.execute("""
DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_latitude'))
""")
conn.execute("""
DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_longitude'))
""")
conn.execute("""
DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_longitude'))
""")
conn.execute("""
DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_internal_id'))
""")
conn.execute("""
DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_internal_id'))
""")
conn.execute("""
DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_bgp_community_id'))
""")
conn.execute("""
DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_bgp_community_id'))
""")
conn.execute("""
DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_tier'))
""")
conn.execute("""
DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_tier'))
""")
conn.execute("""
DELETE FROM subscription_instance_values WHERE subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('site_internal_id', 'site_country', 'site_longitude', 'site_country_code', 'site_tier', 'site_city', 'site_latitude', 'site_name', 'site_bgp_community_id'))
""")
conn.execute("""
DELETE FROM resource_types WHERE resource_types.resource_type IN ('site_internal_id', 'site_country', 'site_longitude', 'site_country_code', 'site_tier', 'site_city', 'site_latitude', 'site_name', 'site_bgp_community_id')
""")
conn.execute("""
DELETE FROM product_product_blocks WHERE product_product_blocks.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Site')) AND product_product_blocks.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock'))
""")
conn.execute("""
DELETE FROM subscription_instances WHERE subscription_instances.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('SiteBlock'))
""")
conn.execute("""
DELETE FROM product_blocks WHERE product_blocks.name IN ('SiteBlock')
""")
conn.execute("""
DELETE FROM processes WHERE processes.pid IN (SELECT processes_subscriptions.pid FROM processes_subscriptions WHERE processes_subscriptions.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Site'))))
""")
conn.execute("""
DELETE FROM processes_subscriptions WHERE processes_subscriptions.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Site')))
""")
conn.execute("""
DELETE FROM subscription_instances WHERE subscription_instances.subscription_id IN (SELECT subscriptions.subscription_id FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Site')))
""")
conn.execute("""
DELETE FROM subscriptions WHERE subscriptions.product_id IN (SELECT products.product_id FROM products WHERE products.name IN ('Site'))
""")
conn.execute("""
DELETE FROM products WHERE products.name IN ('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 ...@@ -2,9 +2,11 @@ from orchestrator.domain import SUBSCRIPTION_MODEL_REGISTRY
from gso.products.product_types.device import Device from gso.products.product_types.device import Device
from gso.products.product_types.iptrunk import Iptrunk from gso.products.product_types.iptrunk import Iptrunk
from gso.products.product_types.site import Site
SUBSCRIPTION_MODEL_REGISTRY.update( SUBSCRIPTION_MODEL_REGISTRY.update(
{ {
"Site": Site,
"Router": Device, "Router": Device,
"Switch": Device, "Switch": Device,
"Iptrunk": Iptrunk, "Iptrunk": Iptrunk,
......
from typing import Optional from typing import Optional
from orchestrator.domain.base import ProductBlockModel 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 import ipaddress
class DeviceVendor(strEnum):
juniper = "juniper"
nokia = "nokia"
class DeviceRole(strEnum):
p = "p"
pe = "pe"
amt = "amt"
class DeviceBlockInactive(ProductBlockModel, class DeviceBlockInactive(ProductBlockModel,
lifecycle=[SubscriptionLifecycle.INITIAL], lifecycle=[SubscriptionLifecycle.INITIAL],
product_block_name="DeviceBlock"): product_block_name="DeviceBlock"):
fqdn: Optional[str] = None device_fqdn: Optional[str] = None
ts_address: Optional[str] = None device_ts_address: Optional[str] = None
ts_port: Optional[int] = None device_ts_port: Optional[int] = None
lo_ipv4_address: Optional[ipaddress.IPv4Address] = None device_lo_ipv4_address: Optional[ipaddress.IPv4Address] = None
lo_ipv6_address: Optional[ipaddress.IPv6Address] = None device_lo_ipv6_address: Optional[ipaddress.IPv6Address] = None
lo_iso_address: Optional[str] = None device_lo_iso_address: Optional[str] = None
si_ipv4_network: Optional[ipaddress.IPv4Network] = None device_si_ipv4_network: Optional[ipaddress.IPv4Network] = None
ias_lt_ipv4_network: Optional[ipaddress.IPv4Network] = None device_ias_lt_ipv4_network: Optional[ipaddress.IPv4Network] = None
ias_lt_ipv6_network: Optional[ipaddress.IPv6Network] = None device_ias_lt_ipv6_network: Optional[ipaddress.IPv6Network] = None
site_city: Optional[str] = None device_vendor: Optional[DeviceVendor] = None
site_country: Optional[str] = None device_role: Optional[DeviceRole] = None
site_country_code: Optional[str] = None device_site: Optional[SiteBlockInactive]
site_latitude: Optional[str] = None
site_longitude: Optional[str] = None
snmp_location: Optional[str] = None
class DeviceBlockProvisioning(DeviceBlockInactive, class DeviceBlockProvisioning(DeviceBlockInactive,
lifecycle=[SubscriptionLifecycle.PROVISIONING]): lifecycle=[SubscriptionLifecycle.PROVISIONING]):
fqdn: str device_fqdn: str
ts_address: str device_ts_address: str
ts_port: str device_ts_port: str
lo_ipv4_address: Optional[ipaddress.IPv4Address] = None device_lo_ipv4_address: Optional[ipaddress.IPv4Address] = None
lo_ipv6_address: Optional[ipaddress.IPv6Address] = None device_lo_ipv6_address: Optional[ipaddress.IPv6Address] = None
lo_iso_address: Optional[str] = None device_lo_iso_address: Optional[str] = None
si_ipv4_network: Optional[ipaddress.IPv4Network] = None device_si_ipv4_network: Optional[ipaddress.IPv4Network] = None
ias_lt_ipv4_network: Optional[ipaddress.IPv4Network] = None device_ias_lt_ipv4_network: Optional[ipaddress.IPv4Network] = None
ias_lt_ipv6_network: Optional[ipaddress.IPv6Network] = None device_ias_lt_ipv6_network: Optional[ipaddress.IPv6Network] = None
site_city: Optional[str] = None device_vendor: Optional[DeviceVendor] = None
site_country: Optional[str] = None device_role: Optional[DeviceRole] = None
site_country_code: Optional[str] = None device_site: Optional[SiteBlockProvisioning]
site_latitude: Optional[str] = None
site_longitude: Optional[str] = None
snmp_location: Optional[str] = None
class DeviceBlock(DeviceBlockProvisioning, class DeviceBlock(DeviceBlockProvisioning,
lifecycle=[SubscriptionLifecycle.ACTIVE]): lifecycle=[SubscriptionLifecycle.ACTIVE]):
fqdn: str device_fqdn: str
ts_address: str device_ts_address: str
ts_port: str device_ts_port: str
lo_ipv4_address: ipaddress.IPv4Address device_lo_ipv4_address: ipaddress.IPv4Address
lo_ipv6_address: ipaddress.IPv6Address device_lo_ipv6_address: ipaddress.IPv6Address
lo_iso_address: str device_lo_iso_address: str
si_ipv4_network: ipaddress.IPv4Network device_si_ipv4_network: ipaddress.IPv4Network
ias_lt_ipv4_network: ipaddress.IPv4Network device_ias_lt_ipv4_network: ipaddress.IPv4Network
ias_lt_ipv6_network: ipaddress.IPv6Network device_ias_lt_ipv6_network: ipaddress.IPv6Network
site_city: str device_vendor: DeviceVendor
site_country: str device_role: DeviceRole
site_country_code: str device_site: SiteBlock
site_latitude: str
site_longitude: str
snmp_location: str
...@@ -27,14 +27,14 @@ class IptrunkBlockInactive(ProductBlockModel, ...@@ -27,14 +27,14 @@ class IptrunkBlockInactive(ProductBlockModel,
iptrunk_sideA_node: DeviceBlockInactive iptrunk_sideA_node: DeviceBlockInactive
iptrunk_sideA_ae_iface: Optional[str] = None iptrunk_sideA_ae_iface: Optional[str] = None
iptrunk_sideA_ae_geant_a_sid: Optional[str] = None iptrunk_sideA_ae_geant_a_sid: Optional[str] = None
iptrunk_sideA_ae_members: Optional[list] = None iptrunk_sideA_ae_members: list[str] = Field(default_factory=list)
iptrunk_sideA_ae_members_description: Optional[list] = None iptrunk_sideA_ae_members_description: list[str] = Field(default_factory=list)
# #
iptrunk_sideB_node: DeviceBlockInactive iptrunk_sideB_node: DeviceBlockInactive
iptrunk_sideB_ae_iface: Optional[str] = None iptrunk_sideB_ae_iface: Optional[str] = None
iptrunk_sideB_ae_geant_a_sid: Optional[str] = None iptrunk_sideB_ae_geant_a_sid: Optional[str] = None
iptrunk_sideB_ae_members: Optional[list] = None iptrunk_sideB_ae_members: list[str] = Field(default_factory=list)
iptrunk_sideB_ae_members_description: Optional[list] = None iptrunk_sideB_ae_members_description: list[str] = Field(default_factory=list)
class IptrunkBlockProvisioning(IptrunkBlockInactive, 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): ...@@ -10,25 +10,17 @@ class DeviceType(strEnum):
switch = "switch" switch = "switch"
class DeviceVendor(strEnum):
Juniper = "Juniper"
Newvendor = "Newvendor"
class DeviceInactive(SubscriptionModel, is_base=True): class DeviceInactive(SubscriptionModel, is_base=True):
device_type: DeviceType device_type: DeviceType
device_vendor: DeviceVendor
device: DeviceBlockInactive device: DeviceBlockInactive
class DeviceProvisioning(DeviceInactive, class DeviceProvisioning(DeviceInactive,
lifecycle=[SubscriptionLifecycle.PROVISIONING]): lifecycle=[SubscriptionLifecycle.PROVISIONING]):
device_type: DeviceType device_type: DeviceType
device_vendor: DeviceVendor
device: DeviceBlockProvisioning device: DeviceBlockProvisioning
class Device(DeviceProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): class Device(DeviceProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
device_type: DeviceType device_type: DeviceType
device_vendor: DeviceVendor
device: DeviceBlock 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
...@@ -5,3 +5,6 @@ LazyWorkflowInstance("gso.workflows.device.terminate_device", ...@@ -5,3 +5,6 @@ LazyWorkflowInstance("gso.workflows.device.terminate_device",
"terminate_device") "terminate_device")
LazyWorkflowInstance("gso.workflows.device.get_facts", "get_facts") LazyWorkflowInstance("gso.workflows.device.get_facts", "get_facts")
LazyWorkflowInstance("gso.workflows.iptrunk.create_iptrunk", "create_iptrunk") LazyWorkflowInstance("gso.workflows.iptrunk.create_iptrunk", "create_iptrunk")
LazyWorkflowInstance("gso.workflows.iptrunk.terminate_iptrunk",
"terminate_iptrunk")
LazyWorkflowInstance("gso.workflows.site.create_site", "create_site")
...@@ -9,12 +9,35 @@ from orchestrator.workflow import done, init, step, workflow ...@@ -9,12 +9,35 @@ from orchestrator.workflow import done, init, step, workflow
from orchestrator.workflows.steps import resync, set_status from orchestrator.workflows.steps import resync, set_status
from orchestrator.workflows.steps import store_process_subscription from orchestrator.workflows.steps import store_process_subscription
from orchestrator.workflows.utils import wrap_create_initial_input_form from orchestrator.workflows.utils import wrap_create_initial_input_form
from gso.products.product_types import device
from gso.products.product_types.device import DeviceVendor, DeviceInactive, \ from gso.products.product_blocks import device as device_pb
DeviceProvisioning from orchestrator.db.models import ProductTable, SubscriptionTable
from gso.services import provisioning_proxy from orchestrator.forms.validators import Choice, choice_list
from gso.services.provisioning_proxy import confirm_pp_results, \ from gso.products.product_types.site import Site
await_pp_results # 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: def initial_input_form_generator(product_name: str) -> FormGenerator:
...@@ -22,10 +45,12 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: ...@@ -22,10 +45,12 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
class Config: class Config:
title = product_name title = product_name
fqdn: str device_site: site_selector()
hostname: str
ts_address: ipaddress.IPv4Address ts_address: ipaddress.IPv4Address
ts_port: int ts_port: int
device_vendor: DeviceVendor device_vendor: device_pb.DeviceVendor
device_role: device_pb.DeviceRole
user_input = yield CreateDeviceForm user_input = yield CreateDeviceForm
...@@ -48,78 +73,128 @@ def get_info_from_ipam(subscription: DeviceInactive) -> State: ...@@ -48,78 +73,128 @@ def get_info_from_ipam(subscription: DeviceInactive) -> State:
# subscription.device.lo_ipv4_address = lo.v4 # subscription.device.lo_ipv4_address = lo.v4
# subscription.device.lo_ipv6_address = lo.v6 # subscription.device.lo_ipv6_address = lo.v6
# TODO: get info about how these should be generated # TODO: get info about how these should be generated
subscription.device.lo_ipv4_address = '10.10.10.10' subscription.device.device_lo_ipv4_address = ipaddress.ip_address('10.10.10.10')
subscription.device.lo_ipv6_address = 'fc00:798:10::10' subscription.device.device_lo_ipv6_address = ipaddress.ip_address('fc00:798:10::10')
subscription.device.lo_iso_address = '49.51e5.0001.0620.4009.6047.00' subscription.device.device_lo_iso_address \
subscription.device.si_ipv4_network = '192.168.0.0/31' = "49.51e5.0001.0620.4009.6047.00"
subscription.device.ias_lt_ipv4_network = '192.168.1.0/31' subscription.device.device_si_ipv4_network = "192.168.0.0/31"
subscription.device.ias_lt_ipv6_network = 'fc00:798:1::150/126' 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} 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}]'
)
return {'subscription': subscription}
@step('Initialize subscription') @step('Initialize subscription')
def initialize_subscription( def initialize_subscription(
subscription: DeviceInactive, subscription: device.DeviceInactive,
fqdn: str, hostname: str,
ts_address: ipaddress.IPv4Address, ts_address: ipaddress.IPv4Address,
ts_port: str, ts_port: str,
device_vendor: DeviceVendor device_vendor: device_pb.DeviceVendor,
device_site: str,
device_role: device_pb.DeviceRole
) -> State: ) -> State:
subscription.device.fqdn = fqdn subscription.device.device_ts_address = str(ts_address)
subscription.device.ts_address = str(ts_address) subscription.device.device_ts_port = str(ts_port)
subscription.device.ts_port = str(ts_port) subscription.device.device_vendor = device_vendor
subscription.device_vendor = device_vendor subscription.device.device_site \
subscription.description = f'Device {fqdn} ' \ = Site.from_subscription(device_site[0]).site
f'({subscription.device_type})' fqdn = str(hostname + "." +
subscription = DeviceProvisioning.from_other_lifecycle( 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 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
)
return {'subscription': subscription}
@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
)
return {'subscription': subscription}
@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( @workflow(
'Create device', 'Create device',
......
...@@ -41,7 +41,7 @@ def deprovision_user(subscription: Device) -> None: ...@@ -41,7 +41,7 @@ def deprovision_user(subscription: Device) -> None:
initial_input_form_generator), initial_input_form_generator),
target=Target.TERMINATE, target=Target.TERMINATE,
) )
def terminate_user(): def terminate_device():
return ( return (
init init
>> store_process_subscription(Target.TERMINATE) >> store_process_subscription(Target.TERMINATE)
......
# import ipaddress import ipaddress
from uuid import uuid4 from uuid import uuid4
from orchestrator.forms import FormPage from orchestrator.forms import FormPage
...@@ -19,6 +19,8 @@ from gso.products.product_types.device import Device ...@@ -19,6 +19,8 @@ from gso.products.product_types.device import Device
# from gso.products.product_types import device # from gso.products.product_types import device
from orchestrator.db.models import ProductTable, SubscriptionTable from orchestrator.db.models import ProductTable, SubscriptionTable
from orchestrator.forms.validators import Choice, choice_list from orchestrator.forms.validators import Choice, choice_list
from orchestrator.utils.json import json_dumps
def device_selector(choice_value: str) -> list: def device_selector(choice_value: str) -> list:
...@@ -91,8 +93,8 @@ def create_subscription(product: UUIDstr) -> State: ...@@ -91,8 +93,8 @@ def create_subscription(product: UUIDstr) -> State:
@step("Get information from IPAM ") @step("Get information from IPAM ")
def get_info_from_ipam(subscription: iptrunk.IptrunkInactive) -> State: def get_info_from_ipam(subscription: iptrunk.IptrunkInactive) -> State:
# TODO: get info about how these should be generated # TODO: get info about how these should be generated
subscription.iptrunk.iptrunk_ipv4_network = "192.168.1.0/31" subscription.iptrunk.iptrunk_ipv4_network = ipaddress.ip_network('192.168.255.0/31')
subscription.iptrunk.iptrunk_ipv6_network = "fc00:798:1::150/126" subscription.iptrunk.iptrunk_ipv6_network = ipaddress.ip_network('fc00:798:255::150/126')
return {"subscription": subscription} return {"subscription": subscription}
...@@ -153,35 +155,30 @@ def initialize_subscription( ...@@ -153,35 +155,30 @@ def initialize_subscription(
def provision_iptrunk_dry( def provision_iptrunk_dry(
subscription: iptrunk.IptrunkProvisioning, subscription: iptrunk.IptrunkProvisioning,
) -> State: ) -> State:
# import ansible_runner import ansible_runner
#
# r = ansible_runner.run( r = ansible_runner.run(
# private_data_dir="/opt/geant-gap-ansible", private_data_dir="/opt/geant-gap-ansible",
# playbook="base_config.yaml", playbook="iptrunks.yaml",
# inventory=subscription.iptrunk.fqdn, inventory= str(subscription.iptrunk.iptrunk_sideA_node.device_fqdn +"\n"+
# extravars={ subscription.iptrunk.iptrunk_sideB_node.device_fqdn+"\n"),
# "lo_ipv4_address": str(subscription.iptrunk.lo_ipv4_address), extravars={
# "lo_ipv6_address": str(subscription.iptrunk.lo_ipv6_address), "verb": "compile",
# "lo_iso_address": subscription.iptrunk.lo_iso_address, "wfo_trunk_json":json_dumps(subscription),
# "snmp_location": subscription.iptrunk.snmp_location, "config_object":"trunk_interface",
# "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), out = r.stdout.read()
# "site_country_code": subscription.iptrunk.site_country_code, out_splitted = out.splitlines()
# "verb": "deploy",
# },
# )
# out = r.stdout.read()
# out_splitted = out.splitlines()
# # if r.rc != 0: # # if r.rc != 0:
# # raise ValueError("Ansible has failed") # # raise ValueError("Ansible has failed")
# return {"dry_run_output": out_splitted, "return_code": r.rc} return {"dry_run_output": out_splitted, "return_code": r.rc}
# provisioning_proxy.provision_node( # provisioning_proxy.provision_node(
# node_subscription_params=subscription, # node_subscription_params=subscription,
# dry_run=True) # dry_run=True)
# TODO: figure out what to return when we are suspending & waiting # TODO: figure out what to return when we are suspending & waiting
# for the provisioning-proxy to call back # for the provisioning-proxy to call back
return {"return_code": 0} #return {"return_code": 0}
@inputstep("Confirm step", assignee="CHANGES") @inputstep("Confirm step", assignee="CHANGES")
......
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 gso.products.product_types.iptrunk import Iptrunk
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}?"
) # type:ignore
return TerminateForm
def _deprovision_in_user_management_system(fqdn: str) -> str:
pass
@step("Deprovision Iptrunk")
def deprovision_iptrunk(subscription: Iptrunk) -> None:
# _deprovision_in_user_management_system(subscription.user.user_id)
pass
@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_iptrunk
>> set_status(SubscriptionLifecycle.TERMINATED)
>> resync
>> done
)
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 site
from gso.products.product_blocks import site as site_pb
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.
Please register or to comment