Initial commit to gerrit repo 54/8554/8 hackfest release-v8.0-start v7.1.2 v7.1.3 v8.0.0rc1
authormagnussonl <lars-goran.magnusson@arctoslabs.com>
Tue, 4 Feb 2020 09:52:46 +0000 (10:52 +0100)
committermagnussonl <lars-goran.magnusson@arctoslabs.com>
Tue, 31 Mar 2020 12:10:57 +0000 (14:10 +0200)
Made non-functional change to trigger Jenkins
Add/update license headers
Dockerfile modification
Corrected Dockerfile faults
Dockerfile update
Yet another Dockerfile update
Support for placement without vld:s

Change-Id: I63c1733656f682233c96f6bcadeac1a2765ed085
Signed-off-by: magnussonl <lars-goran.magnusson@arctoslabs.com>
56 files changed:
.gitignore
Dockerfile
Jenkinsfile
MANIFEST.in [new file with mode: 0755]
Makefile [new file with mode: 0755]
README.md
debian/python3-osm-pla.postinst [new file with mode: 0755]
devops-stages/stage-archive.sh
devops-stages/stage-build.sh
devops-stages/stage-test.sh
docker/Dockerfile [new file with mode: 0755]
docker/integrationtest.Dockerfile [new file with mode: 0755]
docker/scripts/start.sh [new file with mode: 0755]
docs/img/PLA_SW_Arch.png [new file with mode: 0644]
docs/img/gui_instantiate.png [new file with mode: 0644]
docs/img/nsd_w_constraints.png [new file with mode: 0644]
docs/img/osm_gui_ns_create.png [new file with mode: 0644]
docs/pla_design_spec.md [new file with mode: 0644]
docs/pla_users_guide.md [new file with mode: 0644]
osm_pla/__init__.py [new file with mode: 0755]
osm_pla/cmd/__init__.py [new file with mode: 0755]
osm_pla/cmd/pla_server.py [new file with mode: 0755]
osm_pla/config/config.py [new file with mode: 0644]
osm_pla/config/pla.yaml [new file with mode: 0644]
osm_pla/placement/__init__.py [new file with mode: 0755]
osm_pla/placement/macros.j2 [new file with mode: 0644]
osm_pla/placement/mznplacement.py [new file with mode: 0755]
osm_pla/placement/osm_pla_dynamic_template.j2 [new file with mode: 0644]
osm_pla/server/server.py [new file with mode: 0644]
osm_pla/test/__init__.py [new file with mode: 0644]
osm_pla/test/corrupt_pil_endpoints_config_unittest1.yaml [new file with mode: 0644]
osm_pla/test/not_yaml_conformant.yaml [new file with mode: 0644]
osm_pla/test/nsd_unittest1.yaml [new file with mode: 0644]
osm_pla/test/nsd_unittest2.yaml [new file with mode: 0644]
osm_pla/test/nsd_unittest3.yaml [new file with mode: 0644]
osm_pla/test/nsd_unittest4.yaml [new file with mode: 0644]
osm_pla/test/nsd_unittest_no_vld_constraints.yaml [new file with mode: 0644]
osm_pla/test/pil_price_list.yaml [new file with mode: 0644]
osm_pla/test/pil_price_list_rel7_webinar.yaml [new file with mode: 0644]
osm_pla/test/pil_unittest1.yaml [new file with mode: 0644]
osm_pla/test/pil_unittest2.yaml [new file with mode: 0644]
osm_pla/test/test_five_nsd.yaml [new file with mode: 0644]
osm_pla/test/test_mznModelGenerator.py [new file with mode: 0644]
osm_pla/test/test_mznPlacementConductor.py [new file with mode: 0644]
osm_pla/test/test_mznmodels.py [new file with mode: 0644]
osm_pla/test/test_nsPlacementDataFactory.py [new file with mode: 0644]
osm_pla/test/test_server.py [new file with mode: 0644]
osm_pla/test/vnf_price_list.yaml [new file with mode: 0644]
osm_pla/test/vnf_price_list_more_vims.yaml [new file with mode: 0644]
osm_pla/test/vnf_price_list_rel7_webinar.yaml [new file with mode: 0644]
requirements.txt [new file with mode: 0755]
setup.cfg [new file with mode: 0755]
setup.py [new file with mode: 0755]
stdeb.cfg [new file with mode: 0644]
test-requirements.txt [new file with mode: 0755]
tox.ini [new file with mode: 0644]

index 302f4e1..9007d45 100644 (file)
@@ -31,7 +31,7 @@ deb_dist
 pool
 dist
 
-#local stuff files that end in ".local" or folders called "local"
+#local stuff, e.g. files that end in ".local" or folders called "local"
 *.local
 local
-
+venv
index 54f0fd5..234679c 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM ubuntu:18.04
+FROM ubuntu:16.04
 
-RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install git \
-    make python3 debhelper python3-setuptools apt-utils
+RUN apt-get update && \
+    DEBIAN_FRONTEND=noninteractive apt-get -y install git tox make python3 python3-pip python-all && \
+    DEBIAN_FRONTEND=noninteractive apt-get -y install python3-all debhelper python3-setuptools apt-utils libgl1-mesa-glx && \
+    DEBIAN_FRONTEND=noninteractive pip3 install -U setuptools setuptools-version-command stdeb
+
+ADD https://github.com/MiniZinc/MiniZincIDE/releases/download/2.4.2/MiniZincIDE-2.4.2-bundle-linux-x86_64.tgz /minizinc.tgz
+
+RUN tar -zxf /minizinc.tgz && \
+    mv /MiniZincIDE-2.4.2-bundle-linux /minizinc
+
+RUN mkdir /entry_data \
+    && mkdir /entry_data/mzn-lib \
+    && ln -s /entry_data/mzn-lib /minizinc/share/minizinc/exec
+
+ENV FZNEXEC "/entry_data/fzn-exec"
+ENV PATH "/minizinc/bin:${PATH}"
+
+RUN mkdir /placement
+COPY ./osm_pla/test/pil_price_list.yaml /placement/.
+COPY ./osm_pla/test/vnf_price_list.yaml /placement/.
index 5915574..40e35aa 100644 (file)
@@ -1,3 +1,17 @@
+/*
+  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.
+*/
 properties([
     parameters([
         string(defaultValue: env.BRANCH_NAME, description: '', name: 'GERRIT_BRANCH'),
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100755 (executable)
index 0000000..22675f6
--- /dev/null
@@ -0,0 +1,20 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+# ****************************************
+# This file is part of OSM Placement module
+# All Rights Reserved to Intel Corporation
+
+# 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.
+include requirements.txt
+include README.md
+recursive-include osm_pla *.py *.sh *.yaml
+recursive-include devops-stages *
diff --git a/Makefile b/Makefile
new file mode 100755 (executable)
index 0000000..7e82f44
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,28 @@
+# Copyright 2020 ArctosLabs Scandinava AB
+# *************************************************************
+
+# This file is part of OSM Placement module
+# All Rights Reserved to ArctosLabs Scandinavia AB
+
+# 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.
+##
+
+all: clean package
+
+clean:
+       rm -rf dist deb_dist osm_pla-*.tar.gz osm_pla.egg-info .eggs
+
+package:
+       python3 setup.py --command-packages=stdeb.command sdist_dsc
+       cp debian/python3-osm-pla.postinst deb_dist/osm-pla*/debian
+       cd deb_dist/osm-pla*/  && dpkg-buildpackage -rfakeroot -uc -us
index e0f6898..5fa8e3d 100644 (file)
--- a/README.md
+++ b/README.md
@@ -14,55 +14,40 @@ implied.
 See the License for the specific language governing permissions and
 limitations under the License
 -->
-# Project Title
+# OSM PLA
 
-One Paragraph of project description goes here
+The PLA module provides computation of optimal placement of xNFs over VIMs by matching NS specific requirements to infrastructure availability and run-time metrics, while considering cost of compute/network.
 
 ## Getting Started
 
-These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.
+Please refer to the [PLA User's Guide](docs/pla_users_guide.md) for a description on how to enable and configure the placement functionality.
 
-### Prerequisites
 
-What things you need to install the software and how to install them
-
-```
-Give examples
-```
-
-### Installing
-
-A step by step series of examples that tell you how to get a development env running
-
-Say what the step will be
-
-```
-Give the example
-```
-
-And repeat
-
-```
-until finished
-```
+## Running the tests
 
-End with an example of getting some data out of the system or using it for a little demo
+The preferred method to run the PLA unit test is to use tox.
 
-## Running the tests
+`$ tox`
 
-Explain how to run the automated tests for this system
+Please note that some of the unit test modules have dependencies to Minizinc, e.g. test_mznmodels.py and test_mznPlacementConductor.py.
+If these tests are to be performed outside a PLA container context, like .e.g. from CLI or from within an IDE, setup the environment as follows (linux example):
 
 ```
-Give an example
+$ sudo snap install minizinc --classic
+$ sudo mkdir -p /minizinc/bin
+$ sudo ln -s /snap/bin/minizinc /minizinc/bin/minizinc 
 ```
 
 ## Deployment
 
-Add additional notes about how to deploy this on a live system
+PLA is an optional module in OSM. It is installed together with OSM by adding ``--pla`` to the install script.
+
+`$ ./install_osm.sh --pla`
 
 ## Built With
 
-* [Python](www.python.org/) - The language used
+* [Python](www.python.org/) - the primary programming language for OSM
+* [Minizinc](www.minizinc.org) - a free and open source constraint modelling language
 
 ## Contributing
 
@@ -78,5 +63,6 @@ This project is licensed under the Apache2 License - see the [LICENSE.md](LICENS
 
 ## Acknowledgments
 
-* **Billie Thompson** - *Initial work* - [PurpleBooth](https://github.com/PurpleBooth)
+* **Paolo Dragone** - *PyMzn, a python library that wraps and enhance Minizinc* - [pymzn](https://github.com/paolodragone/pymzn)
+* **Billie Thompson** - *Initial work on README.md* - [PurpleBooth](https://github.com/PurpleBooth)
 
diff --git a/debian/python3-osm-pla.postinst b/debian/python3-osm-pla.postinst
new file mode 100755 (executable)
index 0000000..bed12db
--- /dev/null
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+##
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: OSM_TECH@list.etsi.org
+##
+
+echo "POST INSTALL OSM-PLA"
+# Currently it is not needed pip3 installation
+# echo "Installing python dependencies via pip..."
+# pip3 install pip==9.0.3
+# pip3 install --user aiokafka
+
+#Creation of log folder
+mkdir -p /var/log/osm
+
+# systemctl enable osm-pla.service
index 013953f..831c8c8 100755 (executable)
 # implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-echo "ARCHIVE"
+MDG=PLA
+rm -rf pool
+rm -rf dists
+mkdir -p pool/$MDG
+mv deb_dist/*.deb pool/$MDG/
+mkdir -p dists/unstable/$MDG/binary-amd64/
+apt-ftparchive packages pool/$MDG > dists/unstable/$MDG/binary-amd64/Packages
+gzip -9fk dists/unstable/$MDG/binary-amd64/Packages
+echo "dists/**,pool/$MDG/*.deb"
index b58d1f3..9c2b16b 100755 (executable)
@@ -12,4 +12,4 @@
 # implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-echo "BUILD"
+make
index 337cf59..69c6fcd 100755 (executable)
@@ -11,4 +11,4 @@
 # implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-echo "TEST"
+tox
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100755 (executable)
index 0000000..1b4a01c
--- /dev/null
@@ -0,0 +1,72 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+# *************************************************************
+
+# This file is part of OSM Placement module
+# All Rights Reserved to ArctosLabs Scandinavia AB
+
+# 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.
+FROM ubuntu:18.04
+
+LABEL authors="Martin Björklund, Lars-Göran Magnusson"
+
+RUN apt-get --yes update \
+ && apt-get --yes install git python3 python3-pip libgl1-mesa-glx \ 
+ && pip3 install pip==9.0.3
+
+COPY requirements.txt /pla/requirements.txt
+
+RUN pip3 install -r /pla/requirements.txt
+
+ADD https://github.com/MiniZinc/MiniZincIDE/releases/download/2.3.1/MiniZincIDE-2.3.1-bundle-linux-x86_64.tgz /minizinc.tgz
+#COPY MiniZincIDE-2.3.1-bundle-linux-x86_64.tgz /minizinc.tgz
+
+RUN tar -zxf /minizinc.tgz && \
+    mv /MiniZincIDE-2.3.1-bundle-linux /minizinc
+
+RUN mkdir /entry_data \
+    && mkdir /entry_data/mzn-lib \
+    && ln -s /entry_data/mzn-lib /minizinc/share/minizinc/exec
+
+COPY . /pla
+
+RUN pip3 install /pla
+
+RUN mkdir /placement
+
+ENV OSMPLA_MESSAGE_DRIVER kafka
+ENV OSMPLA_MESSAGE_HOST kafka
+ENV OSMPLA_MESSAGE_PORT 9092
+
+ENV OSMPLA_DATABASE_DRIVER mongo
+ENV OSMPLA_DATABASE_URI mongodb://mongo:27017
+
+ENV OSMPLA_SQL_DATABASE_URI sqlite:///pla_sqlite.db
+ENV OSMPLA_GLOBAL_REQUEST_TIMEOUT 10
+ENV OSMPLA_GLOBAL_LOGLEVEL INFO
+ENV OSMPLA_VCA_HOST localhost
+ENV OSMPLA_VCA_SECRET secret
+ENV OSMPLA_VCA_USER admin
+ENV OSMPLA_DATABASE_COMMONKEY changeme
+
+ENV FZNEXEC "/entry_data/fzn-exec"
+ENV PATH "/minizinc/bin:${PATH}"
+ENV LD_LIBRARY_PATH "/minizinc/lib:${LD_LIBRARY_PATH}"
+
+EXPOSE 1234
+
+#HEALTHCHECK --interval=5s --timeout=2s --retries=12 \
+#  CMD osm-pla-healthcheck || exit 1
+
+CMD /bin/bash pla/docker/scripts/start.sh
+
+#WORKDIR /minizinc
diff --git a/docker/integrationtest.Dockerfile b/docker/integrationtest.Dockerfile
new file mode 100755 (executable)
index 0000000..a4cedf6
--- /dev/null
@@ -0,0 +1,74 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+# *************************************************************
+
+# This file is part of OSM Placement module
+# All Rights Reserved to ArctosLabs Scandinavia AB
+
+# 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.
+FROM ubuntu:18.04
+
+LABEL authors="Martin Björklund, Lars-Göran Magnusson"
+
+RUN apt-get --yes update \
+ && apt-get --yes install git python3 python3-pip libgl1-mesa-glx \ 
+ && pip3 install pip==9.0.3
+
+COPY requirements.txt /pla/requirements.txt
+
+RUN pip3 install -r /pla/requirements.txt
+
+ADD https://github.com/MiniZinc/MiniZincIDE/releases/download/2.3.1/MiniZincIDE-2.3.1-bundle-linux-x86_64.tgz /minizinc.tgz
+#COPY MiniZincIDE-2.3.1-bundle-linux-x86_64.tgz /minizinc.tgz
+
+RUN tar -zxf /minizinc.tgz && \
+    mv /MiniZincIDE-2.3.1-bundle-linux /minizinc
+
+RUN mkdir /entry_data \
+    && mkdir /entry_data/mzn-lib \
+    && ln -s /entry_data/mzn-lib /minizinc/share/minizinc/exec
+
+COPY . /pla
+
+RUN pip3 install /pla
+
+RUN mkdir /placement
+COPY ./osm_pla/test/pil_price_list.yaml /placement/.
+COPY ./osm_pla/test/vnf_price_list.yaml /placement/.
+
+ENV OSMPLA_MESSAGE_DRIVER kafka
+ENV OSMPLA_MESSAGE_HOST kafka
+ENV OSMPLA_MESSAGE_PORT 9092
+
+ENV OSMPLA_DATABASE_DRIVER mongo
+ENV OSMPLA_DATABASE_URI mongodb://mongo:27017
+
+ENV OSMPLA_SQL_DATABASE_URI sqlite:///pla_sqlite.db
+ENV OSMPLA_GLOBAL_REQUEST_TIMEOUT 10
+ENV OSMPLA_GLOBAL_LOGLEVEL INFO
+ENV OSMPLA_VCA_HOST localhost
+ENV OSMPLA_VCA_SECRET secret
+ENV OSMPLA_VCA_USER admin
+ENV OSMPLA_DATABASE_COMMONKEY changeme
+
+ENV FZNEXEC "/entry_data/fzn-exec"
+ENV PATH "/minizinc/bin:${PATH}"
+ENV LD_LIBRARY_PATH "/minizinc/lib:${LD_LIBRARY_PATH}"
+
+EXPOSE 1234
+
+#HEALTHCHECK --interval=5s --timeout=2s --retries=12 \
+#  CMD osm-pla-healthcheck || exit 1
+
+CMD /bin/bash pla/docker/scripts/start.sh
+
+#WORKDIR /minizinc
diff --git a/docker/scripts/start.sh b/docker/scripts/start.sh
new file mode 100755 (executable)
index 0000000..66b96c8
--- /dev/null
@@ -0,0 +1,69 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+DB_EXISTS=""
+
+max_attempts=120
+function wait_db(){
+    db_host=$1
+    db_port=$2
+    attempt=0
+    echo "Wait until $max_attempts seconds for MySQL mano Server ${db_host}:${db_port} "
+    while ! mysqladmin ping -h"$db_host" -P"$db_port" --silent; do
+        #wait 120 sec
+        if [ $attempt -ge $max_attempts ]; then
+            echo
+            echo "Can not connect to database ${db_host}:${db_port} during $max_attempts sec"
+            return 1
+        fi
+        attempt=$[$attempt+1]
+        echo -n "."
+        sleep 1
+    done
+    return 0
+}
+
+function is_db_created() {
+    db_host=$1
+    db_port=$2
+    db_user=$3
+    db_pswd=$4
+    db_name=$5
+
+    if mysqlshow -h"$db_host" -P"$db_port" -u"$db_user" -p"$db_pswd" | grep -v Wildcard | grep -q $db_name; then
+        echo "DB $db_name exists"
+        return 0
+    else
+        echo "DB $db_name does not exist"
+        return 1
+    fi
+}
+
+if [[ $OSMPLA_SQL_DATABASE_URI == *'mysql'* ]]; then
+    DB_HOST=$(echo $OSMPLA_SQL_DATABASE_URI | sed -r 's|^.+://.+:.+@(.+):.*$|\1|')
+    DB_PORT=$(echo $OSMPLA_SQL_DATABASE_URI | sed -r 's|^.+://.*:([0-9]+).*$|\1|')
+    DB_USER=$(echo $OSMPLA_SQL_DATABASE_URI | sed -r 's|^.+://(.+):.+@.+$|\1|')
+    DB_PASSWORD=$(echo $OSMPLA_SQL_DATABASE_URI | sed -r 's|^.+://.+:(.+)@.*$|\1|')
+    DB_NAME=$(echo $OSMPLA_SQL_DATABASE_URI | sed -r 's|^.+://.+:.+@.+:.*/(\w+)(\?.*)?$|\1|')
+    
+    wait_db "$DB_HOST" "$DB_PORT" || exit 1
+
+    is_db_created "$DB_HOST" "$DB_PORT" "$DB_USER" "$DB_PASSWORD" "$DB_NAME" && DB_EXISTS="Y"
+
+    if [ -z $DB_EXISTS ]; then
+        mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASSWORD" --default_character_set utf8 -e "CREATE DATABASE $DB_NAME"
+    fi
+fi
+
+osm-pla-server
diff --git a/docs/img/PLA_SW_Arch.png b/docs/img/PLA_SW_Arch.png
new file mode 100644 (file)
index 0000000..99afc4b
Binary files /dev/null and b/docs/img/PLA_SW_Arch.png differ
diff --git a/docs/img/gui_instantiate.png b/docs/img/gui_instantiate.png
new file mode 100644 (file)
index 0000000..4a76b28
Binary files /dev/null and b/docs/img/gui_instantiate.png differ
diff --git a/docs/img/nsd_w_constraints.png b/docs/img/nsd_w_constraints.png
new file mode 100644 (file)
index 0000000..395eb53
Binary files /dev/null and b/docs/img/nsd_w_constraints.png differ
diff --git a/docs/img/osm_gui_ns_create.png b/docs/img/osm_gui_ns_create.png
new file mode 100644 (file)
index 0000000..ce7950d
Binary files /dev/null and b/docs/img/osm_gui_ns_create.png differ
diff --git a/docs/pla_design_spec.md b/docs/pla_design_spec.md
new file mode 100644 (file)
index 0000000..a55e73c
--- /dev/null
@@ -0,0 +1,93 @@
+<!--
+Copyright 2020 ArctosLabs Scandinavia AB
+
+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
+-->
+
+# Placement module (PLA) design description
+## Overview
+The PLA module provides computation of optimal placement of VNFs over VIMs by matching NS specific requirements to infrastructure availability and run-time metrics, while considering cost of compute/network.
+
+This document supplement the Placement Module (PLA) Users's Guide by providing details on the SW design.
+
+PLA is a separate module in the OSM architecture. As other OSM modules it runs in a Docker container.
+PLA interacts with LCM over the Kafka message bus and handles the following message:  
+* topic: pla - command: get_placement
+
+When LCM receives a ns instantiation command with config parameter `placement-engine: PLA` it shall request placement computation from PLA. 
+See PLA User's Guide for details and capabilities for different types of instantiation commands and necessary configuration of PLA.
+
+The placement computation is done by Minizinc (www.minizinc.org) on a constraints model that is created according to the content of the ns instantiation command.
+
+The minizinc models are created on demand within PLA using the Jinja2 templating language.
+
+## SW components overview
+The diagram below illustrates the important classes, data structures and libraries within PLA.
+
+![PLA SW Architecture](img/PLA_SW_Arch.png)
+
+**PLA Server**
+
+This is the PLA server (server.py).
+Capability: Produce placement suggestions based on a placement request from LCM OSM module. The get_placement() method extract information based on the referred instantiation operation id and calculates possible deployments for the NS by matching the NS specific requirements to infrastructure, - availability and, -run-time metrics,  while considering cost of compute/network.
+Collaborates with: MznPlacementConductor, NsPlacementDataFactory
+
+**MznPlacementConductor**
+
+Capability: Manages minizinc model creation, execution and processing of minizinc output. Use the NsPlacementData provided from the PLA Server and collaborates with MznModelGenerator for model creation and uses PyMzn for execution of the created minizinc model.
+Collaborates with: PLA Server, MznModelGenerator
+
+**MznModelGenerator**
+
+Capability: Create instance of MznModel using the information kept in NsPlacementData combined with Jinja2 templating language/templating engine. 
+Collaborates with: MznPlacementConductor
+
+**NsPlacementDataFactory**
+
+Capability: Knows how to create a NsPlacementData instance. Receives placement request data and collects additional information on; NSD/VNFD, Infrastructure topology, link delays and possibly more things (e.g. resource utilization, after rel.7).
+Collaborates with: PLA Server
+
+**NsPlacementData**
+
+Dictionary keeping relevant data for a placement request so that a corresponding minizinc model can be created. Content includes e.g. vim account information, price lists, network topology, link characteristics and network service characteristics.
+
+**PlacementResult**
+
+Carries processed result of the optimal placement solution as computed by minizinc
+
+**MznModel**
+
+String representation of a mzn model
+
+**PyMzn**
+
+PyMzn is a Python library providing bindings to minizinc.
+
+**Jinja2**
+
+Jinja2 is a template engine used in PLA when creating the minizinc model for a placement request.
+
+
+## Unit tests
+### Unit testing dependencies
+Some of the unit test modules have dependencies to Minizinc, e.g. test_mznmodels.py and test_mznPlacementConductor.py.
+If these tests are to be performed outside a PLA container context, like .e.g. from CLI or from within an IDE, setup the environment as follows (linux example):
+1. install minizinc as a snap from snapcraft.io/minizinc
+2. create a softlink from /minizinc/bin/minizinc to /snap/bin/minizinc to mimic the container structure in the development host
+
+```
+$ sudo snap install minizinc --classic
+$ sudo mkdir -p /minizinc/bin
+$ sudo ln -s /snap/bin/minizinc /minizinc/bin/minizinc 
+```
diff --git a/docs/pla_users_guide.md b/docs/pla_users_guide.md
new file mode 100644 (file)
index 0000000..d7ec810
--- /dev/null
@@ -0,0 +1,160 @@
+<!--
+Copyright 2020 Arctos Labs Scandinavia AB
+
+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
+-->
+
+# Placement module (PLA) User's Guide
+
+## Introduction
+To use the placement functionality of OSM several steps needs to be taken to configure and enable the function.
+1.     OSM needs to be installed with the PLA module included
+2.     Create the price lists for compute and transport links
+3.     Create the inventory of PoP interconnecting links
+4.     Update the PLA container with price list and inventory file
+5.     Usage - Instantiate the service using the placement engine
+
+## Install OSM including PLA
+PLA is an optional module in OSM. It is installed together with OSM by adding ``--pla`` to the install script.
+
+`$ ./install_osm.sh --pla`
+
+## Create the price lists
+The price list for compute determines the price for each VNF at each VIM (or Point of Presence - PoP). The file (vnf_price_list.yaml) is written in Yaml and is exemplified below.
+
+```
+- vnfd: testVnfOne
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 10
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 9
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 8
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4
+      price: 7
+- vnfd: hackfest_multivdu-vnf
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 17
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 18
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 19
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4      
+      price: 20
+```
+
+The price list for transport links between VIMs (PoP Interconnecting Link â€“ PiL). In current release the price is given per link without any consideration to BW or other QoS parameter. The file (pil_price_list.yaml) is written in Yaml and is exemplified below. Note: In current OSM release the link characteristics are hard coded into this file, in future releases this data should be retrieved from the infrastructure by monitoring mechanisms.
+
+```
+pil:
+  - pil_description: Link between OpenStack1 and OpenStack2
+    pil_price: 12
+    pil_latency: 120
+    pil_jitter: 12
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.44:5000/v3
+  - pil_description: Link between OpenStack1 and OpenStack3
+    pil_price: 13
+    pil_latency: 130
+    pil_jitter: 13
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.46:5000/v3
+  - pil_description: Link between OpenStack1 and OpenStack4
+    pil_price: 14
+    pil_latency: 140
+    pil_jitter: 14
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.43:5000/v3
+  - pil_description: Link between OpenStack2 and OpenStack3
+    pil_price: 23
+    pil_latency: 230
+    pil_jitter: 23
+    pil_endpoints:
+      - http://10.234.12.44:5000/v3
+      - http://10.234.12.46:5000/v3
+  - pil_description: Link between OpenStack2 and OpenStack4
+    pil_price: 24
+    pil_latency: 240
+    pil_jitter: 24
+    pil_endpoints:
+      - http://10.234.12.44:5000/v3
+      - http://10.234.12.43:5000/v3
+  - pil_description: Link between OpenStack3 and OpenStack4
+    pil_price: 34
+    pil_latency: 340
+    pil_jitter: 34
+    pil_endpoints:
+      - http://10.234.12.46:5000/v3
+      - http://10.234.12.43:5000/v3
+
+```
+
+## Create the inventory file
+The VIMs configured in OSM are assumed to be connected to each other with transport links - PiLs. In current release the inventory file describes the available PiLs with latency and jitter. If there is no PiL in the inventory file for any pair of PoPs the placement engine will not be able to use that relation for a VL.
+
+## Update the PLA container
+Copy the price lists and inventory files to the PLA container using the following commands:
+
+`$ docker cp vnf_price_list.yaml $(docker ps -qf name=osm_pla):/placement/.`
+
+`$ docker cp pil_price_list.yaml $(docker ps -qf name=osm_pla):/placement/.`
+
+## Instantiate the service
+When creating a NS instance, it is possible to pass instantiation parameters to OSM using the `--config` option of the client or the `config` parameter of the UI. To invoke the placement engine following directives are used.
+
+`
+--config '{placement-engine: PLA}'
+`
+
+### Usage examples
+
+#### Basic usage
+`$ osm ns-create --ns_name ThreeVNFTest --nsd_name three_vnf_constrained_nsd --vim_account OpenStack1 --config '{placement-engine: PLA}'
+`
+
+Using PLA support from the GUI network service create form:
+
+![Instantiate with PLA support](img/osm_gui_ns_create.png)
+
+### With pinning of member-vnf-index: "3" to vim_account: OpenStack3
+To enable automatic placement with one (or multiple) VNFs at a pre-determined place (e.g. near CPE), PLA has the ability do placement with one or multiple VNFs pinned to datacenter(s). The pinning is done in the same way as explained in section [Multi-site deployments](https://osm.etsi.org/wikipub/index.php/OSM_instantiation_parameters#Multi-site_deployments_.28specifying_different_VIM_accounts_for_different_VNFs.29) in the OSM wiki.
+
+Example NS instantiation using CLI command (pinning one of three VNFs in the used NSD):
+`$ osm ns-create --ns_name ThreeVnfTest2 --nsd_name three_vnf_constrained_nsd --vim_account OpenStack1 --config '{placement-engine: PLA, vnf: [{member-vnf-index: â€œ3", vim_account: OpenStack3}]}'`
+
+
+### With constraints as instantiation parameters
+It is also possible to provide constraints as instantiation parameters. Such constraints are included in a dictionary belonging to the `constraints` key as follows: 
+
+`config: {placement-engine: PLA, constraints: {<constraints specifications>}}`
+
+#### vld constraints as instantiation parameters
+Currently the supported type of constraints is placed on the vlds, and is identified with the `vld-constraints` key in the constraints specifications. Each vld that is given constraints have the form `{id: <id from nsd>, link-constraints: {latency: <max allowed latency>, jitter: <max allowed jitter>}`. It is not necessary to place constraints on all links, it is also possible to freely mix the supported constraint types `latency` and `jitter` as desired.
+
+Example NS instantiation using CLI command with constraints put on `vld_1` and `vld_2`
+
+`$ osm ns-create --ns_name ThreeVnfTest2 --nsd_name three_vnf_constrained_nsd --vim_account OpenStack1 --config '{placement-engine: PLA, vnf: [{member-vnf-index: â€œ3", vim_account: OpenStack3}], constraints: {vld-constraints: [{id:vld_1, link-constraints: {latency: 120, jitter: 20}}, {id:vld_2, link-constraints: {latency: 120, jitter: 20 }}]}}'`
diff --git a/osm_pla/__init__.py b/osm_pla/__init__.py
new file mode 100755 (executable)
index 0000000..0847222
--- /dev/null
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+##
diff --git a/osm_pla/cmd/__init__.py b/osm_pla/cmd/__init__.py
new file mode 100755 (executable)
index 0000000..468be48
--- /dev/null
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
diff --git a/osm_pla/cmd/pla_server.py b/osm_pla/cmd/pla_server.py
new file mode 100755 (executable)
index 0000000..767e61d
--- /dev/null
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+import argparse
+import asyncio
+import logging
+import sys
+
+from osm_pla.config.config import Config
+from osm_pla.server.server import Server
+
+
+def main():
+    parser = argparse.ArgumentParser(prog='osm-policy-agent')
+    parser.add_argument('--config-file', nargs='?', help='PLA configuration file')
+    args = parser.parse_args()
+    cfg = Config(args.config_file)
+
+    root = logging.getLogger()
+    root.setLevel(logging.getLevelName(cfg.get('global', 'loglevel')))
+    ch = logging.StreamHandler(sys.stdout)
+    ch.setLevel(logging.getLevelName(cfg.get('global', 'loglevel')))
+    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s', '%m/%d/%Y %I:%M:%S %p')
+    ch.setFormatter(formatter)
+    root.addHandler(ch)
+
+    log = logging.getLogger(__name__)
+    log.info("Starting PLA Server...")
+
+    loop = asyncio.get_event_loop()
+    server = Server(cfg, loop)
+    server.run()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/osm_pla/config/config.py b/osm_pla/config/config.py
new file mode 100644 (file)
index 0000000..114fc5f
--- /dev/null
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+"""Global configuration managed by environment variables."""
+
+import logging
+import os
+
+import pkg_resources
+import yaml
+
+logger = logging.getLogger(__name__)
+
+
+class Config:
+    def __init__(self, config_file: str = ''):
+        self.conf = {}
+        self._read_config_file(config_file)
+        self._read_env()
+
+    def _read_config_file(self, config_file):
+        if not config_file:
+            path = 'pla.yaml'
+            config_file = pkg_resources.resource_filename(__name__, path)
+        with open(config_file) as f:
+            self.conf = yaml.load(f)
+
+    def _read_env(self):
+        for env in os.environ:
+            if not env.startswith("OSMPLA_"):
+                continue
+            elements = env.lower().split("_")
+            if len(elements) < 3:
+                logger.warning(
+                    "Environment variable %s=%s does not comply with required format. Section and/or field missing.",
+                    env, os.getenv(env))
+                continue
+            section = elements[1]
+            field = '_'.join(elements[2:])
+            value = os.getenv(env)
+            if section not in self.conf:
+                self.conf[section] = {}
+            self.conf[section][field] = value
+
+    def get(self, section, field=None):
+        if not field:
+            return self.conf[section]
+        return self.conf[section][field]
+
+    def set(self, section, field, value):
+        if section not in self.conf:
+            self.conf[section] = {}
+        self.conf[section][field] = value
diff --git a/osm_pla/config/pla.yaml b/osm_pla/config/pla.yaml
new file mode 100644 (file)
index 0000000..108f2b4
--- /dev/null
@@ -0,0 +1,30 @@
+## Copyright 2020 ArctosLabs Scandinavia AB
+##
+## 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.
+
+global:
+  loglevel: INFO
+
+message:
+  driver: kafka
+  host: kafka
+  port: 9092
+  group_id: pla
+
+database:
+  driver: mongo
+  host: mongo
+  port: 27017
+  name: osm
+  
\ No newline at end of file
diff --git a/osm_pla/placement/__init__.py b/osm_pla/placement/__init__.py
new file mode 100755 (executable)
index 0000000..2669d22
--- /dev/null
@@ -0,0 +1,14 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
diff --git a/osm_pla/placement/macros.j2 b/osm_pla/placement/macros.j2
new file mode 100644 (file)
index 0000000..7b2dcf5
--- /dev/null
@@ -0,0 +1,105 @@
+% Copyright 2020 ArctosLabs Scandinavia AB
+%
+% 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.
+{%- macro vim_accounts(vim_accounts) -%}
+enum  Vims = {
+{%- for vim in vim_accounts %}
+{{vim}}{% if loop.nextitem is defined%},{% endif %}
+{%- endfor -%}
+}; % The vim-accounts
+{%- endmacro -%}
+
+{%- macro variables_vnf(ns_desc) -%}
+{%- for vnf in ns_desc -%}
+{%- if vnf.vim_account %}
+Vims: VNF{{vnf.vnf_id}} = {{vnf.vim_account}};
+{%- else %}
+var Vims: VNF{{vnf.vnf_id}};
+{%- endif -%}
+{% endfor -%}
+{%- endmacro -%}
+
+{%- macro trp_link_latency(trp_link_latency) -%}
+array[Vims, Vims] of int: trp_link_latency = [
+{%- for row in trp_link_latency -%}
+|
+{%- for col in row -%}
+{{col}},
+{%- endfor %}
+{% endfor -%}
+|]; % Transport link latency between data centers
+{%- endmacro -%}
+
+{%- macro trp_link_jitter(trp_link_jitter) -%}
+array[Vims, Vims] of int: trp_link_jitter = [
+{%- for row in trp_link_jitter -%}
+|
+{%- for col in row -%}
+{{col}},
+{%- endfor %}
+{% endfor -%}
+|]; % Transport link jitter between data centers
+{%- endmacro -%}
+
+{%- macro trp_link_price_list(trp_link_price_list) -%}
+array[Vims, Vims] of int: trp_link_price_list = [
+{%- for row in trp_link_price_list -%}
+|
+{%- for col in row -%}
+{{col}},
+{%- endfor %}
+{% endfor -%}
+|]; % Transport link price list
+{%- endmacro -%}
+
+{%- macro vnf_price_list_per_vim(ns_desc) -%}
+{%- for vnf in ns_desc -%}
+array[Vims] of int: vim_price_list_{{vnf.vnf_id}} = [
+{%- for price in vnf.vnf_price_per_vim -%}
+{{price}}{% if loop.nextitem is defined%},{% endif %}
+{%- endfor -%}
+];
+{% endfor %}
+{%- endmacro -%}
+
+{%- macro vld_constraints(vld_desc) -%}
+{%- for cp in vld_desc -%}
+{%- if 'latency' in cp.keys()%}
+constraint trp_link_latency[VNF{{cp.cp_refs[0]}}, VNF{{cp.cp_refs[1]}}] <= {{cp.latency}};
+{% endif -%}
+{% endfor -%}
+{%- for cp in vld_desc -%}
+{%- if 'jitter' in cp.keys()%}
+constraint trp_link_jitter[VNF{{cp.cp_refs[0]}}, VNF{{cp.cp_refs[1]}}] <= {{cp.jitter}};
+{% endif -%}
+{% endfor -%}
+{%- endmacro -%}
+
+{% macro transport_cost(vld_desc) -%}
+var int: used_transport_cost =
+{%- if not vld_desc -%}
+0;
+{% else %}
+{%- for cp in vld_desc -%}
+trp_link_price_list[VNF{{cp.cp_refs[0]}}, VNF{{cp.cp_refs[1]}}]{% if loop.nextitem is defined %}+{% else %};{% endif %}
+{% endfor -%}
+{% endif -%}
+{%- endmacro -%}
+
+{%- macro used_vim_cost(ns_desc) -%}
+var int: used_vim_cost =
+{%- for vnf in ns_desc -%}
+vim_price_list_{{vnf.vnf_id}}[VNF{{vnf.vnf_id}}]{% if loop.nextitem is defined %}+{% else %};{% endif %}
+{% endfor -%}
+{%- endmacro -%}
diff --git a/osm_pla/placement/mznplacement.py b/osm_pla/placement/mznplacement.py
new file mode 100755 (executable)
index 0000000..cf6236a
--- /dev/null
@@ -0,0 +1,254 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+import datetime
+import platform
+import itertools
+
+import pymzn
+from jinja2 import Environment
+from jinja2.loaders import FileSystemLoader
+
+
+class MznPlacementConductor(object):
+    """
+    Knows how to process placement req using minizinc
+    """
+    if platform.system() == 'Windows':
+        default_mzn_path = 'C:\\Program Files\\MiniZinc IDE (bundled)\\minizinc.exe'
+    else:
+        default_mzn_path = '/minizinc/bin/minizinc'
+
+    def __init__(self, log, mzn_path=default_mzn_path):
+        pymzn.config['minizinc'] = mzn_path
+        self.log = log  # FIXME what to log (besides forwarding it to MznModelGenerator) here?
+
+    def _run_placement_model(self, mzn_model, ns_desc, mzn_model_data={}):
+        """
+        Runs the minizinc placement model and post process the result
+        Note: in this revision we use the 'item' output mode from pymzn.minizinc since it ease
+        post processing of the solutions when we use enumerations in mzn_model
+        Note: minizinc does not support '-' in identifiers and therefore we convert back from use of '_' when we
+        process the result
+        Note: minizinc does not support identifiers starting with numbers and therefore we skip the leading 'vim_'
+        when we process the result
+
+        :param mzn_model: a minizinc model as str (note: may also be path to .mzn file)
+        :param ns_desc: network service descriptor, carries information about pinned VNFs so those can be included in
+         the result
+        :param mzn_model_data: minizinc model data dictionary (typically not used with our models)
+        :return: list of dicts formatted as {'vimAccountId': '<account id>', 'member-vnf-index': <'index'>}
+        or formatted as [{}] if unsatisfiable model
+        """
+        solns = pymzn.minizinc(mzn_model, data=mzn_model_data, output_mode='item')
+
+        if 'UNSATISFIABLE' in str(solns):
+            return [{}]
+
+        solns_as_str = str(solns[0])
+
+        # make it easier to extract the desired information by cleaning from newline, whitespace etc.
+        solns_as_str = solns_as_str.replace('\n', '').replace(' ', '').rstrip(';')
+
+        vnf_vim_mapping = (e.split('=') for e in solns_as_str.split(';'))
+
+        res = [{'vimAccountId': e[1][3:].replace('_', '-'), 'member-vnf-index': e[0][3:]} for e in
+               vnf_vim_mapping]
+        # add any pinned VNFs
+        pinned = [{'vimAccountId': e['vim_account'][3:].replace('_', '-'), 'member-vnf-index': e['vnf_id']} for e in
+                  ns_desc if 'vim_account' in e.keys()]
+
+        return res + pinned
+
+    def do_placement_computation(self, nspd):
+        """
+        Orchestrates the placement computation
+
+        :param nspd: placement data
+        :return: see _run_placement_model
+        """
+        mzn_model = MznModelGenerator(self.log).create_model(nspd)
+        return self._run_placement_model(mzn_model, nspd['ns_desc'])
+
+
+class MznModelGenerator(object):
+    '''
+    Has the capability to generate minizinc models from information contained in
+    NsPlacementData objects. Uses jinja2 as templating language for the model
+    '''
+    default_j2_template = "osm_pla_dynamic_template.j2"
+    template_search_path = ['osm_pla/placement', '../placement', '/pla/osm_pla/placement']
+
+    def __init__(self, log):
+        '''
+        Constructor
+        '''
+        self.log = log  # FIXME we do not log anything so far
+
+    def create_model(self, ns_placement_data):
+        '''
+        Creates a minizinc model according to the content of nspd
+        nspd - NSPlacementData
+        return MZNModel
+        '''
+        self.log.info('ns_desc: {}'.format(ns_placement_data['ns_desc']))
+        self.log.info('vld_desc: {}'.format(ns_placement_data['vld_desc']))
+        mzn_model_template = self._load_jinja_template()
+        mzn_model = mzn_model_template.render(ns_placement_data)
+        self.log.info('Minizinc model: {}'.format(mzn_model))
+        return mzn_model
+
+    def _load_jinja_template(self, template_name=default_j2_template):
+        """loads the jinja template used for model generation"""
+        env = Environment(loader=FileSystemLoader(MznModelGenerator.template_search_path))
+        return env.get_template(template_name)
+
+
+class NsPlacementDataFactory(object):
+    """
+    process information an network service and applicable network infrastructure resources in order to produce
+    information tailored for the minizinc model code generator
+    """
+
+    def __init__(self, vim_accounts_info, vnf_prices, nsd, pil_info, pinning=None, order_constraints=None):
+        """
+        :param vim_accounts_info: a dictionary with vim url as key and id as value, we add a unique index to it for use
+        in the mzn array constructs and adjust the value of the id to minizinc acceptable identifier syntax
+        :param vnf_prices: a dictionary with 'vnfd-id-ref' as key and a dictionary with vim_urls: cost as value
+        :param nsd: the network service descriptor
+        :param pil_info: price list and metrics for PoP interconnection links
+        :param pinning: list of {'member-vnf-index': '<idx>', 'vim_account': '<vim-account>'}
+        :param order_constraints: any constraints provided at instantiation time
+        """
+        next_idx = itertools.count()
+        self._vim_accounts_info = {k: {'id': 'vim' + v.replace('-', '_'), 'idx': next(next_idx)} for k, v in
+                                   vim_accounts_info.items()}
+        self._vnf_prices = vnf_prices
+        self._nsd = nsd
+        self._pil_info = pil_info
+        self._pinning = pinning
+        self._order_constraints = order_constraints
+
+    def _produce_trp_link_characteristics_data(self, characteristics):
+        """
+        :param characteristics: one of  {pil_latency, pil_price, pil_jitter}
+        :return: 2d array of requested trp_link characteristics data
+        """
+        if characteristics not in {'pil_latency', 'pil_price', 'pil_jitter'}:
+            raise Exception('characteristic \'{}\' not supported'.format(characteristics))
+        num_vims = len(self._vim_accounts_info)
+        trp_link_characteristics = [[0 if col == row else 0x7fff for col in range(num_vims)] for row in range(num_vims)]
+        for pil in self._pil_info['pil']:
+            if characteristics in pil.keys():
+                url1 = pil['pil_endpoints'][0]
+                url2 = pil['pil_endpoints'][1]
+                # only consider links between applicable vims
+                if url1 in self._vim_accounts_info and url2 in self._vim_accounts_info:
+                    idx1 = self._vim_accounts_info[url1]['idx']
+                    idx2 = self._vim_accounts_info[url2]['idx']
+                    trp_link_characteristics[idx1][idx2] = pil[characteristics]
+                    trp_link_characteristics[idx2][idx1] = pil[characteristics]
+
+        return trp_link_characteristics
+
+    def _produce_vld_desc(self):
+        """
+        Creates the expected vlds from the nsd. Includes constraints if part of nsd.
+        Overrides constraints with any syntactically correct instantiation parameters
+        :return:
+        """
+        vld_desc = []
+        for vld in self._nsd['vld']:
+            if vld['mgmt-network'] is False:
+                vld_desc_entry = {}
+                cp_refs = [ep_ref['member-vnf-index-ref'] for ep_ref in vld['vnfd-connection-point-ref']]
+                vld_desc_entry['cp_refs'] = cp_refs
+                if 'link-constraint' in vld.keys():
+                    for constraint in vld['link-constraint']:
+                        if constraint['constraint-type'] == 'LATENCY':
+                            vld_desc_entry['latency'] = constraint['value']
+                        elif constraint['constraint-type'] == 'JITTER':
+                            vld_desc_entry['jitter'] = constraint['value']
+                vld_desc.append(vld_desc_entry)
+
+        # create candidates from instantiate params
+        if self._order_constraints is not None:
+            candidate_vld_desc = []
+            # use id to find the endpoints in the nsd
+            for entry in self._order_constraints.get('vld-constraints'):
+                for vld in self._nsd['vld']:
+                    if entry['id'] == vld['id']:
+                        vld_desc_instantiate_entry = {}
+                        cp_refs = [ep_ref['member-vnf-index-ref'] for ep_ref in vld['vnfd-connection-point-ref']]
+                        vld_desc_instantiate_entry['cp_refs'] = cp_refs
+                        # add whatever constraints that are provided to the vld_desc_entry
+                        # misspelled 'link-constraints' => empty dict
+                        # lack (or misspelling) of one or both supported constraints => entry not appended
+                        for constraint, value in entry.get('link-constraints', {}).items():
+                            if constraint == 'latency':
+                                vld_desc_instantiate_entry['latency'] = value
+                            elif constraint == 'jitter':
+                                vld_desc_instantiate_entry['jitter'] = value
+                        if set(['latency', 'jitter']).intersection(vld_desc_instantiate_entry.keys()):
+                            candidate_vld_desc.append(vld_desc_instantiate_entry)
+            # merge with nsd originated, FIXME log any deviations?
+            for vld_d in vld_desc:
+                for vld_d_i in candidate_vld_desc:
+                    if set(vld_d['cp_refs']) == set(vld_d_i['cp_refs']):
+                        if vld_d_i.get('jitter'):
+                            vld_d['jitter'] = vld_d_i['jitter']
+                        if vld_d_i.get('latency'):
+                            vld_d['latency'] = vld_d_i['latency']
+
+        return vld_desc
+
+    def _produce_ns_desc(self):
+        """
+        collect information for the ns_desc part of the placement data
+        for the vim_accounts that are applicable, collect the vnf_price
+        """
+        ns_desc = []
+        for vnfd in self._nsd['constituent-vnfd']:
+            vnf_info = {'vnf_id': vnfd['member-vnf-index']}
+            # prices
+            prices_for_vnfd = self._vnf_prices[vnfd['vnfd-id-ref']]
+            # the list of prices must be ordered according to the indexing of the vim_accounts
+            price_list = [_ for _ in range(len(self._vim_accounts_info))]
+            for k in prices_for_vnfd.keys():
+                if k in self._vim_accounts_info.keys():
+                    price_list[self._vim_accounts_info[k]['idx']] = prices_for_vnfd[k]
+            vnf_info['vnf_price_per_vim'] = price_list
+
+            # pinning to dc
+            if self._pinning is not None:
+                for pinned_vnf in self._pinning:
+                    if vnfd['member-vnf-index'] == pinned_vnf['member-vnf-index']:
+                        vnf_info['vim_account'] = 'vim' + pinned_vnf['vimAccountId'].replace('-', '_')
+
+            ns_desc.append(vnf_info)
+        return ns_desc
+
+    def create_ns_placement_data(self):
+        """populate NsPlacmentData object
+        """
+        ns_placement_data = {'vim_accounts': [vim_data['id'] for
+                                              vim_data in self._vim_accounts_info.values()],
+                             'trp_link_latency': self._produce_trp_link_characteristics_data('pil_latency'),
+                             'trp_link_jitter': self._produce_trp_link_characteristics_data('pil_jitter'),
+                             'trp_link_price_list': self._produce_trp_link_characteristics_data('pil_price'),
+                             'ns_desc': self._produce_ns_desc(),
+                             'vld_desc': self._produce_vld_desc(),
+                             'generator_data': {'file': __file__, 'time': datetime.datetime.now()}}
+
+        return ns_placement_data
diff --git a/osm_pla/placement/osm_pla_dynamic_template.j2 b/osm_pla/placement/osm_pla_dynamic_template.j2
new file mode 100644 (file)
index 0000000..e95be0c
--- /dev/null
@@ -0,0 +1,40 @@
+% Copyright 2020 ArctosLabs Scandinavia AB
+%
+% 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 minizinc model is generated using {{generator_data.file}}
+% at {{generator_data.time}}.
+
+{% import 'macros.j2' as macros -%}
+%This is the NETWORK RESOURCE MODEL
+{{ macros.vim_accounts(vim_accounts) }}
+{{ macros.trp_link_latency(trp_link_latency) }}
+{{ macros.trp_link_jitter(trp_link_jitter) }}
+{{ macros.trp_link_price_list(trp_link_price_list) }}
+{{ macros.vnf_price_list_per_vim(ns_desc) }}
+
+% This is the NETWORK BASIC LOAD MODEL (CONSUMED)
+% NOTE. This is not applicable in OSM Release 7
+
+% This is the SERVICE CONSUMPTION MODEL
+% These are the variables, i.e. which DC to select for each VNF
+{{ macros.variables_vnf(ns_desc)}}
+
+% These are the set of rules for selecting DCs to VNFs
+{{ macros.vld_constraints(vld_desc) }}
+% Calculate the cost for VNFs and cost for transport link and total cost
+{{ macros.transport_cost(vld_desc) }}
+{{ macros.used_vim_cost(ns_desc) }}
+var int: total_cost = used_transport_cost + used_vim_cost;
+
+solve minimize total_cost;
\ No newline at end of file
diff --git a/osm_pla/server/server.py b/osm_pla/server/server.py
new file mode 100644 (file)
index 0000000..8d25879
--- /dev/null
@@ -0,0 +1,176 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+
+import asyncio
+import logging
+# import platform
+from pathlib import Path
+
+# import pkg_resources
+import yaml
+from osm_common import dbmemory, dbmongo, msglocal, msgkafka
+
+from osm_pla.config.config import Config
+from osm_pla.placement.mznplacement import MznPlacementConductor
+from osm_pla.placement.mznplacement import NsPlacementDataFactory
+
+
+class Server:
+    pil_price_list_file = Path('/placement/pil_price_list.yaml')
+    vnf_price_list_file = Path('/placement/vnf_price_list.yaml')
+
+    def __init__(self, config: Config, loop=None):
+        self.log = logging.getLogger("pla.server")
+        self.db = None
+        self.msgBus = None
+        self.config = config
+        self.loop = loop or asyncio.get_event_loop()
+
+        try:
+            if config.get('database', 'driver') == "mongo":
+                self.db = dbmongo.DbMongo()
+                self.db.db_connect(config.get('database'))
+            elif config.get('database', 'driver') == "memory":
+                self.db = dbmemory.DbMemory()
+                self.db.db_connect(config.get('database'))
+            else:
+                raise Exception("Invalid configuration param '{}' at '[database]':'driver'".format(
+                    config.get('database', 'driver')))
+
+            if config.get('message', 'driver') == "local":
+                self.msgBus = msglocal.MsgLocal()
+            elif config.get('message', 'driver') == "kafka":
+                self.msgBus = msgkafka.MsgKafka()
+            else:
+                raise Exception("Invalid message bus driver {}".format(
+                    config.get('message', 'driver')))
+            self.msgBus.loop = loop
+            self.msgBus.connect(config.get('message'))
+
+        except Exception as e:
+            self.log.exception("kafka setup error. Exception: {}".format(e))
+
+    def _get_nslcmop(self, nsdlcmop_id):
+        """
+        :param nsdlcmop_id:
+        :return: nslcmop from database corresponding to nslcmop_id
+        """
+        db_filter = {"_id": nsdlcmop_id}
+        nslcmop = self.db.get_one("nslcmops", db_filter)
+        return nslcmop
+
+    def _get_nsd(self, nsd_id):
+        """
+        :param nsd_id:
+        :return: nsd from database corresponding to nsd_id
+        """
+        db_filter = {"_id": nsd_id}
+        return self.db.get_one("nsds", db_filter)
+
+    def _get_vim_accounts(self, vim_account_ids):
+        """
+        :param vim_account_ids: list of VIM account ids
+        :return: list of vim account entries from database corresponding to list in vim_accounts_id
+        """
+        db_filter = {"_id": vim_account_ids}
+        return self.db.get_list("vim_accounts", db_filter)
+
+    def _get_vnf_price_list(self, price_list_file_path):
+        """
+        read vnf price list configuration file and reformat its content
+
+        :param: price_list_file: Path to price list file
+        :return: dictionary formatted as {'<vnfd>': {'<vim-url>':'<price>'}}
+        """
+        with open(str(price_list_file_path)) as pl_fd:
+            price_list_data = yaml.safe_load_all(pl_fd)
+            return {i['vnfd']: {i1['vim_url']: i1['price'] for i1 in i['prices']} for i in next(price_list_data)}
+
+    def _get_pil_info(self, pil_info_file_path):
+        """
+        read and return pil information from file
+        :param pil_info_file_path: Path to pil_info file
+        :return pil configuration file content as Python object
+        """
+        with open(str(pil_info_file_path)) as pil_fd:
+            data = yaml.safe_load_all(pil_fd)
+            return next(data)
+
+    async def get_placement(self, nslcmop_id):
+        """
+        - Collects and prepares placement information.
+        - Request placement computation.
+        - Formats and distribute placement result
+
+        Note: exceptions result in empty response message
+
+        :param nslcmop_id:
+        :return:
+        """
+        try:
+            nslcmop = self._get_nslcmop(nslcmop_id)
+            nsd = self._get_nsd(nslcmop['operationParams']['nsdId'])
+            self.log.info("nsd: {}".format(nsd))
+            valid_vim_accounts = nslcmop['operationParams']['validVimAccounts']
+            vim_accounts_data = self._get_vim_accounts(valid_vim_accounts)
+            vims_information = {_['vim_url']: _['_id'] for _ in vim_accounts_data}
+            price_list = self._get_vnf_price_list(Server.vnf_price_list_file)
+            pil_info = self._get_pil_info(Server.pil_price_list_file)
+            pinning = nslcmop['operationParams'].get('vnf')
+            self.log.info("pinning: {}".format(pinning))
+            order_constraints = nslcmop['operationParams'].get('placement-constraints')
+            self.log.info("order constraints: {}".format(order_constraints))
+
+            nspd = NsPlacementDataFactory(vims_information,
+                                          price_list,
+                                          nsd,
+                                          pil_info,
+                                          pinning, order_constraints).create_ns_placement_data()
+
+            vnf_placement = MznPlacementConductor(self.log).do_placement_computation(nspd)
+
+        except Exception as e:
+            # Note: there is no cure for failure so we have a catch-all clause here
+            self.log.exception("PLA fault. Exception: {}".format(e))
+            vnf_placement = []
+        finally:
+            await self.msgBus.aiowrite("pla", "placement",
+                                       {'placement': {'vnf': vnf_placement, 'nslcmopId': nslcmop_id}})
+
+    def handle_kafka_command(self, topic, command, params):
+        self.log.info("Kafka msg arrived: {} {} {}".format(topic, command, params))
+        if topic == "pla" and command == "get_placement":
+            nslcmop_id = params.get('nslcmopId')
+            self.loop.create_task(self.get_placement(nslcmop_id))
+
+    async def kafka_read(self):
+        self.log.info("Task kafka_read start")
+        while True:
+            try:
+                topics = "pla"
+                await self.msgBus.aioread(topics, self.loop, self.handle_kafka_command)
+            except Exception as e:
+                self.log.error("kafka read error. Exception: {}".format(e))
+                await asyncio.sleep(5, loop=self.loop)
+
+    def run(self):
+        self.loop.run_until_complete(self.kafka_read())
+        self.loop.close()
+        self.loop = None
+        if self.msgBus:
+            self.msgBus.disconnect()
diff --git a/osm_pla/test/__init__.py b/osm_pla/test/__init__.py
new file mode 100644 (file)
index 0000000..968ffb4
--- /dev/null
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-\r
+\r
+# Copyright 2019 ArctosLabs Scandinavia AB\r
+#\r
+# Licensed under the Apache License, Version 2.0 (the "License");\r
+# you may not use this file except in compliance with the License.\r
+# You may obtain a copy of the License at\r
+#\r
+#    http://www.apache.org/licenses/LICENSE-2.0\r
+#\r
+# Unless required by applicable law or agreed to in writing, software\r
+# distributed under the License is distributed on an "AS IS" BASIS,\r
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\r
+# implied.\r
+# See the License for the specific language governing permissions and\r
+# limitations under the License.\r
+##\r
diff --git a/osm_pla/test/corrupt_pil_endpoints_config_unittest1.yaml b/osm_pla/test/corrupt_pil_endpoints_config_unittest1.yaml
new file mode 100644 (file)
index 0000000..1e53b37
--- /dev/null
@@ -0,0 +1,94 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+
+# Point Of Precence (POP), price list
+pop:
+  - vim_url: http://10.234.12.47:5000/v3
+    vim_name: OpenStack1
+    num_vm: 10
+    vm_price:
+      - x_large: 10
+      - large: 5
+      - medium: 4
+      - small: 3
+      - tiny: 2
+  - vim_url: http://10.234.12.44:5000/v3
+    vim_name: OpenStack2
+    num_vm: 10
+    vm_price:
+      - large: 10
+      - medium: 8
+      - small: 6
+      - tiny: 4
+  - vim_url: http://10.234.12.46:5000/v3
+    vim_name: OpenStack3
+    num_vm: 10
+    vm_price:
+      - large: 8
+      - medium: 6
+      - small: 3
+      - tiny: 2
+  - vim_url: http://10.234.12.43:5000/v3
+    vim_name: OpenStack4
+    num_vm: 10
+    vm_price:
+      - large: 9
+      - medium: 7
+      - small: 4
+      - tiny: 3
+# POP Interconnecting Link (PIL), price list and latency
+pil:
+  - pil_description: Link between OpenStack1 and OpenStack2
+    pil_price: 12
+    pil_latency: 120
+    pil_jitter: 1200
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.44:5000/v3
+  - pil_description: Link between OpenStack1 and OpenStack3
+    pil_price: 13
+    pil_latency: 130
+    pil_jitter: 1300
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.46:5000/v3
+  - pil_description: Link between OpenStack1 and OpenStack4
+    pil_price: 14
+    pil_latency: 140
+    pil_jitter: 1400
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.43:5000/v3
+  - pil_description: Link between OpenStack2 and OpenStack3
+    pil_price: 23
+    pil_latency: 230
+    pil_jitter: 2300
+    pil_endpoints:
+      - http://10.234.12.44:5000/v3
+      - http://10.234.12.46:5000/v3
+  - pil_description: Link between OpenStack2 and OpenStack4
+    pil_price: 24
+    pil_latency: 240
+    pil_jitter: 2400
+    pil_endpoints:
+      - http://10.234.12.44:5000/v3
+      - http://10.234.12.43:5000/v3
+  - pil_description: Link between OpenStack3 and OpenStack4
+    pil_price: 34
+    pil_latency: 340
+    pil_jitter: 3400
+    pil_endpoints:
+#      - http://10.234.12.46:5000/v3
+      - http://10.234.12.43:5000/v3
\ No newline at end of file
diff --git a/osm_pla/test/not_yaml_conformant.yaml b/osm_pla/test/not_yaml_conformant.yaml
new file mode 100644 (file)
index 0000000..90e3679
--- /dev/null
@@ -0,0 +1,94 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+
+# Point Of Precence (POP), price list
+pop
+  - vim_url: http://10.234.12.47:5000/v3
+    vim_name: OpenStack1
+    num_vm: 10
+    vm_price:
+      - x_large: 10
+      - large: 5
+      - medium: 4
+      - small: 3
+      - tiny: 2
+  - vim_url: http://10.234.12.44:5000/v3
+    vim_name: OpenStack2
+    num_vm: 10
+    vm_price:
+      - large: 10
+      - medium: 8
+      - small: 6
+      - tiny: 4
+  - vim_url: http://10.234.12.46:5000/v3
+    vim_name: OpenStack3
+    num_vm: 10
+    vm_price:
+      - large: 8
+      - medium: 6
+      - small: 3
+      - tiny: 2
+  - vim_url: http://10.234.12.43:5000/v3
+    vim_name: OpenStack4
+    num_vm: 10
+    vm_price:
+      - large: 9
+      - medium: 7
+      - small: 4
+      - tiny: 3
+# POP Interconnecting Link (PIL), price list and latency
+pil:
+  - pil_description: Link between OpenStack1 and OpenStack2
+    pil_price: 12
+    pil_latency: 120
+    pil_jitter: 1200
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.44:5000/v3
+  - pil_description: Link between OpenStack1 and OpenStack3
+    pil_price: 13
+    pil_latency: 130
+    pil_jitter: 1300
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.46:5000/v3
+  - pil_description: Link between OpenStack1 and OpenStack4
+    pil_price: 14
+    pil_latency: 140
+    pil_jitter: 1400
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.43:5000/v3
+  - pil_description: Link between OpenStack2 and OpenStack3
+    pil_price: 23
+    pil_latency: 230
+    pil_jitter: 2300
+    pil_endpoints:
+      - http://10.234.12.44:5000/v3
+      - http://10.234.12.46:5000/v3
+  - pil_description: Link between OpenStack2 and OpenStack4
+    pil_price: 24
+    pil_latency: 240
+    pil_jitter: 2400
+    pil_endpoints:
+      - http://10.234.12.44:5000/v3
+      - http://10.234.12.43:5000/v3
+  - pil_description: Link between OpenStack3 and OpenStack4
+    pil_price: 34
+    pil_latency: 340
+    pil_jitter: 3400
+    pil_endpoints:
+      - http://10.234.12.46:5000/v3
+      - http://10.234.12.43:5000/v3
diff --git a/osm_pla/test/nsd_unittest1.yaml b/osm_pla/test/nsd_unittest1.yaml
new file mode 100644 (file)
index 0000000..b4832a7
--- /dev/null
@@ -0,0 +1,66 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+nsd:nsd-catalog:
+    nsd:
+    -   constituent-vnfd:
+        -   member-vnf-index: 1
+            vnfd-id-ref: cirros_vnfd_v2
+        -   member-vnf-index: 2
+            vnfd-id-ref: cirros_vnfd_v2
+        -   member-vnf-index: 3
+            vnfd-id-ref: cirros_vnfd_v2
+        description: Placement constraints NSD
+        id: three_vnf_constrained_nsd
+        name: three_vnf_constrained_nsd
+        short-name: three_vnf_constrained_nsd
+        vendor: ArctosLabs
+        version: '1.0'
+        vld:
+        -   id: three_vnf_constrained_nsd_vld1
+            link-constraint:
+            -   constraint-type: LATENCY
+                value: 150
+            -   constraint-type: JITTER
+                value: 30
+            mgmt-network: !!bool False
+            name: ns_constrained_nsd_vld1
+            short-name: ns_constrained_nsd_vld1
+            type: ELAN
+            vim-network-name: private
+            vnfd-connection-point-ref:
+            -   member-vnf-index-ref: 1
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
+            -   member-vnf-index-ref: 2
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
+        -   id: three_vnf_constrained_nsd_vld2
+            link-constraint:
+            -   constraint-type: LATENCY
+                value: 90
+            -   constraint-type: JITTER
+                value: 30
+            mgmt-network: !!bool False
+            name: ns_constrained_nsd_vld2
+            short-name: ns_constrained_nsd_vld2
+            type: ELAN
+            vim-network-name: private
+            vnfd-connection-point-ref:
+            -   member-vnf-index-ref: 2
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
+            -   member-vnf-index-ref: 3
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
\ No newline at end of file
diff --git a/osm_pla/test/nsd_unittest2.yaml b/osm_pla/test/nsd_unittest2.yaml
new file mode 100644 (file)
index 0000000..be54166
--- /dev/null
@@ -0,0 +1,62 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+nsd:nsd-catalog:
+    nsd:
+    -   constituent-vnfd:
+        -   member-vnf-index: one
+            vnfd-id-ref: cirros_vnfd_v2
+        -   member-vnf-index: two
+            vnfd-id-ref: cirros_vnfd_v2
+        -   member-vnf-index: three
+            vnfd-id-ref: cirros_vnfd_v2
+        description: Placement no constraints NSD
+        id: three_vnf_no_constrained_nsd
+        name: three_vnf_no_constrained_nsd
+        short-name: three_vnf_no_constrained_nsd
+        vendor: ArctosLabs
+        version: '1.0'
+        vld:
+        -   id: three_vnf_no_constrained_nsd_vld1
+            link-constraint:
+                - constraint-type: JITTER
+                  value: 30
+            mgmt-network: !!bool False
+            name: ns_no_constrained_nsd_vld1
+            short-name: ns_no_constrained_nsd_vld1
+            type: ELAN
+            vim-network-name: private
+            vnfd-connection-point-ref:
+            -   member-vnf-index-ref: one
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
+            -   member-vnf-index-ref: two
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
+        -   id: three_vnf_no_constrained_nsd_vld2
+            link-constraint:
+                - constraint-type: LATENCY
+                  value: 120
+            mgmt-network: !!bool False
+            name: ns_no_constrained_nsd_vld2
+            short-name: ns_no_constrained_nsd_vld2
+            type: ELAN
+            vim-network-name: private
+            vnfd-connection-point-ref:
+            -   member-vnf-index-ref: two
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
+            -   member-vnf-index-ref: three
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
\ No newline at end of file
diff --git a/osm_pla/test/nsd_unittest3.yaml b/osm_pla/test/nsd_unittest3.yaml
new file mode 100644 (file)
index 0000000..c66df82
--- /dev/null
@@ -0,0 +1,66 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+nsd:nsd-catalog:
+    nsd:
+    -   constituent-vnfd:
+        -   member-vnf-index: one
+            vnfd-id-ref: cirros_vnfd_v2
+        -   member-vnf-index: two
+            vnfd-id-ref: cirros_vnfd_v2
+        -   member-vnf-index: three
+            vnfd-id-ref: cirros_vnfd_v2
+        description: Placement constraints NSD
+        id: three_vnf_constrained_nsd
+        name: three_vnf_constrained_nsd
+        short-name: three_vnf_constrained_nsd
+        vendor: ArctosLabs
+        version: '1.0'
+        vld:
+        -   id: three_vnf_constrained_nsd_vld1
+            link-constraint:
+            -   constraint-type: LATENCY
+                value: 150
+            -   constraint-type: JITTER
+                value: 30
+            mgmt-network: !!bool False
+            name: ns_constrained_nsd_vld1
+            short-name: ns_constrained_nsd_vld1
+            type: ELAN
+            vim-network-name: private
+            vnfd-connection-point-ref:
+            -   member-vnf-index-ref: one
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
+            -   member-vnf-index-ref: two
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
+        -   id: three_vnf_constrained_nsd_vld2
+            link-constraint:
+            -   constraint-type: LATENCY
+                value: 90
+            -   constraint-type: JITTER
+                value: 30
+            mgmt-network: !!bool False
+            name: ns_constrained_nsd_vld2
+            short-name: ns_constrained_nsd_vld2
+            type: ELAN
+            vim-network-name: private
+            vnfd-connection-point-ref:
+            -   member-vnf-index-ref: two
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
+            -   member-vnf-index-ref: three
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
\ No newline at end of file
diff --git a/osm_pla/test/nsd_unittest4.yaml b/osm_pla/test/nsd_unittest4.yaml
new file mode 100644 (file)
index 0000000..68d8f7e
--- /dev/null
@@ -0,0 +1,35 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+nsd:nsd-catalog:
+    nsd:
+    -   constituent-vnfd:
+        -   member-vnf-index: 1
+            vnfd-id-ref: hackfest-basic_vnfd
+        description: Generated by OSM package generator
+        id: hackfest-basic_nsd
+        name: hackfest-basic_nsd
+        short-name: hackfest-basic_nsd
+        vendor: Abubakr Magzoub, Lancaster University
+        version: '1.0'
+        vld:
+        -   id: hackfest-basic_nsd_vld0
+            mgmt-network: !!bool True
+            name: management
+            short-name: management
+            type: ELAN
+            vnfd-connection-point-ref:
+            -   member-vnf-index-ref: 1
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: hackfest-basic_vnfd
diff --git a/osm_pla/test/nsd_unittest_no_vld_constraints.yaml b/osm_pla/test/nsd_unittest_no_vld_constraints.yaml
new file mode 100644 (file)
index 0000000..7a440f4
--- /dev/null
@@ -0,0 +1,56 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+nsd:nsd-catalog:
+    nsd:
+    -   constituent-vnfd:
+        -   member-vnf-index: one
+            vnfd-id-ref: cirros_vnfd_v2
+        -   member-vnf-index: two
+            vnfd-id-ref: cirros_vnfd_v2
+        -   member-vnf-index: three
+            vnfd-id-ref: cirros_vnfd_v2
+        description: Placement constraints NSD
+        id: three_vnf_constrained_nsd
+        name: three_vnf_constrained_nsd
+        short-name: three_vnf_constrained_nsd
+        vendor: ArctosLabs
+        version: '1.0'
+        vld:
+        -   id: three_vnf_constrained_nsd_vld1
+            mgmt-network: !!bool False
+            name: ns_constrained_nsd_vld1
+            short-name: ns_constrained_nsd_vld1
+            type: ELAN
+            vim-network-name: private
+            vnfd-connection-point-ref:
+            -   member-vnf-index-ref: one
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
+            -   member-vnf-index-ref: two
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
+        -   id: three_vnf_constrained_nsd_vld2
+            mgmt-network: !!bool False
+            name: ns_constrained_nsd_vld2
+            short-name: ns_constrained_nsd_vld2
+            type: ELAN
+            vim-network-name: private
+            vnfd-connection-point-ref:
+            -   member-vnf-index-ref: two
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
+            -   member-vnf-index-ref: three
+                vnfd-connection-point-ref: vnf-cp0
+                vnfd-id-ref: cirros_vnfd_v2
\ No newline at end of file
diff --git a/osm_pla/test/pil_price_list.yaml b/osm_pla/test/pil_price_list.yaml
new file mode 100644 (file)
index 0000000..45f7577
--- /dev/null
@@ -0,0 +1,60 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+
+# POP Interconnecting Link (PIL), price list and latency
+pil:
+  - pil_description: Link between OpenStack1 and OpenStack2
+    pil_price: 5
+    pil_latency: 30
+    pil_jitter: 5
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.44:5000/v3
+  - pil_description: Link between OpenStack1 and OpenStack3
+    pil_price: 5
+    pil_latency: 70
+    pil_jitter: 5
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.46:5000/v3
+  - pil_description: Link between OpenStack1 and OpenStack4
+    pil_price: 10
+    pil_latency: 80
+    pil_jitter: 10
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.43:5000/v3
+  - pil_description: Link between OpenStack2 and OpenStack3
+    pil_price: 5
+    pil_latency: 75
+    pil_jitter: 5
+    pil_endpoints:
+      - http://10.234.12.44:5000/v3
+      - http://10.234.12.46:5000/v3
+  - pil_description: Link between OpenStack2 and OpenStack4
+    pil_price: 10
+    pil_latency: 60
+    pil_jitter: 10
+    pil_endpoints:
+      - http://10.234.12.44:5000/v3
+      - http://10.234.12.43:5000/v3
+  - pil_description: Link between OpenStack3 and OpenStack4
+    pil_price: 10
+    pil_latency: 40
+    pil_jitter: 10
+    pil_endpoints:
+      - http://10.234.12.46:5000/v3
+      - http://10.234.12.43:5000/v3
+      
diff --git a/osm_pla/test/pil_price_list_rel7_webinar.yaml b/osm_pla/test/pil_price_list_rel7_webinar.yaml
new file mode 100644 (file)
index 0000000..24d492f
--- /dev/null
@@ -0,0 +1,47 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+
+# POP Interconnecting Link (PIL), price list and latency
+pil:
+  - pil_description: Link between OpenStack1 and OpenStack2
+    pil_price: 5
+    pil_latency: 20
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.44:5000/v3
+  - pil_description: Link between OpenStack1 and OpenStack3
+    pil_price: 30
+    pil_latency: 30
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.46:5000/v3
+  - pil_description: Link between OpenStack1 and OpenStack4
+    pil_price: 30
+    pil_latency: 30
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.43:5000/v3
+  - pil_description: Link between OpenStack2 and OpenStack3
+    pil_price: 10
+    pil_latency: 10
+    pil_endpoints:
+      - http://10.234.12.44:5000/v3
+      - http://10.234.12.46:5000/v3
+  - pil_description: Link between OpenStack2 and OpenStack4
+    pil_price: 10
+    pil_latency: 10
+    pil_endpoints:
+      - http://10.234.12.44:5000/v3
+      - http://10.234.12.43:5000/v3
diff --git a/osm_pla/test/pil_unittest1.yaml b/osm_pla/test/pil_unittest1.yaml
new file mode 100644 (file)
index 0000000..c1e228c
--- /dev/null
@@ -0,0 +1,59 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+
+# POP Interconnecting Link (PIL), price list and latency
+pil:
+  - pil_description: Link between OpenStack1 and OpenStack2
+    pil_price: 12
+    pil_latency: 120
+    pil_jitter: 1200
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.44:5000/v3
+  - pil_description: Link between OpenStack1 and OpenStack3
+    pil_price: 13
+    pil_latency: 130
+    pil_jitter: 1300
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.46:5000/v3
+  - pil_description: Link between OpenStack1 and OpenStack4
+    pil_price: 14
+    pil_latency: 140
+    pil_jitter: 1400
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.43:5000/v3
+  - pil_description: Link between OpenStack2 and OpenStack3
+    pil_price: 23
+    pil_latency: 230
+    pil_jitter: 2300
+    pil_endpoints:
+      - http://10.234.12.44:5000/v3
+      - http://10.234.12.46:5000/v3
+  - pil_description: Link between OpenStack2 and OpenStack4
+    pil_price: 24
+    pil_latency: 240
+    pil_jitter: 2400
+    pil_endpoints:
+      - http://10.234.12.44:5000/v3
+      - http://10.234.12.43:5000/v3
+  - pil_description: Link between OpenStack3 and OpenStack4
+    pil_price: 34
+    pil_latency: 340
+    pil_jitter: 3400
+    pil_endpoints:
+      - http://10.234.12.46:5000/v3
+      - http://10.234.12.43:5000/v3
diff --git a/osm_pla/test/pil_unittest2.yaml b/osm_pla/test/pil_unittest2.yaml
new file mode 100644 (file)
index 0000000..a3165a8
--- /dev/null
@@ -0,0 +1,55 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+
+# POP Interconnecting Link (PIL), price list and latency
+pil:
+  - pil_description: Link between OpenStack1 and OpenStack2
+    pil_price: 12
+    pil_latency: 120
+    pil_jitter: 1200
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.44:5000/v3
+  - pil_description: Link between OpenStack1 and OpenStack3
+    pil_price: 13
+    pil_latency: 130
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.46:5000/v3
+  - pil_description: Link between OpenStack1 and OpenStack4
+    pil_price: 14
+    pil_jitter: 1400
+    pil_endpoints:
+      - http://10.234.12.47:5000/v3
+      - http://10.234.12.43:5000/v3
+  - pil_description: Link between OpenStack2 and OpenStack3
+    pil_price: 23
+    pil_endpoints:
+      - http://10.234.12.44:5000/v3
+      - http://10.234.12.46:5000/v3
+  - pil_description: Link between OpenStack2 and OpenStack4
+    pil_price: 24
+    pil_latency: 240
+    pil_jitter: 2400
+    pil_endpoints:
+      - http://10.234.12.44:5000/v3
+      - http://10.234.12.43:5000/v3
+  - pil_description: Link between OpenStack3 and OpenStack4
+    pil_price: 34
+    pil_latency: 340
+    pil_jitter: 3400
+    pil_endpoints:
+      - http://10.234.12.46:5000/v3
+      - http://10.234.12.43:5000/v3
diff --git a/osm_pla/test/test_five_nsd.yaml b/osm_pla/test/test_five_nsd.yaml
new file mode 100644 (file)
index 0000000..0a209d1
--- /dev/null
@@ -0,0 +1,104 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+nsd:nsd-catalog:
+  nsd:
+  - description: Four cirros VNF latency and jitter constrained
+    id: test_five_nsd
+    name: test_five_nsd
+    short-name: test_five_nsd
+    vendor: ArctosLabs
+    version: '1.0'
+    constituent-vnfd:
+    - member-vnf-index: 1
+      vnfd-id-ref: test_one_a_vnfd
+    - member-vnf-index: 2
+      vnfd-id-ref: test_two_vnfd
+    - member-vnf-index: 3
+      vnfd-id-ref: test_one_a_vnfd
+    - member-vnf-index: 4
+      vnfd-id-ref: test_one_a_vnfd
+    vld:
+    - name: vl_two_vld
+      id: vl_two_vld
+      mgmt-network: !!bool False
+      type: ELAN
+      link-constraint:
+      - constraint-type: LATENCY
+        value: 120
+      - constraint-type: JITTER
+        value: 20
+      vnfd-connection-point-ref:
+      - member-vnf-index-ref: '1'
+        vnfd-connection-point-ref: vnf_cp_one_cp
+        vnfd-id-ref: test_one_a_vnfd
+      - member-vnf-index-ref: '2'
+        vnfd-connection-point-ref: vnf_cp_one_cp
+        vnfd-id-ref: test_two_vnfd
+    - name: vl_four_vld
+      id: vl_four_vld
+      mgmt-network: !!bool False
+      type: ELAN
+      link-constraint:
+      - constraint-type: LATENCY
+        value: 50
+      - constraint-type: JITTER
+        value: 10
+      vnfd-connection-point-ref:
+      - member-vnf-index-ref: '2'
+        vnfd-connection-point-ref: vnf_cp_three_cp
+        vnfd-id-ref: test_two_vnfd
+      - member-vnf-index-ref: '4'
+        vnfd-connection-point-ref: vnf_cp_one_cp
+        vnfd-id-ref: test_one_a_vnfd
+    - name: vl_five_vld
+      id: vl_five_vld
+      mgmt-network: !!bool False
+      type: ELAN
+      link-constraint:
+      - constraint-type: LATENCY
+        value: 20
+      - constraint-type: JITTER
+        value: 10
+      vnfd-connection-point-ref:
+      - member-vnf-index-ref: '2'
+        vnfd-connection-point-ref: vnf_cp_two_cp
+        vnfd-id-ref: test_two_vnfd
+      - member-vnf-index-ref: '3'
+        vnfd-connection-point-ref: vnf_cp_one_cp
+        vnfd-id-ref: test_one_a_vnfd
+    - name: vld_vnf_mgmt
+      id: vld_vnf_mgmt1
+      mgmt-network: !!bool True
+      type: ELAN
+      vnfd-connection-point-ref:
+      - member-vnf-index-ref: '1'
+        vnfd-connection-point-ref: vnf_cp_two_cp
+        vnfd-id-ref: test_one_a_vnfd
+      - member-vnf-index-ref: '3'
+        vnfd-connection-point-ref: vnf_cp_two_cp
+        vnfd-id-ref: test_one_a_vnfd
+    - name: vld_vnf_mgmt
+      id: vld_vnf_mgmt2
+      mgmt-network: !!bool True
+      type: ELAN
+      vnfd-connection-point-ref:
+      - member-vnf-index-ref: '1'
+        vnfd-connection-point-ref: vnf_cp_two_cp
+        vnfd-id-ref: test_one_a_vnfd
+      - member-vnf-index-ref: '4'
+        vnfd-connection-point-ref: vnf_cp_two_cp
+        vnfd-id-ref: test_one_a_vnfd
+
+
diff --git a/osm_pla/test/test_mznModelGenerator.py b/osm_pla/test/test_mznModelGenerator.py
new file mode 100644 (file)
index 0000000..ed571f4
--- /dev/null
@@ -0,0 +1,701 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+import datetime
+import logging
+# import unittest
+from unittest import TestCase
+# import random
+# from operator import itemgetter
+import re
+
+from jinja2 import Template
+
+from osm_pla.placement.mznplacement import MznModelGenerator
+
+test_ns_placement_data_str = {
+    'vim_accounts': ['vim' + vim_account.replace('-', '_') for vim_account in ['aaaaaaaa-38f5-438d-b8ee-3f93b3531f87',
+                                                                               'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87',
+                                                                               'cccccccc-ed84-4e49-b5df-a9d117bd731f',
+                                                                               'dddddddd-ed84-4e49-b5df-a9d117bd731f',
+                                                                               'eeeeeeee-38f5-438d-b8ee-3f93b3531f87']],
+    'trp_link_latency': [[0, 50, 100, 150, 200], [0, 0, 100, 150, 200], [0, 0, 0, 150, 200], [0, 0, 0, 0, 200],
+                         [0, 0, 0, 0, 0]],
+    'trp_link_jitter': [[0, 5, 10, 15, 20], [0, 0, 10, 15, 20], [0, 0, 0, 15, 20], [0, 0, 0, 0, 20],
+                        [0, 0, 0, 0, 0]],
+    'trp_link_price_list': [[0, 5, 6, 6, 7], [0, 0, 6, 6, 7], [0, 0, 0, 6, 7], [0, 0, 0, 0, 7], [0, 0, 0, 0, 0]],
+    'ns_desc': [
+        {'vnf_id': 'one', 'vnf_price_per_vim': [50, 51, 52, 53, 54]},
+        {'vnf_id': 'two', 'vnf_price_per_vim': [20, 21, 22, 23, 24]},
+        {'vnf_id': 'three', 'vnf_price_per_vim': [70, 71, 72, 73, 74]},
+        {'vnf_id': 'four', 'vnf_price_per_vim': [40, 41, 42, 43, 44]}],
+    'vld_desc': [{'cp_refs': ['one', 'two'], 'latency': 150, 'jitter': 30},
+                 {'cp_refs': ['two', 'three'], 'latency': 140, 'jitter': 30},
+                 {'cp_refs': ['three', 'four'], 'latency': 130, 'jitter': 30}],
+    'generator_data': {'file': __file__, 'time': datetime.datetime.now()}
+}
+
+test_ns_placement_data_str_no_vld_constraints = {
+    'vim_accounts': ['vim' + vim_account.replace('-', '_') for vim_account in ['aaaaaaaa-38f5-438d-b8ee-3f93b3531f87',
+                                                                               'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87',
+                                                                               'cccccccc-ed84-4e49-b5df-a9d117bd731f',
+                                                                               'dddddddd-ed84-4e49-b5df-a9d117bd731f',
+                                                                               'eeeeeeee-38f5-438d-b8ee-3f93b3531f87']],
+    'trp_link_latency': [[0, 50, 100, 150, 200], [0, 0, 100, 150, 200], [0, 0, 0, 150, 200], [0, 0, 0, 0, 200],
+                         [0, 0, 0, 0, 0]],
+    'trp_link_jitter': [[0, 5, 10, 15, 20], [0, 0, 10, 15, 20], [0, 0, 0, 15, 20], [0, 0, 0, 0, 20],
+                        [0, 0, 0, 0, 0]],
+    'trp_link_price_list': [[0, 5, 6, 6, 7], [0, 0, 6, 6, 7], [0, 0, 0, 6, 7], [0, 0, 0, 0, 7], [0, 0, 0, 0, 0]],
+    'ns_desc': [
+        {'vnf_id': 'one', 'vnf_price_per_vim': [50, 51, 52, 53, 54]},
+        {'vnf_id': 'two', 'vnf_price_per_vim': [20, 21, 22, 23, 24]},
+        {'vnf_id': 'three', 'vnf_price_per_vim': [70, 71, 72, 73, 74]},
+        {'vnf_id': 'four', 'vnf_price_per_vim': [40, 41, 42, 43, 44]}],
+    'vld_desc': [{'cp_refs': ['one', 'two']},
+                 {'cp_refs': ['two', 'three']},
+                 {'cp_refs': ['three', 'four']}],
+    'generator_data': {'file': __file__, 'time': datetime.datetime.now()}
+}
+
+test_ns_placement_data = {
+    'vim_accounts': ['vim' + vim_account.replace('-', '_') for vim_account in ['aaaaaaaa-38f5-438d-b8ee-3f93b3531f87',
+                                                                               'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87',
+                                                                               'cccccccc-ed84-4e49-b5df-a9d117bd731f',
+                                                                               'dddddddd-ed84-4e49-b5df-a9d117bd731f',
+                                                                               'eeeeeeee-38f5-438d-b8ee-3f93b3531f87']],
+    'trp_link_latency': [[0, 50, 100, 150, 200], [0, 0, 100, 150, 200], [0, 0, 0, 150, 200], [0, 0, 0, 0, 200],
+                         [0, 0, 0, 0, 0]],
+    'trp_link_jitter': [[0, 5, 10, 15, 20], [0, 0, 10, 15, 20], [0, 0, 0, 15, 20], [0, 0, 0, 0, 20],
+                        [0, 0, 0, 0, 0]],
+    'trp_link_price_list': [[0, 5, 6, 6, 7], [0, 0, 6, 6, 7], [0, 0, 0, 6, 7], [0, 0, 0, 0, 7], [0, 0, 0, 0, 0]],
+    'ns_desc': [
+        {'vnf_id': '1', 'vnf_price_per_vim': [50, 51, 52, 53, 54]},
+        {'vnf_id': '2', 'vnf_price_per_vim': [20, 21, 22, 23, 24]},
+        {'vnf_id': '3', 'vnf_price_per_vim': [70, 71, 72, 73, 74]},
+        {'vnf_id': '4', 'vnf_price_per_vim': [40, 41, 42, 43, 44]}],
+    'vld_desc': [{'cp_refs': ['1', '2'], 'latency': 150, 'jitter': 30},
+                 {'cp_refs': ['2', '3'], 'latency': 140, 'jitter': 30},
+                 {'cp_refs': ['3', '4'], 'latency': 130, 'jitter': 30}],
+    'generator_data': {'file': __file__, 'time': datetime.datetime.now()}
+}
+
+test_ns_placement_data_w_pinning = {
+    'vim_accounts': ['vim' + vim_account.replace('-', '_') for vim_account in ['aaaaaaaa-38f5-438d-b8ee-3f93b3531f87',
+                                                                               'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87',
+                                                                               'cccccccc-ed84-4e49-b5df-a9d117bd731f',
+                                                                               'dddddddd-ed84-4e49-b5df-a9d117bd731f',
+                                                                               'eeeeeeee-38f5-438d-b8ee-3f93b3531f87']],
+    'trp_link_latency': [[0, 50, 100, 150, 200], [0, 0, 100, 150, 200], [0, 0, 0, 150, 200], [0, 0, 0, 0, 200],
+                         [0, 0, 0, 0, 0]],
+    'trp_link_jitter': [[0, 5, 10, 15, 20], [0, 0, 10, 15, 20], [0, 0, 0, 15, 20], [0, 0, 0, 0, 20],
+                        [0, 0, 0, 0, 0]],
+    'trp_link_price_list': [[0, 5, 6, 6, 7], [0, 0, 6, 6, 7], [0, 0, 0, 6, 7], [0, 0, 0, 0, 7], [0, 0, 0, 0, 0]],
+    'ns_desc': [
+        {'vnf_id': '1', 'vnf_price_per_vim': [50, 51, 52, 53, 54]},
+        {'vnf_id': '2', 'vnf_price_per_vim': [20, 21, 22, 23, 24],
+         'vim_account': 'vimeeeeeeee_38f5_438d_b8ee_3f93b3531f87'},
+        {'vnf_id': '3', 'vnf_price_per_vim': [70, 71, 72, 73, 74]},
+        {'vnf_id': '4', 'vnf_price_per_vim': [40, 41, 42, 43, 44],
+         'vim_account': 'vimcccccccc_ed84_4e49_b5df_a9d117bd731f'}],
+    'vld_desc': [{'cp_refs': ['1', '2'], 'latency': 150, 'jitter': 30},
+                 {'cp_refs': ['2', '3'], 'latency': 140, 'jitter': 30},
+                 {'cp_refs': ['3', '4'], 'latency': 130, 'jitter': 30}],
+    'generator_data': {'file': __file__, 'time': datetime.datetime.now()}
+}
+
+test_ns_placement_data_w_pinning_str = {
+    'vim_accounts': ['vim' + vim_account.replace('-', '_') for vim_account in ['aaaaaaaa-38f5-438d-b8ee-3f93b3531f87',
+                                                                               'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87',
+                                                                               'cccccccc-ed84-4e49-b5df-a9d117bd731f',
+                                                                               'dddddddd-ed84-4e49-b5df-a9d117bd731f',
+                                                                               'eeeeeeee-38f5-438d-b8ee-3f93b3531f87']],
+    'trp_link_latency': [[0, 50, 100, 150, 200], [0, 0, 100, 150, 200], [0, 0, 0, 150, 200], [0, 0, 0, 0, 200],
+                         [0, 0, 0, 0, 0]],
+    'trp_link_jitter': [[0, 5, 10, 15, 20], [0, 0, 10, 15, 20], [0, 0, 0, 15, 20], [0, 0, 0, 0, 20],
+                        [0, 0, 0, 0, 0]],
+    'trp_link_price_list': [[0, 5, 6, 6, 7], [0, 0, 6, 6, 7], [0, 0, 0, 6, 7], [0, 0, 0, 0, 7], [0, 0, 0, 0, 0]],
+    'ns_desc': [
+        {'vnf_id': 'one', 'vnf_price_per_vim': [50, 51, 52, 53, 54]},
+        {'vnf_id': 'two', 'vnf_price_per_vim': [20, 21, 22, 23, 24],
+         'vim_account': 'vimeeeeeeee_38f5_438d_b8ee_3f93b3531f87'},
+        {'vnf_id': 'three', 'vnf_price_per_vim': [70, 71, 72, 73, 74]},
+        {'vnf_id': 'four', 'vnf_price_per_vim': [40, 41, 42, 43, 44],
+         'vim_account': 'vimcccccccc_ed84_4e49_b5df_a9d117bd731f'}],
+    'vld_desc': [{'cp_refs': ['one', 'two'], 'latency': 150, 'jitter': 30},
+                 {'cp_refs': ['two', 'three'], 'latency': 140, 'jitter': 30},
+                 {'cp_refs': ['three', 'four'], 'latency': 130, 'jitter': 30}],
+    'generator_data': {'file': __file__, 'time': datetime.datetime.now()}
+}
+
+test_ns_placement_data_str_no_vld = {
+    'vim_accounts': ['vim' + vim_account.replace('-', '_') for vim_account in ['aaaaaaaa-38f5-438d-b8ee-3f93b3531f87',
+                                                                               'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87',
+                                                                               'cccccccc-ed84-4e49-b5df-a9d117bd731f',
+                                                                               'dddddddd-ed84-4e49-b5df-a9d117bd731f',
+                                                                               'eeeeeeee-38f5-438d-b8ee-3f93b3531f87']],
+    'trp_link_latency': [[0, 50, 100, 150, 200], [0, 0, 100, 150, 200], [0, 0, 0, 150, 200], [0, 0, 0, 0, 200],
+                         [0, 0, 0, 0, 0]],
+    'trp_link_jitter': [[0, 5, 10, 15, 20], [0, 0, 10, 15, 20], [0, 0, 0, 15, 20], [0, 0, 0, 0, 20],
+                        [0, 0, 0, 0, 0]],
+    'trp_link_price_list': [[0, 5, 6, 6, 7], [0, 0, 6, 6, 7], [0, 0, 0, 6, 7], [0, 0, 0, 0, 7], [0, 0, 0, 0, 0]],
+    'ns_desc': [
+        {'vnf_id': 'one', 'vnf_price_per_vim': [50, 51, 52, 53, 54]}],
+    'vld_desc': [],
+    'generator_data': {'file': __file__, 'time': datetime.datetime.now()}
+}
+
+expected_model_fragment = """
+%This is the NETWORK RESOURCE MODEL
+enum  Vims = {
+vimaaaaaaaa_38f5_438d_b8ee_3f93b3531f87,
+vimbbbbbbbb_38f5_438d_b8ee_3f93b3531f87,
+vimcccccccc_ed84_4e49_b5df_a9d117bd731f,
+vimdddddddd_ed84_4e49_b5df_a9d117bd731f,
+vimeeeeeeee_38f5_438d_b8ee_3f93b3531f87}; % The vim-accounts
+array[Vims, Vims] of int: trp_link_latency = [|0,50,100,150,200,
+|0,0,100,150,200,
+|0,0,0,150,200,
+|0,0,0,0,200,
+|0,0,0,0,0,
+|]; % Transport link latency between data centers
+array[Vims, Vims] of int: trp_link_jitter = [|0,5,10,15,20,
+|0,0,10,15,20,
+|0,0,0,15,20,
+|0,0,0,0,20,
+|0,0,0,0,0,
+|]; % Transport link jitter between data centers
+array[Vims, Vims] of int: trp_link_price_list = [|0,5,6,6,7,
+|0,0,6,6,7,
+|0,0,0,6,7,
+|0,0,0,0,7,
+|0,0,0,0,0,
+|]; % Transport link price list
+array[Vims] of int: vim_price_list_1 = [50,51,52,53,54];
+array[Vims] of int: vim_price_list_2 = [20,21,22,23,24];
+array[Vims] of int: vim_price_list_3 = [70,71,72,73,74];
+array[Vims] of int: vim_price_list_4 = [40,41,42,43,44];
+
+
+% This is the NETWORK BASIC LOAD MODEL (CONSUMED)
+% NOTE. This is not applicable in OSM Release 7
+
+% This is the SERVICE CONSUMPTION MODEL
+% These are the variables, i.e. which DC to select for each VNF
+var Vims: VNF1;
+var Vims: VNF2;
+var Vims: VNF3;
+var Vims: VNF4;
+
+
+% These are the set of rules for selecting DCs to VNFs
+constraint trp_link_latency[VNF1, VNF2] <= 150;
+constraint trp_link_latency[VNF2, VNF3] <= 140;
+constraint trp_link_latency[VNF3, VNF4] <= 130;
+constraint trp_link_jitter[VNF1, VNF2] <= 30;
+constraint trp_link_jitter[VNF2, VNF3] <= 30;
+constraint trp_link_jitter[VNF3, VNF4] <= 30;
+
+% Calculate the cost for VNFs and cost for transport link and total cost
+var int: used_transport_cost =trp_link_price_list[VNF1, VNF2]+
+trp_link_price_list[VNF2, VNF3]+
+trp_link_price_list[VNF3, VNF4];
+
+var int: used_vim_cost =vim_price_list_1[VNF1]+
+vim_price_list_2[VNF2]+
+vim_price_list_3[VNF3]+
+vim_price_list_4[VNF4];
+
+var int: total_cost = used_transport_cost + used_vim_cost;
+
+solve minimize total_cost;
+"""
+expected_model_fragment_str = """
+%This is the NETWORK RESOURCE MODEL
+enum  Vims = {
+vimaaaaaaaa_38f5_438d_b8ee_3f93b3531f87,
+vimbbbbbbbb_38f5_438d_b8ee_3f93b3531f87,
+vimcccccccc_ed84_4e49_b5df_a9d117bd731f,
+vimdddddddd_ed84_4e49_b5df_a9d117bd731f,
+vimeeeeeeee_38f5_438d_b8ee_3f93b3531f87}; % The vim-accounts
+array[Vims, Vims] of int: trp_link_latency = [|0,50,100,150,200,
+|0,0,100,150,200,
+|0,0,0,150,200,
+|0,0,0,0,200,
+|0,0,0,0,0,
+|]; % Transport link latency between data centers
+array[Vims, Vims] of int: trp_link_jitter = [|0,5,10,15,20,
+|0,0,10,15,20,
+|0,0,0,15,20,
+|0,0,0,0,20,
+|0,0,0,0,0,
+|]; % Transport link jitter between data centers
+array[Vims, Vims] of int: trp_link_price_list = [|0,5,6,6,7,
+|0,0,6,6,7,
+|0,0,0,6,7,
+|0,0,0,0,7,
+|0,0,0,0,0,
+|]; % Transport link price list
+array[Vims] of int: vim_price_list_one = [50,51,52,53,54];
+array[Vims] of int: vim_price_list_two = [20,21,22,23,24];
+array[Vims] of int: vim_price_list_three = [70,71,72,73,74];
+array[Vims] of int: vim_price_list_four = [40,41,42,43,44];
+
+
+% This is the NETWORK BASIC LOAD MODEL (CONSUMED)
+% NOTE. This is not applicable in OSM Release 7
+
+% This is the SERVICE CONSUMPTION MODEL
+% These are the variables, i.e. which DC to select for each VNF
+var Vims: VNFone;
+var Vims: VNFtwo;
+var Vims: VNFthree;
+var Vims: VNFfour;
+
+
+% These are the set of rules for selecting DCs to VNFs
+constraint trp_link_latency[VNFone, VNFtwo] <= 150;
+constraint trp_link_latency[VNFtwo, VNFthree] <= 140;
+constraint trp_link_latency[VNFthree, VNFfour] <= 130;
+constraint trp_link_jitter[VNFone, VNFtwo] <= 30;
+constraint trp_link_jitter[VNFtwo, VNFthree] <= 30;
+constraint trp_link_jitter[VNFthree, VNFfour] <= 30;
+
+% Calculate the cost for VNFs and cost for transport link and total cost
+var int: used_transport_cost =trp_link_price_list[VNFone, VNFtwo]+
+trp_link_price_list[VNFtwo, VNFthree]+
+trp_link_price_list[VNFthree, VNFfour];
+
+var int: used_vim_cost =vim_price_list_one[VNFone]+
+vim_price_list_two[VNFtwo]+
+vim_price_list_three[VNFthree]+
+vim_price_list_four[VNFfour];
+
+var int: total_cost = used_transport_cost + used_vim_cost;
+
+solve minimize total_cost;
+"""
+
+expected_model_fragment_str_no_vld_constraints = """
+%This is the NETWORK RESOURCE MODEL
+enum  Vims = {
+vimaaaaaaaa_38f5_438d_b8ee_3f93b3531f87,
+vimbbbbbbbb_38f5_438d_b8ee_3f93b3531f87,
+vimcccccccc_ed84_4e49_b5df_a9d117bd731f,
+vimdddddddd_ed84_4e49_b5df_a9d117bd731f,
+vimeeeeeeee_38f5_438d_b8ee_3f93b3531f87}; % The vim-accounts
+array[Vims, Vims] of int: trp_link_latency = [|0,50,100,150,200,
+|0,0,100,150,200,
+|0,0,0,150,200,
+|0,0,0,0,200,
+|0,0,0,0,0,
+|]; % Transport link latency between data centers
+array[Vims, Vims] of int: trp_link_jitter = [|0,5,10,15,20,
+|0,0,10,15,20,
+|0,0,0,15,20,
+|0,0,0,0,20,
+|0,0,0,0,0,
+|]; % Transport link jitter between data centers
+array[Vims, Vims] of int: trp_link_price_list = [|0,5,6,6,7,
+|0,0,6,6,7,
+|0,0,0,6,7,
+|0,0,0,0,7,
+|0,0,0,0,0,
+|]; % Transport link price list
+array[Vims] of int: vim_price_list_one = [50,51,52,53,54];
+array[Vims] of int: vim_price_list_two = [20,21,22,23,24];
+array[Vims] of int: vim_price_list_three = [70,71,72,73,74];
+array[Vims] of int: vim_price_list_four = [40,41,42,43,44];
+
+
+% This is the NETWORK BASIC LOAD MODEL (CONSUMED)
+% NOTE. This is not applicable in OSM Release 7
+
+% This is the SERVICE CONSUMPTION MODEL
+% These are the variables, i.e. which DC to select for each VNF
+var Vims: VNFone;
+var Vims: VNFtwo;
+var Vims: VNFthree;
+var Vims: VNFfour;
+
+
+% These are the set of rules for selecting DCs to VNFs
+
+% Calculate the cost for VNFs and cost for transport link and total cost
+var int: used_transport_cost =trp_link_price_list[VNFone, VNFtwo]+
+trp_link_price_list[VNFtwo, VNFthree]+
+trp_link_price_list[VNFthree, VNFfour];
+
+var int: used_vim_cost =vim_price_list_one[VNFone]+
+vim_price_list_two[VNFtwo]+
+vim_price_list_three[VNFthree]+
+vim_price_list_four[VNFfour];
+
+var int: total_cost = used_transport_cost + used_vim_cost;
+
+solve minimize total_cost;
+"""
+
+expected_model_fragment_w_pinning = """
+%This is the NETWORK RESOURCE MODEL
+enum  Vims = {
+vimaaaaaaaa_38f5_438d_b8ee_3f93b3531f87,
+vimbbbbbbbb_38f5_438d_b8ee_3f93b3531f87,
+vimcccccccc_ed84_4e49_b5df_a9d117bd731f,
+vimdddddddd_ed84_4e49_b5df_a9d117bd731f,
+vimeeeeeeee_38f5_438d_b8ee_3f93b3531f87}; % The vim-accounts
+array[Vims, Vims] of int: trp_link_latency = [|0,50,100,150,200,
+|0,0,100,150,200,
+|0,0,0,150,200,
+|0,0,0,0,200,
+|0,0,0,0,0,
+|]; % Transport link latency between data centers
+array[Vims, Vims] of int: trp_link_jitter = [|0,5,10,15,20,
+|0,0,10,15,20,
+|0,0,0,15,20,
+|0,0,0,0,20,
+|0,0,0,0,0,
+|]; % Transport link jitter between data centers
+array[Vims, Vims] of int: trp_link_price_list = [|0,5,6,6,7,
+|0,0,6,6,7,
+|0,0,0,6,7,
+|0,0,0,0,7,
+|0,0,0,0,0,
+|]; % Transport link price list
+array[Vims] of int: vim_price_list_1 = [50,51,52,53,54];
+array[Vims] of int: vim_price_list_2 = [20,21,22,23,24];
+array[Vims] of int: vim_price_list_3 = [70,71,72,73,74];
+array[Vims] of int: vim_price_list_4 = [40,41,42,43,44];
+
+
+% This is the NETWORK BASIC LOAD MODEL (CONSUMED)
+% NOTE. This is not applicable in OSM Release 7
+
+% This is the SERVICE CONSUMPTION MODEL
+% These are the variables, i.e. which DC to select for each VNF
+var Vims: VNF1;
+Vims: VNF2 = vimeeeeeeee_38f5_438d_b8ee_3f93b3531f87;
+var Vims: VNF3;
+Vims: VNF4 = vimcccccccc_ed84_4e49_b5df_a9d117bd731f;
+
+
+% These are the set of rules for selecting DCs to VNFs
+constraint trp_link_latency[VNF1, VNF2] <= 150;
+constraint trp_link_latency[VNF2, VNF3] <= 140;
+constraint trp_link_latency[VNF3, VNF4] <= 130;
+constraint trp_link_jitter[VNF1, VNF2] <= 30;
+constraint trp_link_jitter[VNF2, VNF3] <= 30;
+constraint trp_link_jitter[VNF3, VNF4] <= 30;
+
+% Calculate the cost for VNFs and cost for transport link and total cost
+var int: used_transport_cost =trp_link_price_list[VNF1, VNF2]+
+trp_link_price_list[VNF2, VNF3]+
+trp_link_price_list[VNF3, VNF4];
+
+var int: used_vim_cost =vim_price_list_1[VNF1]+
+vim_price_list_2[VNF2]+
+vim_price_list_3[VNF3]+
+vim_price_list_4[VNF4];
+
+var int: total_cost = used_transport_cost + used_vim_cost;
+
+solve minimize total_cost;
+"""
+
+expected_model_fragment_w_pinning_str = """
+%This is the NETWORK RESOURCE MODEL
+enum  Vims = {
+vimaaaaaaaa_38f5_438d_b8ee_3f93b3531f87,
+vimbbbbbbbb_38f5_438d_b8ee_3f93b3531f87,
+vimcccccccc_ed84_4e49_b5df_a9d117bd731f,
+vimdddddddd_ed84_4e49_b5df_a9d117bd731f,
+vimeeeeeeee_38f5_438d_b8ee_3f93b3531f87}; % The vim-accounts
+array[Vims, Vims] of int: trp_link_latency = [|0,50,100,150,200,
+|0,0,100,150,200,
+|0,0,0,150,200,
+|0,0,0,0,200,
+|0,0,0,0,0,
+|]; % Transport link latency between data centers
+array[Vims, Vims] of int: trp_link_jitter = [|0,5,10,15,20,
+|0,0,10,15,20,
+|0,0,0,15,20,
+|0,0,0,0,20,
+|0,0,0,0,0,
+|]; % Transport link jitter between data centers
+array[Vims, Vims] of int: trp_link_price_list = [|0,5,6,6,7,
+|0,0,6,6,7,
+|0,0,0,6,7,
+|0,0,0,0,7,
+|0,0,0,0,0,
+|]; % Transport link price list
+array[Vims] of int: vim_price_list_one = [50,51,52,53,54];
+array[Vims] of int: vim_price_list_two = [20,21,22,23,24];
+array[Vims] of int: vim_price_list_three = [70,71,72,73,74];
+array[Vims] of int: vim_price_list_four = [40,41,42,43,44];
+
+
+% This is the NETWORK BASIC LOAD MODEL (CONSUMED)
+% NOTE. This is not applicable in OSM Release 7
+
+% This is the SERVICE CONSUMPTION MODEL
+% These are the variables, i.e. which DC to select for each VNF
+var Vims: VNFone;
+Vims: VNFtwo = vimeeeeeeee_38f5_438d_b8ee_3f93b3531f87;
+var Vims: VNFthree;
+Vims: VNFfour = vimcccccccc_ed84_4e49_b5df_a9d117bd731f;
+
+
+% These are the set of rules for selecting DCs to VNFs
+constraint trp_link_latency[VNFone, VNFtwo] <= 150;
+constraint trp_link_latency[VNFtwo, VNFthree] <= 140;
+constraint trp_link_latency[VNFthree, VNFfour] <= 130;
+constraint trp_link_jitter[VNFone, VNFtwo] <= 30;
+constraint trp_link_jitter[VNFtwo, VNFthree] <= 30;
+constraint trp_link_jitter[VNFthree, VNFfour] <= 30;
+
+% Calculate the cost for VNFs and cost for transport link and total cost
+var int: used_transport_cost =trp_link_price_list[VNFone, VNFtwo]+
+trp_link_price_list[VNFtwo, VNFthree]+
+trp_link_price_list[VNFthree, VNFfour];
+
+var int: used_vim_cost =vim_price_list_one[VNFone]+
+vim_price_list_two[VNFtwo]+
+vim_price_list_three[VNFthree]+
+vim_price_list_four[VNFfour];
+
+var int: total_cost = used_transport_cost + used_vim_cost;
+
+solve minimize total_cost;
+"""
+
+expected_model_fragment_str_no_vld = """
+%This is the NETWORK RESOURCE MODEL
+enum  Vims = {
+vimaaaaaaaa_38f5_438d_b8ee_3f93b3531f87,
+vimbbbbbbbb_38f5_438d_b8ee_3f93b3531f87,
+vimcccccccc_ed84_4e49_b5df_a9d117bd731f,
+vimdddddddd_ed84_4e49_b5df_a9d117bd731f,
+vimeeeeeeee_38f5_438d_b8ee_3f93b3531f87}; % The vim-accounts
+array[Vims, Vims] of int: trp_link_latency = [|0,50,100,150,200,
+|0,0,100,150,200,
+|0,0,0,150,200,
+|0,0,0,0,200,
+|0,0,0,0,0,
+|]; % Transport link latency between data centers
+array[Vims, Vims] of int: trp_link_jitter = [|0,5,10,15,20,
+|0,0,10,15,20,
+|0,0,0,15,20,
+|0,0,0,0,20,
+|0,0,0,0,0,
+|]; % Transport link jitter between data centers
+array[Vims, Vims] of int: trp_link_price_list = [|0,5,6,6,7,
+|0,0,6,6,7,
+|0,0,0,6,7,
+|0,0,0,0,7,
+|0,0,0,0,0,
+|]; % Transport link price list
+array[Vims] of int: vim_price_list_one = [50,51,52,53,54];
+
+
+% This is the NETWORK BASIC LOAD MODEL (CONSUMED)
+% NOTE. This is not applicable in OSM Release 7
+
+% This is the SERVICE CONSUMPTION MODEL
+% These are the variables, i.e. which DC to select for each VNF
+
+var Vims: VNFone;
+
+% These are the set of rules for selecting DCs to VNFs
+
+% Calculate the cost for VNFs and cost for transport link and total cost
+var int: used_transport_cost =0;
+
+var int: used_vim_cost =vim_price_list_one[VNFone];
+
+var int: total_cost = used_transport_cost + used_vim_cost;
+
+solve minimize total_cost;
+"""
+
+
+class TestMznModelGenerator(TestCase):
+    def test_create_model(self):
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        mzn_model = mg.create_model(test_ns_placement_data_str)
+
+        # so asserting exact content is difficult due to the datetime.now(), therefore we ignore the first lines
+        self.assertTrue(expected_model_fragment_str.replace('\n', '') in
+                        mzn_model.replace('\n', ''), "faulty model generated")
+
+    def test_create_model_no_vld_constraints(self):
+        """
+        instantiate w/o constraints in nsd or order params has a valid model
+        :return:
+        """
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        mzn_model = mg.create_model(test_ns_placement_data_str_no_vld_constraints)
+
+        # so asserting exact content is difficult due to the datetime.now(), therefore we ignore the first lines
+        self.assertTrue(expected_model_fragment_str_no_vld_constraints.replace('\n', '') in
+                        mzn_model.replace('\n', ''), "faulty model generated")
+
+    def test_create_model_w_pinning(self):
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        mzn_model = mg.create_model(test_ns_placement_data_w_pinning_str)
+
+        # so asserting exact content is difficult due to the datetime.now(), therefore we ignore the first lines
+        self.assertTrue(expected_model_fragment_w_pinning_str.replace('\n', '') in
+                        mzn_model.replace('\n', ''), "faulty model generated")
+
+    def test_create_model_no_vld(self):
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        mzn_model = mg.create_model(test_ns_placement_data_str_no_vld)
+
+        # so asserting exact content is difficult due to the datetime.now(), therefore we ignore the first lines
+        self.assertTrue(expected_model_fragment_str_no_vld.replace('\n', '') in
+                        mzn_model.replace('\n', ''), "faulty model generated")
+
+    def test__load_jinja_template(self):
+        """
+
+        add other test to check exception if template not loaded (e.g. invalid template name,
+        perhaps also valid name but invalid content (in case jinja2 detects such things))
+        """
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        template = mg._load_jinja_template()  # Note we use the default template
+        self.assertTrue(isinstance(template, Template), "failed to load jinja2 template")
+
+    def test_vim_account_replace(self):
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        nspd = test_ns_placement_data_str
+        mzn_model = mg.create_model(nspd)
+
+        expected = '%This is the NETWORK RESOURCE MODEL' + '\n' + 'enum  Vims = {' + '\n'
+        for val in test_ns_placement_data_str['vim_accounts']:
+            expected = expected + val.replace('-', '_') + ',\n'
+        expected = expected[:-2] + '}; % The vim-accounts'
+        res = re.findall(expected, mzn_model)
+        self.assertEqual(1, len(res), "vim accounts didnt replace from - to _")
+
+    def test_trp_link_price_list(self):
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        mzn_model = mg.create_model(test_ns_placement_data_str)
+
+        expected = 'array\\[Vims, Vims\\] of int: trp_link_price_list = \\['
+        for price_list in test_ns_placement_data_str['trp_link_price_list']:
+            expected = expected + '\\|' + (str(price_list)[1:-1]).replace(" ", "") + ',\n'
+        expected = expected + '\\|\\]; % Transport link price list'
+        res = re.findall(expected, mzn_model)
+        self.assertEqual(1, len(res), "price list is not correct")
+
+    def test_link_latency(self):
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        mzn_model = mg.create_model(test_ns_placement_data_str)
+
+        expected = 'array\\[Vims, Vims\\] of int: trp_link_latency = \\['
+        for link_latency in test_ns_placement_data_str['trp_link_latency']:
+            expected = expected + '\\|' + (str(link_latency)[1:-1]).replace(" ", "") + ',\n'
+        expected = expected + '\\|\\]; % Transport link latency between data centers'
+        res = re.findall(expected, mzn_model)
+        self.assertEqual(1, len(res), "trp_link_latency values is not correct")
+
+    def test_link_jitter(self):
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        mzn_model = mg.create_model(test_ns_placement_data_str)
+
+        expected = 'array\\[Vims, Vims\\] of int: trp_link_jitter = \\['
+        for link_jitter in test_ns_placement_data_str['trp_link_jitter']:
+            expected = expected + '\\|' + (str(link_jitter)[1:-1]).replace(" ", "") + ',\n'
+        expected = expected + '\\|\\]; % Transport link jitter between data centers'
+
+        res = re.findall(expected, mzn_model)
+
+        self.assertEqual(1, len(res), "trp_link_jitter values is not correct")
+
+    def test_price_per_vim(self):
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        mzn_model = mg.create_model(test_ns_placement_data_w_pinning_str)
+
+        expected = ""
+        for price_list in test_ns_placement_data_w_pinning_str['ns_desc']:
+            expected += 'array\\[Vims\\] of int: vim_price_list_' + price_list.get('vnf_id') + " = "
+            temp = str(price_list.get('vnf_price_per_vim'))[1:-1].replace(" ", "")
+            expected += "\\[" + temp + "\\];\n"
+
+        res = re.findall(expected, mzn_model)
+        self.assertEqual(1, len(res), "mzn_model contains pinning")
+
+    def test_pinning(self):
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        mzn_model = mg.create_model(test_ns_placement_data_str)
+
+        expected = ""
+        for pin_list in test_ns_placement_data_str['ns_desc']:
+            if pin_list.get('vim_account'):
+                expected += 'Vims: VNF' + pin_list.get('vnf_id') + ' = ' + pin_list.get('vim_account') + ';\n'
+            else:
+                expected += 'var Vims: VNF' + pin_list.get('vnf_id') + ';\n'
+
+        res = re.findall(expected, mzn_model)
+        self.assertEqual(1, len(res), "mzn_model has no pinning")
+
+    def test__without_pinning(self):
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        mzn_model = mg.create_model(test_ns_placement_data_w_pinning_str)
+
+        expected = ""
+        for pin_list in test_ns_placement_data_w_pinning_str['ns_desc']:
+            if pin_list.get('vim_account'):
+                expected += 'Vims: VNF' + pin_list.get('vnf_id') + ' = ' + pin_list.get('vim_account') + ';\n'
+            else:
+                expected += 'var Vims: VNF' + pin_list.get('vnf_id') + ';\n'
+
+        res = re.findall(expected, mzn_model)
+        self.assertEqual(1, len(res), "mzn_model contains pinning")
+
+    def test__without_constraints_for_jitter_and_latency(self):
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        mzn_model = mg.create_model(test_ns_placement_data_str_no_vld_constraints)
+
+        expected_latency = "constraint trp_link_latency"
+        expected_jitter = "constraint trp_link_jitter"
+        latency_or_jitter_was_found = 0
+        for l_o_j in test_ns_placement_data_str_no_vld_constraints['vld_desc']:
+            if l_o_j.get('latency') or l_o_j.get('jitter'):
+                latency_or_jitter_was_found = 1
+
+        res_latency = re.findall(expected_latency, mzn_model)
+        res_jitter = re.findall(expected_jitter, mzn_model)
+        self.assertEqual(0, latency_or_jitter_was_found, "Jitter or latency was found in the test input")
+        self.assertEqual(0, len(res_latency), "constraint trp_link_latency was found in mzn_model")
+        self.assertEqual(0, len(res_jitter), "constraint trp_link_latency was found in mzn_model")
+
+    def test__constraints_for_jitter_and_latency(self):
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        mzn_model = mg.create_model(test_ns_placement_data_str)
+
+        expected_latency = ""
+        expected_jitter = ""
+        latency_or_jitter_was_found = 0
+        for l_o_j in test_ns_placement_data_str['vld_desc']:
+            if not (l_o_j.get('latency') or l_o_j.get('jitter')):
+                latency_or_jitter_was_found = 1
+            expected_latency += "constraint trp_link_latency" + "\\[VNF" + l_o_j.get('cp_refs')[0] + ", VNF" + \
+                                l_o_j.get('cp_refs')[1] + "\\] \\<= " + str(l_o_j.get('latency')) + ";\n\n"
+
+            expected_jitter += "constraint trp_link_jitter" + "\\[VNF" + l_o_j.get('cp_refs')[0] + ", VNF" + \
+                               l_o_j.get('cp_refs')[1] + "\\] \\<= " + str(l_o_j.get('jitter')) + ";\n\n"
+
+        res = re.findall(expected_latency + expected_jitter, mzn_model)
+        self.assertEqual(0, latency_or_jitter_was_found, "Jitter or latency was not found in the test input")
+        self.assertEqual(1, len(res), "faulty model generated")
diff --git a/osm_pla/test/test_mznPlacementConductor.py b/osm_pla/test/test_mznPlacementConductor.py
new file mode 100644 (file)
index 0000000..6bc42b6
--- /dev/null
@@ -0,0 +1,218 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+import logging
+# from collections import Counter
+from unittest import TestCase, mock
+
+# import osm_pla
+from osm_pla.placement.mznplacement import MznPlacementConductor, MznModelGenerator
+
+test_mzn_model = """
+% This minizinc model is generated using
+% C:/Users/LG/PycharmProjects/dynamic_jijna2_mzn/osm_pla/placement/mznplacement.py
+% at 2019-10-24 11:12:02.058905.
+
+%This is the NETWORK RESOURCE MODEL
+enum  Vims = {
+vimaaaaaaaa_38f5_438d_b8ee_3f93b3531f87,
+vimbbbbbbbb_38f5_438d_b8ee_3f93b3531f87,
+vimcccccccc_ed84_4e49_b5df_a9d117bd731f,
+vimdddddddd_ed84_4e49_b5df_a9d117bd731f,
+vimeeeeeeee_38f5_438d_b8ee_3f93b3531f87}; % The vim-accounts
+array[Vims, Vims] of int: trp_link_latency = [|0,50,100,150,200,
+|0,0,100,150,200,
+|0,0,0,150,200,
+|0,0,0,0,200,
+|0,0,0,0,0,
+|]; % Transport link latency between data centers
+array[Vims, Vims] of int: trp_link_jitter = [|0,50,100,150,200,
+|0,0,100,150,200,
+|0,0,0,150,200,
+|0,0,0,0,200,
+|0,0,0,0,0,
+|]; % Transport link jitter between data centers
+array[Vims, Vims] of int: trp_link_price_list = [|0,5,6,6,7,
+|0,0,6,6,7,
+|0,0,0,6,7,
+|0,0,0,0,7,
+|0,0,0,0,0,
+|]; % Transport link price list
+array[Vims] of int: vim_price_list_1 = [500,51,52,53,54];
+array[Vims] of int: vim_price_list_2 = [20,21,22,23,24];
+array[Vims] of int: vim_price_list_3 = [70,71,72,73,74];
+array[Vims] of int: vim_price_list_4 = [40,41,42,43,44];
+
+
+% This is the NETWORK BASIC LOAD MODEL (CONSUMED)
+% NOTE. This is not applicable in OSM Release 7
+
+% This is the SERVICE CONSUMPTION MODEL
+% These are the variables, i.e. which DC to select for each VNF
+var Vims: VNF1;
+var Vims: VNF2;
+var Vims: VNF3;
+var Vims: VNF4;
+
+
+% These are the set of rules for selecting DCs to VNFs
+constraint trp_link_latency[VNF1, VNF2] <= 150;
+constraint trp_link_latency[VNF2, VNF3] <= 140;
+constraint trp_link_latency[VNF3, VNF4] <= 130;
+constraint trp_link_jitter[VNF1, VNF2] <= 30;
+constraint trp_link_jitter[VNF2, VNF3] <= 30;
+constraint trp_link_jitter[VNF3, VNF4] <= 30;
+
+% Calculate the cost for VNFs and cost for transport link and total cost
+var int: used_transport_cost =trp_link_price_list[VNF1, VNF2]+
+trp_link_price_list[VNF2, VNF3]+
+trp_link_price_list[VNF3, VNF4];
+
+var int: used_vim_cost =vim_price_list_1[VNF1]+
+vim_price_list_2[VNF2]+
+vim_price_list_3[VNF3]+
+vim_price_list_4[VNF4];
+
+var int: total_cost = used_transport_cost + used_vim_cost;
+
+solve minimize total_cost;
+
+"""
+
+test_mzn_model_w_pinning = """
+% This minizinc model is generated using
+% C:/Users/LG/PycharmProjects/dynamic_jijna2_mzn/osm_pla/placement/mznplacement.py
+% at 2019-10-24 11:12:02.058905.
+
+%This is the NETWORK RESOURCE MODEL
+enum  Vims = {
+vimaaaaaaaa_38f5_438d_b8ee_3f93b3531f87,
+vimbbbbbbbb_38f5_438d_b8ee_3f93b3531f87,
+vimcccccccc_ed84_4e49_b5df_a9d117bd731f,
+vimdddddddd_ed84_4e49_b5df_a9d117bd731f,
+vimeeeeeeee_38f5_438d_b8ee_3f93b3531f87}; % The vim-accounts
+array[Vims, Vims] of int: trp_link_latency = [|0,50,100,150,200,
+|0,0,100,150,200,
+|0,0,0,150,200,
+|0,0,0,0,200,
+|0,0,0,0,0,
+|]; % Transport link latency between data centers
+array[Vims, Vims] of int: trp_link_jitter = [|0,50,100,150,200,
+|0,0,100,150,200,
+|0,0,0,150,200,
+|0,0,0,0,200,
+|0,0,0,0,0,
+|]; % Transport link jitter between data centers
+array[Vims, Vims] of int: trp_link_price_list = [|0,5,6,6,7,
+|0,0,6,6,7,
+|0,0,0,6,7,
+|0,0,0,0,7,
+|0,0,0,0,0,
+|]; % Transport link price list
+array[Vims] of int: vim_price_list_1 = [500,51,52,53,54];
+array[Vims] of int: vim_price_list_2 = [20,21,22,23,24];
+array[Vims] of int: vim_price_list_3 = [70,71,72,73,74];
+array[Vims] of int: vim_price_list_4 = [40,41,42,43,44];
+
+
+% This is the NETWORK BASIC LOAD MODEL (CONSUMED)
+% NOTE. This is not applicable in OSM Release 7
+
+% This is the SERVICE CONSUMPTION MODEL
+% These are the variables, i.e. which DC to select for each VNF
+Vims: VNF1 = vimbbbbbbbb_38f5_438d_b8ee_3f93b3531f87;
+var Vims: VNF2;
+Vims: VNF3 = vimbbbbbbbb_38f5_438d_b8ee_3f93b3531f87;
+var Vims: VNF4;
+
+
+% These are the set of rules for selecting DCs to VNFs
+constraint trp_link_latency[VNF1, VNF2] <= 150;
+constraint trp_link_latency[VNF2, VNF3] <= 140;
+constraint trp_link_latency[VNF3, VNF4] <= 130;
+constraint trp_link_jitter[VNF1, VNF2] <= 30;
+constraint trp_link_jitter[VNF2, VNF3] <= 30;
+constraint trp_link_jitter[VNF3, VNF4] <= 30;
+
+% Calculate the cost for VNFs and cost for transport link and total cost
+var int: used_transport_cost =trp_link_price_list[VNF1, VNF2]+
+trp_link_price_list[VNF2, VNF3]+
+trp_link_price_list[VNF3, VNF4];
+
+var int: used_vim_cost =vim_price_list_1[VNF1]+
+vim_price_list_2[VNF2]+
+vim_price_list_3[VNF3]+
+vim_price_list_4[VNF4];
+
+var int: total_cost = used_transport_cost + used_vim_cost;
+
+solve minimize total_cost;
+
+"""
+
+test_mzn_unsatisfiable_model = """
+var 1..2: item1;
+var 1..2: item2;
+constraint item1 + item2 == 5;
+
+solve satisfy;
+"""
+
+
+class TestMznPlacementConductor(TestCase):
+    def test__run_placement_model(self):
+        expected_result = [{'vimAccountId': 'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'},
+                           {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'},
+                           {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'},
+                           {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '4'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc={})
+        # sort the result to ease assert with expected result
+        sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+
+    def test__run_placement_model_w_pinning(self):
+        expected_result = [{'vimAccountId': 'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'},
+                           {'vimAccountId': 'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'},
+                           {'vimAccountId': 'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'},
+                           {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '4'}]
+
+        ns_desc = [{'vnf_price_per_vim': [10, 9, 7, 8], 'vnf_id': '2'},
+                   {'vim_account': 'vimbbbbbbbb_38f5_438d_b8ee_3f93b3531f87', 'vnf_price_per_vim': [10, 9, 7, 8],
+                    'vnf_id': '1'},
+                   {'vnf_price_per_vim': [10, 9, 7, 8], 'vnf_id': '4'},
+                   {'vim_account': 'vimbbbbbbbb_38f5_438d_b8ee_3f93b3531f87', 'vnf_price_per_vim': [10, 9, 7, 8],
+                    'vnf_id': '3'}
+                   ]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model_w_pinning, ns_desc=ns_desc)
+        # sort the result to ease assert with expected result
+        sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+
+    def test__run_placement_model_unsatisfiable(self):
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        self.assertEqual([{}], mpc._run_placement_model(mzn_model=test_mzn_unsatisfiable_model, ns_desc={}),
+                         "Faulty syntax or content for unsatisfiable model")
+
+    @mock.patch.object(MznModelGenerator, 'create_model', side_effect=['%model'])
+    @mock.patch.object(MznPlacementConductor, '_run_placement_model')
+    def test_do_placement_computation(self, mock_run, mock_create):
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        dummy_nspd = {'ns_desc': {}}
+        _ = mpc.do_placement_computation(dummy_nspd)
+        mock_create.assert_called_with(dummy_nspd)
+        mock_run.assert_called_with('%model', {})
diff --git a/osm_pla/test/test_mznmodels.py b/osm_pla/test/test_mznmodels.py
new file mode 100644 (file)
index 0000000..b32ec94
--- /dev/null
@@ -0,0 +1,631 @@
+
+# Copyright 2019 ArctosLabs Scandinavia AB
+#
+# 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.
+
+# Note:
+# This unit test file is generated - 
+# from: c:\Users\LG\Desktop\plarepo\documents\Test\pla_algo_test (pr_update_4).xlsx
+# by code generator: mzntestcasegenerator.py
+# at: 2019-12-04 09:26:38.412430
+#############
+
+import datetime
+import logging
+from unittest import TestCase
+
+from osm_pla.placement.mznplacement import MznPlacementConductor, MznModelGenerator
+
+
+class TestMznModels(TestCase):
+    def test_mznmodel_scenario1_subtestcase1(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '3'], 'latency': 120, 'jitter': 20}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario1_subtestcase2(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim33333333_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '3'], 'latency': 120, 'jitter': 20}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '33333333-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim33333333_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario1_subtestcase3(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim22222222_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '3'], 'latency': 120, 'jitter': 20}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '22222222-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim22222222_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario1_subtestcase4(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim44444444_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '3'], 'latency': 120, 'jitter': 20}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '44444444-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '22222222-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim44444444_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario1_subtestcase5(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim88888888_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '3'], 'latency': 120, 'jitter': 20}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '88888888-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '77777777-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '77777777-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim88888888_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario1_subtestcase6(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim99999999_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '3'], 'latency': 120, 'jitter': 20}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '99999999-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '77777777-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '77777777-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim99999999_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario1_subtestcase7(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim11111111_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150'], 'vim_account': 'vim33333333_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170'], 'vim_account': 'vim77777777_38f5_438d_b8ee_3f93b3531f87'}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '3'], 'latency': 120, 'jitter': 20}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim11111111_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150'], 'vim_account': 'vim33333333_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170'], 'vim_account': 'vim77777777_38f5_438d_b8ee_3f93b3531f87'}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario1_subtestcase8(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim77777777_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '3'], 'latency': 120, 'jitter': 20}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '77777777-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '77777777-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '77777777-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim77777777_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario2_subtestcase1(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '3'], 'latency': 20, 'jitter': 6}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario2_subtestcase2(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim44444444_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '3'], 'latency': 20, 'jitter': 6}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '44444444-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '22222222-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '22222222-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim44444444_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario2_subtestcase3(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '3'], 'latency': 20, 'jitter': 6}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '55555555-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '66666666-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '66666666-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario2_subtestcase4(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '3'], 'latency': 20, 'jitter': 6}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '55555555-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '55555555-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '55555555-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario2_subtestcase5(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim33333333_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150'], 'vim_account': 'vim44444444_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '3'], 'latency': 20, 'jitter': 6}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim33333333_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150'], 'vim_account': 'vim44444444_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario2_subtestcase6(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170'], 'vim_account': 'vim66666666_38f5_438d_b8ee_3f93b3531f87'}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '3'], 'latency': 20, 'jitter': 6}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170'], 'vim_account': 'vim66666666_38f5_438d_b8ee_3f93b3531f87'}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario3_subtestcase1(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 3}, {'cp_refs': ['2', '3'], 'latency': 25, 'jitter': 6}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario3_subtestcase2(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim44444444_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150'], 'vim_account': 'vim22222222_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 3}, {'cp_refs': ['2', '3'], 'latency': 25, 'jitter': 6}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim44444444_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150'], 'vim_account': 'vim22222222_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario3_subtestcase3(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim77777777_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 3}, {'cp_refs': ['2', '3'], 'latency': 25, 'jitter': 6}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '77777777-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '77777777-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '77777777-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim77777777_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario3_subtestcase4(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim88888888_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 3}, {'cp_refs': ['2', '3'], 'latency': 25, 'jitter': 6}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '88888888-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '77777777-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '77777777-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim88888888_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150']}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario3_subtestcase5(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim99999999_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150'], 'vim_account': 'vim77777777_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170'], 'vim_account': 'vim88888888_38f5_438d_b8ee_3f93b3531f87'}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 3}, {'cp_refs': ['2', '3'], 'latency': 25, 'jitter': 6}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim99999999_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150'], 'vim_account': 'vim77777777_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170'], 'vim_account': 'vim88888888_38f5_438d_b8ee_3f93b3531f87'}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario3_subtestcase6(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim77777777_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150'], 'vim_account': 'vim99999999_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 3}, {'cp_refs': ['2', '3'], 'latency': 25, 'jitter': 6}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '77777777-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '99999999-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '77777777-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim77777777_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['10,15,30,30,100,70,40,150,150,150'], 'vim_account': 'vim99999999_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '3', 'vnf_price_per_vim': ['30,30,60,40,100,90,40,150,200,170']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario4_subtestcase1(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}, {'vnf_id': '2', 'vnf_price_per_vim': ['15,20,40,40,110,80,50,110,160,210']}, {'vnf_id': '3', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}, {'vnf_id': '4', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '4'], 'latency': 50, 'jitter': 10}, {'cp_refs': ['2', '3'], 'latency': 20, 'jitter': 10}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}, {'vimAccountId': '00000000-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '4'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}, {'vnf_id': '2', 'vnf_price_per_vim': ['15,20,40,40,110,80,50,110,160,210']}, {'vnf_id': '3', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}, {'vnf_id': '4', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario4_subtestcase2(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim22222222_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['15,20,40,40,110,80,50,110,160,210']}, {'vnf_id': '3', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '4', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '4'], 'latency': 50, 'jitter': 10}, {'cp_refs': ['2', '3'], 'latency': 20, 'jitter': 10}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '22222222-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '44444444-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '55555555-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}, {'vimAccountId': '66666666-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '4'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim22222222_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['15,20,40,40,110,80,50,110,160,210']}, {'vnf_id': '3', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '4', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario4_subtestcase3(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['15,20,40,40,110,80,50,110,160,210']}, {'vnf_id': '3', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}, {'vnf_id': '4', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '4'], 'latency': 50, 'jitter': 10}, {'cp_refs': ['2', '3'], 'latency': 20, 'jitter': 10}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{'vimAccountId': '55555555-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'}, {'vimAccountId': '66666666-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'}, {'vimAccountId': '66666666-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}, {'vimAccountId': '55555555-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '4'}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['15,20,40,40,110,80,50,110,160,210']}, {'vnf_id': '3', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}, {'vnf_id': '4', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim55555555_38f5_438d_b8ee_3f93b3531f87'}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
+    def test_mznmodel_scenario4_subtestcase4(self):
+        # generate the model
+        ns_placement_data = {'vim_accounts': ['vim00000000_38f5_438d_b8ee_3f93b3531f87', 'vim11111111_38f5_438d_b8ee_3f93b3531f87', 'vim22222222_38f5_438d_b8ee_3f93b3531f87', 'vim33333333_38f5_438d_b8ee_3f93b3531f87', 'vim44444444_38f5_438d_b8ee_3f93b3531f87', 'vim55555555_38f5_438d_b8ee_3f93b3531f87', 'vim66666666_38f5_438d_b8ee_3f93b3531f87', 'vim77777777_38f5_438d_b8ee_3f93b3531f87', 'vim88888888_38f5_438d_b8ee_3f93b3531f87', 'vim99999999_38f5_438d_b8ee_3f93b3531f87'],
+                             'trp_link_latency' : [[0, 30, 70, 80, 32767, 32767, 32767, 32767, 32767, 32767], [30, 0, 75, 60, 32767, 32767, 32767, 32767, 32767, 32767], [70, 75, 0, 40, 100, 32767, 32767, 32767, 32767, 32767], [80, 60, 40, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 100, 32767, 0, 5, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 0, 5, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 5, 5, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 30, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 30, 0, 20], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 20, 20, 0]],
+                             'trp_link_jitter' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 5, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 5, 32767, 0, 4, 4, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 0, 10, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 4, 10, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 1, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 0, 1], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 1, 1, 0]],
+                             'trp_link_price_list' : [[0, 5, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 0, 5, 10, 32767, 32767, 32767, 32767, 32767, 32767], [5, 5, 0, 10, 10, 32767, 32767, 32767, 32767, 32767], [10, 10, 10, 0, 32767, 32767, 32767, 32767, 32767, 32767], [32767, 32767, 10, 32767, 0, 20, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 0, 20, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 20, 20, 0, 32767, 32767, 32767], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 0, 15, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 0, 15], [32767, 32767, 32767, 32767, 32767, 32767, 32767, 15, 15, 0]],
+                             'ns_desc' : [{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim00000000_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['15,20,40,40,110,80,50,110,160,210']}, {'vnf_id': '3', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim44444444_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '4', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}],
+                             'vld_desc' : [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20}, {'cp_refs': ['2', '4'], 'latency': 50, 'jitter': 10}, {'cp_refs': ['2', '3'], 'latency': 20, 'jitter': 10}],
+                             'generator_data': {'file': __file__ ,'time': datetime.datetime.now()}
+                            }
+        
+        mg = MznModelGenerator(logging.getLogger(__name__))
+        test_mzn_model = mg.create_model(ns_placement_data)
+
+        # run the model
+        expected_result = [{}]
+
+        mpc = MznPlacementConductor(logging.getLogger(__name__))
+        placement = mpc._run_placement_model(mzn_model=test_mzn_model, ns_desc=[{'vnf_id': '1', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim00000000_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '2', 'vnf_price_per_vim': ['15,20,40,40,110,80,50,110,160,210']}, {'vnf_id': '3', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200'], 'vim_account': 'vim44444444_38f5_438d_b8ee_3f93b3531f87'}, {'vnf_id': '4', 'vnf_price_per_vim': ['5,10,30,30,100,70,40,100,150,200']}])
+        # sort the result to ease assert with expected result
+        if not placement[0]:
+            sorted_placement = placement
+        else:
+            sorted_placement = sorted(placement, key=lambda k: k['member-vnf-index'])
+        self.assertEqual(expected_result, sorted_placement, 'Faulty syntax or content')
\ No newline at end of file
diff --git a/osm_pla/test/test_nsPlacementDataFactory.py b/osm_pla/test/test_nsPlacementDataFactory.py
new file mode 100644 (file)
index 0000000..c53ad57
--- /dev/null
@@ -0,0 +1,777 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+import os
+import unittest
+from collections import Counter
+from pathlib import Path
+from unittest import TestCase, mock
+from unittest.mock import call
+
+import yaml
+
+from osm_pla.placement.mznplacement import NsPlacementDataFactory
+
+
+class TestNsPlacementDataFactory(TestCase):
+    vim_accounts = [{"vim_password": "FxtnynxBCnouzAT4Hkerhg==", "config": {},
+                     "_admin": {"modified": 1564579854.0480285, "created": 1564579854.0480285,
+                                "operationalState": "ENABLED",
+                                "projects_read": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                "deployed": {"RO-account": "6beb4e2e-b397-11e9-a7a3-02420aff0008",
+                                             "RO": "6bcfc3fc-b397-11e9-a7a3-02420aff0008"},
+                                "projects_write": ["69915588-e5e2-46d3-96b0-a29bedef6f73"], "detailed-status": "Done"},
+                     "name": "OpenStack1", "vim_type": "openstack", "_id": "92b056a7-38f5-438d-b8ee-3f93b3531f87",
+                     "schema_version": "1.1", "vim_user": "admin", "vim_url": "http://10.234.12.47:5000/v3",
+                     "vim_tenant_name": "admin"},
+                    {"config": {}, "vim_tenant_name": "osm_demo", "schema_version": "1.1", "name": "OpenStack2",
+                     "vim_password": "gK5v4Gh2Pl41o6Skwp6RCw==", "vim_type": "openstack",
+                     "_admin": {"modified": 1567148372.2490237, "created": 1567148372.2490237,
+                                "operationalState": "ENABLED",
+                                "projects_read": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                "deployed": {"RO-account": "b7fb0034-caf3-11e9-9388-02420aff000a",
+                                             "RO": "b7f129ce-caf3-11e9-9388-02420aff000a"},
+                                "projects_write": ["69915588-e5e2-46d3-96b0-a29bedef6f73"], "detailed-status": "Done"},
+                     "vim_user": "admin", "vim_url": "http://10.234.12.44:5000/v3",
+                     "_id": "6618d412-d7fc-4eb0-a6f8-d2c258e0e900"},
+                    {"config": {}, "schema_version": "1.1", "name": "OpenStack3",
+                     "vim_password": "1R2FoMQnaL6rNSosoRP2hw==", "vim_type": "openstack", "vim_tenant_name": "osm_demo",
+                     "_admin": {"modified": 1567599746.689582, "created": 1567599746.689582,
+                                "operationalState": "ENABLED",
+                                "projects_read": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                "deployed": {"RO-account": "a8161f54-cf0e-11e9-9388-02420aff000a",
+                                             "RO": "a80b6280-cf0e-11e9-9388-02420aff000a"},
+                                "projects_write": ["69915588-e5e2-46d3-96b0-a29bedef6f73"], "detailed-status": "Done"},
+                     "vim_user": "admin", "vim_url": "http://10.234.12.46:5000/v3",
+                     "_id": "331ffdec-44a8-4707-94a1-af7a292d9735"},
+                    {"config": {}, "schema_version": "1.1", "name": "OpenStack4",
+                     "vim_password": "6LScyPeMq3QFh3GRb/xwZw==", "vim_type": "openstack", "vim_tenant_name": "osm_demo",
+                     "_admin": {"modified": 1567599911.5108898, "created": 1567599911.5108898,
+                                "operationalState": "ENABLED",
+                                "projects_read": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                "deployed": {"RO-account": "0a651200-cf0f-11e9-9388-02420aff000a",
+                                             "RO": "0a4defc6-cf0f-11e9-9388-02420aff000a"},
+                                "projects_write": ["69915588-e5e2-46d3-96b0-a29bedef6f73"], "detailed-status": "Done"},
+                     "vim_user": "admin", "vim_url": "http://10.234.12.43:5000/v3",
+                     "_id": "eda92f47-29b9-4007-9709-c1833dbfbe31"}]
+
+    vim_accounts_fewer_vims = [{"vim_password": "FxtnynxBCnouzAT4Hkerhg==", "config": {},
+                                "_admin": {"modified": 1564579854.0480285, "created": 1564579854.0480285,
+                                           "operationalState": "ENABLED",
+                                           "projects_read": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                           "deployed": {"RO-account": "6beb4e2e-b397-11e9-a7a3-02420aff0008",
+                                                        "RO": "6bcfc3fc-b397-11e9-a7a3-02420aff0008"},
+                                           "projects_write": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                           "detailed-status": "Done"},
+                                "name": "OpenStack1", "vim_type": "openstack",
+                                "_id": "92b056a7-38f5-438d-b8ee-3f93b3531f87",
+                                "schema_version": "1.1", "vim_user": "admin", "vim_url": "http://10.234.12.47:5000/v3",
+                                "vim_tenant_name": "admin"},
+                               {"config": {}, "vim_tenant_name": "osm_demo", "schema_version": "1.1",
+                                "name": "OpenStack2",
+                                "vim_password": "gK5v4Gh2Pl41o6Skwp6RCw==", "vim_type": "openstack",
+                                "_admin": {"modified": 1567148372.2490237, "created": 1567148372.2490237,
+                                           "operationalState": "ENABLED",
+                                           "projects_read": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                           "deployed": {"RO-account": "b7fb0034-caf3-11e9-9388-02420aff000a",
+                                                        "RO": "b7f129ce-caf3-11e9-9388-02420aff000a"},
+                                           "projects_write": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                           "detailed-status": "Done"},
+                                "vim_user": "admin", "vim_url": "http://10.234.12.44:5000/v3",
+                                "_id": "6618d412-d7fc-4eb0-a6f8-d2c258e0e900"},
+                               {"config": {}, "schema_version": "1.1", "name": "OpenStack4",
+                                "vim_password": "6LScyPeMq3QFh3GRb/xwZw==", "vim_type": "openstack",
+                                "vim_tenant_name": "osm_demo",
+                                "_admin": {"modified": 1567599911.5108898, "created": 1567599911.5108898,
+                                           "operationalState": "ENABLED",
+                                           "projects_read": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                           "deployed": {"RO-account": "0a651200-cf0f-11e9-9388-02420aff000a",
+                                                        "RO": "0a4defc6-cf0f-11e9-9388-02420aff000a"},
+                                           "projects_write": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                           "detailed-status": "Done"},
+                                "vim_user": "admin", "vim_url": "http://10.234.12.43:5000/v3",
+                                "_id": "eda92f47-29b9-4007-9709-c1833dbfbe31"}]
+
+    vim_accounts_more_vims = [{"vim_password": "FxtnynxBCnouzAT4Hkerhg==", "config": {},
+                               "_admin": {"modified": 1564579854.0480285, "created": 1564579854.0480285,
+                                          "operationalState": "ENABLED",
+                                          "projects_read": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                          "deployed": {"RO-account": "6beb4e2e-b397-11e9-a7a3-02420aff0008",
+                                                       "RO": "6bcfc3fc-b397-11e9-a7a3-02420aff0008"},
+                                          "projects_write": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                          "detailed-status": "Done"},
+                               "name": "OpenStack1", "vim_type": "openstack",
+                               "_id": "92b056a7-38f5-438d-b8ee-3f93b3531f87",
+                               "schema_version": "1.1", "vim_user": "admin", "vim_url": "http://10.234.12.47:5000/v3",
+                               "vim_tenant_name": "admin"},
+                              {"config": {}, "vim_tenant_name": "osm_demo", "schema_version": "1.1",
+                               "name": "OpenStack2",
+                               "vim_password": "gK5v4Gh2Pl41o6Skwp6RCw==", "vim_type": "openstack",
+                               "_admin": {"modified": 1567148372.2490237, "created": 1567148372.2490237,
+                                          "operationalState": "ENABLED",
+                                          "projects_read": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                          "deployed": {"RO-account": "b7fb0034-caf3-11e9-9388-02420aff000a",
+                                                       "RO": "b7f129ce-caf3-11e9-9388-02420aff000a"},
+                                          "projects_write": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                          "detailed-status": "Done"},
+                               "vim_user": "admin", "vim_url": "http://10.234.12.44:5000/v3",
+                               "_id": "6618d412-d7fc-4eb0-a6f8-d2c258e0e900"},
+                              {"config": {}, "schema_version": "1.1", "name": "OpenStack4",
+                               "vim_password": "6LScyPeMq3QFh3GRb/xwZw==", "vim_type": "openstack",
+                               "vim_tenant_name": "osm_demo",
+                               "_admin": {"modified": 1567599911.5108898, "created": 1567599911.5108898,
+                                          "operationalState": "ENABLED",
+                                          "projects_read": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                          "deployed": {"RO-account": "0a651200-cf0f-11e9-9388-02420aff000a",
+                                                       "RO": "0a4defc6-cf0f-11e9-9388-02420aff000a"},
+                                          "projects_write": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                          "detailed-status": "Done"},
+                               "vim_user": "admin", "vim_url": "http://10.234.12.43:5000/v3",
+                               "_id": "eda92f47-29b9-4007-9709-c1833dbfbe31"},
+                              {"config": {}, "schema_version": "1.1", "name": "OpenStack3",
+                               "vim_password": "6LScyPeMq3QFh3GRb/xwZw==", "vim_type": "openstack",
+                               "vim_tenant_name": "osm_demo",
+                               "_admin": {"modified": 1567599911.5108898, "created": 1567599911.5108898,
+                                          "operationalState": "ENABLED",
+                                          "projects_read": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                          "deployed": {"RO-account": "0a651200-cf0f-11e9-9388-02420aff000a",
+                                                       "RO": "0a4defc6-cf0f-11e9-9388-02420aff000a"},
+                                          "projects_write": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                          "detailed-status": "Done"},
+                               "vim_user": "admin", "vim_url": "http://10.234.12.46:5000/v3",
+                               "_id": "eda92f47-29b9-4007-9709-c1833dbfbe31"},
+                              {"config": {}, "schema_version": "1.1", "name": "OpenStack5",
+                               "vim_password": "6LScyPeMq3QFh3GRb/xwZw==", "vim_type": "openstack",
+                               "vim_tenant_name": "osm_demo",
+                               "_admin": {"modified": 1567599911.5108898, "created": 1567599911.5108898,
+                                          "operationalState": "ENABLED",
+                                          "projects_read": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                          "deployed": {"RO-account": "0a651200-cf0f-11e9-9388-02420aff000a",
+                                                       "RO": "0a4defc6-cf0f-11e9-9388-02420aff000a"},
+                                          "projects_write": ["69915588-e5e2-46d3-96b0-a29bedef6f73"],
+                                          "detailed-status": "Done"},
+                               "vim_user": "admin", "vim_url": "http://1.1.1.1:5000/v3",
+                               "_id": "ffffffff-29b9-4007-9709-c1833dbfbe31"}]
+
+    def _produce_ut_vim_accounts_info(self, vim_accounts):
+        """
+        FIXME temporary, we will need more control over vim_urls and _id for test purpose - make a generator
+        :return: vim_url and _id as dict, i.e. extract these from vim_accounts data
+        """
+        return {_['vim_url']: _['_id'] for _ in vim_accounts}
+
+    def _adjust_path(self, file):
+        """In case we are not running from test directory,
+        then assume we are in top level directory (e.g. running from tox) and adjust file path accordingly"""
+        path_component = '/osm_pla/test/'
+        real_path = os.path.realpath(file)
+        if path_component not in real_path:
+            return os.path.dirname(real_path) + path_component + os.path.basename(real_path)
+        else:
+            return real_path
+
+    def _populate_pil_info(self, file):
+        """
+        Note str(Path()) is a 3.5 thing
+        """
+        with open(str(Path(self._adjust_path(file)))) as pp_fd:
+            test_data = yaml.safe_load_all(pp_fd)
+            return next(test_data)
+
+    def _get_ut_nsd_from_file(self, nsd_file_name):
+        """
+        creates the structure representing the nsd.
+
+        IMPORTANT NOTE: If using .yaml files from the NS packages for the unit tests (which we do),
+        then the files must be modified with respect to the way booleans are processed at on-boarding in OSM.
+        The following construct in the NS package yaml file:
+        mgmt-network: 'false'
+        will become a boolean in the MongoDB, and therefore the yaml used in these unit test must use yaml
+        tag as follows:
+        mgmt-network: !!bool False
+        The modification also applies to 'true' => !!bool True
+        This will ensure that the object returned from this function is as expected by PLA.
+        """
+        with open(str(Path(self._adjust_path(nsd_file_name)))) as nsd_fd:
+            test_data = yaml.safe_load_all(nsd_fd)
+            return next(test_data)
+
+    def _produce_ut_vnf_price_list(self):
+        price_list_file = "vnf_price_list.yaml"
+        with open(str(Path(self._adjust_path(price_list_file)))) as pl_fd:
+            price_list_data = yaml.safe_load_all(pl_fd)
+            return {i['vnfd']: {i1['vim_url']: i1['price'] for i1 in i['prices']} for i in next(price_list_data)}
+
+    def _produce_ut_vnf_test_price_list(self, price_list):
+        price_list_file = price_list
+        with open(str(Path(self._adjust_path(price_list_file)))) as pl_fd:
+            price_list_data = yaml.safe_load_all(pl_fd)
+            return {i['vnfd']: {i1['vim_url']: i1['price'] for i1 in i['prices']} for i in next(price_list_data)}
+
+    def test__produce_trp_link_characteristics_link_latency_with_more_vims(self):
+        """
+         -test with more(other) vims compared to pil
+         """
+        content_expected = [0, 0, 0, 0, 0, 120, 120, 130, 130, 140, 140, 230, 230, 240, 240,
+                            340, 340, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767]
+        nspdf = NsPlacementDataFactory(
+            self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts_more_vims),
+            self._produce_ut_vnf_price_list(),
+            nsd=None,
+            pil_info=self._populate_pil_info('pil_unittest1.yaml'),
+            pinning=None)
+        pil_latencies = nspdf._produce_trp_link_characteristics_data('pil_latency')
+        content_produced = [i for row in pil_latencies for i in row]
+        self.assertEqual(Counter(content_expected), Counter(content_produced), 'trp_link_latency incorrect')
+
+    def test__produce_trp_link_characteristics_link_latency_with_fewer_vims(self):
+        """
+        -test with fewer vims compared to pil
+        :return:
+        """
+        content_expected = [0, 0, 0, 120, 120, 140, 140, 240, 240]
+        nspdf = NsPlacementDataFactory(
+            self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts_fewer_vims),
+            self._produce_ut_vnf_price_list(),
+            nsd=None,
+            pil_info=self._populate_pil_info('pil_unittest1.yaml'),
+            pinning=None)
+        pil_latencies = nspdf._produce_trp_link_characteristics_data('pil_latency')
+        content_produced = [i for row in pil_latencies for i in row]
+        self.assertEqual(Counter(content_expected), Counter(content_produced), 'trp_link_latency incorrect')
+
+    def test__produce_trp_link_characteristic_not_supported(self):
+        """
+        - test with non-supported characteristic
+        """
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=None,
+                                       pil_info=self._populate_pil_info('pil_unittest1.yaml'), pinning=None)
+
+        with self.assertRaises(Exception) as e:
+            nspdf._produce_trp_link_characteristics_data('test_no_support')
+        self.assertRegex(str(e.exception), r'characteristic.*not supported', "invalid exception content")
+
+    def test__produce_trp_link_characteristics_link_latency(self):
+        """
+        -test with full set of vims as in pil
+        -test with fewer vims compared to pil
+        -test with more(other) vims compared to pil
+        -test with invalid/corrupt pil configuration file (e.g. missing endpoint), empty file, not yaml conformant
+        - test with non-supported characteristic
+
+        :return:
+        """
+        content_expected = [0, 0, 0, 0, 120, 120, 130, 130, 140, 140, 230, 230, 240, 240, 340, 340]
+
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=None,
+                                       pil_info=self._populate_pil_info('pil_unittest1.yaml'), pinning=None)
+        pil_latencies = nspdf._produce_trp_link_characteristics_data('pil_latency')
+        content_produced = [i for row in pil_latencies for i in row]
+        self.assertEqual(Counter(content_expected), Counter(content_produced), 'trp_link_latency incorrect')
+
+    def test__produce_trp_link_characteristics_link_jitter(self):
+        """
+        -test with full set of vims as in pil
+        """
+        content_expected = [0, 0, 0, 0, 1200, 1200, 1300, 1300, 1400, 1400, 2300, 2300, 2400, 2400, 3400, 3400]
+
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=None,
+                                       pil_info=self._populate_pil_info('pil_unittest1.yaml'), pinning=None)
+        pil_jitter = nspdf._produce_trp_link_characteristics_data('pil_jitter')
+        content_produced = [i for row in pil_jitter for i in row]
+        self.assertEqual(Counter(content_expected), Counter(content_produced), 'trp_link_jitter incorrect')
+
+    def test__produce_trp_link_characteristics_link_jitter_with_fewer_vims(self):
+        """
+        -test with fewer vims compared to pil, link jitter
+        """
+        content_expected = [0, 0, 0, 1200, 1200, 1400, 1400, 2400, 2400]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(self.vim_accounts_fewer_vims),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=None,
+                                       pil_info=self._populate_pil_info('pil_unittest1.yaml'), pinning=None)
+        pil_latencies = nspdf._produce_trp_link_characteristics_data('pil_jitter')
+        content_produced = [i for row in pil_latencies for i in row]
+        self.assertEqual(Counter(content_expected), Counter(content_produced), 'trp_link_jitter incorrect')
+
+    def test__produce_trp_link_characteristics_link_jitter_with_more_vims(self):
+        """
+        -test with more vims compared to pil, link jitter
+        """
+        content_expected = [0, 0, 0, 0, 0, 1200, 1200, 1300, 1300, 1400, 1400, 2300,
+                            2300, 2400, 2400, 3400, 3400, 32767, 32767, 32767, 32767, 32767, 32767, 32767, 32767]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(self.vim_accounts_more_vims),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=None,
+                                       pil_info=self._populate_pil_info('pil_unittest1.yaml'), pinning=None)
+        pil_latencies = nspdf._produce_trp_link_characteristics_data('pil_jitter')
+        content_produced = [i for row in pil_latencies for i in row]
+        self.assertEqual(Counter(content_expected), Counter(content_produced), 'trp_link_jitter incorrect')
+
+    def test__produce_trp_link_characteristics_link_price(self):
+        """
+        -test with full set of vims as in pil
+        """
+        content_expected = [0, 0, 0, 0, 12, 12, 13, 13, 14, 14, 23, 23, 24, 24, 34, 34]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=None,
+                                       pil_info=self._populate_pil_info('pil_unittest1.yaml'), pinning=None)
+        pil_prices = nspdf._produce_trp_link_characteristics_data('pil_price')
+        content_produced = [i for row in pil_prices for i in row]
+        self.assertEqual(Counter(content_expected), Counter(content_produced), 'invalid trp link prices')
+
+    def test__produce_trp_link_characteristics_link_price_with_fewer_vims(self):
+        """
+        -test with fewer vims compared to pil
+        """
+        content_expected = [0, 0, 0, 12, 12, 14, 14, 24, 24]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(self.vim_accounts_fewer_vims),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=None,
+                                       pil_info=self._populate_pil_info('pil_unittest1.yaml'), pinning=None)
+        pil_prices = nspdf._produce_trp_link_characteristics_data('pil_price')
+        content_produced = [i for row in pil_prices for i in row]
+        self.assertEqual(Counter(content_expected), Counter(content_produced), 'invalid trp link prices')
+
+    def test__produce_trp_link_characteristics_partly_constrained(self):
+        content_expected = [0, 0, 0, 0, 32767, 32767, 32767, 32767, 1200, 1200, 1400, 1400, 2400, 2400, 3400, 3400]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=None,
+                                       pil_info=self._populate_pil_info('pil_unittest2.yaml'), pinning=None)
+        pil_jitter = nspdf._produce_trp_link_characteristics_data('pil_jitter')
+        content_produced = [i for row in pil_jitter for i in row]
+        self.assertEqual(Counter(content_expected), Counter(content_produced),
+                         'invalid trp link jitter, partly constrained')
+
+    def test__produce_vld_desc_partly_constrained(self):
+        vld_desc_expected = [{'cp_refs': ['one', 'two'], 'jitter': 30},
+                             {'cp_refs': ['two', 'three'], 'latency': 120}]
+
+        nsd = self._get_ut_nsd_from_file('nsd_unittest2.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=None, pinning=None)
+        self.assertEqual(vld_desc_expected, nspdf._produce_vld_desc(),
+                         "vld_desc incorrect")
+
+    def test__produce_trp_link_characteristics_link_latency_not_yaml_conformant(self):
+        """
+        -test with invalid/corrupt pil configuration file (not yaml conformant)
+        """
+        with self.assertRaises(Exception) as e:
+            _ = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=None,
+                                       pil_info=self._populate_pil_info('not_yaml_conformant.yaml'),
+                                       pinning=None)
+        self.assertRegex(str(e.exception), r'mapping values are not allowed here.*', "invalid exception content")
+
+    def test__produce_trp_link_characteristics_with_invalid_pil_config(self):
+        """
+        -test with invalid/corrupt pil configuration file (missing endpoint)
+        """
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=None,
+                                       pil_info=self._populate_pil_info('corrupt_pil_endpoints_config_unittest1.yaml'),
+                                       pinning=None)
+        with self.assertRaises(Exception) as e:
+            _ = nspdf._produce_trp_link_characteristics_data('pil_latency')
+        self.assertEqual('list index out of range', str(e.exception), "unexpected exception")
+
+    def test__produce_vld_desc_w_instantiate_override(self):
+
+        vld_desc_expected = [{'cp_refs': ['one', 'two'], 'latency': 150, 'jitter': 30},
+                             {'cp_refs': ['two', 'three'], 'latency': 90, 'jitter': 30}]
+
+        nsd = self._get_ut_nsd_from_file('nsd_unittest_no_vld_constraints.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=None, pinning=None,
+                                       order_constraints=None)
+
+        self.assertNotEqual(nspdf._produce_vld_desc(),
+                            vld_desc_expected, "vld_desc incorrect")
+
+    def test__produce_vld_desc_nsd_w_instantiate_wo(self):
+        """
+        nsd w/ constraints, instantiate w/o constraints
+        :return:
+        """
+        vld_desc_expected = [{'cp_refs': ['one', 'two'], 'latency': 150, 'jitter': 30},
+                             {'cp_refs': ['two', 'three'], 'latency': 90, 'jitter': 30}]
+
+        nsd = self._get_ut_nsd_from_file('nsd_unittest3.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=None, pinning=None,
+                                       order_constraints=None)
+
+        self.assertEqual(vld_desc_expected, nspdf._produce_vld_desc(),
+                         "vld_desc incorrect")
+
+    def test__produce_vld_desc_nsd_w_instantiate_w(self):
+        """
+        nsd w/ constraints, instantiate w/ constraints => override
+        :return:
+        """
+        vld_desc_expected = [{'cp_refs': ['one', 'two'], 'latency': 120, 'jitter': 21},
+                             {'cp_refs': ['two', 'three'], 'latency': 121, 'jitter': 22}]
+
+        nsd = self._get_ut_nsd_from_file('nsd_unittest3.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=None, pinning=None,
+                                       order_constraints={
+                                           'vld-constraints': [{'id': 'three_vnf_constrained_nsd_vld1',
+                                                                'link-constraints': {'latency': 120,
+                                                                                     'jitter': 21}},
+                                                               {'id': 'three_vnf_constrained_nsd_vld2',
+                                                                'link-constraints': {'latency': 121,
+                                                                                     'jitter': 22}}]})
+
+        self.assertEqual(vld_desc_expected, nspdf._produce_vld_desc(),
+                         "vld_desc incorrect")
+
+    def test__produce_vld_desc_nsd_wo_instantiate_wo(self):
+        """
+        nsd w/o constraints, instantiate w/o constraints = no constraints in model
+        :return:
+        """
+        vld_desc_expected = [{'cp_refs': ['one', 'two']},
+                             {'cp_refs': ['two', 'three']}]
+
+        nsd = self._get_ut_nsd_from_file('nsd_unittest_no_vld_constraints.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=None, pinning=None,
+                                       order_constraints=None)
+
+        self.assertEqual(vld_desc_expected, nspdf._produce_vld_desc(),
+                         "vld_desc incorrect")
+
+    def test__produce_vld_desc_nsd_wo_instantiate_w(self):
+        """
+        nsd w/o constraints, instantiate w/ constraints => add constraints
+        :return:
+        """
+        vld_desc_expected = [{'cp_refs': ['one', 'two'], 'latency': 140, 'jitter': 41},
+                             {'cp_refs': ['two', 'three'], 'latency': 141, 'jitter': 42}]
+
+        nsd = self._get_ut_nsd_from_file('nsd_unittest_no_vld_constraints.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=None, pinning=None,
+                                       order_constraints={
+                                           'vld-constraints': [{'id': 'three_vnf_constrained_nsd_vld1',
+                                                                'link-constraints': {'latency': 140,
+                                                                                     'jitter': 41}},
+                                                               {'id': 'three_vnf_constrained_nsd_vld2',
+                                                                'link-constraints': {'latency': 141,
+                                                                                     'jitter': 42}}]})
+
+        self.assertEqual(vld_desc_expected, nspdf._produce_vld_desc(),
+                         "vld_desc incorrect")
+
+    def test__produce_vld_desc_nsd_wo_instantiate_w_faulty_input(self):
+        """
+        nsd w/o constraints, instantiate w/ constraints => add constraints that can be parsed
+        :return:
+        """
+        vld_desc_expected = [{'cp_refs': ['one', 'two']},
+                             {'cp_refs': ['two', 'three'], 'latency': 151}]
+
+        nsd = self._get_ut_nsd_from_file('nsd_unittest_no_vld_constraints.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=None, pinning=None,
+                                       order_constraints={'vld-constraints': [{'id': 'not_included_vld',
+                                                                               'misspelled-constraints':
+                                                                                   {'latency': 120,
+                                                                                    'jitter': 20}},
+                                                                              {'id': 'three_vnf_constrained_nsd_vld2',
+                                                                               'link-constraints': {
+                                                                                   'latency': 151}}]})
+
+        self.assertEqual(vld_desc_expected, nspdf._produce_vld_desc(),
+                         "vld_desc incorrect")
+
+    def test__produce_vld_desc_nsd_wo_instantiate_w_faulty_input_again(self):
+        """
+        nsd w/o constraints, instantiate w/ faulty constraints => add constraints that can be parsed
+        :return:
+        """
+        vld_desc_expected = [{'cp_refs': ['one', 'two'], 'jitter': 21},
+                             {'cp_refs': ['two', 'three']}]
+
+        nsd = self._get_ut_nsd_from_file('nsd_unittest_no_vld_constraints.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=None, pinning=None,
+                                       order_constraints={
+                                           'vld-constraints': [{'id': 'three_vnf_constrained_nsd_vld1',
+                                                                'link-constraints': {'delay': 120,
+                                                                                     'jitter': 21}},
+                                                               {'id': 'three_vnf_constrained_nsd_vld2',
+                                                                'misspelled-constraints': {'latency': 121,
+                                                                                           'jitter': 22}}]})
+
+        self.assertEqual(vld_desc_expected, nspdf._produce_vld_desc(),
+                         "vld_desc incorrect")
+
+    def test__produce_vld_desc_mgmt_network(self):
+        vld_desc_expected = [{'cp_refs': ['1', '2'], 'latency': 120, 'jitter': 20},
+                             {'cp_refs': ['2', '4'], 'latency': 50, 'jitter': 10},
+                             {'cp_refs': ['2', '3'], 'latency': 20, 'jitter': 10}, ]
+
+        nsd = self._get_ut_nsd_from_file('test_five_nsd.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=None, pinning=None,
+                                       order_constraints=None)
+
+        self.assertEqual(vld_desc_expected, nspdf._produce_vld_desc(), "vld_desc incorrect")
+
+    def test__produce_vld_desc_single_vnf_nsd(self):
+        vld_desc_expected = []
+
+        nsd = self._get_ut_nsd_from_file('nsd_unittest4.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=None, pinning=None,
+                                       order_constraints=None)
+
+        self.assertEqual(vld_desc_expected, nspdf._produce_vld_desc(), "vld_desc_incorrect")
+
+    def test__produce_vld_desc(self):
+        """
+
+        :return:
+        """
+        vld_desc_expected = [{'cp_refs': ['one', 'two'], 'latency': 150, 'jitter': 30},
+                             {'cp_refs': ['two', 'three'], 'latency': 90, 'jitter': 30}]
+
+        nsd = self._get_ut_nsd_from_file('nsd_unittest3.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=None, pinning=None,
+                                       order_constraints=None)
+
+        self.assertEqual(vld_desc_expected, nspdf._produce_vld_desc(), "vld_desc incorrect")
+
+    def test__produce_ns_desc(self):
+        """
+        ToDo
+        - price list sheet with more vims than associated with session
+        - price list sheet with fewer vims than associated with session
+        - nsd with different vndfd-id-refs
+        - fault case scenarios with non-existing vims, non-existing vnfds
+        """
+        nsd = self._get_ut_nsd_from_file('nsd_unittest3.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=None,
+                                       pinning=None)
+
+        ns_desc = nspdf._produce_ns_desc()
+        # check that all expected member-vnf-index are present
+        vnfs = [e['vnf_id'] for e in ns_desc]
+        self.assertEqual(Counter(['one', 'two', 'three']), Counter(vnfs), 'vnf_id invalid')
+
+        expected_keys = ['vnf_id', 'vnf_price_per_vim']
+        for e in ns_desc:
+            # check that vnf_price_per_vim has proper values
+            self.assertEqual(Counter([5, 10, 30, 30]), Counter(e['vnf_price_per_vim']), 'vnf_price_per_vim invalid')
+            # check that no pinning directives included
+            self.assertEqual(Counter(expected_keys), Counter(e.keys()), 'pinning directive misplaced')
+
+    def test__produce_ns_desc_with_more_vims(self):
+        nsd = self._get_ut_nsd_from_file('nsd_unittest1.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(self.vim_accounts_more_vims),
+                                       self._produce_ut_vnf_test_price_list('vnf_price_list_more_vims.yaml'),
+                                       nsd=nsd,
+                                       pil_info=None,
+                                       pinning=None)
+
+        ns_desc = nspdf._produce_ns_desc()
+        # check that all expected member-vnf-index are present
+        vnfs = [e['vnf_id'] for e in ns_desc]
+        self.assertEqual(Counter([1, 3, 2]), Counter(vnfs), 'vnf_id invalid')
+
+        expected_keys = ['vnf_id', 'vnf_price_per_vim']
+        for e in ns_desc:
+            # check that vnf_price_per_vim has proper values
+            self.assertEqual(Counter([5, 10, 30, 30, 3]), Counter(e['vnf_price_per_vim']), 'vnf_price_per_vim invalid')
+            # check that no pinning directives included
+            self.assertEqual(Counter(expected_keys), Counter(e.keys()), 'pinning directive misplaced')
+
+    def test__produce_ns_desc_with_fewer_vims(self):
+        nsd = self._get_ut_nsd_from_file('nsd_unittest1.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(self.vim_accounts_fewer_vims),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=None,
+                                       pinning=None)
+
+        ns_desc = nspdf._produce_ns_desc()
+        # check that all expected member-vnf-index are present
+        vnfs = [e['vnf_id'] for e in ns_desc]
+        self.assertEqual(Counter([1, 3, 2]), Counter(vnfs), 'vnf_id invalid')
+
+        expected_keys = ['vnf_id', 'vnf_price_per_vim']
+        for e in ns_desc:
+            # check that vnf_price_per_vim has proper values
+            self.assertEqual(Counter([5, 10, 30]), Counter(e['vnf_price_per_vim']), 'vnf_price_per_vim invalid')
+            # check that no pinning directives included
+            self.assertEqual(Counter(expected_keys), Counter(e.keys()), 'pinning directive misplaced')
+
+    def test__produce_ns_desc_w_pinning(self):
+        nsd = self._get_ut_nsd_from_file('nsd_unittest3.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        pinning = [{'member-vnf-index': 'two', 'vimAccountId': '331ffdec-44a8-4707-94a1-af7a292d9735'}]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=None,
+                                       pinning=pinning)
+        ns_desc = nspdf._produce_ns_desc()
+        # check that all expected member-vnf-index are present
+        vnfs = [e['vnf_id'] for e in ns_desc]
+        self.assertEqual(Counter(['one', 'three', 'two']), Counter(vnfs), 'vnf_id invalid')
+
+        for e in ns_desc:
+            # check that vnf_price_per_vim has proper values
+            self.assertEqual(Counter([5, 10, 30, 30]), Counter(e['vnf_price_per_vim']), 'vnf_price_per_vim invalid')
+            # check that member-vnf-index 2 is pinned correctly
+            if e['vnf_id'] == 'two':
+                self.assertTrue('vim_account' in e.keys(), 'missing pinning directive')
+                self.assertTrue(pinning[0]['vimAccountId'] == e['vim_account'][3:].replace('_', '-'),
+                                'invalid pinning vim-account')
+            else:
+                self.assertTrue('vim-account' not in e.keys(), 'pinning directive misplaced')
+
+    @mock.patch.object(NsPlacementDataFactory, '_produce_trp_link_characteristics_data')
+    @mock.patch.object(NsPlacementDataFactory, '_produce_vld_desc')
+    @mock.patch.object(NsPlacementDataFactory, '_produce_ns_desc')
+    def test_create_ns_placement_data_wo_order(self, mock_prd_ns_desc, mock_prd_vld_desc, mock_prd_trp_link_char):
+        """
+        :return:
+        """
+        vim_accounts_expected = [v.replace('-', '_') for v in ['vim92b056a7-38f5-438d-b8ee-3f93b3531f87',
+                                                               'vim6618d412-d7fc-4eb0-a6f8-d2c258e0e900',
+                                                               'vim331ffdec-44a8-4707-94a1-af7a292d9735',
+                                                               'vimeda92f47-29b9-4007-9709-c1833dbfbe31']]
+
+        nsd = self._get_ut_nsd_from_file('nsd_unittest3.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=self._populate_pil_info('pil_unittest1.yaml'),
+                                       pinning=None,
+                                       order_constraints=None)
+        nspd = nspdf.create_ns_placement_data()
+        self.assertEqual(Counter(vim_accounts_expected), Counter(nspd['vim_accounts']),
+                         "vim_accounts incorrect")
+        # mock1.assert_called_once() Note for python > 3.5
+        self.assertTrue(mock_prd_ns_desc.called, '_produce_ns_desc not called')
+        # mock2.assert_called_once() Note for python > 3.5
+        self.assertTrue(mock_prd_vld_desc.called, ' _produce_vld_desc not called')
+        mock_prd_trp_link_char.assert_has_calls([call('pil_latency'), call('pil_jitter'), call('pil_price')])
+
+        regexps = [r"\{.*\}", r".*'file':.*mznplacement.py", r".*'time':.*datetime.datetime\(.*\)"]
+        generator_data = str(nspd['generator_data'])
+        for regex in regexps:
+            self.assertRegex(generator_data, regex, "generator data invalid")
+
+    @mock.patch.object(NsPlacementDataFactory, '_produce_trp_link_characteristics_data')
+    @mock.patch.object(NsPlacementDataFactory, '_produce_vld_desc')
+    @mock.patch.object(NsPlacementDataFactory, '_produce_ns_desc')
+    def test_create_ns_placement_data_w_order(self, mock_prd_ns_desc, mock_prd_vld_desc,
+                                              mock_prd_trp_link_char):
+        """
+        :return:
+        """
+        vim_accounts_expected = [v.replace('-', '_') for v in ['vim92b056a7-38f5-438d-b8ee-3f93b3531f87',
+                                                               'vim6618d412-d7fc-4eb0-a6f8-d2c258e0e900',
+                                                               'vim331ffdec-44a8-4707-94a1-af7a292d9735',
+                                                               'vimeda92f47-29b9-4007-9709-c1833dbfbe31']]
+
+        nsd = self._get_ut_nsd_from_file('nsd_unittest3.yaml')
+        nsd = nsd['nsd:nsd-catalog']['nsd'][0]
+        nspdf = NsPlacementDataFactory(self._produce_ut_vim_accounts_info(TestNsPlacementDataFactory.vim_accounts),
+                                       self._produce_ut_vnf_price_list(),
+                                       nsd=nsd,
+                                       pil_info=self._populate_pil_info('pil_unittest1.yaml'),
+                                       pinning=None,
+                                       order_constraints={
+                                           'vld-constraints': [{'id': 'three_vnf_constrained_nsd_vld1',
+                                                                'link-constraints': {'latency': 120,
+                                                                                     'jitter': 21}},
+                                                               {'id': 'three_vnf_constrained_nsd_vld2',
+                                                                'link-constraints': {'latency': 121,
+                                                                                     'jitter': 22}}]}
+                                       )
+        nspd = nspdf.create_ns_placement_data()
+        self.assertEqual(Counter(vim_accounts_expected), Counter(nspd['vim_accounts']),
+                         "vim_accounts incorrect")
+        # mock1.assert_called_once() Note for python > 3.5
+        self.assertTrue(mock_prd_ns_desc.called, '_produce_ns_desc not called')
+        # mock2.assert_called_once() Note for python > 3.5
+        self.assertTrue(mock_prd_vld_desc.called, ' _produce_vld_desc not called')
+        mock_prd_trp_link_char.assert_has_calls([call('pil_latency'), call('pil_jitter'), call('pil_price')])
+
+        regexps = [r"\{.*\}", r".*'file':.*mznplacement.py", r".*'time':.*datetime.datetime\(.*\)"]
+        generator_data = str(nspd['generator_data'])
+        for regex in regexps:
+            self.assertRegex(generator_data, regex, "generator data invalid")
+
+
+if __name__ == "__main__":
+    if __name__ == '__main__':
+        unittest.main()
diff --git a/osm_pla/test/test_server.py b/osm_pla/test/test_server.py
new file mode 100644 (file)
index 0000000..56cf212
--- /dev/null
@@ -0,0 +1,517 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+import asyncio
+# import platform
+# import random
+import os
+import sys
+# import unittest
+from unittest import TestCase, mock
+from unittest.mock import Mock
+
+# import pkg_resources
+import yaml
+
+from osm_pla.placement.mznplacement import NsPlacementDataFactory, MznPlacementConductor
+from pathlib import Path
+
+# need to Mock the imports from osm_common made in Server and Config beforehand
+sys.modules['osm_common'] = Mock()
+from osm_pla.server.server import Server  # noqa: E402
+from osm_pla.config.config import Config  # noqa: E402
+
+nslcmop_record_wo_pinning = {'statusEnteredTime': 1574625718.8280587, 'startTime': 1574625718.8280587,
+                             '_admin': {'created': 1574625718.8286533,
+                                        'projects_write': ['61e4bbab-9659-4abc-a01d-ba3a307becf9'],
+                                        'worker': 'e5121e773e8b', 'modified': 1574625718.8286533,
+                                        'projects_read': ['61e4bbab-9659-4abc-a01d-ba3a307becf9']},
+                             'operationState': 'PROCESSING', 'nsInstanceId': '45f588bd-5bf4-4181-b13b-f16a55a23be4',
+                             'lcmOperationType': 'instantiate', 'isCancelPending': False,
+                             'id': 'a571b1de-19e5-48bd-b252-ba0ad7d540c9',
+                             '_id': 'a571b1de-19e5-48bd-b252-ba0ad7d540c9',
+                             'isAutomaticInvocation': False,
+                             'links': {'nsInstance': '/osm/nslcm/v1/ns_instances/45f588bd-5bf4-4181-b13b-f16a55a23be4',
+                                       'self': '/osm/nslcm/v1/ns_lcm_op_occs/a571b1de-19e5-48bd-b252-ba0ad7d540c9'},
+                             'operationParams': {'vimAccountId': 'eb553051-5b6c-4ad6-939b-2ad23bd82e57',
+                                                 'lcmOperationType': 'instantiate', 'nsDescription': 'just a test',
+                                                 'nsdId': '0f4e658f-62a6-4f73-8623-270e8f0a18bc',
+                                                 'nsName': 'ThreeNsd plain placement', 'ssh_keys': [],
+                                                 'validVimAccounts': ['eb553051-5b6c-4ad6-939b-2ad23bd82e57',
+                                                                      '576bbe0a-b95d-4ced-a63e-f387f8e6e2ce',
+                                                                      '3d1ffc5d-b36d-4f69-8356-7f59c740ca2f',
+                                                                      'db54dcd4-9fc4-441c-8820-17bce0aef2c3'],
+                                                 'nsr_id': '45f588bd-5bf4-4181-b13b-f16a55a23be4',
+                                                 'placement-engine': 'PLA',
+                                                 'nsInstanceId': '45f588bd-5bf4-4181-b13b-f16a55a23be4'}}
+
+nslcmop_record_w_pinning = {'statusEnteredTime': 1574627411.420499, 'startTime': 1574627411.420499,
+                            '_admin': {'created': 1574627411.4209971,
+                                       'projects_write': ['61e4bbab-9659-4abc-a01d-ba3a307becf9'],
+                                       'worker': 'e5121e773e8b', 'modified': 1574627411.4209971,
+                                       'projects_read': ['61e4bbab-9659-4abc-a01d-ba3a307becf9']},
+                            'operationState': 'PROCESSING',
+                            'nsInstanceId': '61587478-ea25-44eb-9f13-7005046ddb08', 'lcmOperationType': 'instantiate',
+                            'isCancelPending': False, 'id': '80f95a17-6fa7-408d-930f-40aa4589d074',
+                            '_id': '80f95a17-6fa7-408d-930f-40aa4589d074',
+                            'isAutomaticInvocation': False,
+                            'links': {
+                                'nsInstance': '/osm/nslcm/v1/ns_instances/61587478-ea25-44eb-9f13-7005046ddb08',
+                                'self': '/osm/nslcm/v1/ns_lcm_op_occs/80f95a17-6fa7-408d-930f-40aa4589d074'},
+                            'operationParams': {
+                                'vimAccountId': '576bbe0a-b95d-4ced-a63e-f387f8e6e2ce',
+                                'nsr_id': '61587478-ea25-44eb-9f13-7005046ddb08',
+                                'nsDescription': 'default description', 'nsdId': '0f4e658f-62a6-4f73-8623-270e8f0a18bc',
+                                'validVimAccounts': [
+                                    'eb553051-5b6c-4ad6-939b-2ad23bd82e57', '576bbe0a-b95d-4ced-a63e-f387f8e6e2ce',
+                                    '3d1ffc5d-b36d-4f69-8356-7f59c740ca2f',
+                                    'db54dcd4-9fc4-441c-8820-17bce0aef2c3'], 'nsName': 'ThreeVnfTest2',
+                                'wimAccountId': False, 'vnf': [
+                                    {'vimAccountId': '3d1ffc5d-b36d-4f69-8356-7f59c740ca2f', 'member-vnf-index': '1'}],
+                                'placementEngine': 'PLA',
+                                'nsInstanceId': '61587478-ea25-44eb-9f13-7005046ddb08',
+                                'lcmOperationType': 'instantiate'}}
+
+nslcmop_record_w_pinning_and_order_constraints = {
+    'links': {'nsInstance': '/osm/nslcm/v1/ns_instances/7c4c3d94-ebb2-44e8-b236-d876b118434e',
+              'self': '/osm/nslcm/v1/ns_lcm_op_occs/fd7c9e15-38aa-4fc5-913c-417b26859fb0'},
+    'id': 'fd7c9e15-38aa-4fc5-913c-417b26859fb0', 'operationState': 'PROCESSING', 'isAutomaticInvocation': False,
+    'nsInstanceId': '7c4c3d94-ebb2-44e8-b236-d876b118434e', '_id': 'fd7c9e15-38aa-4fc5-913c-417b26859fb0',
+    'isCancelPending': False, 'startTime': 1574772631.6932728, 'statusEnteredTime': 1574772631.6932728,
+    'lcmOperationType': 'instantiate',
+    'operationParams': {'placementEngine': 'PLA',
+                        'placement-constraints': {
+                            'vld-constraints': [{
+                                'id': 'three_vnf_constrained_vld_1',
+                                'link-constraints': {
+                                    'latency': 120,
+                                    'jitter': 20}},
+                                {
+                                    'link_constraints': {
+                                        'latency': 120,
+                                        'jitter': 20},
+                                    'id': 'three_vnf_constrained_nsd_vld_2'}]},
+                        'nsName': 'ThreeVnfTest2',
+                        'nsDescription': 'default description',
+                        'nsr_id': '7c4c3d94-ebb2-44e8-b236-d876b118434e',
+                        'nsdId': '0f4e658f-62a6-4f73-8623-270e8f0a18bc',
+                        'validVimAccounts': ['eb553051-5b6c-4ad6-939b-2ad23bd82e57',
+                                             '576bbe0a-b95d-4ced-a63e-f387f8e6e2ce',
+                                             '3d1ffc5d-b36d-4f69-8356-7f59c740ca2f',
+                                             'db54dcd4-9fc4-441c-8820-17bce0aef2c3'],
+                        'wimAccountId': False,
+                        'vnf': [{'member-vnf-index': '3', 'vimAccountId': '3d1ffc5d-b36d-4f69-8356-7f59c740ca2f'}],
+                        'nsInstanceId': '7c4c3d94-ebb2-44e8-b236-d876b118434e',
+                        'lcmOperationType': 'instantiate',
+                        'vimAccountId': '576bbe0a-b95d-4ced-a63e-f387f8e6e2ce'},
+    '_admin': {'projects_read': ['61e4bbab-9659-4abc-a01d-ba3a307becf9'], 'modified': 1574772631.693885,
+               'projects_write': ['61e4bbab-9659-4abc-a01d-ba3a307becf9'], 'created': 1574772631.693885,
+               'worker': 'e5121e773e8b'}}
+
+list_of_vims = [{"_id": "73cd1a1b-333e-4e29-8db2-00d23bd9b644", "vim_user": "admin", "name": "OpenStack1",
+                 "vim_url": "http://10.234.12.47:5000/v3", "vim_type": "openstack", "vim_tenant_name": "osm_demo",
+                 "vim_password": "O/mHomfXPmCrTvUbYXVoyg==", "schema_version": "1.1",
+                 "_admin": {"modified": 1565597984.3155663,
+                            "deployed": {"RO": "f0c1b516-bcd9-11e9-bb73-02420aff0030",
+                                         "RO-account": "f0d45496-bcd9-11e9-bb73-02420aff0030"},
+                            "projects_write": ["admin"], "operationalState": "ENABLED", "detailed-status": "Done",
+                            "created": 1565597984.3155663, "projects_read": ["admin"]},
+                 "config": {}},
+                {"_id": "684165ea-2cf9-4fbd-ac22-8464ca07d1d8", "vim_user": "admin",
+                 "name": "OpenStack2", "vim_url": "http://10.234.12.44:5000/v3",
+                 "vim_tenant_name": "osm_demo", "vim_password": "Rw7gln9liP4ClMyHd5OFsw==",
+                 "description": "Openstack on NUC", "vim_type": "openstack",
+                 "admin": {"modified": 1566474766.7288046,
+                           "deployed": {"RO": "5bc59656-c4d3-11e9-b1e5-02420aff0006",
+                                        "RO-account": "5bd772e0-c4d3-11e9-b1e5-02420aff0006"},
+                           "projects_write": ["admin"], "operationalState": "ENABLED",
+                           "detailed-status": "Done", "created": 1566474766.7288046,
+                           "projects_read": ["admin"]},
+                 "config": {}, "schema_version": "1.1"},
+                {"_id": "8460b670-31cf-4fae-9f3e-d0dd6c57b61e", "vim_user": "admin", "name": "OpenStack1",
+                 "vim_url": "http://10.234.12.47:5000/v3", "vim_type": "openstack",
+                 "vim_tenant_name": "osm_demo", "vim_password": "NsgJJDlCdKreX30FQFNz7A==",
+                 "description": "Openstack on Dell",
+                 "_admin": {"modified": 1566992449.5942867,
+                            "deployed": {"RO": "aed94f86-c988-11e9-bb38-02420aff0088",
+                                         "RO-account": "aee72fac-c988-11e9-bb38-02420aff0088"},
+                            "projects_write": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"],
+                            "operationalState": "ENABLED", "detailed-status": "Done", "created": 1566992449.5942867,
+                            "projects_read": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"]}, "config": {},
+                 "schema_version": "1.1"},
+                {"_id": "9b8b5268-acb7-4893-b494-a77656b418f2",
+                 "vim_user": "admin", "name": "OpenStack2",
+                 "vim_url": "http://10.234.12.44:5000/v3",
+                 "vim_type": "openstack", "vim_tenant_name": "osm_demo",
+                 "vim_password": "AnAV3xtoiwwdnAfv0KahSw==",
+                 "description": "Openstack on NUC",
+                 "_admin": {"modified": 1566992484.9190753,
+                            "deployed": {"RO": "c3d61158-c988-11e9-bb38-02420aff0088",
+                                         "RO-account": "c3ec973e-c988-11e9-bb38-02420aff0088"},
+                            "projects_write": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"],
+                            "operationalState": "ENABLED", "detailed-status": "Done",
+                            "created": 1566992484.9190753,
+                            "projects_read": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"]},
+                 "config": {}, "schema_version": "1.1"},
+                {"_id": "3645f215-f32d-4355-b5ab-df0a2e2233c3", "vim_user": "admin", "name": "OpenStack3",
+                 "vim_url": "http://10.234.12.46:5000/v3", "vim_tenant_name": "osm_demo",
+                 "vim_password": "XkG2w8e8/DiuohCFNp0+lQ==", "description": "Openstack on NUC",
+                 "vim_type": "openstack",
+                 "_admin": {"modified": 1567421247.7016313,
+                            "deployed": {"RO": "0e80f6a2-cd6f-11e9-bb50-02420aff00b6",
+                                         "RO-account": "0e974524-cd6f-11e9-bb50-02420aff00b6"},
+                            "projects_write": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"],
+                            "operationalState": "ENABLED", "detailed-status": "Done",
+                            "created": 1567421247.7016313,
+                            "projects_read": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"]},
+                 "schema_version": "1.1", "config": {}},
+                {"_id": "53f8f2bb-88b5-4bf9-babf-556698b5261f",
+                 "vim_user": "admin", "name": "OpenStack4",
+                 "vim_url": "http://10.234.12.43:5000/v3",
+                 "vim_tenant_name": "osm_demo",
+                 "vim_password": "GLrgVn8fMVneXMZq1r4yVA==",
+                 "description": "Openstack on NUC",
+                 "vim_type": "openstack",
+                 "_admin": {"modified": 1567421296.1576457,
+                            "deployed": {
+                                "RO": "2b43c756-cd6f-11e9-bb50-02420aff00b6",
+                                "RO-account": "2b535aea-cd6f-11e9-bb50-02420aff00b6"},
+                            "projects_write": [
+                                "0a5d0c5b-7e08-48a1-a686-642a038bbd70"],
+                            "operationalState": "ENABLED",
+                            "detailed-status": "Done",
+                            "created": 1567421296.1576457,
+                            "projects_read": [
+                                "0a5d0c5b-7e08-48a1-a686-642a038bbd70"]},
+                 "schema_version": "1.1", "config": {}}]
+
+# FIXME this is not correct re mgmt-network setting.
+nsd_from_db = {"_id": "15fc1941-f095-4cd8-af2d-1000bd6d9eaa", "short-name": "three_vnf_constrained_nsd_low",
+               "name": "three_vnf_constrained_nsd_low", "version": "1.0",
+               "description": "Placement constraints NSD",
+               "_admin": {"modified": 1567672251.7531693,
+                          "storage": {"pkg-dir": "ns_constrained_nsd", "fs": "local",
+                                      "descriptor": "ns_constrained_nsd/ns_constrained_nsd.yaml",
+                                      "zipfile": "package.tar.gz",
+                                      "folder": "15fc1941-f095-4cd8-af2d-1000bd6d9eaa", "path": "/app/storage/"},
+                          "onboardingState": "ONBOARDED", "usageState": "NOT_IN_USE",
+                          "projects_write": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"], "operationalState": "ENABLED",
+                          "userDefinedData": {}, "created": 1567672251.7531693,
+                          "projects_read": ["0a5d0c5b-7e08-48a1-a686-642a038bbd70"]},
+               "constituent-vnfd": [{"vnfd-id-ref": "cirros_vnfd_v2", "member-vnf-index": "one"},
+                                    {"vnfd-id-ref": "cirros_vnfd_v2", "member-vnf-index": "two"},
+                                    {"vnfd-id-ref": "cirros_vnfd_v2", "member-vnf-index": "three"}],
+               "id": "three_vnf_constrained_nsd_low", "vendor": "ArctosLabs",
+               "vld": [{"type": "ELAN", "short-name": "ns_constrained_nsd_low_vld1",
+                        "link-constraint": [{"constraint-type": "LATENCY", "value": "100"},
+                                            {"constraint-type": "JITTER", "value": "30"}],
+                        "vim-network-name": "external", "mgmt-network": True,
+                        "id": "three_vnf_constrained_nsd_low_vld1",
+                        "vnfd-connection-point-ref": [
+                            {"vnfd-id-ref": "cirros_vnfd_v2", "member-vnf-index-ref": "one",
+                             "vnfd-connection-point-ref": "vnf-cp0"},
+                            {"vnfd-id-ref": "cirros_vnfd_v2",
+                             "member-vnf-index-ref": "two",
+                             "vnfd-connection-point-ref": "vnf-cp0"}],
+                        "name": "ns_constrained_nsd_vld1"},
+                       {"type": "ELAN", "short-name": "ns_constrained_nsd_low_vld2",
+                        "link-constraint": [{"constraint-type": "LATENCY", "value": "50"},
+                                            {"constraint-type": "JITTER", "value": "30"}],
+                        "vim-network-name": "lanretxe", "mgmt-network": True,
+                        "id": "three_vnf_constrained_nsd_low_vld2",
+                        "vnfd-connection-point-ref": [
+                            {"vnfd-id-ref": "cirros_vnfd_v2", "member-vnf-index-ref": "two",
+                             "vnfd-connection-point-ref": "vnf-cp0"},
+                            {"vnfd-id-ref": "cirros_vnfd_v2", "member-vnf-index-ref": "three",
+                             "vnfd-connection-point-ref": "vnf-cp0"}],
+                        "name": "ns_constrained_nsd_vld2"}]}
+
+
+######################################################
+# These are helper functions to handle unittest of asyncio.
+# Inspired by: https://blog.miguelgrinberg.com/post/unit-testing-asyncio-code
+def _run(co_routine):
+    return asyncio.get_event_loop().run_until_complete(co_routine)
+
+
+def _async_mock(*args, **kwargs):
+    m = mock.MagicMock(*args, **kwargs)
+
+    async def mock_coro(*args, **kwargs):
+        return m(*args, **kwargs)
+
+    mock_coro.mock = m
+    return mock_coro
+
+
+######################################################
+
+class TestServer(TestCase):
+
+    def _produce_ut_vim_accounts_info(self, list_of_vims):
+        """
+        FIXME temporary, we will need more control over vim_urls and _id for test purpose - make a generator
+        :return: vim_url and _id as dict, i.e. extract these from vim_accounts data
+        """
+        return {_['vim_url']: _['_id'] for _ in list_of_vims}
+
+    def _produce_ut_vnf_price_list(self):
+        price_list_file = "vnf_price_list.yaml"
+        with open(str(Path(price_list_file))) as pl_fd:
+            price_list_data = yaml.safe_load_all(pl_fd)
+            return {i['vnfd']: {i1['vim_url']: i1['price'] for i1 in i['prices']} for i in next(price_list_data)}
+
+    def _populate_pil_info(self, file):
+        """
+        FIXME we need more control over content in pil information - more files or generator and data
+        Note str(Path()) is a 3.5 thing
+        """
+        with open(str(Path(file))) as pp_fd:
+            test_data = yaml.safe_load_all(pp_fd)
+            return next(test_data)
+
+    @mock.patch.object(Config, '_read_config_file')
+    @mock.patch.object(Config, 'get', side_effect=['doesnotmatter', 'memory', 'memory', 'local', 'doesnotmatter'])
+    def serverSetup(self, mock_get, mock__read_config_file):
+        """
+        Helper that returns a Server object
+        :return:
+        """
+        cfg = Config(None)
+        return Server(cfg)
+
+    def _adjust_path(self, file):
+        """In case we are not running from test directory,
+        then assume we are in top level directory (e.g. running from tox) and adjust file path accordingly"""
+        path_component = '/osm_pla/test/'
+        real_path = os.path.realpath(file)
+        if path_component not in real_path:
+            return os.path.dirname(real_path) + path_component + os.path.basename(real_path)
+        else:
+            return real_path
+
+    def test__get_nslcmop(self):
+        server = self.serverSetup()
+        server.db = Mock()
+        _ = server._get_nslcmop(nslcmop_record_wo_pinning["id"])
+        server.db.get_one.assert_called_with("nslcmops", {'_id': nslcmop_record_wo_pinning["id"]})
+
+    def test__get_nsd(self):  # OK
+        server = self.serverSetup()
+        server.db = Mock()
+        _ = server._get_nsd(nslcmop_record_wo_pinning['operationParams']['nsdId'])
+        server.db.get_one.assert_called_with("nsds", {'_id': nslcmop_record_wo_pinning['operationParams']['nsdId']})
+
+    def test__get_vim_accounts(self):  # OK
+        server = self.serverSetup()
+        server.db = Mock()
+        _ = server._get_vim_accounts(nslcmop_record_wo_pinning['operationParams']['validVimAccounts'])
+        server.db.get_list.assert_called_with('vim_accounts',
+                                              {'_id': nslcmop_record_wo_pinning['operationParams']['validVimAccounts']})
+
+    def test__get_vnf_price_list(self):
+        server = self.serverSetup()
+        pl = server._get_vnf_price_list(Path(self._adjust_path('./vnf_price_list.yaml')))
+        self.assertIs(type(pl), dict, "price list not a dictionary")
+        for k, v in pl.items():
+            self.assertIs(type(v), dict, "price list values not a dict")
+
+    def test__get_pil_info(self):
+        server = self.serverSetup()
+        ppi = server._get_pil_info(Path(self._adjust_path('./pil_price_list.yaml')))
+        self.assertIs(type(ppi), dict, "pil is not a dict")
+        self.assertIn('pil', ppi.keys(), "pil has no pil key")
+        self.assertIs(type(ppi['pil']), list, "pil does not contain a list")
+        # check for expected keys
+        expected_keys = {'pil_description', 'pil_price', 'pil_latency', 'pil_jitter', 'pil_endpoints'}
+        self.assertEqual(expected_keys, ppi['pil'][0].keys(), 'expected keys not found')
+
+    def test_handle_kafka_command(self):  # OK
+        server = self.serverSetup()
+        server.loop.create_task = Mock()
+        server.handle_kafka_command('pli', 'get_placement', {})
+        server.loop.create_task.assert_not_called()
+        server.loop.create_task.reset_mock()
+        server.handle_kafka_command('pla', 'get_placement', {'nslcmopId': nslcmop_record_wo_pinning["id"]})
+        self.assertTrue(server.loop.create_task.called, 'create_task not called')
+        args, kwargs = server.loop.create_task.call_args
+        self.assertIn('Server.get_placement', str(args[0]), 'get_placement not called')
+
+    @mock.patch.object(NsPlacementDataFactory, '__init__', lambda x0, x1, x2, x3, x4, x5, x6: None)
+    @mock.patch.object(MznPlacementConductor, 'do_placement_computation')
+    @mock.patch.object(NsPlacementDataFactory, 'create_ns_placement_data')
+    @mock.patch.object(Server, '_get_vim_accounts')
+    @mock.patch.object(Server, '_get_nsd')
+    @mock.patch.object(Server, '_get_nslcmop')
+    @mock.patch.object(Server, '_get_vnf_price_list')
+    @mock.patch.object(Server, '_get_pil_info')
+    def test_get_placement(self, mock_get_pil_info, mock_get_vnf_price_list, mock__get_nslcmop, mock__get_nsd,
+                           mock__get_vim_accounts,
+                           mock_create_ns_placement_data,
+                           mock_do_placement_computation):
+        """
+        run _get_placement and check that things get called as expected
+        :return:
+        """
+        placement_ret_val = [{'vimAccountId': 'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'one'},
+                             {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'two'},
+                             {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'three'}]
+        server = self.serverSetup()
+
+        server.msgBus.aiowrite = _async_mock()
+        mock__get_nsd.return_value = nsd_from_db
+        mock__get_vim_accounts.return_value = list_of_vims
+
+        # FIXME need update to match nslcmop, not for test but for consistency
+        mock_do_placement_computation.return_value = placement_ret_val
+        _run(server.get_placement(nslcmop_record_wo_pinning['id']))
+
+        self.assertTrue(mock_get_vnf_price_list.called, '_get_vnf_price_list not called as expected')
+        self.assertTrue(mock_get_pil_info.called, '_get_pil_info not called as expected')
+        self.assertTrue(mock__get_nslcmop.called, '_get_nslcmop not called as expected')
+        # mock_get_nsd.assert_called_once() assert_called_once() for python > 3.5
+        self.assertTrue(mock__get_nsd.called, 'get_nsd not called as expected')
+        # mock_get_enabled_vims.assert_called_once() assert_called_once() for python > 3.5
+        self.assertTrue(mock__get_vim_accounts.called, 'get_vim_accounts not called as expected')
+        # mock_create_ns_placement_data.assert_called_once() assert_called_once() for python > 3.5
+        self.assertTrue(mock_create_ns_placement_data.called, 'create_ns_placement_data not called as expected')
+        # mock_do_placement_computation.assert_called_once()  assert_called_once() for python > 3.5
+        self.assertTrue(mock_do_placement_computation.called, 'do_placement_computation not called as expected')
+        self.assertTrue(server.msgBus.aiowrite.mock.called)
+
+        args, kwargs = server.msgBus.aiowrite.mock.call_args
+        self.assertTrue(len(args) == 3, 'invalid format')
+        self.assertEqual('pla', args[0], 'topic invalid')
+        self.assertEqual('placement', args[1], 'message invalid')
+        # extract placement result and check content
+        rsp_payload = args[2]
+
+        expected_rsp_keys = {'placement'}
+        self.assertEqual(expected_rsp_keys, set(rsp_payload.keys()), "placement response missing keys")
+        self.assertIs(type(rsp_payload['placement']), dict, 'placement not a dict')
+
+        expected_placement_keys = {'vnf', 'nslcmopId'}
+        self.assertEqual(expected_placement_keys, set(rsp_payload['placement']), "placement keys invalid")
+
+        vim_account_candidates = [e['vimAccountId'] for e in placement_ret_val]
+
+        self.assertEqual(nslcmop_record_wo_pinning['id'], rsp_payload['placement']['nslcmopId'], "nslcmopId invalid")
+
+        self.assertIs(type(rsp_payload['placement']['vnf']), list, 'vnf not a list')
+        expected_vnf_keys = {'vimAccountId', 'member-vnf-index'}
+        self.assertEqual(expected_vnf_keys, set(rsp_payload['placement']['vnf'][0]), "placement['vnf'] missing keys")
+        self.assertIn(rsp_payload['placement']['vnf'][0]['vimAccountId'], vim_account_candidates,
+                      "vimAccountId invalid")
+
+    @mock.patch.object(NsPlacementDataFactory, '__init__', lambda x0, x1, x2, x3, x4, x5, x6: None)
+    @mock.patch.object(MznPlacementConductor, 'do_placement_computation')
+    @mock.patch.object(NsPlacementDataFactory, 'create_ns_placement_data')
+    @mock.patch.object(Server, '_get_vim_accounts')
+    @mock.patch.object(Server, '_get_nsd')
+    @mock.patch.object(Server, '_get_nslcmop')
+    @mock.patch.object(Server, '_get_vnf_price_list')
+    @mock.patch.object(Server, '_get_pil_info')
+    def test_get_placement_with_pinning(self, mock_get_pil_info, mock_get_vnf_price_list, mock__get_nslcmop,
+                                        mock__get_nsd, mock__get_vim_accounts,
+                                        mock_create_ns_placement_data,
+                                        mock_do_placement_computation):
+        """
+        run _get_placement and check that things get called as expected
+        :return:
+        """
+        placement_ret_val = [{'vimAccountId': 'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'one'},
+                             {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'two'},
+                             {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': 'three'}]
+        server = self.serverSetup()
+
+        server.msgBus.aiowrite = _async_mock()
+        mock__get_nsd.return_value = nsd_from_db
+        mock__get_vim_accounts.return_value = list_of_vims
+
+        # FIXME need update to match nslcmop, not for test but for consistency
+        mock_do_placement_computation.return_value = placement_ret_val
+        _run(server.get_placement(nslcmop_record_w_pinning['id']))
+
+        self.assertTrue(mock_get_vnf_price_list.called, '_get_vnf_price_list not called as expected')
+        self.assertTrue(mock_get_pil_info.called, '_get_pil_info not called as expected')
+        self.assertTrue(mock__get_nslcmop.called, '_get_nslcmop not called as expected')
+        # mock_get_nsd.assert_called_once() assert_called_once() for python > 3.5
+        self.assertTrue(mock__get_nsd.called, 'get_nsd not called as expected')
+        # mock_get_enabled_vims.assert_called_once() assert_called_once() for python > 3.5
+        self.assertTrue(mock__get_vim_accounts.called, 'get_vim_accounts not called as expected')
+        # mock_create_ns_placement_data.assert_called_once() assert_called_once() for python > 3.5
+        self.assertTrue(mock_create_ns_placement_data.called, 'create_ns_placement_data not called as expected')
+        # mock_do_placement_computation.assert_called_once()  assert_called_once() for python > 3.5
+        self.assertTrue(mock_do_placement_computation.called, 'do_placement_computation not called as expected')
+        self.assertTrue(server.msgBus.aiowrite.mock.called)
+
+        args, kwargs = server.msgBus.aiowrite.mock.call_args
+        self.assertTrue(len(args) == 3, 'invalid format')
+        self.assertEqual('pla', args[0], 'topic invalid')
+        self.assertEqual('placement', args[1], 'message invalid')
+        # extract placement result and check content
+        rsp_payload = args[2]
+
+        expected_rsp_keys = {'placement'}
+        self.assertEqual(expected_rsp_keys, set(rsp_payload.keys()), "placement response missing keys")
+        self.assertIs(type(rsp_payload['placement']), dict, 'placement not a dict')
+
+        expected_placement_keys = {'vnf', 'nslcmopId'}
+        self.assertEqual(expected_placement_keys, set(rsp_payload['placement']), "placement keys invalid")
+
+        vim_account_candidates = [e['vimAccountId'] for e in placement_ret_val]
+
+        self.assertEqual(nslcmop_record_w_pinning['id'], rsp_payload['placement']['nslcmopId'], "nslcmopId invalid")
+
+        self.assertIs(type(rsp_payload['placement']['vnf']), list, 'vnf not a list')
+        expected_vnf_keys = {'vimAccountId', 'member-vnf-index'}
+        self.assertEqual(expected_vnf_keys, set(rsp_payload['placement']['vnf'][0]), "placement['vnf'] missing keys")
+        self.assertIn(rsp_payload['placement']['vnf'][0]['vimAccountId'], vim_account_candidates,
+                      "vimAccountId invalid")
+
+    # Note: does not mock reading of price list and pil_info
+    @mock.patch.object(NsPlacementDataFactory, '__init__', lambda x0, x1, x2, x3, x4, x5: None)
+    @mock.patch.object(MznPlacementConductor, 'do_placement_computation')
+    @mock.patch.object(NsPlacementDataFactory, 'create_ns_placement_data')
+    @mock.patch.object(Server, '_get_vim_accounts')
+    @mock.patch.object(Server, '_get_nsd')
+    @mock.patch.object(Server, '_get_nslcmop')
+    def test_get_placement_w_exception(self, mock__get_nslcmop,
+                                       mock__get_nsd,
+                                       mock__get_vim_accounts,
+                                       mock_create_ns_placement_data,
+                                       mock_do_placement_computation):
+        """
+        check that raised exceptions are handled and response provided accordingly
+        """
+        server = self.serverSetup()
+
+        server.msgBus.aiowrite = _async_mock()
+        mock__get_nsd.return_value = nsd_from_db
+        mock__get_nsd.side_effect = RuntimeError('kaboom!')
+        mock__get_vim_accounts.return_value = list_of_vims
+        mock_do_placement_computation.return_value = \
+            [{'vimAccountId': 'bbbbbbbb-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '1'},
+             {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '2'},
+             {'vimAccountId': 'aaaaaaaa-38f5-438d-b8ee-3f93b3531f87', 'member-vnf-index': '3'}]
+
+        _run(server.get_placement(nslcmop_record_w_pinning['id']))
+        self.assertTrue(server.msgBus.aiowrite.mock.called)
+        args, kwargs = server.msgBus.aiowrite.mock.call_args
+        rsp_payload = args[2]
+        expected_keys = {'placement'}
+        self.assertEqual(expected_keys, set(rsp_payload.keys()), "placement response missing keys")
+        self.assertIs(type(rsp_payload['placement']['vnf']), list, 'vnf not a list')
+        self.assertEqual([], rsp_payload['placement']['vnf'], 'vnf list not empty')
+        self.assertEqual(nslcmop_record_w_pinning['id'], rsp_payload['placement']['nslcmopId'], "nslcmopId invalid")
diff --git a/osm_pla/test/vnf_price_list.yaml b/osm_pla/test/vnf_price_list.yaml
new file mode 100644 (file)
index 0000000..9f02045
--- /dev/null
@@ -0,0 +1,84 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+- vnfd: cirros_vnfd_v2
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 5
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 10
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 30
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4
+      price: 30
+- vnfd: hackfest_multivdu-vnf
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 17
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 18
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 19
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4
+      price: 20
+- vnfd: test_one_a_vnfd
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 10
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 20
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 30
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4
+      price: 30
+- vnfd: test_one_b_vnfd
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 10
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 20
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 30
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4
+      price: 30
+- vnfd: test_one_c_vnfd
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 10
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 20
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 30
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4
+      price: 30
diff --git a/osm_pla/test/vnf_price_list_more_vims.yaml b/osm_pla/test/vnf_price_list_more_vims.yaml
new file mode 100644 (file)
index 0000000..d4ede2d
--- /dev/null
@@ -0,0 +1,46 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+- vnfd: cirros_vnfd_v2
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 5
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 10
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 30
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4
+      price: 30
+    - vim_url: http://1.1.1.1:5000/v3
+      vim_name: OpenStack5
+      price: 3
+
+- vnfd: hackfest_multivdu-vnf
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 17
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 18
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 19
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4
+      price: 20
\ No newline at end of file
diff --git a/osm_pla/test/vnf_price_list_rel7_webinar.yaml b/osm_pla/test/vnf_price_list_rel7_webinar.yaml
new file mode 100644 (file)
index 0000000..e94930f
--- /dev/null
@@ -0,0 +1,98 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+- vnfd: cirros_vnfd_v2
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 5
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 10
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 30
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4
+      price: 30
+- vnfd: hackfest_multivdu-vnf
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 17
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 18
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 19
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4
+      price: 20
+- vnfd: test_one_a_vnfd
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 10
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 20
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 50
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4
+      price: 50
+- vnfd: test_one_b_vnfd
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 10
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 20
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 50
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4
+      price: 50
+- vnfd: test_one_c_vnfd
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 10
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 20
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 50
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4
+      price: 50
+- vnfd: hackfest-basic_vnfd
+  prices:
+    - vim_url: http://10.234.12.47:5000/v3
+      vim_name: OpenStack1
+      price: 10
+    - vim_url: http://10.234.12.44:5000/v3
+      vim_name: OpenStack2
+      price: 20
+    - vim_url: http://10.234.12.46:5000/v3
+      vim_name: OpenStack3
+      price: 50
+    - vim_url: http://10.234.12.43:5000/v3
+      vim_name: OpenStack4
+      price: 50
diff --git a/requirements.txt b/requirements.txt
new file mode 100755 (executable)
index 0000000..960f499
--- /dev/null
@@ -0,0 +1,21 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+# *************************************************************
+
+# This file is part of OSM Placement module
+# All Rights Reserved to ArctosLabs Scandinavia AB
+
+# 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.
+pyyaml==5.1.2
+pymzn==0.18.*
+jinja2==2.10.3
+git+https://osm.etsi.org/gerrit/osm/common.git#egg=osm-common
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
new file mode 100755 (executable)
index 0000000..75ce737
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,38 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+
+[metadata]
+name = PLA
+summary = Placement module for OSM.
+description-file =
+    README.rst
+author = OSM
+home-page = https://osm.etsi.org/
+classifier =
+    Environment :: OSM
+    Intended Audience :: Information Technology
+    Intended Audience :: System Administrators
+    License :: ETSI Approved :: Apache Software License
+    Operating System :: POSIX :: Linux
+    Programming Language :: Python
+    Programming Language :: Python :: 3
+    Programming Language :: Python :: 3.5
+
+[test]
+test_suite=osm_pla.test
+
+[files]
+packages =
+    pbr
diff --git a/setup.py b/setup.py
new file mode 100755 (executable)
index 0000000..3c80986
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,65 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+from setuptools import setup
+
+
+def parse_requirements(requirements):
+    with open(requirements) as f:
+        return [l.strip('\n') for l in f if l.strip('\n') and not l.startswith('#') and '://' not in l]
+
+
+_name = 'osm_pla'
+#  _version_command = ('git describe --match v* --tags --long --dirty', 'pep440-git-full') FIXME we have no tags yet
+_version = '0.0.1'  # FIXME temporary workaround for _version_command
+_description = 'OSM Placement Module'
+_author = "Lars Goran Magnusson"
+_author_email = 'lars-goran.magnusson@arctoslabs.com'
+_maintainer = 'Lars Goran Magnusson'
+_maintainer_email = 'lars-goran.magnusson@arctoslabs.com'
+_license = 'Apache 2.0'
+_url = 'https://osm.etsi.org/gitweb?p=osm/PLA.git;a=tree'
+
+
+setup(
+    name=_name,
+    # version_command=_version_command, FIXME temporary fix
+    version=_version,
+    description=_description,
+    long_description=open('README.md', encoding='utf-8').read(),
+    author=_author,
+    author_email=_author_email,
+    maintainer=_maintainer,
+    maintainer_email=_maintainer_email,
+    url=_url,
+    license=_license,
+    packages=[_name],
+    package_dir={_name: _name},
+    install_requires=[
+        'osm-common',
+        'jinja2==2.10.3',
+        'pymzn==0.18.*',
+        'pyyaml==5.1.2'
+    ],
+    dependency_links=[
+        'git+https://osm.etsi.org/gerrit/osm/common.git#egg=osm-common',
+    ],
+    include_package_data=True,
+    entry_points={
+        "console_scripts": [
+            "osm-pla-server = osm_pla.cmd.pla_server:main",
+        ]
+    },
+    setup_requires=['setuptools-version-command']
+)
diff --git a/stdeb.cfg b/stdeb.cfg
new file mode 100644 (file)
index 0000000..05381db
--- /dev/null
+++ b/stdeb.cfg
@@ -0,0 +1,17 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+[DEFAULT]
+X-Python3-Version : >= 3.5
+Depends3: python3-osm-common, python3-yaml, python3-jinja2, python3-pip
diff --git a/test-requirements.txt b/test-requirements.txt
new file mode 100755 (executable)
index 0000000..429714f
--- /dev/null
@@ -0,0 +1,18 @@
+# Copyright 2020 ArctosLabs Scandinavia AB
+# *************************************************************
+
+# This file is part of OSM Placement module
+# All Rights Reserved to ArctosLabs Scandinavia AB
+
+# 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.
+coverage
\ No newline at end of file
diff --git a/tox.ini b/tox.ini
new file mode 100644 (file)
index 0000000..68c6211
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,50 @@
+##
+# Copyright 2020 ArctosLabs Scandinavia AB
+#
+# 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.
+##
+[tox]
+envlist = py3
+
+[testenv]
+basepython = python3
+install_command = python3 -m pip install -U {opts} {packages}
+deps = -rrequirements.txt
+       -rtest-requirements.txt
+commands = python3 -m unittest discover -v
+
+[testenv:coverage]
+basepython = python3
+deps = -rrequirements.txt
+       -rtest-requirements.txt
+commands = coverage run -m unittest discover
+           coverage report --omit='*site-packages*','*test*','*__init__*'
+          coverage html -d ./.tox/coverage/HTMLreport --omit='*site-packages*','*test*','*__init__*'
+          coverage xml -o ./.tox/coverage/XMLreport/coverage.xml --omit='*site-packages*','*test*','*__init__*'
+           coverage erase
+
+[testenv:flake8]
+basepython = python3
+deps = flake8
+       -rrequirements.txt
+commands = flake8 {toxinidir}/osm_pla/ {toxinidir}/setup.py \
+           --max-line-length 120 \
+           --exclude test_mznmodels.py,.svn,CVS,.gz,.git,__pycache__,.tox,local,temp
+
+[testenv:build]
+basepython = python3
+# changedir ={toxinidir}
+deps = stdeb
+       setuptools-version-command
+       -rrequirements.txt
+commands = python3 setup.py --command-packages=stdeb.command bdist_deb