From: mirabal Date: Thu, 27 Jul 2017 10:21:22 +0000 (+0200) Subject: Affinity and antiaffinity implementation. X-Git-Tag: v3.0.0~17^2~8 X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FRO.git;a=commitdiff_plain;h=2935631c49a315fb078d05380569f7e1ca21fcde Affinity and antiaffinity implementation. - 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 --- diff --git a/database_utils/migrate_mano_db.sh b/database_utils/migrate_mano_db.sh index 52d8cb1a..b6e4f8e1 100755 --- a/database_utils/migrate_mano_db.sh +++ b/database_utils/migrate_mano_db.sh @@ -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 diff --git a/openmanod b/openmanod index 6190a1ec..8d17f389 100755 --- 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 diff --git a/osm_ro/nfvo.py b/osm_ro/nfvo.py index 8f105c32..66ea1b2d 100644 --- a/osm_ro/nfvo.py +++ b/osm_ro/nfvo.py @@ -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"] diff --git a/osm_ro/nfvo_db.py b/osm_ro/nfvo_db.py index ac392c6d..3dcffd0c 100644 --- a/osm_ro/nfvo_db.py +++ b/osm_ro/nfvo_db.py @@ -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() diff --git a/osm_ro/openmano_schemas.py b/osm_ro/openmano_schemas.py index 7d218ec1..ec154781 100644 --- a/osm_ro/openmano_schemas.py +++ b/osm_ro/openmano_schemas.py @@ -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"}, diff --git a/osm_ro/vimconn.py b/osm_ro/vimconn.py index 18f4334d..b0a40114 100644 --- a/osm_ro/vimconn.py +++ b/osm_ro/vimconn.py @@ -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): diff --git a/osm_ro/vimconn_openstack.py b/osm_ro/vimconn_openstack.py index 6c610ca8..c66d74cd 100644 --- a/osm_ro/vimconn_openstack.py +++ b/osm_ro/vimconn_openstack.py @@ -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 index 00000000..deae332f --- /dev/null +++ b/test/RO_tests/afiinity_vnf/scenario_simple_2_vnf_afinnity.yaml @@ -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 index 00000000..9ec7f607 --- /dev/null +++ b/test/RO_tests/afiinity_vnf/vnfd_linux_2_vnfc_affinity.yaml @@ -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: []