Repository: xlab-si/xopera-opera

Issue:
Repair the concurrency deployment integration test
## Description
With this issue, we want to repair the concurrency integration test showing the right interdependency of nodes done in the original test and changed in a [subsequent commit](https://github.com/xlab-si/xopera-opera/commit/a803058071405f02c5be16eedc9ec172175625d1#diff-26bd1afdf3f59f80a604bac88b4519fd737e2fb182b745882653ec3b300224cbR137). 
In the test the node dependency for node `hello-14` should be used as defined in [TOSCA 1.3 5.9.1.3 Definition](https://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.3/csprd01/TOSCA-Simple-Profile-YAML-v1.3-csprd01.html#_Toc9262333) `tosca.nodes.Root` where the `dependency` requirement has an  UNBOUNDED max. limit of occurrences.

## Steps
Running the test in the current version of the test with this code for node `hello-14` executes without returning an error and produces the output not intended by the initial version of the test:
``` YAML
    hello-14:
      type: hello_type
      properties:
        time: "1"
      requirements:
        - host: my-workstation
        - dependency1:  hello-1
        - dependency2:  hello-2
        - dependency7:  hello-7
        - dependency13:  hello-13
```
## Current behavior
Currently, opera executes the test without taking into account the undefined `dependency1` requirement in which will be addressed in a separate issue.
When defining dependencies for node `hello-14` as in the initially intended test:
``` YAML
    hello-14:
      type: hello_type
      properties:
        time: "1"
      requirements:
        - host: my-workstation
        - dependency:  hello-1
        - dependency:  hello-2
        - dependency:  hello-7
        - dependency:  hello-13
```
opera produces the correct outputs.
## Expected results

The execution of the  corrected test should produce this output:
```
[Worker_0]   Deploying my-workstation_0
[Worker_0]   Deployment of my-workstation_0 complete
[Worker_0]   Deploying hello-1_0
[Worker_0]     Executing create on hello-1_0
[Worker_2]   Deploying hello-2_0
[Worker_3]   Deploying hello-3_0
[Worker_4]   Deploying hello-4_0
[Worker_5]   Deploying hello-8_0
[Worker_6]   Deploying hello-9_0
[Worker_7]   Deploying hello-10_0
[Worker_4]     Executing create on hello-4_0
[Worker_2]     Executing create on hello-2_0
[Worker_5]     Executing create on hello-8_0
[Worker_3]     Executing create on hello-3_0
[Worker_6]     Executing create on hello-9_0
[Worker_7]     Executing create on hello-10_0
[Worker_4]     Executing start on hello-4_0
[Worker_3]     Executing start on hello-3_0
[Worker_6]     Executing start on hello-9_0
[Worker_7]     Executing start on hello-10_0
[Worker_3]   Deployment of hello-3_0 complete
[Worker_1]   Deploying hello-12_0
[Worker_1]     Executing create on hello-12_0
[Worker_4]   Deployment of hello-4_0 complete
[Worker_3]   Deploying hello-13_0
[Worker_3]     Executing create on hello-13_0
[Worker_6]   Deployment of hello-9_0 complete
[Worker_5]     Executing start on hello-8_0
[Worker_2]     Executing start on hello-2_0
[Worker_0]     Executing start on hello-1_0
[Worker_1]     Executing start on hello-12_0
[Worker_7]   Deployment of hello-10_0 complete
[Worker_3]     Executing start on hello-13_0
[Worker_1]   Deployment of hello-12_0 complete
[Worker_3]   Deployment of hello-13_0 complete
[Worker_2]   Deployment of hello-2_0 complete
[Worker_0]   Deployment of hello-1_0 complete
[Worker_8]   Deploying hello-5_0
[Worker_4]   Deploying hello-11_0
[Worker_5]   Deployment of hello-8_0 complete
[Worker_4]     Executing create on hello-11_0
[Worker_8]     Executing create on hello-5_0
[Worker_4]     Executing start on hello-11_0
[Worker_8]     Executing start on hello-5_0
[Worker_8]   Deployment of hello-5_0 complete
[Worker_4]   Deployment of hello-11_0 complete
[Worker_4]   Deploying hello-6_0
[Worker_4]     Executing create on hello-6_0
[Worker_4]     Executing start on hello-6_0
[Worker_4]   Deployment of hello-6_0 complete
[Worker_6]   Deploying hello-7_0
[Worker_6]     Executing create on hello-7_0
[Worker_6]     Executing start on hello-7_0
[Worker_6]   Deployment of hello-7_0 complete
[Worker_7]   Deploying hello-14_0
[Worker_7]     Executing create on hello-14_0
[Worker_7]     Executing start on hello-14_0
[Worker_7]   Deployment of hello-14_0 complete
```

Discussion:

Patch:
diff --git a/.gitignore b/.gitignore
index b67d06d..226bf78 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,13 +1,89 @@
+# Byte-compiled / optimized / DLL files
 __pycache__/
 *.py[cod]
+*$py.class
 
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
 eggs/
 .eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
 *.egg-info/
+.installed.cfg
 *.egg
-dist/
+MANIFEST
 
-venv/
-.venv/
+# PyInstaller
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
 .pytest_cache/
+cover/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# pyenv
+.python-version
+
+# pipenv
+Pipfile.lock
+
+# Environments
+.env
+.venv
+.venv*
+env/
+venv/
+venv*
+ENV/
+env.bak/
+venv.bak/
+
+# mypy
 .mypy_cache/
+.dmypy.json
+dmypy.json
+
+# packages
+*.7z
+*.dmg
+*.gz
+*.iso
+*.jar
+*.rar
+*.tar
+*.tar.gz
+*.zip
+*.log.*
+*.csar
+
+# default opera storage folder
+.opera
diff --git a/examples/policy_triggers/service.yaml b/examples/policy_triggers/service.yaml
index 858e329..77c027f 100644
--- a/examples/policy_triggers/service.yaml
+++ b/examples/policy_triggers/service.yaml
@@ -33,6 +33,10 @@ node_types:
         type: radon.interfaces.scaling.ScaleDown
       autoscaling:
         type: radon.interfaces.scaling.AutoScale
+    requirements:
+      - host:
+          capability: tosca.capabilities.Compute
+          relationship: tosca.relationships.HostedOn
 
 interface_types:
   radon.interfaces.scaling.ScaleDown:
diff --git a/src/opera/parser/tosca/v_1_3/node_template.py b/src/opera/parser/tosca/v_1_3/node_template.py
index f2d7040..823844c 100644
--- a/src/opera/parser/tosca/v_1_3/node_template.py
+++ b/src/opera/parser/tosca/v_1_3/node_template.py
@@ -86,4 +86,7 @@ class NodeTemplate(CollectorMixin, Entity):
                 )
             )
 
+        if undeclared_requirements:
+            self.abort("Undeclared requirements: {}.".format(", ".join(undeclared_requirements)), self.loc)
+
         return requirements


Test patch:
diff --git a/tests/integration/concurrency/service.yaml b/tests/integration/concurrency/service.yaml
index 6934ddd..7e13804 100644
--- a/tests/integration/concurrency/service.yaml
+++ b/tests/integration/concurrency/service.yaml
@@ -134,7 +134,7 @@ topology_template:
         time: "1"
       requirements:
         - host: my-workstation
-        - dependency1:  hello-1
-        - dependency2:  hello-2
-        - dependency7:  hello-7
-        - dependency13:  hello-13
+        - dependency:  hello-1
+        - dependency:  hello-2
+        - dependency:  hello-7
+        - dependency:  hello-13
diff --git a/tests/integration/misc_tosca_types/modules/node_types/test/test.yaml b/tests/integration/misc_tosca_types/modules/node_types/test/test.yaml
index cc1c473..3ad3b9a 100644
--- a/tests/integration/misc_tosca_types/modules/node_types/test/test.yaml
+++ b/tests/integration/misc_tosca_types/modules/node_types/test/test.yaml
@@ -40,10 +40,7 @@ node_types:
       test_capability:
         type: daily_test.capabilities.test
     requirements:
-      - host1:
+      - host:
           capability: tosca.capabilities.Compute
           relationship: daily_test.relationships.test
-      - host2:
-          capability: tosca.capabilities.Compute
-          relationship: daily_test.relationships.interfaces
 ...
diff --git a/tests/unit/opera/parser/test_tosca.py b/tests/unit/opera/parser/test_tosca.py
index a81f803..b472187 100644
--- a/tests/unit/opera/parser/test_tosca.py
+++ b/tests/unit/opera/parser/test_tosca.py
@@ -316,3 +316,29 @@ class TestExecute:
         ast = tosca.load(tmp_path, name)
         with pytest.raises(ParseError, match="Missing a required property: property3"):
             ast.get_template({})
+
+    def test_undeclared_requirements(self, tmp_path, yaml_text):
+        name = pathlib.PurePath("template.yaml")
+        (tmp_path / name).write_text(yaml_text(
+            # language=yaml
+            """
+            tosca_definitions_version: tosca_simple_yaml_1_3
+            topology_template:
+              node_templates:
+                node_1:
+                  type: tosca.nodes.SoftwareComponent
+                node_2:
+                  type: tosca.nodes.SoftwareComponent
+                  requirements:
+                    - dependency: node_1
+                node_3:
+                  type: tosca.nodes.SoftwareComponent
+                  requirements:
+                    - dependency_not_defined1: node_1
+        """
+        ))
+        storage = Storage(tmp_path / pathlib.Path(".opera"))
+        storage.write("template.yaml", "root_file")
+        ast = tosca.load(tmp_path, name)
+        with pytest.raises(ParseError, match="Undeclared requirements: dependency_not_defined1"):
+            ast.get_template({})