diff --git a/Networking/SR Linux/README.md b/Networking/SR Linux/README.md index 4b257021..661814f0 100644 --- a/Networking/SR Linux/README.md +++ b/Networking/SR Linux/README.md @@ -117,13 +117,13 @@ A:spine# ## Applying the examples -In order to run the provided examples, you need to prepare a development environment using **Python 3.9** by creating a `Python virtual environment` and installing the required packages: +In order to run the provided examples, you need to prepare a development environment using **Python 3.12** by creating a `Python virtual environment` and installing the required packages: Check the current Python version: ```bash -$ python3 -V -Python 3.9.10 +$ python3 --version +Python 3.12.8 ``` Create a virtual environment and install `Inmanta`: diff --git a/Networking/SR Linux/ci/Jenkinsfile b/Networking/SR Linux/ci/Jenkinsfile index a7d93edb..ffd2cd07 100644 --- a/Networking/SR Linux/ci/Jenkinsfile +++ b/Networking/SR Linux/ci/Jenkinsfile @@ -1,5 +1,5 @@ /**@ -* Updates the topology.yml so that the lab environement +* Updates the topology.yml so that the lab environment * uses the same version as the specified Python package of the OSS product */ def update_topology() { @@ -22,7 +22,7 @@ def convert_python_version_to_docker_tag() { def year = matcher.group(1) def minor = matcher.group(2) def patch = matcher.group(3) ?: "" - def rc = matcher.group(4) ?: "" + def rc = matcher.group(4) ?: "" // Constructing the formatted version def formattedVersion = "${year}.${minor}${patch}" if (rc) { @@ -82,8 +82,8 @@ pipeline { sudo docker pull ghcr.io/nokia/srlinux:latest sudo clab deploy -t containerlab/topology.yml --reconfigure # Get python version used to build the container - python_version=$(docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' clab-srlinux-inmanta-server | grep -P "^PYTHON_VERSION=" | sed 's/[^=]*=//') - + python_version=$(docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' clab-srlinux-inmanta-server | grep -P "^PYTHON_VERSION=" | sed 's/[^=]*=//' | cut -d . -f 1-2) + if [ -n "$version" ]; then if [[ $version == *dev ]]; then constraint="~=${version::-3}.0.dev" @@ -97,8 +97,16 @@ pipeline { # Leave constraint undefined if version is not defined constraint="" fi - - ci/do_test_deployment_and_verify.sh ${python_version} ${constraint} + + # Create python environment + python${python_version} -m venv venv + + venv/bin/pip install -U pip + + # install inmanta-dev-dependencies[extension] rather than pytest-inmanta-extensions directly to + # help pip find matching candidates (see inmanta/infra-tickets#180) + venv/bin/pip install "inmanta${constraint}" inmanta-dev-dependencies[extension] + venv/bin/python -u ci/do_test_deployment_and_verify.py ''' } } diff --git a/Networking/SR Linux/ci/do_test_deployment_and_verify.py b/Networking/SR Linux/ci/do_test_deployment_and_verify.py new file mode 100755 index 00000000..ec7124e6 --- /dev/null +++ b/Networking/SR Linux/ci/do_test_deployment_and_verify.py @@ -0,0 +1,211 @@ +import asyncio +import functools +import logging +import subprocess +import sys +import time + +from inmanta.config import Config +from inmanta.protocol.endpoints import Client +from inmanta_tests.utils import retry_limited +from packaging.version import Version + +logging.basicConfig(level=logging.DEBUG) + +HOST = ("172.30.0.100", "57400") + +INTERFACE_PATH = "srl_nokia-interfaces:interface" +NS_INSTANCE_PATH = "srl_nokia-network-instance:network-instance" +OSPF_PATH = "srl_nokia-ospf:ospf" + + +async def main(): + try: + await do_deploy_and_validate_config() + finally: + fetch_logs() + + +async def do_deploy_and_validate_config(): + + # Create client + + Config.set("client_rest_transport", "host", "172.30.0.3") + Config.set("client_rest_transport", "port", "8888") + client = Client(name="client") + + async def is_inmanta_server_up() -> bool: + status = await client.get_server_status() + if status.code == 200: + orchestator_version = Version(status.result["data"]["version"]) + print(f"Orchestrator version: {orchestator_version}.") + return True + return False + + print("Waiting until the Inmanta server has finished starting...") + await retry_limited(is_inmanta_server_up, timeout=60, interval=1) + + print("Creating project env-test") + result = await client.create_project("env-test") + assert result.code == 200 + project_id = result.result["project"]["id"] + + print("Creating environment dev in project env-test") + result = await client.create_environment(project_id=project_id, name="dev") + assert result.code == 200 + environment_id = result.result["environment"]["id"] + + async def install_project() -> None: + cmd = [ + sys.executable, + "-m", + "inmanta.app", + "-vvv", + "project", + "install", + "--host", + "172.30.0.3", + ] + subprocess.check_call(cmd) + + async def deploy_and_check(file: str, expected_resources: set[str]): + """ + Export a given .cf file and check that the deployed + resources are as expected. + """ + print(f"Checking successful deploy of {file}") + cmd = [ + sys.executable, + "-m", + "inmanta.app", + "-vvv", + "export", + "-f", + file, + "--host", + "172.30.0.3", + "-e", + environment_id, + ] + subprocess.check_call(cmd) + + async def done_deploying(expected_resources: set[str]) -> bool: + if not expected_resources: + return True + result = await client.resource_list(tid=environment_id, deploy_summary=True) + assert result.code == 200 + if result.result["metadata"]["deploy_summary"]["by_state"][ + "deployed" + ] != len(expected_resources): + return False + + return { + res["resource_version_id"] for res in result.result["data"] + } == expected_resources + + await retry_limited( + functools.partial(done_deploying, expected_resources), + timeout=20, + interval=1, + ) + + def make_expected_rids(version: int) -> set[str]: + return { + f"yang::GnmiResource[spine,name=global],v={version}", + f"yang::GnmiResource[leaf2,name=global],v={version}", + f"yang::GnmiResource[leaf1,name=global],v={version}", + f"std::AgentConfig[internal,agentname=spine],v={version}", + f"std::AgentConfig[internal,agentname=leaf2],v={version}", + f"std::AgentConfig[internal,agentname=leaf1],v={version}", + } + + await install_project() + + await deploy_and_check("main.cf", set()) + await deploy_and_check("interfaces.cf", make_expected_rids(version=2)) + await deploy_and_check("ospf.cf", make_expected_rids(version=3)) + + validate_config() + + +def fetch_config(gc): + result = gc.get(path=["interface", "network-instance"], encoding="json_ietf") + + notifications = result["notification"] + + interface_result = None + ospf_result = None + for response in notifications: + if list(response["update"][0]["val"].keys())[0] == INTERFACE_PATH: + interface_result = response["update"][0]["val"][INTERFACE_PATH][0] + if list(response["update"][0]["val"].keys())[0] == NS_INSTANCE_PATH: + ospf_result = response["update"][0]["val"][NS_INSTANCE_PATH][0] + + return interface_result, ospf_result + + +def validate_config() -> None: + # Only available after inmanta project install + from pygnmi.client import gNMIclient + + with gNMIclient( + target=HOST, + username="admin", + password="NokiaSrl1!", + insecure=False, + skip_verify=True, + ) as gc: + interface_result, ospf_result = fetch_config(gc) + + assert interface_result is not None + assert ospf_result is not None + + router_id = ospf_result["protocols"][OSPF_PATH]["instance"][0]["router-id"] + assert router_id == "10.20.30.100" + + sub_int_ip_address = interface_result["subinterface"][0]["ipv4"]["address"][0][ + "ip-prefix" + ] + assert sub_int_ip_address == "10.10.11.1/30" + + # Check if we see the two neighbours + neigbours = ["10.20.30.210", "10.20.30.220"] + count = 0 + while neigbours and count < 60: + for interface in ospf_result["protocols"][OSPF_PATH]["instance"][0]["area"][ + 0 + ]["interface"]: + if "neighbor" not in interface or len(interface["neighbor"]) == 0: + count += 1 + break + + if interface["neighbor"][0]["router-id"] in neigbours: + neigbours.remove(interface["neighbor"][0]["router-id"]) + + if not neigbours: + break + + interface_result, ospf_result = fetch_config(gc) + time.sleep(1) + + print("[+] Deployment was successful!") + + +def fetch_logs(): + subprocess.check_call( + "sudo docker logs clab-srlinux-inmanta-server >server.log", shell=True + ) + subprocess.check_call( + "sudo docker logs clab-srlinux-postgres >postgres.log", shell=True + ) + subprocess.check_call( + "sudo docker exec -i clab-srlinux-inmanta-server sh -c cat /var/log/inmanta/resource-*.log >resource-actions.log", + shell=True, + ) + subprocess.check_call( + "sudo docker exec -i clab-srlinux-inmanta-server sh -c cat /var/log/inmanta/agent-*.log >agents.log", + shell=True, + ) + + +asyncio.run(main()) diff --git a/Networking/SR Linux/ci/do_test_deployment_and_verify.sh b/Networking/SR Linux/ci/do_test_deployment_and_verify.sh deleted file mode 100755 index 1c326a7f..00000000 --- a/Networking/SR Linux/ci/do_test_deployment_and_verify.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash -# wait for inmanta dashboard to be up -until $(curl --output /dev/null --silent --head --fail http://172.30.0.3:8888/console/); do - printf '.' - sleep 1 -done - -set -ex - -base=$(pwd) - - -python"$1" -m venv "$base/venv" -source "$base/venv/bin/activate" - -if [ -n "$2" ]; then - pip install inmanta"$2" -else - pip install inmanta -fi - -inmanta -vvvv project install - -inmanta-cli --host 172.30.0.3 project create -n test -inmanta-cli --host 172.30.0.3 environment create -p test -n SR_Linux --save - -function export_and_assert_successful_deploy() { - local cf_file=$1 - local exported_version=$2 - inmanta -vvv export -f ${cf_file} - - # Wait until deployment finishes - while inmanta-cli --host 172.30.0.3 version list -e SR_Linux | grep deploying; do sleep 1; done - - # Verify outcome of deployment - list_version_output=$(inmanta-cli --host 172.30.0.3 version list -e SR_Linux) - if [ -z "$(echo "${list_version_output}" |grep '^|' |cut -d '|' -f 3,8 |grep "${exported_version}" |grep -i success)" ]; then - echo "${cf_file} was not deployed successfully" - echo "" - echo "${list_version_output}" - exit 1 - fi -} - -# test main -inmanta -vvv export -f main.cf - -# test interfaces -export_and_assert_successful_deploy "interfaces.cf" 2 - -# test ospf -export_and_assert_successful_deploy "ospf.cf" 3 - -# fetch logs -sudo docker logs clab-srlinux-inmanta-server >server.log -sudo docker logs clab-srlinux-postgres >postgres.log -sudo docker exec -i "clab-srlinux-inmanta-server" sh -c "cat /var/log/inmanta/resource-*.log" >resource-actions.log -sudo docker exec -i "clab-srlinux-inmanta-server" sh -c "cat /var/log/inmanta/agent-*.log" >agents.log - -# check if deployment was successful -venv/bin/python3 ci/validate_config.py diff --git a/Networking/SR Linux/ci/validate_config.py b/Networking/SR Linux/ci/validate_config.py deleted file mode 100644 index a436bc4e..00000000 --- a/Networking/SR Linux/ci/validate_config.py +++ /dev/null @@ -1,70 +0,0 @@ -from pygnmi.client import gNMIclient -import logging -import time - -logging.basicConfig(level=logging.DEBUG) - -HOST = ("172.30.0.100", "57400") - -INTERFACE_PATH = "srl_nokia-interfaces:interface" -NS_INSTANCE_PATH = "srl_nokia-network-instance:network-instance" -OSPF_PATH = "srl_nokia-ospf:ospf" - -def fetch_config(gc): - result = gc.get(path=["interface", "network-instance"], encoding="json_ietf") - - notifications = result["notification"] - - interface_result = None - ospf_result = None - for response in notifications: - if list(response["update"][0]["val"].keys())[0] == INTERFACE_PATH: - interface_result = response["update"][0]["val"][INTERFACE_PATH][0] - if list(response["update"][0]["val"].keys())[0] == NS_INSTANCE_PATH: - ospf_result = response["update"][0]["val"][NS_INSTANCE_PATH][0] - - return interface_result, ospf_result - - -def main() -> None: - with gNMIclient( - target=HOST, username="admin", password="NokiaSrl1!", insecure=False, skip_verify=True, - ) as gc: - interface_result, ospf_result = fetch_config(gc) - - assert interface_result is not None - assert ospf_result is not None - - router_id = ospf_result["protocols"][OSPF_PATH]["instance"][0]["router-id"] - assert router_id == "10.20.30.100" - - sub_int_ip_address = interface_result["subinterface"][0]["ipv4"]["address"][0][ - "ip-prefix" - ] - assert sub_int_ip_address == "10.10.11.1/30" - - # Check if we see the two neighbours - neigbours = ["10.20.30.210", "10.20.30.220"] - count = 0 - while neigbours and count < 60: - for interface in ospf_result["protocols"][OSPF_PATH]["instance"][0]["area"][0]["interface"]: - if "neighbor" not in interface or len(interface["neighbor"]) == 0: - count += 1 - break - - if interface["neighbor"][0]["router-id"] in neigbours: - neigbours.remove(interface["neighbor"][0]["router-id"]) - - if not neigbours: - break - - interface_result, ospf_result = fetch_config(gc) - time.sleep(1) - - - print("[+] Deployment was successful!") - - -if __name__ == "__main__": - print("Run validation script") - main() diff --git a/Networking/SR Linux/containerlab/topology.yml b/Networking/SR Linux/containerlab/topology.yml index 415c7068..aba59a93 100644 --- a/Networking/SR Linux/containerlab/topology.yml +++ b/Networking/SR Linux/containerlab/topology.yml @@ -14,12 +14,11 @@ topology: mgmt-ipv4: 172.30.0.3 ports: - 8888:8888 - cmd: "server --wait-for-host 172.30.0.2 --wait-for-port 5432" binds: - ./inmanta.cfg:/etc/inmanta/inmanta.cfg postgres: kind: linux - image: postgres:13 + image: postgres:16 env: POSTGRES_USER: inmanta POSTGRES_PASSWORD: inmanta diff --git a/lsm-srlinux/ci/Jenkinsfile b/lsm-srlinux/ci/Jenkinsfile index 3d460f62..89c3df34 100644 --- a/lsm-srlinux/ci/Jenkinsfile +++ b/lsm-srlinux/ci/Jenkinsfile @@ -16,9 +16,11 @@ def update_project_yml(String isoProductVersion, String token) { pythonPackageRepoUrl = "https://packages.inmanta.com/${token}/inmanta-service-orchestrator-${majorVersion}-stable/python/simple/" } def fileName = "${env.WORKSPACE}/lsm-srlinux/project.yml" + def yamlDct = readYaml file: fileName - yamlDct['repo'][0]['url'] = pythonPackageRepoUrl - yamlDct['pip']['index_url'] = pythonPackageRepoUrl + + yamlDct.pip.index_url = pythonPackageRepoUrl + writeYaml file: fileName, data: yamlDct, overwrite: true } @@ -49,10 +51,21 @@ def convert_iso_version_to_docker_image_url(String isoProductVersion) { if (matcher.matches()) { return "containers.inmanta.com/containers/service-orchestrator:${isoProductVersion}" } - matcher = isoProductVersion =~ /(\d+)dev/ + matcher = isoProductVersion =~ /(\d*)dev/ // It's a development version if (matcher.matches()) { - return "code.inmanta.com:4567/solutions/containers/service-orchestrator:${matcher.group(1)}-dev" + def dev_version_tag + + def specific_dev_version = matcher.group(1) + if (specific_dev_version) { + // Use this specific dev version + dev_version_tag = "${specific_dev_version}-dev" + } else { + // Use latest dev tag + dev_version_tag = "dev" + } + + return "code.inmanta.com:4567/solutions/containers/service-orchestrator:${dev_version_tag}" } // Is it a three-dotted version number matcher = isoProductVersion =~ /(\d+)\.(\d+)\.(\d+)(rc)?(\d*)/ @@ -90,9 +103,16 @@ def get_tear_down_script(boolean strict = true) { pipeline { agent any - parameters { - string(name: 'version', defaultValue: '7dev', description: 'Run the lsm quickstart against the specified version of ISO product. The version can be of format 6.1.1, 6, 6dev or 6.5.0rc.') + string( + name: 'version', + defaultValue: 'dev', + description:''' +Run the lsm quickstart against the specified version of ISO product. +The version can be of format 6.1.1, 6, 6dev, 6.5.0rc or dev to use \ +the latest dev image. + ''' + ) } environment { @@ -157,14 +177,17 @@ pipeline { sudo clab deploy -t containerlab/topology.yml --reconfigure # Get python version used to build the container - python_version=$(docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' clab-srlinux-inmanta-server | grep -P "^PYTHON_VERSION=" | sed 's/[^=]*=//') + python_version=$(docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' clab-srlinux-inmanta-server | grep -P "^PYTHON_VERSION=" | sed 's/[^=]*=//' | cut -d . -f 1-2) # Create python environment python${python_version} -m venv venv source "venv/bin/activate" pip install -U pip - if [[ ${version} == *dev ]]; then + + if [[ ${version} == dev ]]; then + constraint="" + elif [[ ${version} == *dev ]]; then constraint="~=${version::-3}.0.dev" elif [[ ${version} == *rc ]]; then constraint="~=${version::-2}.0rc" diff --git a/lsm-srlinux/ci/do_test_deployment_and_verify.py b/lsm-srlinux/ci/do_test_deployment_and_verify.py index d25ce570..44ac67b6 100644 --- a/lsm-srlinux/ci/do_test_deployment_and_verify.py +++ b/lsm-srlinux/ci/do_test_deployment_and_verify.py @@ -1,13 +1,12 @@ import asyncio import subprocess -from inmanta.protocol.endpoints import Client from inmanta.config import Config -from inmanta_tests.utils import retry_limited - +from inmanta.protocol.endpoints import Client # Load API endpoint definitions from inmanta_lsm import methods - +from inmanta_tests.utils import retry_limited +from packaging.version import Version async def main(): # Create client @@ -16,12 +15,17 @@ async def main(): client = Client(name="client") async def is_inmanta_server_up() -> bool: - result = await client.get_server_status() - return result.code == 200 + status = await client.get_server_status() + if status.code == 200: + orchestator_version = Version(status.result["data"]["version"]) + print(f"Orchestrator version: {orchestator_version}.") + return True + return False print("Waiting until the Inmanta server has finished starting...") await retry_limited(is_inmanta_server_up, timeout=60, interval=1) + print("Creating project env-test") result = await client.create_project("env-test") assert result.code == 200 @@ -33,7 +37,10 @@ async def is_inmanta_server_up() -> bool: environment_id = result.result["environment"]["id"] # Add project directory to environment directory on server - subprocess.check_call(f"sudo docker exec -w /code clab-srlinux-inmanta-server /code/setup.sh {environment_id}", shell=True) + subprocess.check_call( + f"sudo docker exec -w /code clab-srlinux-inmanta-server /code/setup.sh {environment_id}", + shell=True, + ) # Export service definition print("Exporting service definition") @@ -42,7 +49,9 @@ async def is_inmanta_server_up() -> bool: # Wait until service type is added to the catalog async def is_service_definition_available() -> bool: - result = await client.lsm_service_catalog_list(tid=environment_id) + result = await client.lsm_service_catalog_list( + tid=environment_id, instance_summary=True + ) assert result.code == 200 return len(result.result["data"]) > 0 @@ -59,7 +68,7 @@ async def is_service_definition_available() -> bool: "router_ip": "172.30.0.100", "router_name": "spline", "interface_name": "ethernet-1/1", - "address": "10.0.0.4/16" + "address": "10.0.0.4/16", }, ) assert result.code == 200 @@ -79,4 +88,4 @@ async def is_service_instance_up() -> bool: await retry_limited(is_service_instance_up, timeout=600, interval=1) -asyncio.get_event_loop().run_until_complete(main()) +asyncio.run(main()) diff --git a/lsm-srlinux/containerlab/topology.yml b/lsm-srlinux/containerlab/topology.yml index 0c0f0b80..07b9cd24 100644 --- a/lsm-srlinux/containerlab/topology.yml +++ b/lsm-srlinux/containerlab/topology.yml @@ -10,18 +10,23 @@ topology: nodes: inmanta-server: kind: linux - image: containers.inmanta.com/containers/service-orchestrator:7 + image: containers.inmanta.com/containers/service-orchestrator:8 ports: - - 8888:8888 + - 127.0.0.1:8888:8888 binds: - - ./resources/com.inmanta.license:/etc/inmanta/license/com.inmanta.license - - ./resources/com.inmanta.jwe:/etc/inmanta/license/com.inmanta.jwe + - ./resources/com.inmanta.license:/etc/inmanta/license/com.inmanta.license # License files for iso<8 + - ./resources/com.inmanta.jwe:/etc/inmanta/license/com.inmanta.jwe # kept for backwards compatibility + - ./resources/com.inmanta.license:/etc/inmanta/license.key # License files for iso>=8 + - ./resources/com.inmanta.jwe:/etc/inmanta/entitlement.jwe - ..:/code mgmt-ipv4: 172.30.0.3 - cmd: "server --wait-for-host inmanta_db --wait-for-port 5432" + env: + INMANTA_DATABASE_HOST: 172.30.0.2 + INMANTA_DATABASE_USERNAME: inmanta + INMANTA_DATABASE_PASSWORD: inmanta inmanta_db: kind: linux - image: postgres:13 + image: postgres:16 env: POSTGRES_USER: inmanta POSTGRES_PASSWORD: inmanta diff --git a/lsm-srlinux/project.yml b/lsm-srlinux/project.yml index ccf59817..f3dc8892 100644 --- a/lsm-srlinux/project.yml +++ b/lsm-srlinux/project.yml @@ -7,14 +7,8 @@ copyright: 2022 Inmanta modulepath: libs downloadpath: libs install_mode: release -# The repo block is for ISO6, inmanta-core<=10.0 -# This example requires licensed modules, -# replace with inmanta access token you received with your license -repo: - - type: package - url: https://packages.inmanta.com//inmanta-service-orchestrator-6-stable/python/simple/ # The pip block is for ISO7 and up, inmanta-core>10.0 -# This example requires licensed modules, +# This example requires licensed modules, # replace with inmanta access token you received with your license pip: - index_url: https://packages.inmanta.com//inmanta-service-orchestrator-7-stable/python/simple/ + index_url: https://packages.inmanta.com//inmanta-service-orchestrator-8-stable/python/simple/ diff --git a/lsm-srlinux/setup.sh b/lsm-srlinux/setup.sh index 0a99aff1..747e62a8 100755 --- a/lsm-srlinux/setup.sh +++ b/lsm-srlinux/setup.sh @@ -5,9 +5,20 @@ if [ -z "${1}" ]; then exit 1 fi -dir="/var/lib/inmanta/server/environments/${1}" +layout_version=$(cat /var/lib/inmanta/.inmanta_disk_layout_version) -mkdir $dir -cp -r /code/* $dir +if [ "$layout_version" = "2" ]; +then + dir="/var/lib/inmanta/server/${1}/compiler" +else + dir="/var/lib/inmanta/server/environments/${1}" +fi + +mkdir -p $dir + + +cp /code/main.cf $dir +cp /code/project.yml $dir +cp /code/requirements.txt $dir -sudo chown -R inmanta $dir +chown -R inmanta $dir