From c517a273b59b874ef22dc93c84407f5a04e2918a Mon Sep 17 00:00:00 2001 From: "Sridhar K. N. Rao" Date: Tue, 11 Jun 2019 13:28:09 +0530 Subject: CONFIG: VSPERF Config-file Generation Wizard. This patch adds a tool create config file. When run, the wizard asks user a set of questions. A config file, named vsperf.conf, will be created. Fix some pylint Issues. JIRA: VSPERF-603 Change-Id: I1b800d8384cb3c0883b7f3859a9df77bb874b7b8 Signed-off-by: Sridhar K. N. Rao --- tools/confgenwizard/nicinfo.py | 236 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 tools/confgenwizard/nicinfo.py (limited to 'tools/confgenwizard/nicinfo.py') diff --git a/tools/confgenwizard/nicinfo.py b/tools/confgenwizard/nicinfo.py new file mode 100644 index 00000000..631b92c5 --- /dev/null +++ b/tools/confgenwizard/nicinfo.py @@ -0,0 +1,236 @@ +# Copyright 2019-2020 Spirent Communications. +# +# 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. + +""" +Retrieve information from remote host. +In this file, we retrive only NIC PICs +""" + +from __future__ import print_function +import sys +import subprocess +import os +from os.path import exists +from stat import S_ISDIR +import paramiko + +# The PCI device class for ETHERNET devices +ETHERNET_CLASS = "0200" +LSPCI_PATH = '/usr/bin/lspci' +RECV_BYTES = 4096 +ADVANCED = True + + +#pylint: disable=too-many-instance-attributes +class RemoteInfo(object): + """ + Class to extract information from a remote system + """ + + def __init__(self, host, username, password): + """ + Perform Initialization + """ + # Dict of ethernet devices present. Dictionary indexed by PCI address. + # Each device within this is itself a dictionary of device properties + self.nic_devices = {} + if host == 'local': + self.local = True + else: + self.local = False + # Assuming port as 22. + self.port = 22 + self.hostname = host + self.password = password + self.username = username + self.client = paramiko.Transport((self.hostname, self.port)) + self.client.connect(username=self.username, + password=self.password) + self.session = self.client.open_channel(kind='session') + self.session.get_pty() + self.sftp = paramiko.SFTPClient.from_transport(self.client) + + def sftp_exists(self, path): + """ + Check if remote file exist + """ + try: + self.sftp.stat(path) + return True + except IOError: + return False + + def sft_listdir(self, path): + """ + List directories on remote nost + """ + files = [] + for fil in self.sftp.listdir_attr(path): + if not S_ISDIR(fil.st_mode): + files.append(fil.filename) + return files + + def is_connected(self): + """ + Check if session is connected. + """ + return self.client.is_active() + + def new_channel(self): + """ + FOr every command a new session is setup + """ + if not self.is_connected(): + self.client = paramiko.Transport((self.hostname, self.port)) + self.client.connect(username=self.username, + password=self.password) + self.session = self.client.open_channel(kind='session') + + # This is roughly compatible with check_output function in subprocess module + # which is only available in python 2.7. + def check_output(self, args, stderr=None): + ''' + Run a command and capture its output + ''' + stdout_data = [] + stderr_data = [] + if self.local: + return subprocess.Popen(args, stdout=subprocess.PIPE, + stderr=stderr, + universal_newlines=True).communicate()[0] + else: + self.new_channel() + separator = ' ' + command = separator.join(args) + # self.session.get_pty() + self.session.exec_command(command) + while True: + if self.session.recv_ready(): + stdout_data.append(self.session.recv(RECV_BYTES)) + if self.session.recv_stderr_ready(): + stderr_data.append(self.session.recv_stderr(RECV_BYTES)) + if self.session.exit_status_ready(): + break + if stdout_data: + return b"".join(stdout_data) + return b"".join(stderr_data) + + def get_pci_details(self, dev_id): + ''' + This function gets additional details for a PCI device + ''' + device = {} + + extra_info = self.check_output([LSPCI_PATH, + "-vmmks", dev_id]).splitlines() + + # parse lspci details + for line in extra_info: + if not line: + continue + if self.local: + name, value = line.split("\t", 1) + else: + name, value = line.decode().split("\t", 1) + name = name.strip(":") + "_str" + device[name] = value + # check for a unix interface name + sys_path = "/sys/bus/pci/devices/%s/net/" % dev_id + device["Interface"] = "" + if self.local: + if exists(sys_path): + device["Interface"] = ",".join(os.listdir(sys_path)) + else: + if self.sftp_exists(sys_path): + device["Interface"] = ",".join(self.sft_listdir(sys_path)) + + # check if a port is used for ssh connection + device["Ssh_if"] = False + device["Active"] = "" + + return device + + def get_nic_details(self): + ''' + This function populates the "devices" dictionary. The keys used are + the pci addresses (domain:bus:slot.func). The values are themselves + dictionaries - one for each NIC. + ''' + devinfos = [] + # first loop through and read details for all devices + # request machine readable format, with numeric IDs + dev = {} + dev_lines = self.check_output([LSPCI_PATH, "-Dvmmn"]).splitlines() + for dev_line in dev_lines: + if not dev_line: + if dev["Class"] == ETHERNET_CLASS: + # convert device and vendor ids to numbers, then add to + # global + dev["Vendor"] = int(dev["Vendor"], 16) + dev["Device"] = int(dev["Device"], 16) + self.nic_devices[dev["Slot"]] = dict( + dev) # use dict to make copy of dev + else: + # values = re.split(r'\t+', str(dev_line)) + if self.local: + name, value = dev_line.split('\t', 1) + else: + name, value = dev_line.decode().split("\t", 1) + dev[name.rstrip(":")] = value + + # based on the basic info, get extended text details + for dev in self.nic_devices: + # get additional info and add it to existing data + if ADVANCED: + self.nic_devices[dev].update(self.get_pci_details(dev).items()) + devinfos.append(self.nic_devices[dev]) + return devinfos + + def dev_id_from_dev_name(self, dev_name): + ''' + Take a device "name" - a string passed in by user to identify a NIC + device, and determine the device id - i.e. the domain:bus:slot.func-for + it, which can then be used to index into the devices array + ''' + # dev = None + # check if it's already a suitable index + if dev_name in self.nic_devices: + return dev_name + # check if it's an index just missing the domain part + elif "0000:" + dev_name in self.nic_devices: + return "0000:" + dev_name + else: + # check if it's an interface name, e.g. eth1 + for dev in self.nic_devices: + if dev_name in self.nic_devices[dev]["Interface"].split(","): + return self.nic_devices[dev]["Slot"] + # if nothing else matches - error + print("Unknown device: %s. " + "Please specify device in \"bus:slot.func\" format" % dev_name) + sys.exit(1) + + +def main(): + '''program main function''' + host = input("Enter Host IP: ") + username = input("Enter User Name: ") + pwd = input("Enter Password: ") + rhi = RemoteInfo(host, username, pwd) + dev_list = rhi.get_nic_details() + for dev in dev_list: + print(dev["Slot"]) + + +if __name__ == "__main__": + main() -- cgit 1.2.3-korg