diff --git a/compute.py b/compute.py index d80800b..6e01121 100644 --- a/compute.py +++ b/compute.py @@ -142,12 +142,16 @@ class Compute(object): return net # Create a server instance with name vmname - # if exists delete and recreate + # and check that it gets into the ACTIVE state def create_server(self, vmname, image, flavor, key_name, nic, sec_group, avail_zone=None, user_data=None, config_drive=None, retry_count=10): + if sec_group: + security_groups = [sec_group.id] + else: + security_groups = None # Also attach the created security group for the test instance = self.novaclient.servers.create(name=vmname, image=image, @@ -157,12 +161,26 @@ class Compute(object): availability_zone=avail_zone, userdata=user_data, config_drive=config_drive, - security_groups=[sec_group.id]) - flag_exist = self.find_server(vmname, retry_count) - if flag_exist: - return instance - else: + security_groups=security_groups) + if not instance: return None + # Verify that the instance gets into the ACTIVE state + for retry_attempt in range(retry_count): + instance = self.novaclient.servers.get(instance.id) + if instance.status == 'ACTIVE': + return instance + if instance.status == 'ERROR': + print 'Instance creation error:' + instance.fault['message'] + break + if self.config.debug: + print "[%s] VM status=%s, retrying %s of %s..." \ + % (vmname, instance.status, (retry_attempt + 1), retry_count) + time.sleep(2) + + # instance not in ACTIVE state + print('Instance failed status=' + instance.status) + self.delete_server(instance) + return None def get_server_list(self): servers_list = self.novaclient.servers.list() diff --git a/doc/source/usage.rst b/doc/source/usage.rst index be61007..9cb9d4a 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -13,15 +13,16 @@ VMTP Usage [--external-host @[:password>]] [--controller-node @[:]] [--mongod_server ] [--json ] - [--tp-tool nuttcp|iperf] [--hypervisor [:] ] - [--inter-node-only] [--protocols T|U|I] + [--tp-tool ] [--hypervisor [:] ] + [--inter-node-only] [--protocols ] [--bandwidth ] [--tcpbuf ] - [--udpbuf ] [--no-env] [-d] [-v] + [--udpbuf ] [--no-env] + [--vnic-type ] [-d] [-v] [--stop-on-error] [--vm_image_url ] [--test_description ] - - OpenStack VM Throughput V2.0.2 - + + OpenStack VM Throughput V2.0.3 + optional arguments: -h, --help show this help message and exit -c , --config @@ -45,12 +46,12 @@ VMTP Usage --mongod_server provide mongoDB server IP to store results --json store results in json format file - --tp-tool nuttcp|iperf + --tp-tool transport perf tool to use (default=nuttcp) --hypervisor [:] hypervisor to use (1 per arg, up to 2 args) --inter-node-only only measure inter-node - --protocols T|U|I protocols T(TCP), U(UDP), I(ICMP) - default=TUI (all) + --protocols protocols T(TCP), U(UDP), I(ICMP) - default=TUI (all) --bandwidth the bandwidth limit for TCP/UDP flows in K/M/Gbps, e.g. 128K/32M/5G. (default=no limit) @@ -61,6 +62,8 @@ VMTP Usage list of buffer length when transmitting over UDP in Bytes, e.g. --udpbuf 128,2048. (default=128,1024,8192) --no-env do not read env variables + --vnic-type + binding vnic type for test VMs -d, --debug debug flag (very verbose) -v, --version print version of this script and exit --stop-on-error Stop and keep everything as-is on error (must cleanup @@ -71,7 +74,6 @@ VMTP Usage --test_description The test description to be stored in JSON or MongoDB - Configuration File ^^^^^^^^^^^^^^^^^^ @@ -138,6 +140,12 @@ There is a candidate image defined in the default config already. It has been ve **Note:** Due to the limitation of the Python glanceclient API (v2.0), it is not able to create the image directly from a remote URL. So the implementation of this feature used a glance CLI command instead. Be sure to source the OpenStack rc file first before running VMTP with this feature. +VNIC Type +^^^^^^^^^ + +By default test VMs will be created with ports that have a "normal" VNIC type. +To create test VMs with ports that use PCI passthrough SRIOV, specify "--vnic_type direct". This will assume that the host where the VM are instantiated have SRIOV capable NIC. +An exception will be thrown if a test VM is lauched on a host that does not have SRIOV capable NIC or has not been configured to use such feature. Quick guide to run VMTP on an OpenStack Cloud ---------------------------------------------- diff --git a/instance.py b/instance.py index 7533aac..5542ad7 100644 --- a/instance.py +++ b/instance.py @@ -49,6 +49,7 @@ class Instance(object): self.ssh_user = config.ssh_vm_username self.instance = None self.ssh = None + self.port = None if config.gmond_svr_ip: self.gmond_svr = config.gmond_svr_ip else: @@ -75,7 +76,7 @@ class Instance(object): # and extract internal network IP # Retruns True if success, False otherwise def create(self, image, flavor_type, - keypair, nics, + keypair, int_net, az, internal_network_name, sec_group, @@ -90,6 +91,20 @@ class Instance(object): user_data = open(init_file_name) else: user_data = None + + if self.config.vnic_type: + # create the VM by passing a port ID instead of a net ID + self.port = self.net.create_port(int_net['id'], + [sec_group.id], + self.config.vnic_type) + nics = [{'port-id': self.port['id']}] + # no need to create server with a security group since + # we already have the port created with it + sec_group = None + else: + # create the VM by passing a net ID + nics = [{'net-id': int_net['id']}] + self.instance = self.comp.create_server(self.name, image, flavor_type, @@ -104,7 +119,6 @@ class Instance(object): user_data.close() if not self.instance: self.display('Server creation failed') - self.dispose() return False # If reusing existing management network skip the floating ip creation and association to VM @@ -134,6 +148,7 @@ class Instance(object): self.buginf('Floating IP %s created', self.ssh_ip) self.buginf('Started - associating floating IP %s', self.ssh_ip) self.instance.add_floating_ip(self.ssh_ip, ipv4_fixed_address) + # extract the IP for the data network self.buginf('Internal network IP: %s', self.internal_ip) self.buginf('SSH IP: %s', self.ssh_ip) @@ -295,6 +310,8 @@ class Instance(object): self.comp.delete_server(self.instance) self.buginf('Instance deleted') self.instance = None + if self.port: + self.net.delete_port(self.port) if self.ssh: self.ssh.close() self.ssh = None diff --git a/network.py b/network.py index 84efc37..e44cc09 100755 --- a/network.py +++ b/network.py @@ -284,6 +284,26 @@ class Network(object): router = self.neutron_client.update_router(router_id, body) return router['router'] + # Create a port + def create_port(self, net_id, sec_group_list, vnic_type): + body = { + "port": { + "network_id": net_id, + "security_groups": sec_group_list + } + } + if vnic_type: + body['port']['binding:vnic_type'] = vnic_type + port = self.neutron_client.create_port(body) + if self.config.debug: + print 'Created port ' + port['port']['id'] + return port['port'] + + def delete_port(self, port): + if self.config.debug: + print 'Deleting port ' + port['id'] + self.neutron_client.delete_port(port['id']) + # Create a floating ip on the external network and return it def create_floating_ip(self): body = { diff --git a/vmtp.py b/vmtp.py index 2adb569..6799129 100755 --- a/vmtp.py +++ b/vmtp.py @@ -40,7 +40,7 @@ from neutronclient.v2_0 import client as neutronclient from novaclient.client import Client from novaclient.exceptions import ClientException -__version__ = '2.0.2' +__version__ = '2.0.3' from perf_instance import PerfInstance as PerfInstance @@ -201,11 +201,10 @@ class VmtpTest(object): # Create an instance on a particular availability zone def create_instance(self, inst, az, int_net): - nics = [{'net-id': int_net['id']}] self.assert_true(inst.create(self.image_instance, self.flavor_type, config.public_key_name, - nics, + int_net, az, int_net['name'], self.sec_group)) @@ -223,7 +222,6 @@ class VmtpTest(object): nova_client = Client(**creds_nova) neutron = neutronclient.Client(**creds) - self.comp = compute.Compute(nova_client, config) # Add the script public key to openstack self.comp.add_public_key(config.public_key_name, @@ -419,7 +417,8 @@ class VmtpTest(object): self.comp.remove_public_key(config.public_key_name) # Finally remove the security group try: - self.comp.security_group_delete(self.sec_group) + if self.comp: + self.comp.security_group_delete(self.sec_group) except ClientException: # May throw novaclient.exceptions.BadRequest if in use print('Security group in use: not deleted') @@ -432,8 +431,9 @@ class VmtpTest(object): self.measure_vm_flows() except KeyboardInterrupt: traceback.format_exc() - except (VmtpException, sshutils.SSHError, ClientException): - traceback.format_exc() + except (VmtpException, sshutils.SSHError, ClientException, Exception): + print 'print_exc:' + traceback.print_exc() error_flag = True if opts.stop_on_error and error_flag: @@ -572,7 +572,7 @@ if __name__ == '__main__': action='store', default='nuttcp', help='transport perf tool to use (default=nuttcp)', - metavar='nuttcp|iperf') + metavar='') # note there is a bug in argparse that causes an AssertionError # when the metavar is set to '[:]', hence had to insert a space @@ -590,7 +590,7 @@ if __name__ == '__main__': action='store', default='TUI', help='protocols T(TCP), U(UDP), I(ICMP) - default=TUI (all)', - metavar='T|U|I') + metavar='') parser.add_argument('--bandwidth', dest='vm_bandwidth', action='store', @@ -618,6 +618,12 @@ if __name__ == '__main__': action='store_true', help='do not read env variables') + parser.add_argument('--vnic-type', dest='vnic_type', + default=None, + action='store', + help='binding vnic type for test VMs', + metavar='') + parser.add_argument('-d', '--debug', dest='debug', default=False, action='store_true', @@ -662,6 +668,12 @@ if __name__ == '__main__': config.debug = opts.debug config.inter_node_only = opts.inter_node_only + # direct: use SR-IOV ports for all the test VMs + if opts.vnic_type not in [None, 'direct', 'macvtap', 'normal']: + print('Invalid vnic-type: ' + opts.vnic_type) + sys.exit(1) + config.vnic_type = opts.vnic_type + config.hypervisors = opts.hypervisors # time to run each perf test in seconds