Affinity and antiaffinity implementation. 71/2071/8
authormirabal <leonardo.mirabal@altran.com>
Thu, 27 Jul 2017 10:21:22 +0000 (12:21 +0200)
committermirabal <leonardo.mirabal@altran.com>
Mon, 28 Aug 2017 16:18:52 +0000 (18:18 +0200)
- RO descriptor now support "availability_zone" tag in the VNFC descriptor.
- RO database extend table vms table to include availability_zone.
- In case a VNF requires more  zones than available in the datacenter, an error message of instantiation failure will be issued and the usual rollback procedure will  start.
- In case a VDU does not include availability tag, it will be treated as normal, following the default scheduling policy in the VIM.

Change-Id: I335a0db6fa232953f655a598351dd7e7bbb97785
Signed-off-by: mirabal <leonardo.mirabal@altran.com>
database_utils/migrate_mano_db.sh
openmanod
osm_ro/nfvo.py
osm_ro/nfvo_db.py
osm_ro/openmano_schemas.py
osm_ro/vimconn.py
osm_ro/vimconn_openstack.py
test/RO_tests/afiinity_vnf/scenario_simple_2_vnf_afinnity.yaml [new file with mode: 0644]
test/RO_tests/afiinity_vnf/vnfd_linux_2_vnfc_affinity.yaml [new file with mode: 0644]

index 52d8cb1..b6e4f8e 100755 (executable)
@@ -33,7 +33,7 @@ DBPORT="3306"
 DBNAME="mano_db"
 QUIET_MODE=""
 #TODO update it with the last database version
-LAST_DB_VERSION=22
+LAST_DB_VERSION=23
  
 # Detect paths
 MYSQL=$(which mysql)
@@ -190,6 +190,7 @@ fi
 #[ $OPENMANO_VER_NUM -ge 5009 ] && DB_VERSION=20  #0.5.9 =>  20
 #[ $OPENMANO_VER_NUM -ge 5015 ] && DB_VERSION=21  #0.5.15 =>  21
 #[ $OPENMANO_VER_NUM -ge 5016 ] && DB_VERSION=22  #0.5.16 =>  22
+#[ $OPENMANO_VER_NUM -ge 5019 ] && DB_VERSION=23  #0.5.20 =>  23
 #TODO ... put next versions here
 
 function upgrade_to_1(){
@@ -786,6 +787,19 @@ function downgrade_from_22(){
     echo "DELETE FROM schema_version WHERE version_int='22';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
 }
 
+function upgrade_to_23(){
+    # echo "    upgrade database from version 0.22 to version 0.23"
+    echo "      add column 'availability_zone' at table 'vms'"
+    echo "ALTER TABLE mano_db.vms ADD COLUMN availability_zone VARCHAR(255) NULL AFTER modified_at;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "INSERT INTO schema_version (version_int, version, openmano_ver, comments, date) VALUES (23, '0.23', '0.5.20', 'Changed type of ram in flavors from SMALLINT to MEDIUMINT', '2017-08-29');" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+function downgrade_from_23(){
+    # echo "    downgrade database from version 0.23 to version 0.22"
+    echo "      remove column 'availability_zone' from table 'vms'"
+    echo "ALTER TABLE mano_db.vms DROP COLUMN availability_zone;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "DELETE FROM schema_version WHERE version_int='23';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+
 function upgrade_to_X(){
     echo "      change 'datacenter_nets'"
     echo "ALTER TABLE datacenter_nets ADD COLUMN vim_tenant_id VARCHAR(36) NOT NULL AFTER datacenter_id, DROP INDEX name_datacenter_id, ADD UNIQUE INDEX name_datacenter_id (name, datacenter_id, vim_tenant_id);" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
index 6190a1e..8d17f38 100755 (executable)
--- a/openmanod
+++ b/openmanod
@@ -48,13 +48,14 @@ import osm_ro
 
 __author__ = "Alfonso Tierno, Gerardo Garcia, Pablo Montes"
 __date__ = "$26-aug-2014 11:09:29$"
-__version__ = "0.5.19-r528"
+__version__ = "0.5.20-r529"
 version_date = "Aug 2017"
-database_version = 22      # expected database schema version
+database_version = 23      # expected database schema version
 
 global global_config
 global logger
 
+
 class LoadConfigurationException(Exception):
     pass
 
index 8f105c3..66ea1b2 100644 (file)
@@ -759,6 +759,7 @@ def new_vnf(mydb, tenant_id, vnf_descriptor):
         for vnfc in vnf_descriptor['vnf']['VNFC']:
             VNFCitem={}
             VNFCitem["name"] = vnfc['name']
+            VNFCitem["availability_zone"] = vnfc.get('availability_zone')
             VNFCitem["description"] = vnfc.get("description", 'VM %s of the VNF %s' %(vnfc['name'],vnf_name))
 
             #print "Flavor name: %s. Description: %s" % (VNFCitem["name"]+"-flv", VNFCitem["description"])
@@ -1667,6 +1668,7 @@ def start_scenario(mydb, tenant_id, scenario_id, instance_scenario_name, instanc
 
         logger.debug("start_scenario 2. Creating new nets (vnf internal nets) in the VIM")
         #For each vnf net, we create it and we add it to instanceNetlist.
+
         for sce_vnf in scenarioDict['vnfs']:
             for net in sce_vnf['nets']:
                 #print "Net name: %s. Description: %s" % (net["name"], net["description"])
@@ -1698,6 +1700,11 @@ def start_scenario(mydb, tenant_id, scenario_id, instance_scenario_name, instanc
         #myvim.new_vminstance(self,vimURI,tenant_id,name,description,image_id,flavor_id,net_dict)
         i = 0
         for sce_vnf in scenarioDict['vnfs']:
+            nfv_availability_zones = []
+            for vm in sce_vnf['vms']:
+                vm_av = vm.get('availability_zone')
+                if vm_av and vm_av not in nfv_availability_zones:
+                    nfv_availability_zones.append(vm_av)
             for vm in sce_vnf['vms']:
                 i += 1
                 myVMDict = {}
@@ -1784,8 +1791,16 @@ def start_scenario(mydb, tenant_id, scenario_id, instance_scenario_name, instanc
                 #print "networks", yaml.safe_dump(myVMDict['networks'], indent=4, default_flow_style=False)
                 #print "interfaces", yaml.safe_dump(vm['interfaces'], indent=4, default_flow_style=False)
                 #print ">>>>>>>>>>>>>>>>>>>>>>>>>>>"
-                vm_id = myvim.new_vminstance(myVMDict['name'],myVMDict['description'],myVMDict.get('start', None),
-                        myVMDict['imageRef'],myVMDict['flavorRef'],myVMDict['networks'])
+
+                if 'availability_zone' in myVMDict:
+                    counter_availability_zone = nfv_availability_zones.index(myVMDict['availability_zone'])
+                else:
+                    counter_availability_zone = None
+
+                vm_id = myvim.new_vminstance(myVMDict['name'], myVMDict['description'], myVMDict.get('start', None),
+                                             myVMDict['imageRef'], myVMDict['flavorRef'], myVMDict['networks'],
+                                             availavility_zone_index=counter_availability_zone,
+                                             nfv_availability_zones=nfv_availability_zones)
                 #print "VIM vm instance id (server id) for scenario %s: %s" % (scenarioDict['name'],vm_id)
                 vm['vim_id'] = vm_id
                 rollbackList.append({'what':'vm','where':'vim','vim_id':datacenter_id,'uuid':vm_id})
@@ -2158,7 +2173,7 @@ def create_instance(mydb, tenant_id, instance_dict):
                     sce_net["created"] = True
 
         # 2. Creating new nets (vnf internal nets) in the VIM"
-        #For each vnf net, we create it and we add it to instanceNetlist.
+        # For each vnf net, we create it and we add it to instanceNetlist.
         for sce_vnf in scenarioDict['vnfs']:
             for net in sce_vnf['nets']:
                 if sce_vnf.get("datacenter"):
@@ -2187,15 +2202,28 @@ def create_instance(mydb, tenant_id, instance_dict):
                 rollbackList.append({'what':'network','where':'vim','vim_id':datacenter_id,'uuid':task_id})
                 net["created"] = True
 
-
         #print "auxNetDict:"
         #print yaml.safe_dump(auxNetDict, indent=4, default_flow_style=False)
 
         # 3. Creating new vm instances in the VIM
         #myvim.new_vminstance(self,vimURI,tenant_id,name,description,image_id,flavor_id,net_dict)
+
         sce_vnf_list = sorted(scenarioDict['vnfs'], key=lambda k: k['name']) 
         #for sce_vnf in scenarioDict['vnfs']:
         for sce_vnf in sce_vnf_list:
+            nfv_availability_zones = []
+            for vm in sce_vnf['vms']:
+                vm_av = vm.get('availability_zone')
+                if vm_av and vm_av not in nfv_availability_zones:
+                    nfv_availability_zones.append(vm_av)
+
+            # check if there is enough availability zones available at vim level.
+            if myvims[datacenter_id].availability_zone:
+                vim_availability_zones = myvims[datacenter_id].availability_zone
+                nfv_availability_zones_num = len(vim_availability_zones)
+                if len(nfv_availability_zones) > nfv_availability_zones_num:
+                    raise NfvoException('No enough availablity zones for this deployment', HTTP_Bad_Request)
+
             if sce_vnf.get("datacenter"):
                 vim = myvims[ sce_vnf["datacenter"] ]
                 myvim_thread_id = myvim_threads_id[ sce_vnf["datacenter"] ]
@@ -2204,8 +2232,9 @@ def create_instance(mydb, tenant_id, instance_dict):
                 vim = myvims[ default_datacenter_id ]
                 myvim_thread_id = myvim_threads_id[ default_datacenter_id ]
                 datacenter_id = default_datacenter_id
-            sce_vnf["datacenter_id"] =  datacenter_id
+            sce_vnf["datacenter_id"] = datacenter_id
             i = 0
+
             for vm in sce_vnf['vms']:
                 i += 1
                 myVMDict = {}
@@ -2242,6 +2271,7 @@ def create_instance(mydb, tenant_id, instance_dict):
                 vm['vim_flavor_id'] = flavor_id
                 myVMDict['imageRef'] = vm['vim_image_id']
                 myVMDict['flavorRef'] = vm['vim_flavor_id']
+                myVMDict['availability_zone'] = vm.get('availability_zone')
                 myVMDict['networks'] = []
                 task_depends = {}
                 #TODO ALF. connect_mgmt_interfaces. Connect management interfaces if this is true
@@ -2309,9 +2339,15 @@ def create_instance(mydb, tenant_id, instance_dict):
                     cloud_config_vm = unify_cloud_config(vm["boot_data"], cloud_config)
                 else:
                     cloud_config_vm = cloud_config
+
+                if 'availability_zone' in myVMDict and myVMDict.get('availability_zone'):
+                    counter_availability_zone = nfv_availability_zones.index(myVMDict['availability_zone'])
+                else:
+                    counter_availability_zone = None
                 task = new_task("new-vm", (myVMDict['name'], myVMDict['description'], myVMDict.get('start', None),
                                            myVMDict['imageRef'], myVMDict['flavorRef'], myVMDict['networks'],
-                                           cloud_config_vm, myVMDict['disks']), depends=task_depends)
+                                           cloud_config_vm, myVMDict['disks'], counter_availability_zone,
+                                           nfv_availability_zones), depends=task_depends)
                 instance_tasks[task["id"]] = task
                 tasks_to_launch[myvim_thread_id].append(task)
                 vm_id = task["id"]
index ac392c6..3dcffd0 100644 (file)
@@ -590,10 +590,12 @@ class nfvo_db(db_base.db_base):
                         self.cur.execute(cmd)
                         vnf['interfaces'] = self.cur.fetchall()
                         #vms
-                        cmd = "SELECT vms.uuid as uuid, flavor_id, image_id, vms.name as name, vms.description as description, vms.boot_data as boot_data " \
-                                " FROM vnfs join vms on vnfs.uuid=vms.vnf_id " \
-                                " WHERE vnfs.uuid='" + vnf['vnf_id'] +"'"  \
-                                " ORDER BY vms.created_at"
+                        cmd = "SELECT vms.uuid as uuid, flavor_id, image_id, vms.name as name," \
+                              " vms.description as description, vms.boot_data as boot_data," \
+                              " vms.availability_zone as availability_zone" \
+                              " FROM vnfs join vms on vnfs.uuid=vms.vnf_id " \
+                              " WHERE vnfs.uuid='" + vnf['vnf_id'] +"'"  \
+                              " ORDER BY vms.created_at"
                         self.logger.debug(cmd)
                         self.cur.execute(cmd)
                         vnf['vms'] = self.cur.fetchall()
index 7d218ec..ec15478 100644 (file)
@@ -541,8 +541,9 @@ vnfc_schema = {
     "properties":{
         "name": name_schema,
         "description": description_schema,
-        "VNFC image": {"oneOf": [path_schema, http_schema]},
         "image name": name_schema,
+        "availability_zone": name_schema,
+        "VNFC image": {"oneOf": [path_schema, http_schema]},
         "image checksum": checksum_schema,
         "image metadata": metadata_schema, 
         #"cloud-config": cloud_config_schema, #common for all vnfs in the scenario
@@ -592,6 +593,7 @@ vnfd_schema_v01 = {
             "properties":{
                 "name": name_schema,
                 "description": description_schema,
+
                 "class": nameshort_schema,
                 "public": {"type" : "boolean"},
                 "physical": {"type" : "boolean"},
index 18f4334..b0a4011 100644 (file)
@@ -82,6 +82,7 @@ class vimconnNotImplemented(vimconnException):
     def __init__(self, message, http_code=HTTP_Not_Implemented):
         vimconnException.__init__(self, message, http_code)
 
+
 class vimconnector():
     """Abstract base class for all the VIM connector plugins
     These plugins must implement a vimconnector class derived from this 
@@ -115,6 +116,7 @@ class vimconnector():
         self.user      = user
         self.passwd    = passwd
         self.config    = config
+        self.availability_zone = None
         self.logger = logging.getLogger('openmano.vim')
         if log_level:
             self.logger.setLevel( getattr(logging, log_level) )
@@ -353,8 +355,8 @@ class vimconnector():
         """
         raise vimconnNotImplemented( "Should have implemented this" )
 
-    def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None,
-                       disk_list=None):
+    def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
+        availavility_zone_index=None, nfv_availability_zones=None):
         """Adds a VM instance to VIM
         Params:
             'start': (boolean) indicates if VM must start or created in pause mode.
@@ -397,6 +399,9 @@ class vimconnector():
             'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
                 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
                 'size': (mandatory) string with the size of the disk in GB
+            availavility_zone_index: Index of nfv_availability_zones to use for this this VM
+            nfv_availability_zones: list of availability zones given by user in the VNFC descriptor.  Ignore if
+            availability_zone_index is None
         Returns the instance identifier or raises an exception on error
         """
         raise vimconnNotImplemented( "Should have implemented this" )
@@ -458,7 +463,7 @@ class vimconnector():
                 suffix:   extra text, e.g. the http path and query string   
         """
         raise vimconnNotImplemented( "Should have implemented this" )
-        
+
 #NOT USED METHODS in current version        
 
     def host_vim2gui(self, host, server_dict):
index 6c610ca..c66d74c 100644 (file)
@@ -84,6 +84,7 @@ class vimconnector(vimconn.vimconnector):
         if not url:
             raise TypeError, 'url param can not be NoneType'
         self.persistent_info = persistent_info
+        self.availability_zone = persistent_info.get('availability_zone', None)
         self.session = persistent_info.get('session', {'reload_client': True})
         self.nova = self.session.get('nova')
         self.neutron = self.session.get('neutron')
@@ -165,6 +166,9 @@ class vimconnector(vimconn.vimconnector):
             self.glancev1 = self.session['glancev1'] = glClient.Client('1', session=sess)
             self.session['reload_client'] = False
             self.persistent_info['session'] = self.session
+            # add availablity zone info inside  self.persistent_info
+            self._set_availablity_zones()
+            self.persistent_info['availability_zone'] = self.availability_zone
 
     def __net_os2mano(self, net_list_dict):
         '''Transform the net openstack format to mano format
@@ -700,7 +704,64 @@ class vimconnector(vimconn.vimconnector):
             raise vimconn.vimconnException('Timeout waiting for instance ' + vm_id + ' to get ' + status,
                                            http_code=vimconn.HTTP_Request_Timeout)
 
-    def new_vminstance(self,name,description,start,image_id,flavor_id,net_list,cloud_config=None,disk_list=None):
+    def _get_openstack_availablity_zones(self):
+        """
+        Get from openstack availability zones available
+        :return:
+        """
+        try:
+            openstack_availability_zone = self.nova.availability_zones.list()
+            openstack_availability_zone = [str(zone.zoneName) for zone in openstack_availability_zone
+                                           if zone.zoneName != 'internal']
+            return openstack_availability_zone
+        except Exception as e:
+            return None
+
+    def _set_availablity_zones(self):
+        """
+        Set vim availablity zone
+        :return:
+        """
+
+        if 'availability_zone' in self.config:
+            vim_availability_zones = self.config.get('availability_zone')
+            if isinstance(vim_availability_zones, str):
+                self.availability_zone = [vim_availability_zones]
+            elif isinstance(vim_availability_zones, list):
+                self.availability_zone = vim_availability_zones
+        else:
+            self.availability_zone = self._get_openstack_availablity_zones()
+
+    def _get_vm_availavility_zone(self, availavility_zone_index, nfv_availability_zones):
+        """
+        Return a list with all availability zones create during datacenter attach.
+        :return: List with availability zones
+        """
+        openstack_avilability_zone = self.availability_zone
+
+        # check if VIM offer enough availability zones describe in the VNFC
+        if self.availability_zone and availavility_zone_index is not None \
+                and 0 <= len(nfv_availability_zones) <= len(self.availability_zone):
+
+            if nfv_availability_zones:
+                vnf_azone = nfv_availability_zones[availavility_zone_index]
+                zones_available = []
+
+                for nfv_zone in nfv_availability_zones:
+                    for vim_zone in openstack_avilability_zone:
+                        if nfv_zone is vim_zone:
+                            zones_available.append(nfv_zone)
+
+                if len(zones_available) == len(openstack_avilability_zone) and vnf_azone in openstack_avilability_zone:
+                    return vnf_azone
+                else:
+                    return openstack_avilability_zone[availavility_zone_index]
+        else:
+            raise vimconn.vimconnConflictException("No enough availablity zones for this deployment")
+        return None
+
+    def new_vminstance(self, name, description, start, image_id, flavor_id, net_list,cloud_config=None,disk_list=None,
+                       availavility_zone_index=None, nfv_availability_zones=None):
         '''Adds a VM instance to VIM
         Params:
             start: indicates if VM must start or boot in pause mode. Ignored
@@ -715,6 +776,25 @@ class vimconnector(vimconn.vimconnector):
                 type: 'virtual', 'PF', 'VF', 'VFnotShared'
                 vim_id: filled/added by this function
                 floating_ip: True/False (or it can be None)
+                'cloud_config': (optional) dictionary with:
+                'key-pairs': (optional) list of strings with the public key to be inserted to the default user
+                'users': (optional) list of users to be inserted, each item is a dict with:
+                    'name': (mandatory) user name,
+                    'key-pairs': (optional) list of strings with the public key to be inserted to the user
+                'user-data': (optional) string is a text script to be passed directly to cloud-init
+                'config-files': (optional). List of files to be transferred. Each item is a dict with:
+                    'dest': (mandatory) string with the destination absolute path
+                    'encoding': (optional, by default text). Can be one of:
+                        'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64'
+                    'content' (mandatory): string with the content of the file
+                    'permissions': (optional) string with file permissions, typically octal notation '0644'
+                    'owner': (optional) file owner, string with the format 'owner:group'
+                'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk)
+            'disk_list': (optional) list with additional disks to the VM. Each item is a dict with:
+                'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted
+                'size': (mandatory) string with the size of the disk in GB
+            availavility_zone_index:counter for instance order in vim availability_zones availables
+            nfv_availability_zones: Lost given by user in the VNFC descriptor.
                 #TODO ip, security groups
         Returns the instance identifier
         '''
@@ -886,15 +966,17 @@ class vimconnector(vimconn.vimconnector):
 
                     raise vimconn.vimconnException('Timeout creating volumes for instance ' + name,
                                                    http_code=vimconn.HTTP_Request_Timeout)
-
-            self.logger.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}," \
-                                              "availability_zone={}, key_name={}, userdata={}, config_drive={}, " \
-                                              "block_device_mapping={})".format(name, image_id, flavor_id, net_list_vim,
-                                                metadata, security_groups, self.config.get('availability_zone'),
-                                                self.config.get('keypair'), userdata, config_drive, block_device_mapping))
+            # get availability Zone
+            vm_av_zone = self._get_vm_availavility_zone(availavility_zone_index, nfv_availability_zones)
+
+            self.logger.debug("nova.servers.create({}, {}, {}, nics={}, meta={}, security_groups={}, "
+                              "availability_zone={}, key_name={}, userdata={}, config_drive={}, "
+                              "block_device_mapping={})".format(name, image_id, flavor_id, net_list_vim, metadata,
+                                                                security_groups, vm_av_zone, self.config.get('keypair'),
+                              userdata, config_drive, block_device_mapping))
             server = self.nova.servers.create(name, image_id, flavor_id, nics=net_list_vim, meta=metadata,
                                               security_groups=security_groups,
-                                              availability_zone=self.config.get('availability_zone'),
+                                              availability_zone=vm_av_zone,
                                               key_name=self.config.get('keypair'),
                                               userdata=userdata,
                                               config_drive=config_drive,
diff --git a/test/RO_tests/afiinity_vnf/scenario_simple_2_vnf_afinnity.yaml b/test/RO_tests/afiinity_vnf/scenario_simple_2_vnf_afinnity.yaml
new file mode 100644 (file)
index 0000000..deae332
--- /dev/null
@@ -0,0 +1,38 @@
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+---
+schema_version:  2
+scenario:
+  name:          simple_ha
+  description:   Simple network scenario consisting of two VNF connected to an external network
+  vnfs:
+    linux1:                   # vnf/net name in the scenario
+      vnf_name:  linux_test_2vms # VNF name as introduced in OPENMANO DB
+  networks:
+    mgmt:                   # provide a name for this net or connection
+      external:  true
+      interfaces:
+      - linux1:  control0       # Node and its interface
+      - linux1:  control1       # Node and its interface
+
+
+
+
diff --git a/test/RO_tests/afiinity_vnf/vnfd_linux_2_vnfc_affinity.yaml b/test/RO_tests/afiinity_vnf/vnfd_linux_2_vnfc_affinity.yaml
new file mode 100644 (file)
index 0000000..9ec7f60
--- /dev/null
@@ -0,0 +1,61 @@
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openmano
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+---
+
+vnf:
+    name:        linux_test_2vms
+    description: Single-VM VNF with a traditional cloud VM based on generic Linux OS
+    external-connections:
+    -   name:              control0
+        type:              mgmt              # "mgmt" (autoconnect to management net), "bridge", "data"
+        VNFC:              linux-VM-HA-A  # Virtual Machine this interface belongs to
+        local_iface_name:  eth0             # interface name inside this Virtual Machine (must be defined in the VNFC section)
+        description:       Management interface 0
+    -   name:              control1
+        type:              mgmt              # "mgmt" (autoconnect to management net), "bridge", "data"
+        VNFC:              linux-VM-HA-B  # Virtual Machine this interface belongs to
+        local_iface_name:  eth0             # interface name inside this Virtual Machine (must be defined in the VNFC section)
+        description:       Management interface 1
+    VNFC:
+    -   name:        linux-VM-HA-A
+        description: Generic Linux Virtual Machine
+        availability_zone: A  # availanility zone A
+        #Copy the image to a compute path and edit this path
+        image name:  TestVM
+        vcpus: 1          # Only for traditional cloud VMs. Number of virtual CPUs (oversubscription is allowed).
+        ram: 1024         # Only for traditional cloud VMs. Memory in MBytes (not from hugepages, oversubscription is allowed)
+        disk: 10
+        bridge-ifaces:
+        -   name:      eth0
+            vpci:      "0000:00:11.0"
+        numas: []
+    -   name:        linux-VM-HA-B
+        description: Generic Linux Virtual Machine
+        availability_zone: B # availanility zone B
+        #Copy the image to a compute path and edit this path
+        image name:  TestVM
+        vcpus: 1          # Only for traditional cloud VMs. Number of virtual CPUs (oversubscription is allowed).
+        ram: 1024         # Only for traditional cloud VMs. Memory in MBytes (not from hugepages, oversubscription is allowed)
+        disk: 10
+        bridge-ifaces:
+        -   name:      eth0
+            vpci:      "0000:00:12.0"
+        numas: []