More information about post-processing Robot output files [here](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#post-processing-outputs)
+## Autogeneration of tests
+
+There is a tool `robot-systest/autogeneration/generate_osm_test.py` that allows generating a Robot test from a YAML configuration:
+
+```bash
+$ ./generate_osm_test.py -h
+usage: generate_osm_test.py [-h] [-v] --config CONFIG [--template TEMPLATE] [--output OUTPUT]
+
+Generate OSM tests from YAML configuration file.
+
+options:
+ -h, --help show this help message and exit
+ -v, --verbose increase output verbosity
+ --config CONFIG yaml configuration file to create the test
+ --template TEMPLATE template file for rendering the test (default: test_template.j2)
+ --output OUTPUT output file (default: standard output)
+```
+
+A YAML configuration file provides the input parameters to generate the test, such as NF and NS packages, NS instances to be created and some operations to be executed once the NS instances are created. An example can be found in `robot-systest/autogeneration/test_config.yaml`:
+
+```yaml
+documentation: "[BASIC-40] Auto-generated test"
+name: basic_40
+tags:
+ - basic_40
+ - cluster_main
+ - daily
+nfpkg:
+ - package: hackfest_basic_vnf
+ name: hackfest_basic-vnf
+ - package: hackfest_basic2_vnf
+ name: hackfest_basic2-vnf
+nspkg:
+ - package: hackfest_basic_ns
+ name: hackfest_basic-ns
+ - package: hackfest_basic2_ns
+ name: hackfest_basic2-ns
+ns:
+ - name: basic_40
+ nspkg: hackfest_basic-ns
+ config: "{vld: [ {name: mgmtnet, vim-network-name: %{VIM_MGMT_NET}} ] }"
+vnf:
+ - ns: basic_40
+ vnf_member_index: vnf
+ tests:
+ - type: ping
+ - type: ssh
+```
+
+To generate the Robot, you can run:
+
+```bash
+cd robot-systest/autogeneration/
+./generate_osm_test.py --config test_config.yaml > ../testsuite/mynewtest.robot
+```
+
## Built With
* [Python](www.python.org/) - The language used
--- /dev/null
+#!/usr/bin/python3
+# Copyright ETSI Contributors and Others.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+# generate_osm_test.py --config test_config.yaml
+
+import argparse
+import logging
+import os
+import yaml
+
+# from jinja2 import Environment, FileSystemLoader, select_autoescape
+from jinja2 import Template
+
+
+####################################
+# Global functions
+####################################
+def set_logger(verbose):
+ global logger
+ log_format_simple = "%(levelname)s %(message)s"
+ log_format_complete = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(funcName)s(): %(message)s"
+ log_formatter_simple = logging.Formatter(
+ log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S"
+ )
+ handler = logging.StreamHandler()
+ handler.setFormatter(log_formatter_simple)
+ logger = logging.getLogger("generate_osm_test")
+ logger.setLevel(level=logging.WARNING)
+ logger.addHandler(handler)
+ if verbose == 1:
+ logger.setLevel(level=logging.INFO)
+ elif verbose > 1:
+ log_formatter = logging.Formatter(
+ log_format_complete, datefmt="%Y-%m-%dT%H:%M:%S"
+ )
+ handler.setFormatter(log_formatter)
+ logger.setLevel(level=logging.DEBUG)
+
+
+def exit_with_error(message, code=1):
+ logger.error(message)
+ exit(code)
+
+
+def render_template(template_file, test_data, output_file=None):
+ logger.info(f"Rendering {template_file} with test data")
+
+ # Load Jinja template
+ with open(template_file, "r") as template_stream:
+ template = Template(template_stream.read())
+
+ # Render template with test_data and store it in output_file
+ output = template.render(test_data)
+ if output_file:
+ if os.path.exists(output_file):
+ exit_with_error(
+ f"Output file '{output_file}' already exists. Use a different filename or remove it."
+ )
+ with open(output_file, "w") as output_stream:
+ output_stream.write(output)
+ else:
+ print(output)
+
+
+def validate_data(test_data):
+ required_keys = {"nspkg", "ns"}
+ missing_keys = required_keys - test_data.keys()
+ if missing_keys:
+ logger.error(f"Missing required keys in YAML: {', '.join(missing_keys)}")
+ raise ValueError(f"Invalid test data structure: missing keys {missing_keys}")
+
+
+def complete_data(test_data):
+ nspkg_indexes = {nspkg["name"]: i + 1 for i, nspkg in enumerate(test_data["nspkg"])}
+ ns_indexes = {ns["name"]: i + 1 for i, ns in enumerate(test_data["ns"])}
+ for ns in test_data["ns"]:
+ if ns["nspkg"] not in nspkg_indexes:
+ logger.error(
+ f"nspkg '{ns['nspkg']}' referenced in 'ns' not found in 'nspkg'"
+ )
+ raise ValueError(f"Invalid nspkg name: {ns['nspkg']}")
+ ns["nspkg_index"] = nspkg_indexes[ns["nspkg"]]
+ for vnf in test_data["vnf"]:
+ if vnf["ns"] not in ns_indexes:
+ logger.error(f"ns '{vnf['ns']}' referenced in 'vnf' not found in 'ns'")
+ raise ValueError(f"Invalid ns name: {vnf['ns']}")
+ vnf["ns_index"] = ns_indexes[vnf["ns"]]
+
+
+####################################
+# Main
+####################################
+if __name__ == "__main__":
+ # Argument parse
+ parser = argparse.ArgumentParser(
+ description="Generate OSM tests from YAML configuration file."
+ )
+ parser.add_argument(
+ "-v", "--verbose", action="count", default=0, help="increase output verbosity"
+ )
+ # parser.add_argument("--basedir", default=".", help="basedir for the test")
+ parser.add_argument(
+ "--config", required=True, help="yaml configuration file to create the test"
+ )
+ parser.add_argument(
+ "--template",
+ default="test_template.j2",
+ help="template file for rendering the test (default: test_template.j2)",
+ )
+ parser.add_argument(
+ "--output", default=None, help="output file (default: standard output)"
+ )
+ args = parser.parse_args()
+
+ # Calculate paths relative to the script directory
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ config_path = os.path.abspath(args.config)
+ template_path = os.path.join(script_dir, args.template)
+
+ # Initialize logger
+ set_logger(args.verbose)
+
+ # Load test_data
+ try:
+ with open(config_path, "r") as yaml_stream:
+ test_data = yaml.safe_load(yaml_stream)
+ except FileNotFoundError:
+ exit_with_error(f"Configuration file '{args.config}' not found.")
+ except yaml.YAMLError as e:
+ exit_with_error(f"Error parsing YAML file '{args.config}': {e}")
+ validate_data(test_data)
+ complete_data(test_data)
+ logger.debug(f"Test data:\n{yaml.safe_dump(test_data)}")
+ if not os.path.exists(template_path):
+ exit_with_error(f"Template file '{template_path}' not found.")
+ render_template(template_path, test_data, args.output)
--- /dev/null
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+documentation: "[BASIC-40] Auto-generated test"
+name: basic_40
+tags:
+ - basic_40
+ - cluster_main
+ - daily
+nfpkg:
+ - package: hackfest_basic_vnf
+ name: hackfest_basic-vnf
+ - package: hackfest_basic2_vnf
+ name: hackfest_basic2-vnf
+nspkg:
+ - package: hackfest_basic_ns
+ name: hackfest_basic-ns
+ - package: hackfest_basic2_ns
+ name: hackfest_basic2-ns
+ns:
+ - name: basic_40
+ nspkg: hackfest_basic-ns
+ config: "{vld: [ {name: mgmtnet, vim-network-name: %{VIM_MGMT_NET}} ] }"
+vnf:
+ - ns: basic_40
+ vnf_member_index: vnf
+ tests:
+ - type: ping
+ - type: ssh
--- /dev/null
+*** Comments ***
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+*** Settings ***
+Documentation {{ documentation }}
+
+Library OperatingSystem
+Library String
+Library Collections
+Library Process
+Library SSHLibrary
+
+Resource ../lib/vnfd_lib.resource
+Resource ../lib/nsd_lib.resource
+Resource ../lib/ns_lib.resource
+Resource ../lib/connectivity_lib.resource
+Resource ../lib/ssh_lib.resource
+
+Test Tags {% for tag in tags %}{{ tag }}{% if not loop.last %} {% endif %}{% endfor %}
+
+Suite Teardown Run Keyword And Ignore Error Suite Cleanup
+
+
+*** Variables ***
+# NF package folders and names
+{% for item in nfpkg -%}
+${NFPKG{{ loop.index }}_FOLDER} {{ item.package }}
+${NFPKG{{ loop.index }}_NAME} {{ item.name }}
+{% endfor %}
+# NS package folders and names
+{% for item in nspkg -%}
+${NSPKG{{ loop.index }}_FOLDER} {{ item.package }}
+${NSPKG{{ loop.index }}_NAME} {{ item.name }}
+{% endfor %}
+# NS instance name and configuration
+{% for item in ns -%}
+${NS{{ loop.index }}_NAME} {{ item.name }}
+${NS{{ loop.index }}_CONFIG} {{ item.config }}
+{% endfor %}
+# SSH keys and username to be used
+${PUBLICKEY} %{HOME}/.ssh/id_rsa.pub
+${PRIVATEKEY} %{HOME}/.ssh/id_rsa
+${USERNAME} ubuntu
+${PASSWORD} ${EMPTY}
+
+{% for item in ns -%}
+${NS{{ loop.index }}_ID} ${EMPTY}
+{%- endfor %}
+{% for item in vnf -%}
+${VNF{{ loop.index }}_IP_ADDR} ${EMPTY}
+${VNF{{ loop.index }}_MEMBER_INDEX} {{ item.vnf_member_index }}
+${VNF{{ loop.index }}_NS_NAME} {{ item.ns }}
+{%- endfor %}
+
+
+*** Test Cases ***
+{%- for item in nfpkg %}
+Add NF Package {{ item.name }}
+ [Documentation] Upload NF package {{ item.name }} for the testsuite.
+ Create VNFD '%{PACKAGES_FOLDER}/${NFPKG{{ loop.index }}_FOLDER}'
+{% endfor %}
+{%- for item in nspkg %}
+Add NS Package {{ item.name }}
+ [Documentation] Upload NS package {{ item.name }} for the testsuite.
+ Create NSD '%{PACKAGES_FOLDER}/${NSPKG{{ loop.index }}_FOLDER}'
+{% endfor %}
+{%- for item in ns %}
+Network Service Instance {{ item.name }}
+ [Documentation] Instantiate NS {{ item.name }} for the testsuite.
+ ${id}= Create Network Service ${NSPKG{{ item.nspkg_index }}_NAME} %{VIM_TARGET} ${NS{{ loop.index }}_NAME} ${NS{{ loop.index }}_CONFIG} ${PUBLICKEY}
+ Set Suite Variable ${NS{{ loop.index }}_ID} ${id}
+{% endfor %}
+{%- for item in vnf %}
+Get Vnf Ip Address
+ [Documentation] Get the mgmt IP address of the VNF {{ item.vnf_member_index }} of the NS {{ item.ns }}.
+ ${ip_addr}= Get Vnf Management Ip Address ${NS{{ item.ns_index }}_ID} ${VNF{{ loop.index }}_MEMBER_INDEX}
+ Log ${ip_addr}
+ Set Suite Variable ${VNF{{ loop.index }}_IP_ADDR} ${ip_addr}
+{% for item2 in item.tests %}
+{#- Check if "ping" test exists for this VNF -#}
+{% if item2.type == 'ping' %}
+Test Ping
+ [Documentation] Test that the mgmt IP address of the VNF {{ item.vnf_member_index }} of the NS {{ item.ns }} is reachable with ping.
+ Test Connectivity ${VNF{{ loop.index }}_IP_ADDR}
+{% endif %}
+{#- Check if "ssh" test exists for this VNF -#}
+{% if item2.type == 'ssh' %}
+Test SSH Access
+ [Documentation] Check that the VNF {{ item.vnf_member_index }} of the NS {{ item.ns }} is accessible via SSH in its mgmt IP address.
+ Sleep 30s Waiting ssh daemon to be up
+ Test SSH Connection ${VNF{{ loop.index }}_IP_ADDR} ${USERNAME} ${PASSWORD} ${PRIVATEKEY}
+{%- endif %}
+{%- endfor %}
+{%- endfor %}
+
+{% for item in ns -%}
+Delete NS Instance {{ item.name }}
+ [Documentation] Delete NS instance {{ item.name }}.
+ [Tags] cleanup
+ Delete NS ${NS{{ loop.index }}_NAME}
+{% endfor %}
+
+{%- for item in nspkg %}
+Delete NS Package {{ item.name }}
+ [Documentation] Delete NS package {{ item.name }} from OSM.
+ [Tags] cleanup
+ Delete NSD ${NSPKG{{ loop.index }}_NAME}
+{% endfor %}
+
+{%- for item in nfpkg %}
+Delete NF Package {{ item.name }}
+ [Documentation] Delete NF package {{ item.name }} from OSM.
+ [Tags] cleanup
+ Delete VNFD ${NFPKG{{ loop.index }}_NAME}
+{% endfor %}
+
+*** Keywords ***
+Suite Cleanup
+ [Documentation] Test Suit Cleanup: Deleting packages and NS instances
+ {%- for item in ns %}
+ Run Keyword If Any Tests Failed Delete NS ${NS{{ loop.index }}_NAME}
+ {%- endfor -%}
+ {%- for item in nspkg %}
+ Run Keyword If Any Tests Failed Delete NSD ${NSPKG{{ loop.index }}_NAME}
+ {%- endfor -%}
+ {%- for item in nfpkg %}
+ Run Keyword If Any Tests Failed Delete VNFD ${NFPKG{{ loop.index }}_NAME}
+ {%- endfor %}