[ $OPENMANO_VER_NUM -ge 4046 ] && DATABASE_TARGET_VER_NUM=12 #0.4.46=> 12
[ $OPENMANO_VER_NUM -ge 4047 ] && DATABASE_TARGET_VER_NUM=13 #0.4.47=> 13
[ $OPENMANO_VER_NUM -ge 4057 ] && DATABASE_TARGET_VER_NUM=14 #0.4.57=> 14
+[ $OPENMANO_VER_NUM -ge 4059 ] && DATABASE_TARGET_VER_NUM=15 #0.4.59=> 15
#TODO ... put next versions here
function upgrade_to_12(){
echo " upgrade database from version 0.11 to version 0.12"
echo " create ip_profiles table, with foreign keys to all nets tables, and add ip_address column to 'interfaces' and 'sce_interfaces'"
- echo "CREATE TABLE ip_profiles (
+ echo "CREATE TABLE IF NOT EXISTS ip_profiles (
id INT(11) NOT NULL AUTO_INCREMENT,
net_id VARCHAR(36) NULL DEFAULT NULL,
sce_net_id VARCHAR(36) NULL DEFAULT NULL,
echo "DELETE FROM schema_version WHERE version_int='14';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
}
+function upgrade_to_15(){
+ echo " upgrade database from version 0.14 to version 0.15"
+ echo " add columns 'universal_name' and 'checksum' at table 'images', add unique index universal_name_checksum, and change location to allow NULL; change column 'image_path' in table 'vms' to allow NULL"
+ echo "ALTER TABLE images ADD COLUMN checksum VARCHAR(32) NULL DEFAULT NULL AFTER name;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE images ALTER location DROP DEFAULT;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE images ADD COLUMN universal_name VARCHAR(255) NULL AFTER name, CHANGE COLUMN location location VARCHAR(200) NULL AFTER checksum, ADD UNIQUE INDEX universal_name_checksum (universal_name, checksum);" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE vms ALTER image_path DROP DEFAULT;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE vms CHANGE COLUMN image_path image_path VARCHAR(100) NULL COMMENT 'Path where the image of the VM is located' AFTER image_id;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "INSERT INTO schema_version (version_int, version, openmano_ver, comments, date) VALUES (15, '0.15', '0.4.59', 'add columns universal_name and checksum at table images, add unique index universal_name_checksum, and change location to allow NULL; change column image_path in table vms to allow NULL', '2016-09-27');" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+function downgrade_from_15(){
+ echo " downgrade database from version 0.15 to version 0.14"
+ echo " remove columns 'universal_name' and 'checksum' from table 'images', remove index universal_name_checksum, change location NOT NULL; change column 'image_path' in table 'vms' to NOT NULL"
+ echo "ALTER TABLE images DROP INDEX universal_name_checksum;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE images ALTER location DROP DEFAULT;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE images CHANGE COLUMN location location VARCHAR(200) NOT NULL AFTER checksum;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE images DROP COLUMN universal_name;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE images DROP COLUMN checksum;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE vms ALTER image_path DROP DEFAULT;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "ALTER TABLE vms CHANGE COLUMN image_path image_path VARCHAR(100) NOT NULL COMMENT 'Path where the image of the VM is located' AFTER image_id;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+ echo "DELETE FROM schema_version WHERE version_int='15';" | $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
if only_create_at_vim:
image_mano_id = image_dict['uuid']
else:
- images = mydb.get_rows(FROM="images", WHERE={'location':image_dict['location'], 'metadata':image_dict['metadata']})
+ if image_dict['location'] is not None:
+ images = mydb.get_rows(FROM="images", WHERE={'location':image_dict['location'], 'metadata':image_dict['metadata']})
+ else:
+ images = mydb.get_rows(FROM="images", WHERE={'universal_name':image_dict['universal_name'], 'checksum':image_dict['checksum']})
if len(images)>=1:
image_mano_id = images[0]['uuid']
else:
#create image
temp_image_dict={'name':image_dict['name'], 'description':image_dict.get('description',None),
- 'location':image_dict['location'], 'metadata':image_dict.get('metadata',None)
+ 'location':image_dict['location'], 'metadata':image_dict.get('metadata',None),
+ 'universal_name':image_dict['universal_name'] , 'checksum':image_dict['checksum']
}
image_mano_id = mydb.new_row('images', temp_image_dict, add_uuid=True)
rollback_list.append({"where":"mano", "what":"image","uuid":image_mano_id})
image_db = mydb.get_rows(FROM="datacenters_images", WHERE={'datacenter_id':vim_id, 'image_id':image_mano_id})
#look at VIM if this image exist
try:
- image_vim_id = vim.get_image_id_from_path(image_dict['location'])
+ if image_dict['location'] is not None:
+ image_vim_id = vim.get_image_id_from_path(image_dict['location'])
+ else:
+ filter_dict={}
+ filter_dict['name']=image_dict['universal_name']
+ filter_dict['checksum']=image_dict['checksum']
+ vim_images = vim.get_image_list(filter_dict)
+ if len(vim_images) > 1:
+ raise NfvoException("More than one candidate VIM image found for filter: " + str(filter_dict), HTTP_Conflict)
+ elif len(vim_nets) == 0:
+ raise NfvoException("Image not found at VIM with filter: '%s'", str(filter_dict))
+ else:
+ image_vim_id = vim_images[0].id
+
except vimconn.vimconnNotFoundException as e:
#Create the image in VIM
try:
logger.warn("Error creating image at VIM: %s", str(e))
continue
except vimconn.vimconnException as e:
- logger.warn("Error contacting VIM to know if the image exist at VIM: %s", str(e))
+ logger.warn("Error contacting VIM to know if the image exists at VIM: %s", str(e))
image_vim_id = str(e)
continue
- #if reach here the image has been create or exist
+ #if we reach here, the image has been created or existed
if len(image_db)==0:
#add new vim_id at datacenters_images
mydb.new_row('datacenters_images', {'datacenter_id':vim_id, 'image_id':image_mano_id, 'vim_id': image_vim_id, 'created':image_created})
if 'extended' in flavor_dict and flavor_dict['extended']!=None:
dev_nb=0
for device in flavor_dict['extended'].get('devices',[]):
- if "image" not in device:
+ if "image" not in device or "image name" not in device:
continue
- image_dict={'location':device['image'], 'name':flavor_dict['name']+str(dev_nb)+"-img", 'description':flavor_dict.get('description')}
+ image_dict={}
+ image_dict['name']=device.get('image name',flavor_dict['name']+str(dev_nb)+"-img")
+ image_dict['universal_name']=device.get('image name')
+ image_dict['description']=flavor_dict['name']+str(dev_nb)+"-img"
+ image_dict['location']=device.get('image')
+ image_dict['checksum']=device.get('image checksum')
image_metadata_dict = device.get('image metadata', None)
image_metadata_str = None
if image_metadata_dict != None:
dev_nb=0
for index in range(0,len(devices_original)) :
device=devices_original[index]
- if "image" not in device:
+ if "image" not in device or "image name" not in device:
continue
- image_dict={'location':device['image'], 'name':flavor_dict['name']+str(dev_nb)+"-img", 'description':flavor_dict.get('description')}
+ image_dict={}
+ image_dict['name']=device.get('image name',flavor_dict['name']+str(dev_nb)+"-img")
+ image_dict['universal_name']=device.get('image name')
+ image_dict['description']=flavor_dict['name']+str(dev_nb)+"-img"
+ image_dict['location']=device.get('image')
+ image_dict['checksum']=device.get('image checksum')
image_metadata_dict = device.get('image metadata', None)
image_metadata_str = None
if image_metadata_dict != None:
#print "Flavor name: %s. Description: %s" % (VNFCitem["name"]+"-flv", VNFCitem["description"])
myflavorDict = {}
- myflavorDict["name"] = vnfc['name']+"-flv"
+ myflavorDict["name"] = vnfc['name']+"-flv" #Maybe we could rename the flavor by using the field "image name" if exists
myflavorDict["description"] = VNFCitem["description"]
myflavorDict["ram"] = vnfc.get("ram", 0)
myflavorDict["vcpus"] = vnfc.get("vcpus", 0)
#In case this integration is made, the VNFCDict might become a VNFClist.
for vnfc in vnf_descriptor['vnf']['VNFC']:
#print "Image name: %s. Description: %s" % (vnfc['name']+"-img", VNFCDict[vnfc['name']]['description'])
- image_dict={'location':vnfc['VNFC image'], 'name':vnfc['name']+"-img", 'description':VNFCDict[vnfc['name']]['description']}
+ image_dict={}
+ image_dict['name']=vnfc.get('image name',vnf_name+"-"+vnfc['name']+"-img")
+ image_dict['universal_name']=vnfc.get('image name')
+ image_dict['description']=vnfc.get('image name', VNFCDict[vnfc['name']]['description'])
+ image_dict['location']=vnfc.get('VNFC image')
+ image_dict['checksum']=vnfc.get('image checksum')
image_metadata_dict = vnfc.get('image metadata', None)
image_metadata_str = None
if image_metadata_dict is not None:
image_id = create_or_use_image(mydb, vims, image_dict, rollback_list)
#print "Image id for VNFC %s: %s" % (vnfc['name'],image_id)
VNFCDict[vnfc['name']]["image_id"] = image_id
- VNFCDict[vnfc['name']]["image_path"] = vnfc['VNFC image']
+ VNFCDict[vnfc['name']]["image_path"] = vnfc.get('VNFC image')
# Step 7. Storing the VNF descriptor in the repository
#print "Flavor name: %s. Description: %s" % (VNFCitem["name"]+"-flv", VNFCitem["description"])
myflavorDict = {}
- myflavorDict["name"] = vnfc['name']+"-flv"
+ myflavorDict["name"] = vnfc['name']+"-flv" #Maybe we could rename the flavor by using the field "image name" if exists
myflavorDict["description"] = VNFCitem["description"]
myflavorDict["ram"] = vnfc.get("ram", 0)
myflavorDict["vcpus"] = vnfc.get("vcpus", 0)
#In case this integration is made, the VNFCDict might become a VNFClist.
for vnfc in vnf_descriptor['vnf']['VNFC']:
#print "Image name: %s. Description: %s" % (vnfc['name']+"-img", VNFCDict[vnfc['name']]['description'])
- image_dict={'location':vnfc['VNFC image'], 'name':vnfc['name']+"-img", 'description':VNFCDict[vnfc['name']]['description']}
+ image_dict={}
+ image_dict['name']=vnfc.get('image name',vnf_name+"-"+vnfc['name']+"-img")
+ image_dict['universal_name']=vnfc.get('image name')
+ image_dict['description']=vnfc.get('image name', VNFCDict[vnfc['name']]['description'])
+ image_dict['location']=vnfc.get('VNFC image')
+ image_dict['checksum']=vnfc.get('image checksum')
image_metadata_dict = vnfc.get('image metadata', None)
image_metadata_str = None
if image_metadata_dict is not None:
image_id = create_or_use_image(mydb, vims, image_dict, rollback_list)
#print "Image id for VNFC %s: %s" % (vnfc['name'],image_id)
VNFCDict[vnfc['name']]["image_id"] = image_id
- VNFCDict[vnfc['name']]["image_path"] = vnfc['VNFC image']
+ VNFCDict[vnfc['name']]["image_path"] = vnfc.get('VNFC image')
# Step 7. Storing the VNF descriptor in the repository
schema_version_2={"type":"integer","minimum":2,"maximum":2}
#schema_version_string={"type":"string","enum": ["0.1", "2", "0.2", "3", "0.3"]}
log_level_schema={"type":"string", "enum":["DEBUG", "INFO", "WARNING","ERROR","CRITICAL"]}
+checksum_schema={"type":"string", "pattern":"^[0-9a-fA-F]{32}$"}
metadata_schema={
"type":"object",
"properties":{
"type":{"type":"string", "enum":["disk","cdrom","xml"] },
"image": path_schema,
+ "image name": name_schema,
+ "image checksum": checksum_schema,
"image metadata": metadata_schema,
"vpci":pci_schema,
"xml":xml_text_schema,
"name": name_schema,
"description": description_schema,
"VNFC image": {"oneOf": [path_schema, http_schema]},
+ "image name": name_schema,
+ "image checksum": checksum_schema,
"image metadata": metadata_schema,
#"cloud-config": cloud_config_schema, #common for all vnfs in the scenario
"processor": {
"devices": devices_schema
},
- "required": ["name", "VNFC image"],
+ "required": ["name"],
+ "oneOf": [
+ {"required": ["VNFC image"]},
+ {"required": ["image name"]}
+ ],
"additionalProperties": False
}
'''
__author__="Alfonso Tierno, Gerardo Garcia, Pablo Montes"
__date__ ="$26-aug-2014 11:09:29$"
-__version__="0.4.58-r501"
+__version__="0.4.59-r502"
version_date="Sep 2016"
-database_version="0.14" #expected database schema version
+database_version="0.15" #expected database schema version
import httpserver
import time
${DIRmano}/openmano vnf-delete -f linux_2VMs_v02 || echo "fail"
${DIRmano}/openmano vnf-delete -f dataplaneVNF_2VMs || echo "fail"
${DIRmano}/openmano vnf-delete -f dataplaneVNF_2VMs_v02 || echo "fail"
+ ${DIRmano}/openmano vnf-delete -f dataplaneVNF_2VMs_v02_withimagename || echo "fail"
${DIRmano}/openmano vnf-delete -f dataplaneVNF2 || echo "fail"
${DIRmano}/openmano vnf-delete -f dataplaneVNF3 || echo "fail"
${DIRmano}/openmano datacenter-detach TEST-dc || echo "fail"
[[ $? != 0 ]] && echo "FAIL" && echo " $result" && $_exit 1
echo OK
- for VNF in linux dataplaneVNF1 dataplaneVNF2 dataplaneVNF_2VMs dataplaneVNF_2VMs_v02 dataplaneVNF3 linux_2VMs_v02
+ for VNF in linux dataplaneVNF1 dataplaneVNF2 dataplaneVNF_2VMs dataplaneVNF_2VMs_v02 dataplaneVNF3 linux_2VMs_v02 dataplaneVNF_2VMs_v02_withimagename
do
printf "%-50s" "Creating VNF '${VNF}': "
result=`$DIRmano/openmano vnf-create $DIRmano/vnfs/examples/${VNF}.yaml`
raise vimconnNotImplemented( "Should have implemented this" )
def get_image_id_from_path(self, path):
- '''Get the image id from image path in the VIM database'''
- '''Returns:
- 0,"Image not found" if there are no images with that path
- 1,image-id if there is one image with that path
- <0,message if there was an error (Image not found, error contacting VIM, more than 1 image with that path, etc.)
- '''
+ '''Get the image id from image path in the VIM database. Returns the image_id'''
raise vimconnNotImplemented( "Should have implemented this" )
+ def get_image_list(self, filter_dict={}):
+ '''Obtain tenant images from VIM
+ Filter_dict can be:
+ name: image name
+ id: image uuid
+ checksum: image checksum
+ location: image path
+ Returns the image list of dictionaries:
+ [{<the fields at Filter_dict plus some VIM specific>}, ...]
+ List can be empty
+ '''
+ raise vimconnNotImplemented( "Should have implemented this" )
+
def new_vminstance(self,name,description,start,image_id,flavor_id,net_list,cloud_config=None):
'''Adds a VM instance to VIM
Params:
self._format_exception(e)
def get_image_id_from_path(self, path):
- '''Get the image id from image path in the VIM database. Returns the image_id
- '''
+ '''Get the image id from image path in the VIM database. Returns the image_id'''
try:
self._reload_connection()
images = self.nova.images.list()
except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
self._format_exception(e)
+ def get_image_list(self, filter_dict={}):
+ '''Obtain tenant images from VIM
+ Filter_dict can be:
+ id: image id
+ name: image name
+ checksum: image checksum
+ Returns the image list of dictionaries:
+ [{<the fields at Filter_dict plus some VIM specific>}, ...]
+ List can be empty
+ '''
+ self.logger.debug("Getting image list from VIM filter: '%s'", str(filter_dict))
+ try:
+ self._reload_connection()
+ filter_dict_os=filter_dict.copy()
+ #First we filter by the available filter fields: name, id. The others are removed.
+ filter_dict_os.pop('checksum',None)
+ image_list=self.nova.images.findall(**filter_dict_os)
+ if len(image_list)==0:
+ return []
+ #Then we filter by the rest of filter fields: checksum
+ filtered_list = []
+ for image in image_list:
+ image_dict=glance.images.get(image.id)
+ if image_dict['checksum']==filter_dict.get('checksum'):
+ filtered_list.append(image)
+ return filtered_list
+ except (ksExceptions.ClientException, nvExceptions.ClientException, gl1Exceptions.CommunicationError, ConnectionError) as e:
+ self._format_exception(e)
+
def new_vminstance(self,name,description,start,image_id,flavor_id,net_list,cloud_config=None):
'''Adds a VM instance to VIM
Params:
def get_image_id_from_path(self, path):
- '''Get the image id from image path in the VIM database'''
+ '''Get the image id from image path in the VIM database. Returns the image_id'''
try:
self._get_my_tenant()
url=self.url + '/' + self.tenant + '/images?path='+quote(path)
except (requests.exceptions.RequestException, js_e.ValidationError) as e:
self._format_request_exception(e)
+ def get_image_list(self, filter_dict={}):
+ '''Obtain tenant images from VIM
+ Filter_dict can be:
+ name: image name
+ id: image uuid
+ checksum: image checksum
+ location: image path
+ Returns the image list of dictionaries:
+ [{<the fields at Filter_dict plus some VIM specific>}, ...]
+ List can be empty
+ '''
+ try:
+ self._get_my_tenant()
+ filterquery=[]
+ filterquery_text=''
+ for k,v in filter_dict.iteritems():
+ filterquery.append(str(k)+'='+str(v))
+ if len(filterquery)>0:
+ filterquery_text='?'+ '&'.join(filterquery)
+ url = self.url+'/'+self.tenant+'/images'+filterquery_text
+ self.logger.info("Getting image list GET %s", url)
+ vim_response = requests.get(url, headers = self.headers_req)
+ self._check_http_request_response(vim_response)
+ self.logger.debug(vim_response.text)
+ #print json.dumps(vim_response.json(), indent=4)
+ response = vim_response.json()
+ return response['images']
+ except (requests.exceptions.RequestException, js_e.ValidationError) as e:
+ self._format_request_exception(e)
+
def new_vminstancefromJSON(self, vm_data):
'''Adds a VM instance to VIM'''
'''Returns the instance identifier'''
--- /dev/null
+##\r
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.\r
+# This file is part of openmano\r
+# All Rights Reserved.\r
+#\r
+# Licensed under the Apache License, Version 2.0 (the "License"); you may\r
+# not use this file except in compliance with the License. You may obtain\r
+# 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, WITHOUT\r
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\r
+# License for the specific language governing permissions and limitations\r
+# under the License.\r
+#\r
+# For those usages not covered by the Apache License, Version 2.0 please\r
+# contact with: nfvlabs@tid.es\r
+##\r
+---\r
+schema_version: "0.2"\r
+vnf:\r
+ name: dataplaneVNF_2VMs_v02_withimagename\r
+ description: "Example of a dataplane VNF consisting of two VMs for data plane workloads with one internal network"\r
+ # class: parent # Optional. Used to organize VNFs\r
+ internal-connections:\r
+ - name: datanet\r
+ description: datanet\r
+ type: e-lan\r
+ implementation: underlay\r
+ ip-profile:\r
+ ip-version: IPv4\r
+ subnet-address: 192.168.1.0/24\r
+ gateway-address: 192.168.1.1\r
+ dns-address: 8.8.8.8\r
+ dhcp:\r
+ enabled: true\r
+ start-address: 192.168.1.100\r
+ count: 100\r
+ elements:\r
+ - VNFC: VNF_2VMs-VM1\r
+ local_iface_name: xe0\r
+ ip_address: 192.168.1.2\r
+ - VNFC: VNF_2VMs-VM2\r
+ local_iface_name: xe0\r
+ ip_address: 192.168.1.3\r
+ external-connections:\r
+ - name: control0\r
+ type: mgmt\r
+ VNFC: VNF_2VMs-VM1\r
+ local_iface_name: eth0\r
+ description: control interface VM1\r
+ - name: control1\r
+ type: mgmt\r
+ VNFC: VNF_2VMs-VM2\r
+ local_iface_name: eth0\r
+ description: control interface VM2\r
+ - name: in\r
+ type: data\r
+ VNFC: VNF_2VMs-VM1\r
+ local_iface_name: xe1\r
+ description: Dataplane interface input\r
+ - name: out\r
+ type: data\r
+ VNFC: VNF_2VMs-VM2\r
+ local_iface_name: xe1\r
+ description: Dataplane interface output\r
+ VNFC:\r
+ - name: VNF_2VMs-VM1\r
+ description: "Dataplane VM1 with 4 threads, 2 GB hugepages, 2 SR-IOV interface"\r
+ #Copy the image to a compute path and edit this path\r
+ image name: dataplaneVNF_2VMs-image\r
+ disk: 10\r
+ numas: \r
+ - paired-threads: 2 # "cores", "paired-threads", "threads"\r
+ memory: 2 # GBytes\r
+ interfaces:\r
+ - name: xe0\r
+ vpci: "0000:00:11.0"\r
+ dedicated: "no" # "yes"(passthrough), "no"(sriov with vlan tags), "yes:sriov"(sriovi, but exclusive and without vlan tag)\r
+ bandwidth: 1 Gbps\r
+ - name: xe1\r
+ vpci: "0000:00:12.0"\r
+ dedicated: "no"\r
+ bandwidth: 1 Gbps\r
+ bridge-ifaces:\r
+ - name: eth0\r
+ vpci: "0000:00:09.0"\r
+ bandwidth: 1 Mbps # Optional, informative only\r
+\r
+ - name: VNF_2VMs-VM2\r
+ description: "Dataplane VM1 with 2 threads, 2 GB hugepages, 2 SR-IOV interface"\r
+ #Copy the image to a compute path and edit this path\r
+ image name: dataplaneVNF_2VMs-image\r
+ disk: 10\r
+ numas: \r
+ - paired-threads: 1 # "cores", "paired-threads", "threads"\r
+ memory: 2 # GBytes\r
+ interfaces:\r
+ - name: xe0\r
+ vpci: "0000:00:11.0"\r
+ dedicated: "no" # "yes"(passthrough), "no"(sriov with vlan tags), "yes:sriov"(sriovi, but exclusive and without vlan tag)\r
+ bandwidth: 1 Gbps\r
+ - name: xe1\r
+ vpci: "0000:00:12.0"\r
+ dedicated: "no"\r
+ bandwidth: 1 Gbps\r
+ bridge-ifaces:\r
+ - name: eth0\r
+ vpci: "0000:00:09.0"\r
+ bandwidth: 1 Mbps # Optional, informative only\r
+\r