Update from master part 2

Squashed commit of the following:

commit 364627c364a86a85696781766326dd690a362bc4
Author: vegall <lvega@whitestack.com>
Date:   Fri Mar 17 15:09:50 2023 +0000

    Feature 10972: Support of volume multi-attach

    Change-Id: I6e88ee52e5e882dbb4ec7d66cf648fbe07d40509
    Signed-off-by: vegall <lvega@whitestack.com>

commit 0e51779fd37dc5c12f3bd19d78f7341ed0a67b7a
Author: gifrerenom <lluis.gifre@cttc.es>
Date:   Tue Apr 18 16:38:42 2023 +0000

    Feature 10937: Transport API (TAPI) WIM connector for RO

    Change-Id: If0dac9f8ba2d00945eb86a89fb0b2f174c672794
    Signed-off-by: gifrerenom <lluis.gifre@cttc.es>

commit 370e36bafdcb90f212e289b87290f39be141b3d4
Author: elumalai <deepika.e@tataelxsi.co.in>
Date:   Tue Apr 25 16:22:56 2023 +0530

    Feature 10979: Static IPv6 Dual Stack Assignment

    Added support for static dual stack IP assignment

    Change-Id: Ief10ae955fb870a3417f68e1c5f7bda570cb6470
    Signed-off-by: elumalai <deepika.e@tataelxsi.co.in>

commit b1bc66933aa392b9d7518f7cebc711700335389c
Author: Gabriel Cuba <gcuba@whitestack.com>
Date:   Fri Aug 19 18:23:00 2022 -0500

    Fix Bug 2098: Get VDUs from VNFR when Heal op has no additionalPrameters

    When Heal is requested without vdu or count-index parameters, RO will recreate all VDUs from VNFR

    Change-Id: Idf2cf158bcb33e7b0c307298c14504cc7aa77e2a
    Signed-off-by: Gabriel Cuba <gcuba@whitestack.com>
    (cherry picked from commit 2fbb3a264e4117f4a6569fede6558836d67ac4a4)

commit aba1518f487b4b65861eb30f553c4edb72ad972e
Author: Gulsum Atici <gulsum.atici@canonical.com>
Date:   Mon May 15 11:55:13 2023 +0300

    Fix VimAdminThread run method

    The run_coroutine_threadsafe() function is used to schedule a coroutine object from a different thread and returns a concurrent.futures.Future.
    run_coroutine_threadsafe is unnecessary to run the main task and replaced with asyncio.run().

    Change-Id: I8ea31828a9798140d596165443bdf26659b4eef8
    Signed-off-by: Gulsum Atici <gulsum.atici@canonical.com>

commit f17e5bb6b6da4432628dd65ce9ad633e6441f67c
Author: Gulsum Atici <gulsum.atici@canonical.com>
Date:   Wed May 10 22:52:57 2023 +0300

    Minor updates in Dockerfile

    Change-Id: I79b43654d181f6976a4e544d58fb92aa1b67e760
    Signed-off-by: Gulsum Atici <gulsum.atici@canonical.com>

commit a264b7a460b28d7454fc95fe659da46f55b0c155
Author: Gulsum Atici <gulsum.atici@canonical.com>
Date:   Tue May 9 14:57:22 2023 +0300

    Ubuntu 22.04 and Python 3.10 preparation

    Change-Id: I87164827a8849c16b5e3a804d9673a578e5a5593
    Signed-off-by: Gulsum Atici <gulsum.atici@canonical.com>

commit 1c89c08a0dd1c79b5adff3ac1cc123239762e06a
Author: garciadeblas <gerardo.garciadeblas@telefonica.com>
Date:   Tue Apr 18 15:06:30 2023 +0200

    Clean stage-archive.sh and use allowlist_extenals in tox.ini

    Change-Id: I18f0bc3e263063b5b1d2cf211f028f6bb0e4bceb
    Signed-off-by: garciadeblas <gerardo.garciadeblas@telefonica.com>

commit 51e72a0f7479b3064b4b11891eb524d42f4738b0
Author: elumalai <deepika.e@tataelxsi.co.in>
Date:   Fri Apr 28 19:41:49 2023 +0530

    Coverity CWE 330: Use of Insufficiently Random Values

    Added support to fix CWE 330: Use of Insufficiently Random Values
    Coverity issue

    Change-Id: Ib12ebeeb9b0cc10af9980fe8661eb6230c2f6d6d
    Signed-off-by: elumalai <deepika.e@tataelxsi.co.in>

commit e17cd946aed699b5feca83d37591d04f129a8f52
Author: elumalai <deepika.e@tataelxsi.co.in>
Date:   Fri Apr 28 18:04:24 2023 +0530

    Coverity CWE 688: Function Call With Incorrect Variable or Reference as Argument

    Added fix for CWE 688 Typo in Identifier

    Change-Id: I53b5142451b809be638d73626265531057722169
    Signed-off-by: elumalai <deepika.e@tataelxsi.co.in>

commit 730cfaff466fb3c9b1446ecef5213916195ff861
Author: Gabriel Cuba <gcuba@whitestack.com>
Date:   Mon Mar 13 22:26:38 2023 -0500

    Feature 10975: get flavor-id from additionalParams if specified

    Change-Id: I1c9b1ec43c80f3793b475187681f4c2005d77375
    Signed-off-by: Gabriel Cuba <gcuba@whitestack.com>

commit 2d3f63b055e6a38e95bcff56a8ddef32767b11ef
Author: garciadeblas <gerardo.garciadeblas@telefonica.com>
Date:   Tue Apr 11 10:08:26 2023 +0200

    Update stage-build to run tox sequentially

    Change-Id: I967f19a8c35700290e93c9d8bd863b63b7c2d239
    Signed-off-by: garciadeblas <gerardo.garciadeblas@telefonica.com>
    (cherry picked from commit ea063c7a6ae6a5d7e11e8c22f9707d5c8f674ac7)

commit b3dbfcad6f4b2bebc9ebc20fd7129a18879cb20c
Author: Gabriel Cuba <gcuba@whitestack.com>
Date:   Tue Mar 14 10:58:39 2023 -0500

    Feature 10978: Add support of ipv6_address_mode and ipv6_ra_mode to openstack connector

    Change-Id: I8ca741a215bd2c52999dee1ea301d4e02aafcb24
    Signed-off-by: Gabriel Cuba <gcuba@whitestack.com>

commit 01619d5b596e01ac8cd6d27bf01a1174e6b3f97b
Author: Gulsum Atici <gulsum.atici@canonical.com>
Date:   Wed Mar 22 22:57:26 2023 +0300

    Keep vim_details while reporting VM deletion

    Change-Id: I27577b2fc93a585affc947abcec8352562f23f49
    Signed-off-by: Gulsum Atici <gulsum.atici@canonical.com>

commit 98740c03567ff8c5a22f06fd3f049248a9e5f98d
Author: Pedro Escaleira <escaleira@av.it.pt>
Date:   Wed Feb 22 10:48:52 2023 +0000

    Bug 2217 fixed: modified the cloud-init merge configs and defined the default SSH keys within the system_info instead of users

    Change-Id: I12e26a88fb9b50c4a78b9fa8ee2cb5d4b4bf6d00
    Signed-off-by: Pedro Escaleira <escaleira@av.it.pt>

commit d586d89bde00acaf22debd7f657d605c9d095571
Author: Gulsum Atici <gulsum.atici@canonical.com>
Date:   Mon Feb 13 18:40:03 2023 +0300

    Feature 10960 Performance optimizations for the polling of VM status in RO

    Change-Id: If785bbeaab66e0839541bf94184ce37114e67bd4
    Signed-off-by: Gulsum Atici <gulsum.atici@canonical.com>

commit 4c1dd54ae02e82f11a60058a1b7c7b0137ac572e
Author: Gabriel Cuba <gcuba@whitestack.com>
Date:   Tue Feb 14 12:43:32 2023 -0500

    Refactor ns.py so that RO uses the IP profile as it comes from LCM

    Change-Id: I36983c86d7c76ad8a9b93eb6eae254f844717e0e
    Signed-off-by: Gabriel Cuba <gcuba@whitestack.com>

commit 3822010a26b2e21290b6acdf288db277c7f36605
Author: garciadeblas <gerardo.garciadeblas@telefonica.com>
Date:   Mon Feb 13 17:48:32 2023 +0100

    Fix bug 2216 to remove hardcoded numa affinity in VIO

    Change-Id: I0912c2841e7c5c1febe056ba092afedaea77f6a1
    Signed-off-by: garciadeblas <gerardo.garciadeblas@telefonica.com>

commit 778f3cc8c052bd17d0da32f07b880616d25f935a
Author: Lovejeet Singh <lovejeet.singh@hsc.com>
Date:   Mon Feb 13 16:15:40 2023 +0530

    Bug 2202: Adding support for cinder V3 API with V2 API for persistent volumes.

    Change-Id: I7034564b91b94e6be242cb2ce0f4a5b147b87d64
    Signed-off-by: Lovejeet Singh <lovejeet.singh@hsc.com>

Change-Id: I7ac1bd1d9896788812f456c678b1f5222a1f1ad6
Signed-off-by: Dario Faccin <dario.faccin@canonical.com>
diff --git a/RO-SDN-tapi/osm_rosdn_tapi/services_composer.py b/RO-SDN-tapi/osm_rosdn_tapi/services_composer.py
new file mode 100644
index 0000000..720e423
--- /dev/null
+++ b/RO-SDN-tapi/osm_rosdn_tapi/services_composer.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+
+#######################################################################################
+# This file is part of OSM RO module
+#
+# Copyright ETSI Contributors and Others.
+#
+# 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.
+#######################################################################################
+# This work has been performed in the context of the TeraFlow Project -
+# funded by the European Commission under Grant number 101015857 through the
+# Horizon 2020 program.
+# Contributors:
+# - Lluis Gifre <lluis.gifre@cttc.es>
+# - Ricard Vilalta <ricard.vilalta@cttc.es>
+#######################################################################################
+
+"""This file contains the ServiceComposer class used by the Transport API (TAPI) WIM
+connector to compose the services based on the service_endpoint_ids and their
+directionality."""
+
+from .exceptions import (
+    WimTapiIncongruentDirectionality,
+    WimTapiIncongruentEndPoints,
+    WimTapiMissingMappingField,
+    WimTapiSipNotFound,
+)
+from .message_composers import (
+    compose_endpoint,
+    compose_requested_capacity,
+    # compose_vlan_constraint,
+)
+
+
+class ServicesComposer:
+    def __init__(self, service_interface_points) -> None:
+        self.sips = service_interface_points
+
+        # if unidirectional
+        #   - a single service_endpoint item is created
+        #   - the service_endpoint item contains with the 2 bidirectional SIPs
+        # if bidirectional
+        #   - two service_endpoint items are created
+        #   - each service_endpoint item containing a list of 2 unidirectional SIPs (in, out)
+        self.services = list()
+
+        # TODO: populate dynamically capacity of the connection
+        self.requested_capacity = compose_requested_capacity(1, unit="GBPS")
+
+        self.vlan_constraint = None
+        # TODO: VLAN needs to be processed by connection point; by now deactivated
+        # if connection_point.get("service_endpoint_encapsulation_type") == "dot1q":
+        #    encap_info = connection_point.get("service_endpoint_encapsulation_info", {})
+        #    vlan_id = encap_info.get("vlan")
+        #    if vlan_id is not None:
+        #        vlan_constraint = compose_vlan_constraint(vlan_id)
+
+    def add_bidirectional(self, service_endpoint_id):
+        if len(self.services) == 0:
+            # assume bidirectional, SIP is service_endpoint_id
+            service_interface_point = self.sips[service_endpoint_id]
+            self.services.append([compose_endpoint(service_interface_point)])
+        elif len(self.services) == 1:
+            # is bidirectional, SIP is service_endpoint_id
+            if len(self.services[0]) > 1:
+                # too much endpoints per service
+                raise WimTapiIncongruentEndPoints(self.services, service_endpoint_id)
+            self.services[0].append(compose_endpoint(self.sips[service_endpoint_id]))
+        else:
+            raise WimTapiIncongruentDirectionality(self.services, service_endpoint_id)
+
+    def add_unidirectional(self, service_endpoint_id, sip_input, sip_output):
+        if len(self.services) == 0:
+            # assume unidirectional
+            self.services.append([compose_endpoint(self.sips[sip_output])])  # AZ
+            self.services.append([compose_endpoint(self.sips[sip_input])])  # ZA
+        elif len(self.services) == 2:
+            # is unidirectional
+
+            if len(self.services[0]) > 1:
+                # too much endpoints per service
+                raise WimTapiIncongruentEndPoints(self.services[0], service_endpoint_id)
+            self.services[0].append(compose_endpoint(self.sips[sip_input]))  # AZ
+
+            if len(self.services[1]) > 1:
+                # too much endpoints per service
+                raise WimTapiIncongruentEndPoints(self.services[1], service_endpoint_id)
+            self.services[1].insert(0, compose_endpoint(self.sips[sip_output]))  # ZA
+        else:
+            raise WimTapiIncongruentDirectionality(self.services, service_endpoint_id)
+
+    def add_service_endpoint(self, service_endpoint_id, mapping):
+        service_mapping_info = mapping.get("service_mapping_info", {})
+
+        if (
+            len(service_mapping_info) == 0
+            or "sip_input" not in service_mapping_info
+            or "sip_output" not in service_mapping_info
+        ):
+            # bidirectional (no mapping or no sip_input or no sip_output)
+            if service_endpoint_id not in self.sips:
+                raise WimTapiSipNotFound(service_endpoint_id, self.sips)
+            self.add_bidirectional(service_endpoint_id)
+
+        else:
+            # unidirectional, sip_input and sip_output provided in mapping
+
+            sip_input = service_mapping_info.get("sip_input")
+            if sip_input is None:
+                raise WimTapiMissingMappingField(
+                    mapping, "service_mapping_info.sip_input"
+                )
+
+            if sip_input not in self.sips:
+                raise WimTapiSipNotFound(sip_input, self.sips)
+
+            sip_output = service_mapping_info.get("sip_output")
+            if sip_output is None:
+                raise WimTapiMissingMappingField(
+                    mapping, "service_mapping_info.sip_output"
+                )
+
+            if sip_output not in self.sips:
+                raise WimTapiSipNotFound(sip_output, self.sips)
+
+            self.add_unidirectional(service_endpoint_id, sip_input, sip_output)
+
+    def is_bidirectional(self):
+        return len(self.services) == 1
+
+    def dump(self, logger):
+        str_data = "\n".join(
+            [
+                "services_composer {",
+                "  services={:s}".format(str(self.services)),
+                "  requested_capacity={:s}".format(str(self.requested_capacity)),
+                "  vlan_constraint={:s}".format(str(self.vlan_constraint)),
+                "}",
+            ]
+        )
+        logger.debug(str_data)