diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5e54ad156d4279d0bba424c9ee36aeff2548dc19..0ed900004f54ad77ce3c2cf9a1a327e498f54e83 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -47,7 +47,7 @@ run-tox-pipeline: sonarqube: stage: sonarqube - image: sonarsource/sonar-scanner-cli + image: sonarsource/sonar-scanner-cli:10.0 script: - sonar-scanner -Dsonar.login=$SONAR_TOKEN -Dproject.settings=./sonar.properties tags: diff --git a/Changelog.md b/Changelog.md index cead4f9864c1a03b5ad75134e9d577931ebd4bc6..0e7414e785025c3291e9eeaf2651cce4c15ff454 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,4 +1,10 @@ # Changelog + +## [2.15] - 2024-09-30 +- Show current license usage when updating Kentik license of a router +- Fix the bug of clearing all the AE members and creating new objects instead of updating it. +- make orchestrator-core==2.7.5 + ## [2.14] - 2024-09-19 - Fixes to Infoblox client. diff --git a/gso/services/kentik_client.py b/gso/services/kentik_client.py index 95d55e22d53f056d08bd9376956808c9a11785c0..7849adeeec4f6347f2545c5ec76fffaf5f6ee0e1 100644 --- a/gso/services/kentik_client.py +++ b/gso/services/kentik_client.py @@ -109,7 +109,42 @@ class KentikClient: return {} def get_plans(self) -> list[dict[str, Any]]: - """Get all Kentik plans available.""" + """Get all Kentik plans available. + + Returns a list of ``plans`` that each have the following shape: + + .. vale off + .. code-block:: json + + "plan": { + "active": true, + "bgp_enabled": true, + "cdate" "1970-01-01T01:01:01.000Z", + "company_id": 111111, + "description": "A description of this plan", + "deviceTypes": [ + {"device_type": "router"}, + {"device_type": "host-nprobe-dns-www"} + ], + "devices": [ + { + "id": "111111", + "device_name": "rt0.city.tld.internal", + "device_type": "router" + }, + ], + "edate": "2999-01-01T09:09:09.000Z", + "fast_retention": 10, + "full_retention": 5, + "id": 11111, + "max_bigdata_fps": 100, + "max_devices": 9001, + "max_fps": 200, + "name": "KENTIK-PLAN-01", + "metadata": {}, + } + .. vale on + """ return self._send_request("GET", "v5/plans")["plans"] def get_plan(self, plan_id: int) -> dict[str, Any]: diff --git a/gso/services/lso_client.py b/gso/services/lso_client.py index 6eca3f6559943df80660b3f71ba50e9c1feb1d64..77c2264798a009c79fb5a28dcd15a317e17b87c5 100644 --- a/gso/services/lso_client.py +++ b/gso/services/lso_client.py @@ -60,6 +60,7 @@ def execute_playbook( For example, an inventory consisting of two hosts, which each a unique host variable assigned to them looks as follows: + .. vale off .. code-block:: json "inventory": { @@ -74,6 +75,7 @@ def execute_playbook( } } } + .. vale on .. warning:: Note the fact that the collection of all hosts is a dictionary, and not a list of strings. Ansible expects each diff --git a/gso/workflows/iptrunk/modify_trunk_interface.py b/gso/workflows/iptrunk/modify_trunk_interface.py index 6d82090c56d539c3456344ec6c08b1580d33dcf4..e947fd52b4569506d452629b3d2a1fd547468ef1 100644 --- a/gso/workflows/iptrunk/modify_trunk_interface.py +++ b/gso/workflows/iptrunk/modify_trunk_interface.py @@ -216,6 +216,29 @@ def check_ip_trunk_lldp(subscription: Iptrunk, callback_route: str) -> State: return {"subscription": subscription} +def update_side_members(subscription: Iptrunk, side_index: int, new_members: list[dict]) -> None: + """Update the AE members for a given side without removing unchanged members.""" + # Prepare a dictionary for quick lookup of existing members by name + current_members = subscription.iptrunk.iptrunk_sides[side_index].iptrunk_side_ae_members + existing_members_dict = {member.interface_name: member for member in current_members} + + # Iterate over new members and update or add them + for new_member in new_members: + interface_name = new_member["interface_name"] + if interface_name in existing_members_dict: + # Member exists, update details but keep the same subscription ID + existing_member = existing_members_dict[interface_name] + existing_member.interface_description = new_member["interface_description"] + else: + # New member, create a new subscription ID + current_members.append(IptrunkInterfaceBlock.new(subscription_id=uuid4(), **new_member)) + + # Remove members that are no longer in the new members list + subscription.iptrunk.iptrunk_sides[side_index].iptrunk_side_ae_members = [ + member for member in current_members if member.interface_name in [m["interface_name"] for m in new_members] + ] + + @step("Update subscription") def modify_iptrunk_subscription( subscription: Iptrunk, @@ -242,12 +265,16 @@ def modify_iptrunk_subscription( for side in subscription.iptrunk.iptrunk_sides ] removed_ae_members = [] - + # Compare previous and current members to determine which ones were removed for side_index in range(2): previous_members = previous_ae_members[side_index] current_members = side_a_ae_members if side_index == 0 else side_b_ae_members - removed_ae_members.append([ae_member for ae_member in previous_members if ae_member not in current_members]) - + removed_ae_members.append([ + ae_member + for ae_member in previous_members + if ae_member["interface_name"] not in [m["interface_name"] for m in current_members] + ]) + # Update the subscription subscription.iptrunk.geant_s_sid = geant_s_sid subscription.iptrunk.iptrunk_description = iptrunk_description subscription.iptrunk.iptrunk_type = iptrunk_type @@ -255,20 +282,9 @@ def modify_iptrunk_subscription( subscription.iptrunk.iptrunk_minimum_links = iptrunk_minimum_links subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_geant_a_sid = side_a_ae_geant_a_sid - # Flush the old list of member interfaces - subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members.clear() - # And update the list to only include the new member interfaces - for member in side_a_ae_members: - subscription.iptrunk.iptrunk_sides[0].iptrunk_side_ae_members.append( - IptrunkInterfaceBlock.new(subscription_id=uuid4(), **member), - ) - + update_side_members(subscription, 0, side_a_ae_members) subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_geant_a_sid = side_b_ae_geant_a_sid - subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members.clear() - for member in side_b_ae_members: - subscription.iptrunk.iptrunk_sides[1].iptrunk_side_ae_members.append( - IptrunkInterfaceBlock.new(subscription_id=uuid4(), **member), - ) + update_side_members(subscription, 1, side_b_ae_members) side_names = sorted([ subscription.iptrunk.iptrunk_sides[0].iptrunk_side_node.router_site.site_name, diff --git a/gso/workflows/router/modify_kentik_license.py b/gso/workflows/router/modify_kentik_license.py index 28d9d21f945a55001f5b1601227f26c5494067ec..bd97b9c04605ed475fb7800a6cf965b5c3c895a1 100644 --- a/gso/workflows/router/modify_kentik_license.py +++ b/gso/workflows/router/modify_kentik_license.py @@ -22,7 +22,11 @@ logger = logging.getLogger() def _initial_input_form(subscription_id: UUIDstr) -> FormGenerator: router = Router.from_subscription(subscription_id) - active_kentik_plans = {str(plan["id"]): plan["name"] for plan in KentikClient().get_plans() if plan["active"]} + active_kentik_plans = { + str(plan["id"]): f"{plan["name"]} - ({len(plan["devices"])}/{plan["max_devices"]})" + for plan in KentikClient().get_plans() + if plan["active"] + } available_kentik_plans = Choice( "Select a Kentik license", zip(active_kentik_plans.keys(), active_kentik_plans.items(), strict=True), # type: ignore[arg-type] diff --git a/requirements.txt b/requirements.txt index 632c74b3af3c83c1ea442da2e4bf11530f682e7b..433a9e17894886e0b2648cebae089f677c3b0a87 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -orchestrator-core==2.7.4 +orchestrator-core==2.7.5 requests==2.31.0 infoblox-client~=0.6.0 pycountry==23.12.11 diff --git a/setup.py b/setup.py index aeee187a3057e09141b3c382836aa62f2a814c2f..dd65151d87623a0f976e4edb58cb05f4a473d593 100644 --- a/setup.py +++ b/setup.py @@ -4,14 +4,14 @@ from setuptools import find_packages, setup setup( name="geant-service-orchestrator", - version="2.14", + version="2.15", author="GÉANT Orchestration and Automation Team", author_email="goat@geant.org", description="GÉANT Service Orchestrator", url="https://gitlab.software.geant.org/goat/gap/geant-service-orchestrator", packages=find_packages(), install_requires=[ - "orchestrator-core==2.7.4", + "orchestrator-core==2.7.5", "requests==2.31.0", "infoblox-client~=0.6.0", "pycountry==23.12.11",