Skip to content
Snippets Groups Projects
Commit 22fa5626 authored by geant-release-service's avatar geant-release-service
Browse files

Finished release 3.7.

parents eccfc865 9abe361a
Branches
Tags 3.7
No related merge requests found
Pipeline #94361 passed
# Changelog # Changelog
# [3.7] - 2025-05-26
- Add service version for IP Trunks
# [3.6] - 2025-05-15 # [3.6] - 2025-05-15
- Stop validating Edge Ports that are on a Juniper router. - Stop validating Edge Ports that are on a Juniper router.
- Allow skipping Ansible steps in the Edge Port termination workflow. - Allow skipping Ansible steps in the Edge Port termination workflow.
......
"""add config version to ip trunk.
Revision ID: 54477431c9ef
Revises: 465008ed496e
Create Date: 2025-05-12 04:46:47.410668
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = '54477431c9ef'
down_revision = '465008ed496e'
branch_labels = None
depends_on = None
def upgrade() -> None:
conn = op.get_bind()
conn.execute(sa.text("""
INSERT INTO resource_types (resource_type, description) VALUES ('iptrunk_config_version', 'adding option of service version controlled rollout') RETURNING resource_types.resource_type_id
"""))
conn.execute(sa.text("""
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 ('IptrunkBlock')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('iptrunk_config_version')))
"""))
def downgrade() -> None:
conn = op.get_bind()
conn.execute(sa.text("""
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 ('IptrunkBlock')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('iptrunk_config_version'))
"""))
conn.execute(sa.text("""
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 ('IptrunkBlock'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('iptrunk_config_version'))
"""))
conn.execute(sa.text("""
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 ('iptrunk_config_version'))
"""))
conn.execute(sa.text("""
DELETE FROM resource_types WHERE resource_types.resource_type IN ('iptrunk_config_version')
"""))
...@@ -130,5 +130,21 @@ ...@@ -130,5 +130,21 @@
"MOODI": { "MOODI": {
"host": "moodi.test.gap.geant.org", "host": "moodi.test.gap.geant.org",
"moodi_enabled": true "moodi_enabled": true
},
"SERVICE_VERSIONS": {
"IP_TRUNK": {
"version": {
"1.0": "Base Version",
"1.1": "Minor Upgrade"
},
"default_version": "1.0"
},
"GEANT_IP": {
"version": {
"1.0": "Base Version",
"2.0": "Major Upgrade"
},
"default_version": "1.0"
}
} }
} }
...@@ -109,6 +109,7 @@ class IptrunkBlockInactive( ...@@ -109,6 +109,7 @@ class IptrunkBlockInactive(
iptrunk_ipv6_network: ipaddress.IPv6Network | None = None iptrunk_ipv6_network: ipaddress.IPv6Network | None = None
iptrunk_sides: IptrunkSides[IptrunkSideBlockInactive] iptrunk_sides: IptrunkSides[IptrunkSideBlockInactive]
iptrunk_description_suffix: str | None = None iptrunk_description_suffix: str | None = None
iptrunk_config_version: str | None = None
class IptrunkBlockProvisioning(IptrunkBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]): class IptrunkBlockProvisioning(IptrunkBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
...@@ -124,6 +125,7 @@ class IptrunkBlockProvisioning(IptrunkBlockInactive, lifecycle=[SubscriptionLife ...@@ -124,6 +125,7 @@ class IptrunkBlockProvisioning(IptrunkBlockInactive, lifecycle=[SubscriptionLife
iptrunk_ipv6_network: ipaddress.IPv6Network | None iptrunk_ipv6_network: ipaddress.IPv6Network | None
iptrunk_sides: IptrunkSides[IptrunkSideBlockProvisioning] # type: ignore[assignment] iptrunk_sides: IptrunkSides[IptrunkSideBlockProvisioning] # type: ignore[assignment]
iptrunk_description_suffix: str | None iptrunk_description_suffix: str | None
iptrunk_config_version: str | None
class IptrunkBlock(IptrunkBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): class IptrunkBlock(IptrunkBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
...@@ -152,3 +154,4 @@ class IptrunkBlock(IptrunkBlockProvisioning, lifecycle=[SubscriptionLifecycle.AC ...@@ -152,3 +154,4 @@ class IptrunkBlock(IptrunkBlockProvisioning, lifecycle=[SubscriptionLifecycle.AC
iptrunk_ipv6_network: ipaddress.IPv6Network iptrunk_ipv6_network: ipaddress.IPv6Network
iptrunk_sides: IptrunkSides[IptrunkSideBlock] # type: ignore[assignment] iptrunk_sides: IptrunkSides[IptrunkSideBlock] # type: ignore[assignment]
iptrunk_description_suffix: str | None iptrunk_description_suffix: str | None
iptrunk_config_version: str | None
...@@ -228,6 +228,20 @@ class MoodiParams(BaseSettings): ...@@ -228,6 +228,20 @@ class MoodiParams(BaseSettings):
moodi_enabled: bool = False moodi_enabled: bool = False
class ServiceConfig(BaseSettings):
"""Base configuration object for setting version information of a service."""
version: dict[str, str]
default_version: str
class ServiceVersionConfig(BaseSettings):
"""Services offered by GSO that support multiple versions."""
IP_TRUNK: ServiceConfig
GEANT_IP: ServiceConfig
class OSSParams(BaseSettings): class OSSParams(BaseSettings):
"""The set of parameters required for running GSO.""" """The set of parameters required for running GSO."""
...@@ -242,6 +256,7 @@ class OSSParams(BaseSettings): ...@@ -242,6 +256,7 @@ class OSSParams(BaseSettings):
KENTIK: KentikParams KENTIK: KentikParams
SENTRY: SentryParams | None = None SENTRY: SentryParams | None = None
MOODI: MoodiParams MOODI: MoodiParams
SERVICE_VERSIONS: ServiceVersionConfig
def load_oss_params() -> OSSParams: def load_oss_params() -> OSSParams:
......
...@@ -10,7 +10,6 @@ from orchestrator.types import SubscriptionLifecycle ...@@ -10,7 +10,6 @@ from orchestrator.types import SubscriptionLifecycle
from pydantic_forms.types import UUIDstr from pydantic_forms.types import UUIDstr
from pydantic_forms.validators import Choice from pydantic_forms.validators import Choice
from gso import settings
from gso.products.product_blocks.layer_2_circuit import Layer2CircuitType from gso.products.product_blocks.layer_2_circuit import Layer2CircuitType
from gso.products.product_blocks.router import RouterRole from gso.products.product_blocks.router import RouterRole
from gso.products.product_types.router import Router from gso.products.product_types.router import Router
...@@ -25,6 +24,7 @@ from gso.services.subscriptions import ( ...@@ -25,6 +24,7 @@ from gso.services.subscriptions import (
get_router_subscriptions, get_router_subscriptions,
is_virtual_circuit_id_available, is_virtual_circuit_id_available,
) )
from gso.settings import load_oss_params
from gso.utils.shared_enums import Vendor from gso.utils.shared_enums import Vendor
from gso.utils.types.interfaces import PhysicalPortCapacity from gso.utils.types.interfaces import PhysicalPortCapacity
from gso.utils.types.ip_address import IPv4AddressType, IPv4NetworkType, IPv6NetworkType from gso.utils.types.ip_address import IPv4AddressType, IPv4NetworkType, IPv6NetworkType
...@@ -131,13 +131,13 @@ def iso_from_ipv4(ipv4_address: IPv4AddressType) -> str: ...@@ -131,13 +131,13 @@ def iso_from_ipv4(ipv4_address: IPv4AddressType) -> str:
def generate_fqdn(hostname: str, site_name: str, country_code: str) -> str: def generate_fqdn(hostname: str, site_name: str, country_code: str) -> str:
"""Generate an FQDN from a hostname, site name, and a country code.""" """Generate an FQDN from a hostname, site name, and a country code."""
oss = settings.load_oss_params() oss = load_oss_params()
return f"{hostname}.{site_name.lower()}.{country_code.lower()}{oss.IPAM.LO.domain_name}" return f"{hostname}.{site_name.lower()}.{country_code.lower()}{oss.IPAM.LO.domain_name}"
def generate_lan_switch_interconnect_subnet_v4(site_internal_id: int) -> IPv4NetworkType: def generate_lan_switch_interconnect_subnet_v4(site_internal_id: int) -> IPv4NetworkType:
"""Generate an IPv4 network in which a LAN Switch Interconnect resides, given a Site internal ID.""" """Generate an IPv4 network in which a LAN Switch Interconnect resides, given a Site internal ID."""
ipam_oss = settings.load_oss_params().IPAM.LAN_SWITCH_INTERCONNECT ipam_oss = load_oss_params().IPAM.LAN_SWITCH_INTERCONNECT
result = str(ipam_oss.V4.containers[0]).split(".")[:2] # Take the first two octets from the IPv4 network. result = str(ipam_oss.V4.containers[0]).split(".")[:2] # Take the first two octets from the IPv4 network.
result.append(str(site_internal_id)) # Append the side ID as the third octet. result.append(str(site_internal_id)) # Append the side ID as the third octet.
...@@ -148,7 +148,7 @@ def generate_lan_switch_interconnect_subnet_v4(site_internal_id: int) -> IPv4Net ...@@ -148,7 +148,7 @@ def generate_lan_switch_interconnect_subnet_v4(site_internal_id: int) -> IPv4Net
def generate_lan_switch_interconnect_subnet_v6(site_internal_id: int) -> IPv6NetworkType: def generate_lan_switch_interconnect_subnet_v6(site_internal_id: int) -> IPv6NetworkType:
"""Generate an IPv6 network in which a LAN Switch Interconnect resides, given a Site internal ID.""" """Generate an IPv6 network in which a LAN Switch Interconnect resides, given a Site internal ID."""
ipam_oss = settings.load_oss_params().IPAM.LAN_SWITCH_INTERCONNECT ipam_oss = load_oss_params().IPAM.LAN_SWITCH_INTERCONNECT
result = IPv6Network(ipam_oss.V6.containers[0]).exploded[:17] # Take the first 56 bits of the network result = IPv6Network(ipam_oss.V6.containers[0]).exploded[:17] # Take the first 56 bits of the network
result += str(hex(site_internal_id)[2:]) # Append the site internal id for bytes 57 to 64 as hexadecimal number result += str(hex(site_internal_id)[2:]) # Append the site internal id for bytes 57 to 64 as hexadecimal number
...@@ -283,6 +283,19 @@ def active_edge_port_selector(*, partner_id: UUIDstr | None = None) -> TypeAlias ...@@ -283,6 +283,19 @@ def active_edge_port_selector(*, partner_id: UUIDstr | None = None) -> TypeAlias
) )
def ip_trunk_service_version_selector() -> TypeAlias:
"""Generate a dropdown selector for choosing a service version."""
iptrunk_versions = load_oss_params().SERVICE_VERSIONS.IP_TRUNK.version
return cast(
type[Choice],
Choice.__call__(
"Select an IP trunk service version.",
[(k, f"Version {k} - {iptrunk_versions[k]}") for k in iptrunk_versions],
),
)
def partner_choice() -> Choice: def partner_choice() -> Choice:
"""Return a Choice object containing a list of available partners.""" """Return a Choice object containing a list of available partners."""
partners = {partner.partner_id: partner.name for partner in get_all_partners()} partners = {partner.partner_id: partner.name for partner in get_all_partners()}
......
...@@ -68,6 +68,7 @@ from gso.utils.helpers import ( ...@@ -68,6 +68,7 @@ from gso.utils.helpers import (
available_lags_choices, available_lags_choices,
calculate_recommended_minimum_links, calculate_recommended_minimum_links,
get_router_vendor, get_router_vendor,
ip_trunk_service_version_selector,
) )
from gso.utils.shared_enums import Vendor from gso.utils.shared_enums import Vendor
from gso.utils.types.interfaces import JuniperLAGMember, LAGMember, LAGMemberList, PhysicalPortCapacity from gso.utils.types.interfaces import JuniperLAGMember, LAGMember, LAGMemberList, PhysicalPortCapacity
...@@ -95,6 +96,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: ...@@ -95,6 +96,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
iptrunk_speed: PhysicalPortCapacity iptrunk_speed: PhysicalPortCapacity
iptrunk_number_of_members: int iptrunk_number_of_members: int
iptrunk_description_suffix: str | None = None iptrunk_description_suffix: str | None = None
iptrunk_config_version: ip_trunk_service_version_selector() # type: ignore[valid-type]
initial_user_input = yield CreateIptrunkForm initial_user_input = yield CreateIptrunkForm
recommended_minimum_links = calculate_recommended_minimum_links( recommended_minimum_links = calculate_recommended_minimum_links(
...@@ -203,6 +205,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: ...@@ -203,6 +205,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:
"iptrunk_description", "iptrunk_description",
"iptrunk_minimum_links", "iptrunk_minimum_links",
"iptrunk_description_suffix", "iptrunk_description_suffix",
"iptrunk_config_version",
"side_a_node", "side_a_node",
"side_a_ae_iface", "side_a_ae_iface",
"side_a_ae_members", "side_a_ae_members",
...@@ -327,6 +330,7 @@ def initialize_subscription( ...@@ -327,6 +330,7 @@ def initialize_subscription(
iptrunk_speed: PhysicalPortCapacity, iptrunk_speed: PhysicalPortCapacity,
iptrunk_minimum_links: int, iptrunk_minimum_links: int,
iptrunk_description_suffix: str | None, iptrunk_description_suffix: str | None,
iptrunk_config_version: str,
side_a_node_id: str, side_a_node_id: str,
side_a_ae_iface: str, side_a_ae_iface: str,
side_a_ae_members: list[dict], side_a_ae_members: list[dict],
...@@ -346,6 +350,7 @@ def initialize_subscription( ...@@ -346,6 +350,7 @@ def initialize_subscription(
subscription.iptrunk.iptrunk_isis_metric = oss_params.GENERAL.isis_high_metric subscription.iptrunk.iptrunk_isis_metric = oss_params.GENERAL.isis_high_metric
subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links
subscription.iptrunk.iptrunk_description_suffix = iptrunk_description_suffix subscription.iptrunk.iptrunk_description_suffix = iptrunk_description_suffix
subscription.iptrunk.iptrunk_config_version = iptrunk_config_version
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node = side_a subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node = side_a
subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_iface = side_a_ae_iface subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_iface = side_a_ae_iface
......
...@@ -35,6 +35,7 @@ from gso.utils.helpers import ( ...@@ -35,6 +35,7 @@ from gso.utils.helpers import (
available_interfaces_choices_including_current_members, available_interfaces_choices_including_current_members,
calculate_recommended_minimum_links, calculate_recommended_minimum_links,
get_router_vendor, get_router_vendor,
ip_trunk_service_version_selector,
) )
from gso.utils.shared_enums import Vendor from gso.utils.shared_enums import Vendor
from gso.utils.types.interfaces import JuniperLAGMember, LAGMember, LAGMemberList, PhysicalPortCapacity from gso.utils.types.interfaces import JuniperLAGMember, LAGMember, LAGMemberList, PhysicalPortCapacity
...@@ -95,6 +96,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: ...@@ -95,6 +96,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
| None | None
) = subscription.iptrunk.gs_id ) = subscription.iptrunk.gs_id
iptrunk_description: str | None = subscription.iptrunk.iptrunk_description iptrunk_description: str | None = subscription.iptrunk.iptrunk_description
iptrunk_config_version: ip_trunk_service_version_selector() | str = subscription.iptrunk.iptrunk_config_version # type: ignore[valid-type]
iptrunk_type: IptrunkType | str = subscription.iptrunk.iptrunk_type # FIXME: remove str workaround iptrunk_type: IptrunkType | str = subscription.iptrunk.iptrunk_type # FIXME: remove str workaround
warning_label: Label = ( warning_label: Label = (
"Changing the PhyPortCapacity will result in the deletion of all AE members. " "Changing the PhyPortCapacity will result in the deletion of all AE members. "
...@@ -268,6 +270,7 @@ def modify_iptrunk_subscription( ...@@ -268,6 +270,7 @@ def modify_iptrunk_subscription(
iptrunk_speed: PhysicalPortCapacity, iptrunk_speed: PhysicalPortCapacity,
iptrunk_minimum_links: int, iptrunk_minimum_links: int,
iptrunk_description_suffix: str | None, iptrunk_description_suffix: str | None,
iptrunk_config_version: str,
side_a_ga_id: str | None, side_a_ga_id: str | None,
side_a_ae_members: list[dict], side_a_ae_members: list[dict],
side_b_ga_id: str | None, side_b_ga_id: str | None,
...@@ -302,7 +305,7 @@ def modify_iptrunk_subscription( ...@@ -302,7 +305,7 @@ def modify_iptrunk_subscription(
subscription.iptrunk.iptrunk_speed = iptrunk_speed subscription.iptrunk.iptrunk_speed = iptrunk_speed
subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links
subscription.iptrunk.iptrunk_description_suffix = iptrunk_description_suffix subscription.iptrunk.iptrunk_description_suffix = iptrunk_description_suffix
subscription.iptrunk.iptrunk_config_version = iptrunk_config_version
subscription.iptrunk.iptrunk_sides[0].ga_id = side_a_ga_id subscription.iptrunk.iptrunk_sides[0].ga_id = side_a_ga_id
update_side_members(subscription, 0, side_a_ae_members) update_side_members(subscription, 0, side_a_ae_members)
subscription.iptrunk.iptrunk_sides[1].ga_id = side_b_ga_id subscription.iptrunk.iptrunk_sides[1].ga_id = side_b_ga_id
......
...@@ -4,7 +4,7 @@ from setuptools import find_packages, setup ...@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
setup( setup(
name="geant-service-orchestrator", name="geant-service-orchestrator",
version="3.6", version="3.7",
author="GÉANT Orchestration and Automation Team", author="GÉANT Orchestration and Automation Team",
author_email="goat@geant.org", author_email="goat@geant.org",
description="GÉANT Service Orchestrator", description="GÉANT Service Orchestrator",
......
...@@ -74,6 +74,7 @@ def input_form_wizard_data(request, router_subscription_factory, faker): ...@@ -74,6 +74,7 @@ def input_form_wizard_data(request, router_subscription_factory, faker):
"iptrunk_speed": PhysicalPortCapacity.HUNDRED_GIGABIT_PER_SECOND, "iptrunk_speed": PhysicalPortCapacity.HUNDRED_GIGABIT_PER_SECOND,
"iptrunk_number_of_members": 2, "iptrunk_number_of_members": 2,
"iptrunk_description_suffix": faker.word(), "iptrunk_description_suffix": faker.word(),
"iptrunk_config_version": "Version 1.0 - Base Version",
} }
create_ip_trunk_confirm_step = {"iptrunk_minimum_links": 1} create_ip_trunk_confirm_step = {"iptrunk_minimum_links": 1}
create_ip_trunk_side_a_router_name = {"side_a_node_id": router_side_a} create_ip_trunk_side_a_router_name = {"side_a_node_id": router_side_a}
......
...@@ -121,6 +121,7 @@ def input_form_iptrunk_data( ...@@ -121,6 +121,7 @@ def input_form_iptrunk_data(
"iptrunk_speed": new_speed, "iptrunk_speed": new_speed,
"iptrunk_number_of_members": new_link_count, "iptrunk_number_of_members": new_link_count,
"iptrunk_description_suffix": faker.word(), "iptrunk_description_suffix": faker.word(),
"iptrunk_config_version": "1.0",
}, },
{}, {},
{ {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment