From: garciadeblas Date: Thu, 12 Jan 2017 14:03:58 +0000 (+0100) Subject: Merge branch 'v1.0' X-Git-Tag: v1.1.0~28 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=refs%2Fchanges%2F33%2F933%2F1;hp=fda5f7c30615df1edd2f982c18cba17f1200bba4;p=osm%2FRO.git Merge branch 'v1.0' --- diff --git a/.gitignore-common b/.gitignore-common index c71eced3..77f6798d 100644 --- a/.gitignore-common +++ b/.gitignore-common @@ -22,7 +22,8 @@ # This is a template with common files to be igonored, after clone make a copy to .gitignore # cp .gitignore-common .gitignore -*.pyc +*.pyc +*.pyo #auto-ignore .gitignore diff --git a/database_utils/migrate_mano_db.sh b/database_utils/migrate_mano_db.sh index 208ec7d0..9fc49665 100755 --- a/database_utils/migrate_mano_db.sh +++ b/database_utils/migrate_mano_db.sh @@ -185,6 +185,7 @@ DATABASE_TARGET_VER_NUM=0 [ $OPENMANO_VER_NUM -ge 4059 ] && DATABASE_TARGET_VER_NUM=15 #0.4.59=> 15 [ $OPENMANO_VER_NUM -ge 5002 ] && DATABASE_TARGET_VER_NUM=16 #0.5.2 => 16 [ $OPENMANO_VER_NUM -ge 5003 ] && DATABASE_TARGET_VER_NUM=17 #0.5.3 => 17 +[ $OPENMANO_VER_NUM -ge 5004 ] && DATABASE_TARGET_VER_NUM=18 #0.5.4 => 18 #TODO ... put next versions here @@ -696,6 +697,25 @@ function downgrade_from_17(){ echo "DELETE FROM schema_version WHERE version_int='17';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 } +function upgrade_to_18(){ + echo " upgrade database from version 0.17 to version 0.18" + echo " add columns 'floating_ip' and 'port_security' at tables 'interfaces' and 'instance_interfaces'" + echo "ALTER TABLE interfaces ADD floating_ip BOOL DEFAULT 0 NOT NULL COMMENT 'Indicates if a floating_ip must be associated to this interface';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE interfaces ADD port_security BOOL DEFAULT 1 NOT NULL COMMENT 'Indicates if port security must be enabled or disabled. By default it is enabled';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE instance_interfaces ADD floating_ip BOOL DEFAULT 0 NOT NULL COMMENT 'Indicates if a floating_ip must be associated to this interface';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE instance_interfaces ADD port_security BOOL DEFAULT 1 NOT NULL COMMENT 'Indicates if port security must be enabled or disabled. By default it is enabled';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "INSERT INTO schema_version (version_int, version, openmano_ver, comments, date) VALUES (18, '0.18', '0.5.4', 'Add columns \'floating_ip\' and \'port_security\' at tables \'interfaces\' and \'instance_interfaces\'', '2017-01-09');" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 +} +function downgrade_from_18(){ + echo " downgrade database from version 0.18 to version 0.17" + echo " remove columns 'floating_ip' and 'port_security' from tables 'interfaces' and 'instance_interfaces'" + echo "ALTER TABLE interfaces DROP COLUMN floating_ip;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE interfaces DROP COLUMN port_security;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE instance_interfaces DROP COLUMN floating_ip;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE instance_interfaces DROP COLUMN port_security;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "DELETE FROM schema_version WHERE version_int='18';" | $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/db_base.py b/db_base.py index 807c73d5..10f94045 100644 --- a/db_base.py +++ b/db_base.py @@ -125,8 +125,11 @@ def _convert_str2boolean(data, items): _convert_str2boolean(data[k], items) if k in items: if type(data[k]) is str: - if data[k]=="false" or data[k]=="False": data[k]=False - elif data[k]=="true" or data[k]=="True": data[k]=True + if data[k]=="false" or data[k]=="False" or data[k]=="0": data[k]=False + elif data[k]=="true" or data[k]=="True" or data[k]=="1": data[k]=True + elif type(data[k]) is int: + if data[k]==0: data[k]=False + elif data[k]==1: data[k]=True if type(data) is tuple or type(data) is list: for k in data: if type(k) is dict or type(k) is tuple or type(k) is list: diff --git a/nfvo.py b/nfvo.py index 49587626..9a2dd05d 100644 --- a/nfvo.py +++ b/nfvo.py @@ -1493,6 +1493,10 @@ def start_scenario(mydb, tenant_id, scenario_id, instance_scenario_name, instanc netDict['vpci'] = iface['vpci'] if "mac" in iface and iface["mac"] is not None: netDict['mac_address'] = iface['mac'] + if "port-security" in iface and iface["port-security"] is not None: + netDict['port_security'] = iface['port-security'] + if "floating-ip" in iface and iface["floating-ip"] is not None: + netDict['floating_ip'] = iface['floating-ip'] netDict['name'] = iface['internal_name'] if iface['net_id'] is None: for vnf_iface in sce_vnf["interfaces"]: @@ -2014,6 +2018,10 @@ def create_instance(mydb, tenant_id, instance_dict): netDict['vpci'] = iface['vpci'] if "mac" in iface and iface["mac"] is not None: netDict['mac_address'] = iface['mac'] + if "port-security" in iface and iface["port-security"] is not None: + netDict['port_security'] = iface['port-security'] + if "floating-ip" in iface and iface["floating-ip"] is not None: + netDict['floating_ip'] = iface['floating-ip'] netDict['name'] = iface['internal_name'] if iface['net_id'] is None: for vnf_iface in sce_vnf["interfaces"]: diff --git a/nfvo_db.py b/nfvo_db.py index 6ab73e95..5e51c3b1 100644 --- a/nfvo_db.py +++ b/nfvo_db.py @@ -93,12 +93,20 @@ class nfvo_db(db_base.db_base): if 'bridge-ifaces' in vm: bridgeInterfacesDict[vm['name']] = {} for bridgeiface in vm['bridge-ifaces']: + if 'port-security' in bridgeiface: + bridgeiface['port_security'] = bridgeiface.pop('port-security') + if 'floating-ip' in bridgeiface: + bridgeiface['floating_ip'] = bridgeiface.pop('floating-ip') db_base._convert_bandwidth(bridgeiface, logger=self.logger) bridgeInterfacesDict[vm['name']][bridgeiface['name']] = {} bridgeInterfacesDict[vm['name']][bridgeiface['name']]['vpci'] = bridgeiface.get('vpci',None) bridgeInterfacesDict[vm['name']][bridgeiface['name']]['mac'] = bridgeiface.get('mac_address',None) bridgeInterfacesDict[vm['name']][bridgeiface['name']]['bw'] = bridgeiface.get('bandwidth', None) bridgeInterfacesDict[vm['name']][bridgeiface['name']]['model'] = bridgeiface.get('model', None) + bridgeInterfacesDict[vm['name']][bridgeiface['name']]['port_security'] = \ + int(bridgeiface.get('port_security', True)) + bridgeInterfacesDict[vm['name']][bridgeiface['name']]['floating_ip'] = \ + int(bridgeiface.get('floating_ip', False)) #For each internal connection, we add it to the interfaceDict and we create the appropriate net in the NFVO database. #print "Adding new nets (VNF internal nets) to the NFVO database (if any)" @@ -133,6 +141,10 @@ class nfvo_db(db_base.db_base): ifaceItem["mac"] = bridgeInterfacesDict[ element['VNFC'] ][ element['local_iface_name'] ]['mac_address'] ifaceItem["bw"] = bridgeInterfacesDict[ element['VNFC'] ][ element['local_iface_name'] ]['bw'] ifaceItem["model"] = bridgeInterfacesDict[ element['VNFC'] ][ element['local_iface_name'] ]['model'] + ifaceItem["port_security"] = \ + bridgeInterfacesDict[element['VNFC']][element['local_iface_name']]['port_security'] + ifaceItem["floating_ip"] = \ + bridgeInterfacesDict[element['VNFC']][element['local_iface_name']]['floating_ip'] internalconnList.append(ifaceItem) #print "Internal net id in NFVO DB: %s" % net_id @@ -161,6 +173,10 @@ class nfvo_db(db_base.db_base): myIfaceDict["bw"] = bridgeInterfacesDict[ iface['VNFC'] ][ iface['local_iface_name'] ]['bw'] myIfaceDict["model"] = bridgeInterfacesDict[ iface['VNFC'] ][ iface['local_iface_name'] ]['model'] myIfaceDict["mac"] = bridgeInterfacesDict[ iface['VNFC'] ][ iface['local_iface_name'] ]['mac'] + myIfaceDict["port_security"] = \ + bridgeInterfacesDict[iface['VNFC']][iface['local_iface_name']]['port_security'] + myIfaceDict["floating_ip"] = \ + bridgeInterfacesDict[iface['VNFC']][iface['local_iface_name']]['floating_ip'] print "Iface name: %s" % iface['name'] created_time += 0.00001 iface_id = self._new_row_internal('interfaces', myIfaceDict, add_uuid=True, root_uuid=vnf_id, created_time=created_time) @@ -222,11 +238,19 @@ class nfvo_db(db_base.db_base): bridgeInterfacesDict[vm['name']] = {} for bridgeiface in vm['bridge-ifaces']: db_base._convert_bandwidth(bridgeiface, logger=self.logger) + if 'port-security' in bridgeiface: + bridgeiface['port_security'] = bridgeiface.pop('port-security') + if 'floating-ip' in bridgeiface: + bridgeiface['floating_ip'] = bridgeiface.pop('floating-ip') bridgeInterfacesDict[vm['name']][bridgeiface['name']] = {} bridgeInterfacesDict[vm['name']][bridgeiface['name']]['vpci'] = bridgeiface.get('vpci',None) bridgeInterfacesDict[vm['name']][bridgeiface['name']]['mac'] = bridgeiface.get('mac_address',None) bridgeInterfacesDict[vm['name']][bridgeiface['name']]['bw'] = bridgeiface.get('bandwidth', None) bridgeInterfacesDict[vm['name']][bridgeiface['name']]['model'] = bridgeiface.get('model', None) + bridgeInterfacesDict[vm['name']][bridgeiface['name']]['port_security'] = \ + int(bridgeiface.get('port_security', True)) + bridgeInterfacesDict[vm['name']][bridgeiface['name']]['floating_ip'] = \ + int(bridgeiface.get('floating_ip', False)) #For each internal connection, we add it to the interfaceDict and we create the appropriate net in the NFVO database. #print "Adding new nets (VNF internal nets) to the NFVO database (if any)" @@ -287,6 +311,10 @@ class nfvo_db(db_base.db_base): ifaceItem["mac"] = bridgeInterfacesDict[ element['VNFC'] ][ element['local_iface_name'] ]['mac'] ifaceItem["bw"] = bridgeInterfacesDict[ element['VNFC'] ][ element['local_iface_name'] ]['bw'] ifaceItem["model"] = bridgeInterfacesDict[ element['VNFC'] ][ element['local_iface_name'] ]['model'] + ifaceItem["port_security"] = \ + bridgeInterfacesDict[element['VNFC']][element['local_iface_name']]['port_security'] + ifaceItem["floating_ip"] = \ + bridgeInterfacesDict[element['VNFC']][element['local_iface_name']]['floating_ip'] internalconnList.append(ifaceItem) #print "Internal net id in NFVO DB: %s" % net_id @@ -315,6 +343,10 @@ class nfvo_db(db_base.db_base): myIfaceDict["bw"] = bridgeInterfacesDict[ iface['VNFC'] ][ iface['local_iface_name'] ]['bw'] myIfaceDict["model"] = bridgeInterfacesDict[ iface['VNFC'] ][ iface['local_iface_name'] ]['model'] myIfaceDict["mac"] = bridgeInterfacesDict[ iface['VNFC'] ][ iface['local_iface_name'] ]['mac'] + myIfaceDict["port_security"] = \ + bridgeInterfacesDict[iface['VNFC']][iface['local_iface_name']]['port_security'] + myIfaceDict["floating_ip"] = \ + bridgeInterfacesDict[iface['VNFC']][iface['local_iface_name']]['floating_ip'] print "Iface name: %s" % iface['name'] created_time += 0.00001 iface_id = self._new_row_internal('interfaces', myIfaceDict, add_uuid=True, root_uuid=vnf_id, created_time=created_time) @@ -643,13 +675,17 @@ class nfvo_db(db_base.db_base): vm['vim_flavor_id']=vim_flavor_dict['vim_id'] #interfaces - cmd = "SELECT uuid,internal_name,external_name,net_id,type,vpci,mac,bw,model,ip_address" \ + cmd = "SELECT uuid,internal_name,external_name,net_id,type,vpci,mac,bw,model,ip_address," \ + "floating_ip, port_security" \ " FROM interfaces" \ " WHERE vm_id='{}'" \ " ORDER BY created_at".format(vm['uuid']) self.logger.debug(cmd) self.cur.execute(cmd) vm['interfaces'] = self.cur.fetchall() + for index in range(0,len(vm['interfaces'])): + vm['interfaces'][index]['port-security'] = vm['interfaces'][index].pop("port_security") + vm['interfaces'][index]['floating-ip'] = vm['interfaces'][index].pop("floating_ip") #nets every net of a vms cmd = "SELECT uuid,name,type,description FROM nets WHERE vnf_id='{}'".format(vnf['vnf_id']) self.logger.debug(cmd) @@ -700,7 +736,7 @@ class nfvo_db(db_base.db_base): net['vim_id']=d_net['vim_net_id'] db_base._convert_datetime2str(scenario_dict) - db_base._convert_str2boolean(scenario_dict, ('public','shared','external') ) + db_base._convert_str2boolean(scenario_dict, ('public','shared','external','port-security','floating-ip') ) return scenario_dict except (mdb.Error, AttributeError) as e: self._format_error(e, tries) @@ -846,7 +882,8 @@ class nfvo_db(db_base.db_base): interface_type='external' if interface['external_name'] is not None else 'internal' INSERT_={'instance_vm_id': instance_vm_uuid, 'instance_net_id': net_scene2instance[net_id][datacenter_site_id], 'interface_id': interface['uuid'], 'vim_interface_id': interface.get('vim_id'), 'type': interface_type, - 'ip_address': interface.get('ip_address') } + 'ip_address': interface.get('ip_address'), 'floating_ip': int(interface.get('floating-ip',False)), + 'port_security': int(interface.get('port-security',True))} #created_time += 0.00001 interface_uuid = self._new_row_internal('instance_interfaces', INSERT_, True, instance_uuid) #, created_time) interface['uuid'] = interface_uuid #overwrite scnario uuid by instance uuid diff --git a/openmano b/openmano index 11cc726f..59fd7e8d 100755 --- a/openmano +++ b/openmano @@ -591,7 +591,7 @@ def instance_create(args): if args.scenario != None: scenario = args.scenario if not scenario: - print "you must provide an scenario in the file descriptor or with --scenario" + print "you must provide a scenario in the file descriptor or with --scenario" return -1 myInstance["instance"]["scenario"] = _get_item_uuid("scenarios", scenario, tenant) if args.netmap_use: diff --git a/openmano_schemas.py b/openmano_schemas.py index 013234f5..a8c92a0f 100644 --- a/openmano_schemas.py +++ b/openmano_schemas.py @@ -450,7 +450,9 @@ bridge_interfaces_schema={ "bandwidth":bandwidth_schema, "vpci":pci_schema, "mac_address": mac_schema, - "model": {"type":"string", "enum":["virtio","e1000","ne2k_pci","pcnet","rtl8139"]} + "model": {"type":"string", "enum":["virtio","e1000","ne2k_pci","pcnet","rtl8139"]}, + "port-security": {"type" : "boolean"}, + "floating-ip": {"type" : "boolean"} }, "additionalProperties": False, "required": ["name"] diff --git a/openmanod.py b/openmanod.py index b50ca9dd..d00f6182 100755 --- a/openmanod.py +++ b/openmanod.py @@ -33,9 +33,9 @@ It loads the configuration file and launches the http_server thread that will li ''' __author__="Alfonso Tierno, Gerardo Garcia, Pablo Montes" __date__ ="$26-aug-2014 11:09:29$" -__version__="0.5.3-r511" -version_date="Dec 2016" -database_version="0.17" #expected database schema version +__version__="0.5.4-r512" +version_date="Jan 2017" +database_version="0.18" #expected database schema version import httpserver import time diff --git a/scenarios/examples/scenario_vnf_floating_ip.yaml b/scenarios/examples/scenario_vnf_floating_ip.yaml new file mode 100644 index 00000000..61da9e75 --- /dev/null +++ b/scenarios/examples/scenario_vnf_floating_ip.yaml @@ -0,0 +1,40 @@ +## +# 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: vnf_floating_ip + description: vnf_floating_ip + public: false # if available for other tenants + vnfs: + vnf_floating_ip: # vnf name in the scenario + #identify an already openmano uploaded VNF either by vnf_id (uuid, prefered) or vnf_name + #vnf_id: 0c0dcc20-c5d5-11e6-a9fb-fa163e2ae06e #prefered id method + vnf_name: vnf_floating_ip #can fail if several vnfs matches this name + #graph: {"y":399,"x":332,"ifaces":{"left":[["xe0","d"],["xe1","d"]],"bottom":[["eth0","v"],["eth1","m"]]}} + networks: + internal: + # Connections based on external networks (datacenter nets) must include the external network in the list of nodes + type: bridge + external: true #this will be connected outside + interfaces: + - vnf_floating_ip: mgmt0 + diff --git a/scenarios/examples/scenario_vnf_no_port_security.yaml b/scenarios/examples/scenario_vnf_no_port_security.yaml new file mode 100644 index 00000000..df0a53b5 --- /dev/null +++ b/scenarios/examples/scenario_vnf_no_port_security.yaml @@ -0,0 +1,40 @@ +## +# 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: vnf_no_port_security + description: vnf_no_port_security + public: false # if available for other tenants + vnfs: + vnf_no_port_security: # vnf name in the scenario + #identify an already openmano uploaded VNF either by vnf_id (uuid, prefered) or vnf_name + #vnf_id: 0c0dcc20-c5d5-11e6-a9fb-fa163e2ae06e #prefered id method + vnf_name: vnf_no_port_security #can fail if several vnfs matches this name + #graph: {"y":399,"x":332,"ifaces":{"left":[["xe0","d"],["xe1","d"]],"bottom":[["eth0","v"],["eth1","m"]]}} + networks: + internal: + # Connections based on external networks (datacenter nets) must include the external network in the list of nodes + type: bridge + external: true #this will be connected outside + interfaces: + - vnf_no_port_security: mgmt0 + diff --git a/scripts/install-openmano.sh b/scripts/install-openmano.sh index f141076e..9e76b0df 100755 --- a/scripts/install-openmano.sh +++ b/scripts/install-openmano.sh @@ -273,8 +273,8 @@ sudo pip install pyvmomi [ "$_DISTRO" == "CentOS" -o "$_DISTRO" == "Red" ] && easy_install -U bottle #install openstack client needed for using openstack as a VIM -[ "$_DISTRO" == "Ubuntu" ] && install_packages "python-novaclient python-keystoneclient python-glanceclient python-neutronclient" -[ "$_DISTRO" == "CentOS" -o "$_DISTRO" == "Red" ] && install_packages "python-devel" && easy_install python-novaclient python-keystoneclient python-glanceclient python-neutronclient #TODO revise if gcc python-pip is needed +[ "$_DISTRO" == "Ubuntu" ] && install_packages "python-novaclient python-keystoneclient python-glanceclient python-neutronclient python-cinderclient" +[ "$_DISTRO" == "CentOS" -o "$_DISTRO" == "Red" ] && install_packages "python-devel" && easy_install python-novaclient python-keystoneclient python-glanceclient python-neutronclient python-cinderclient #TODO revise if gcc python-pip is needed fi #[[ -z "$NO_PACKAGES" ]] if [[ -z $NOCLONE ]]; then diff --git a/test/test_openmanoclient.py b/test/test_openmanoclient.py new file mode 100755 index 00000000..5e8876b8 --- /dev/null +++ b/test/test_openmanoclient.py @@ -0,0 +1,432 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +## +# 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 +## + +''' +Module to test openmanoclient class and indirectly the whole openmano +It allows both python 2 and python 3 +''' +__author__="Alfonso Tierno" +__date__ ="$09-Mar-2016 09:09:48$" +__version__="0.0.2" +version_date="May 2016" + +import logging +import imp + + + +def _get_random_name(maxLength): + '''generates a string with random craracters from space (ASCCI 32) to ~(ASCCI 126) + with a random length up to maxLength + ''' + long_name = "testing up to {} size name: ".format(maxLength) + #long_name += ''.join(chr(random.randint(32,126)) for _ in range(random.randint(20, maxLength-len(long_name)))) + long_name += ''.join(random.choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ') for _ in range(20, maxLength-len(long_name))) + return long_name + + +if __name__=="__main__": + import getopt + #import os + import sys + + + + usage =\ + """Make a test against an openmano server.\nUsage: test_openmanoclient [options] + -v|--verbose: prints more info in the test + --version: shows current version + -h|--help: shows this help + -d|--debug: set logs to debug level + -t|--tenant: set the tenant name to test. By default creates one + --datacenter: set the datacenter name to test. By default creates one at http://localhost:9080/openvim + -u|--url: set the openmano server url. By default 'http://localhost:9090/openmano' + --image: use this image path for testing a VNF. By default a fake one is generated, valid for VIM in test mode' + """ + + #import openmanoclient from relative path + module_info = imp.find_module("openmanoclient", [".."] ) + Client = imp.load_module("Client", *module_info) + + streamformat = "%(asctime)s %(name)s %(levelname)s: %(message)s" + logging.basicConfig(format=streamformat) + try: + opts, args = getopt.getopt(sys.argv[1:], "t:u:dhv", ["url=", "tenant=", "debug", "help", "version", "verbose", "datacenter=", "image="]) + except getopt.GetoptError as err: + print ("Error: {}\n Try '{} --help' for more information".format(str(err), sys.argv[0])) + sys.exit(2) + + debug = False + verbose = False + url = "http://localhost:9090/openmano" + to_delete_list=[] + test_tenant = None + test_datacenter = None + test_vim_tenant = None + test_image = None + for o, a in opts: + if o in ("-v", "--verbose"): + verbose = True + elif o in ("--version"): + print ("{} version".format(sys.argv[0]), __version__, version_date) + print ("(c) Copyright Telefonica") + sys.exit() + elif o in ("-h", "--help"): + print(usage) + sys.exit() + elif o in ("-d", "--debug"): + debug = True + elif o in ("-u", "--url"): + url = a + elif o in ("-t", "--tenant"): + test_tenant = a + elif o in ("--datacenter"): + test_datacenter = a + elif o in ("--image"): + test_image = a + else: + assert False, "Unhandled option" + + + + client = Client.openmanoclient( + endpoint_url=url, + tenant_name=test_tenant, + datacenter_name = test_datacenter, + debug = debug) + + import random + test_number=1 + + #TENANTS + print(" {}. TEST create_tenant".format(test_number)) + test_number += 1 + long_name = _get_random_name(60) + + tenant = client.create_tenant(name=long_name, description=long_name) + if verbose: print(tenant) + + print(" {}. TEST list_tenants".format(test_number)) + test_number += 1 + tenants = client.list_tenants() + if verbose: print(tenants) + + print(" {}. TEST list_tenans filter by name".format(test_number)) + test_number += 1 + tenants_ = client.list_tenants(name=long_name) + if not tenants_["tenants"]: + raise Exception("Text error, no TENANT found with name") + if verbose: print(tenants_) + + print(" {}. TEST get_tenant by UUID".format(test_number)) + test_number += 1 + tenant = client.get_tenant(uuid=tenants_["tenants"][0]["uuid"]) + if verbose: print(tenant) + + print(" {}. TEST delete_tenant by name".format(test_number)) + test_number += 1 + tenant = client.delete_tenant(name = long_name) + if verbose: print(tenant) + + if not test_tenant: + print(" {}. TEST create_tenant for remaining tests".format(test_number)) + test_number += 1 + test_tenant = "test-tenant "+\ + ''.join(random.choice('abcdefghijklmnopqrstuvwxyz') for _ in range(40)) + tenant = client.create_tenant(name = test_tenant) + if verbose: print(tenant) + client["tenant_name"] = test_tenant + + to_delete_list.insert(0,{"item": "tenant", "function": client.delete_tenant, "params":{"name": test_tenant} }) + + #DATACENTERS + print(" {}. TEST create_datacenter".format(test_number)) + test_number += 1 + long_name = _get_random_name(60) + + datacenter = client.create_datacenter(name=long_name, vim_url="http://fakeurl/fake") + if verbose: print(datacenter) + + print(" {}. TEST list_datacenters".format(test_number)) + test_number += 1 + datacenters = client.list_datacenters(all_tenants=True) + if verbose: print(datacenters) + + print(" {}. TEST list_tenans filter by name".format(test_number)) + test_number += 1 + datacenters_ = client.list_datacenters(all_tenants=True, name=long_name) + if not datacenters_["datacenters"]: + raise Exception("Text error, no TENANT found with name") + if verbose: print(datacenters_) + + print(" {}. TEST get_datacenter by UUID".format(test_number)) + test_number += 1 + datacenter = client.get_datacenter(uuid=datacenters_["datacenters"][0]["uuid"], all_tenants=True) + if verbose: print(datacenter) + + print(" {}. TEST delete_datacenter by name".format(test_number)) + test_number += 1 + datacenter = client.delete_datacenter(name=long_name) + if verbose: print(datacenter) + + if not test_datacenter: + print(" {}. TEST create_datacenter for remaining tests".format(test_number)) + test_number += 1 + test_datacenter = "test-datacenter "+\ + ''.join(random.choice('abcdefghijklmnopqrstuvwxyz') for _ in range(40)) + datacenter = client.create_datacenter(name=test_datacenter, vim_url="http://127.0.0.1:9080/openvim") + if verbose: print(datacenter) + client["datacenter_name"] = test_datacenter + to_delete_list.insert(0,{"item": "datacenter", "function": client.delete_datacenter, + "params":{ + "name": test_datacenter + } + }) + + print(" {}. TEST datacenter new tenenat".format(test_number)) + test_number += 1 + test_vim_tenant = "test-vimtenant "+\ + ''.join(random.choice('abcdefghijklmnopqrstuvwxyz') for _ in range(40)) + vim_tenant = client.vim_action("create", "tenants", datacenter_name=test_datacenter, all_tenants=True, name=test_vim_tenant) + if verbose: print(vim_tenant) + client["datacenter_name"] = test_datacenter + to_delete_list.insert(0,{"item": "vim_tenant", + "function": client.vim_action, + "params":{ + "action":"delete", + "item":"tenants", + "datacenter_name": test_datacenter, + "all_tenants": True, + "uuid": vim_tenant["tenant"]["id"] + } + }) + + print(" {}. TEST datacenter attach".format(test_number)) + test_number += 1 + datacenter = client.attach_datacenter(name=test_datacenter, vim_tenant_name=test_vim_tenant) + if verbose: print(datacenter) + client["datacenter_name"] = test_datacenter + to_delete_list.insert(0,{"item": "datacenter-detach", "function": client.detach_datacenter, "params":{"name": test_datacenter} }) + + client["datacenter_name"] = test_datacenter + + + #VIM_ACTIONS + print(" {}. TEST create_VIM_tenant".format(test_number)) + test_number += 1 + long_name = _get_random_name(60) + + tenant = client.vim_action("create", "tenants", name=long_name) + if verbose: print(tenant) + tenant_uuid = tenant["tenant"]["id"] + + print(" {}. TEST list_VIM_tenants".format(test_number)) + test_number += 1 + tenants = client.vim_action("list", "tenants") + if verbose: print(tenants) + + print(" {}. TEST get_VIM_tenant by UUID".format(test_number)) + test_number += 1 + tenant = client.vim_action("show", "tenants", uuid=tenant_uuid) + if verbose: print(tenant) + + print(" {}. TEST delete_VIM_tenant by id".format(test_number)) + test_number += 1 + tenant = client.vim_action("delete", "tenants", uuid = tenant_uuid) + if verbose: print(tenant) + + print(" {}. TEST create_VIM_network".format(test_number)) + test_number += 1 + long_name = _get_random_name(60) + + network = client.vim_action("create", "networks", name=long_name) + if verbose: print(network) + network_uuid = network["network"]["id"] + + print(" {}. TEST list_VIM_networks".format(test_number)) + test_number += 1 + networks = client.vim_action("list", "networks") + if verbose: print(networks) + + print(" {}. TEST get_VIM_network by UUID".format(test_number)) + test_number += 1 + network = client.vim_action("show", "networks", uuid=network_uuid) + if verbose: print(network) + + print(" {}. TEST delete_VIM_network by id".format(test_number)) + test_number += 1 + network = client.vim_action("delete", "networks", uuid = network_uuid) + if verbose: print(network) + #VNFS + print(" {}. TEST create_vnf".format(test_number)) + test_number += 1 + test_vnf_name = _get_random_name(255) + if test_image: + test_vnf_path = test_image + else: + test_vnf_path = "/random/path/" + "".join(random.choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ') for _ in range(20)) + + vnf_descriptor={'vnf': {'name': test_vnf_name, + 'VNFC': [{'description': _get_random_name(255), + 'name': 'linux-VM', + 'VNFC image': test_vnf_path, + 'ram': 1024, + 'vcpus': 1, + 'bridge-ifaces': [{'name': 'eth0'}] + }], + 'description': _get_random_name(255), + 'nets': [], + 'external-connections': [{'name': 'eth0', + 'local_iface_name': 'eth0', + 'VNFC': 'linux-VM', + 'type': 'bridge'}], + 'public': False}} + + vnf = client.create_vnf(descriptor=vnf_descriptor) + if verbose: print(vnf) + to_delete_list.insert(0,{"item": "vnf", "function": client.delete_vnf, "params":{"name": test_vnf_name} }) + + print(" {}. TEST list_vnfs".format(test_number)) + test_number += 1 + vnfs = client.list_vnfs() + if verbose: print(vnfs) + + print(" {}. TEST list_vnfs filter by name".format(test_number)) + test_number += 1 + vnfs_ = client.list_vnfs(name=test_vnf_name) + if not vnfs_["vnfs"]: + raise Exception("Text error, no VNF found with name") + if verbose: print(vnfs_) + + print(" {}. TEST get_vnf by UUID".format(test_number)) + test_number += 1 + vnf = client.get_vnf(uuid=vnfs_["vnfs"][0]["uuid"]) + if verbose: print(vnf) + + #SCENARIOS + print(" {}. TEST create_scenario".format(test_number)) + test_number += 1 + test_scenario_name = _get_random_name(255) + + scenario_descriptor={ 'schema_version': 2, + 'scenario': { + 'name': test_scenario_name, + 'description': _get_random_name(255), + 'public': True, + 'vnfs':{ + 'vnf1': { + 'vnf_name': test_vnf_name + } + }, + 'networks':{ + 'net1':{ + 'external': True, + 'interfaces': [ + {'vnf1': 'eth0'} + ] + } + } + } + } + + scenario = client.create_scenario(descriptor=scenario_descriptor) + if verbose: print(scenario) + to_delete_list.insert(0,{"item": "scenario", "function": client.delete_scenario, "params":{"name": test_scenario_name} }) + + print(" {}. TEST list_scenarios".format(test_number)) + test_number += 1 + scenarios = client.list_scenarios() + if verbose: print(scenarios) + + print(" {}. TEST list_scenarios filter by name".format(test_number)) + test_number += 1 + scenarios_ = client.list_scenarios(name=test_scenario_name) + if not scenarios_["scenarios"]: + raise Exception("Text error, no VNF found with name") + if verbose: print(scenarios_) + + print(" {}. TEST get_scenario by UUID".format(test_number)) + test_number += 1 + scenario = client.get_scenario(uuid=scenarios_["scenarios"][0]["uuid"]) + if verbose: print(scenario) + + + + #INSTANCES + print(" {}. TEST create_instance".format(test_number)) + test_number += 1 + test_instance_name = _get_random_name(255) + + instance_descriptor={ 'schema_version': 2, + 'instance': { + 'name': test_instance_name, + 'description': _get_random_name(255), + 'public': True, + 'vnfs':{ + 'vnf1': { + 'vnf_name': test_vnf_name + } + }, + 'networks':{ + 'net1':{ + 'external': True, + 'interfaces': [ + {'vnf1': 'eth0'} + ] + } + } + } + } + + instance = client.create_instance(scenario_name=test_scenario_name, name=test_instance_name ) + if verbose: print(instance) + to_delete_list.insert(0,{"item": "instance", "function": client.delete_instance, "params":{"name": test_instance_name} }) + + print(" {}. TEST list_instances".format(test_number)) + test_number += 1 + instances = client.list_instances() + if verbose: print(instances) + + print(" {}. TEST list_instances filter by name".format(test_number)) + test_number += 1 + instances_ = client.list_instances(name=test_instance_name) + if not instances_["instances"]: + raise Exception("Text error, no VNF found with name") + if verbose: print(instances_) + + print(" {}. TEST get_instance by UUID".format(test_number)) + test_number += 1 + instance = client.get_instance(uuid=instances_["instances"][0]["uuid"]) + if verbose: print(instance) + + + + + #DELETE Create things + for item in to_delete_list: + print(" {}. TEST delete_{}".format(test_number, item["item"])) + test_number += 1 + response = item["function"](**item["params"]) + if verbose: print(response) + diff --git a/vimconn_openstack.py b/vimconn_openstack.py index 999aceed..96d89e36 100644 --- a/vimconn_openstack.py +++ b/vimconn_openstack.py @@ -62,6 +62,7 @@ netStatus2manoFormat={'ACTIVE':'ACTIVE','PAUSED':'PAUSED','INACTIVE':'INACTIVE', #global var to have a timeout creating and deleting volumes volume_timeout = 60 +server_timeout = 60 class vimconnector(vimconn.vimconnector): def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None, config={}): @@ -95,6 +96,9 @@ class vimconnector(vimconn.vimconnector): if self.osc_api_version == 'v3.3': self.k_creds['project_name'] = tenant_name self.k_creds['project_id'] = tenant_id + if config.get('region_name'): + self.k_creds['region_name'] = config.get('region_name') + self.n_creds['region_name'] = config.get('region_name') self.reload_client = True self.logger = logging.getLogger('openmano.vim.openstack') @@ -705,6 +709,8 @@ class vimconnector(vimconn.vimconnector): port_dict["name"]=name if net.get("mac_address"): port_dict["mac_address"]=net["mac_address"] + if net.get("port_security") == False: + port_dict["port_security_enabled"]=net["port_security"] new_port = self.neutron.create_port({"port": port_dict }) net["mac_adress"] = new_port["port"]["mac_address"] net["vim_id"] = new_port["port"]["id"] @@ -787,7 +793,7 @@ class vimconnector(vimconn.vimconnector): #delete ports we just created for net_item in net_list_vim: if 'port-id' in net_item: - self.neutron.delete_port(net_item['port_id']) + self.neutron.delete_port(net_item['port-id']) raise vimconn.vimconnException('Timeout creating volumes for instance ' + name, http_code=vimconn.HTTP_Request_Timeout) @@ -800,15 +806,29 @@ class vimconnector(vimconn.vimconnector): block_device_mapping = block_device_mapping ) # , description=description) #print "DONE :-)", server - pool_id = None floating_ips = self.neutron.list_floatingips().get("floatingips", ()) for floating_network in external_network: + # wait until vm is active + elapsed_time = 0 + while elapsed_time < server_timeout: + status = self.nova.servers.get(server.id).status + if status == 'ACTIVE': + break + time.sleep(1) + elapsed_time += 1 + + #if we exceeded the timeout rollback + if elapsed_time >= server_timeout: + self.delete_vminstance(server.id) + raise vimconn.vimconnException('Timeout creating instance ' + name, + http_code=vimconn.HTTP_Request_Timeout) + assigned = False while(assigned == False): if floating_ips: ip = floating_ips.pop(0) - if not ip.get("port_id", False): + if not ip.get("port_id", False) and ip.get('tenant_id') == server.tenant_id: free_floating_ip = ip.get("floating_ip_address") try: fix_ip = floating_network.get('ip') @@ -818,8 +838,25 @@ class vimconnector(vimconn.vimconnector): self.delete_vminstance(server.id) raise vimconn.vimconnException(type(e).__name__ + ": Cannot create floating_ip "+ str(e), http_code=vimconn.HTTP_Conflict) else: - pool_id = floating_network.get('net_id') - param = {'floatingip': {'floating_network_id': pool_id}} + #Find the external network + external_nets = list() + for net in self.neutron.list_networks()['networks']: + if net['router:external']: + external_nets.append(net) + + if len(external_nets) == 0: + self.delete_vminstance(server.id) + raise vimconn.vimconnException("Cannot create floating_ip automatically since no external " + "network is present", + http_code=vimconn.HTTP_Conflict) + if len(external_nets) > 1: + self.delete_vminstance(server.id) + raise vimconn.vimconnException("Cannot create floating_ip automatically since multiple " + "external networks are present", + http_code=vimconn.HTTP_Conflict) + + pool_id = external_nets[0].get('id') + param = {'floatingip': {'floating_network_id': pool_id, 'tenant_id': server.tenant_id}} try: #self.logger.debug("Creating floating IP") new_floating_ip = self.neutron.create_floatingip(param) @@ -837,6 +874,15 @@ class vimconnector(vimconn.vimconnector): # error_text= "vm instance %s not found" % vm_id except (ksExceptions.ClientException, nvExceptions.ClientException, ConnectionError ) as e: + # delete the volumes we just created + if block_device_mapping != None: + for volume_id in block_device_mapping.itervalues(): + self.cinder.volumes.delete(volume_id) + + # delete ports we just created + for net_item in net_list_vim: + if 'port-id' in net_item: + self.neutron.delete_port(net_item['port-id']) self._format_exception(e) except TypeError as e: raise vimconn.vimconnException(type(e).__name__ + ": "+ str(e), http_code=vimconn.HTTP_Bad_Request) diff --git a/vimconn_openvim.py b/vimconn_openvim.py index 241f63d0..62c16774 100644 --- a/vimconn_openvim.py +++ b/vimconn_openvim.py @@ -591,8 +591,10 @@ class vimconnector(vimconn.vimconnector): '''Adds a tenant flavor to VIM''' '''Returns the flavor identifier''' try: + new_flavor_dict = flavor_data.copy() + new_flavor_dict["name"] = flavor_data["name"][:64] self._get_my_tenant() - payload_req = json.dumps({'flavor': flavor_data}) + payload_req = json.dumps({'flavor': new_flavor_dict}) url = self.url+'/'+self.tenant+'/flavors' self.logger.info("Adding a new VIM flavor POST %s", url) vim_response = requests.post(url, headers = self.headers_req, data=payload_req) @@ -647,7 +649,7 @@ class vimconnector(vimconn.vimconnector): ''' Adds a tenant image to VIM, returns image_id''' try: self._get_my_tenant() - new_image_dict={'name': image_dict['name']} + new_image_dict={'name': image_dict['name'][:64]} if image_dict.get('description'): new_image_dict['description'] = image_dict['description'] if image_dict.get('metadata'): @@ -811,7 +813,7 @@ class vimconnector(vimconn.vimconnector): if net.get("model"): net_dict["model"] = net["model"] if net.get("mac_address"): net_dict["mac_address"] = net["mac_address"] virtio_net_list.append(net_dict) - payload_dict={ "name": name, + payload_dict={ "name": name[:64], "description": description, "imageRef": image_id, "flavorRef": flavor_id, diff --git a/vnfs/examples/vnf_floating_ip.yaml b/vnfs/examples/vnf_floating_ip.yaml new file mode 100644 index 00000000..b8fe82a1 --- /dev/null +++ b/vnfs/examples/vnf_floating_ip.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: vnf_floating_ip + description: VNF disabling port_security option in mgmt interface + # class: parent # Optional. Used to organize VNFs + external-connections: + - name: mgmt0 + type: mgmt # "mgmt" (autoconnect to management net), "bridge", "data" + VNFC: vnf_floating_ip # Virtual Machine this interface belongs to + local_iface_name: mgmt0 # interface name inside this Virtual Machine (must be defined in the VNFC section) + description: Management interface + VNFC: # Virtual machine array + - name: vnf_floating_ip # name of Virtual Machine + description: vnf_floating_ip +# VNFC image: /path/to/imagefolder/TEMPLATE-VM.qcow2 + image name: ubuntu16.04 + image checksum: 7373edba82a31eedd182d29237b746cf + # image metadata: {"bus":"ide", "os_type":"windows", "use_incremental": "no" } #Optional + # processor: #Optional + # model: Intel(R) Xeon(R) CPU E5-4620 0 @ 2.20GHz + # features: ["64b", "iommu", "lps", "tlbps", "hwsv", "dioc", "ht"] + # hypervisor: #Optional + # type: QEMU-kvm + # version: "10002|12001|2.6.32-358.el6.x86_64" + vcpus: 1 # Only for traditional cloud VMs. Number of virtual CPUs (oversubscription is allowed). + ram: 1000 # Only for traditional cloud VMs. Memory in MBytes (not from hugepages, oversubscription is allowed) + disk: 5 # disk size in GiB, by default 1 + #numas: + #- paired-threads: 5 # "cores", "paired-threads", "threads" + # paired-threads-id: [ [0,1], [2,3], [4,5], [6,7], [8,9] ] # By default follows incremental order + # memory: 14 # GBytes + # interfaces: [] + bridge-ifaces: + - name: mgmt0 + vpci: "0000:00:0a.0" # Optional. Virtual PCI address + bandwidth: 1 Mbps # Optional. Informative only + floating-ip: True + # mac_address: '20:33:45:56:77:46' #avoid this option if possible + # model: 'virtio' # ("virtio","e1000","ne2k_pci","pcnet","rtl8139") By default, it is automatically filled by libvirt + # Additional Virtual Machines would be included here + diff --git a/vnfs/examples/vnf_no_port_security.yaml b/vnfs/examples/vnf_no_port_security.yaml new file mode 100644 index 00000000..1c26c242 --- /dev/null +++ b/vnfs/examples/vnf_no_port_security.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: vnf_no_port_security + description: VNF disabling port_security option in mgmt interface + # class: parent # Optional. Used to organize VNFs + external-connections: + - name: mgmt0 + type: mgmt # "mgmt" (autoconnect to management net), "bridge", "data" + VNFC: vnf_no_port_security # Virtual Machine this interface belongs to + local_iface_name: mgmt0 # interface name inside this Virtual Machine (must be defined in the VNFC section) + description: Management interface + VNFC: # Virtual machine array + - name: vnf_no_port_security # name of Virtual Machine + description: vnf_no_port_security +# VNFC image: /path/to/imagefolder/TEMPLATE-VM.qcow2 + image name: ubuntu16.04 + image checksum: 7373edba82a31eedd182d29237b746cf + # image metadata: {"bus":"ide", "os_type":"windows", "use_incremental": "no" } #Optional + # processor: #Optional + # model: Intel(R) Xeon(R) CPU E5-4620 0 @ 2.20GHz + # features: ["64b", "iommu", "lps", "tlbps", "hwsv", "dioc", "ht"] + # hypervisor: #Optional + # type: QEMU-kvm + # version: "10002|12001|2.6.32-358.el6.x86_64" + vcpus: 1 # Only for traditional cloud VMs. Number of virtual CPUs (oversubscription is allowed). + ram: 1000 # Only for traditional cloud VMs. Memory in MBytes (not from hugepages, oversubscription is allowed) + disk: 5 # disk size in GiB, by default 1 + #numas: + #- paired-threads: 5 # "cores", "paired-threads", "threads" + # paired-threads-id: [ [0,1], [2,3], [4,5], [6,7], [8,9] ] # By default follows incremental order + # memory: 14 # GBytes + # interfaces: [] + bridge-ifaces: + - name: mgmt0 + vpci: "0000:00:0a.0" # Optional. Virtual PCI address + bandwidth: 1 Mbps # Optional. Informative only + port-security: False + # mac_address: '20:33:45:56:77:46' #avoid this option if possible + # model: 'virtio' # ("virtio","e1000","ne2k_pci","pcnet","rtl8139") By default, it is automatically filled by libvirt + # Additional Virtual Machines would be included here +