From 0e8526b2272578a02d8747b6509ddf95d379abb1 Mon Sep 17 00:00:00 2001 From: "Gu, Forrest" Date: Mon, 14 Nov 2016 04:29:21 -0500 Subject: [PATCH 1/2] bugfix/sync_ipmi_console_interface Added `ipmi_console_ssh` attribute to infrasim.yml. Enable environment for ipmi-console - PORT_TELNET_TO_VBMC - PORT_SSH_FOR_CLIENT - VBMC_IP - VBMC_PORT All these environment variables are init from runtime instance configuration. Added functional test class `test_ipmi_console_config_change` - test_ipmi_console_valid_port - test_ipmi_console_invalid_port - test_ipmi_console_valid_ssh - test_ipmi_console_invalid_ssh --- etc/infrasim.full.yml.example | 4 + infrasim/console.py | 33 ++++-- infrasim/ipmicons/common.py | 107 +++++++++++++++-- infrasim/ipmicons/env.py | 11 ++ infrasim/model.py | 2 - test/functional/test_ipmi_console.py | 169 +++++++++++++++++++++++++-- 6 files changed, 294 insertions(+), 32 deletions(-) create mode 100644 infrasim/ipmicons/env.py diff --git a/etc/infrasim.full.yml.example b/etc/infrasim.full.yml.example index ee9e680..642b058 100644 --- a/etc/infrasim.full.yml.example +++ b/etc/infrasim.full.yml.example @@ -129,7 +129,11 @@ bmc: config_file: emu_file: chassis/node1/quanta_d51.emu +# SSH to this port to visit ipmi-console +ipmi_console_ssh: 9300 + # Renamed from telnet_listen_port to ipmi_console_port, extracted from bmc +# ipmi-console talk with vBMC via this port ipmi_console_port: 9000 # Used by ipmi_sim and qemu diff --git a/infrasim/console.py b/infrasim/console.py index e6ba33d..a5d2632 100644 --- a/infrasim/console.py +++ b/infrasim/console.py @@ -13,6 +13,7 @@ from . import logger from .ipmicons.command import Command_Handler from .ipmicons.common import msg_queue +from .ipmicons import env import re @@ -88,7 +89,7 @@ def run(self): def _start_console(): global server - server = sshim.Server(IPMI_CONSOLE, port=9300) + server = sshim.Server(IPMI_CONSOLE, port=env.PORT_SSH_FOR_CLIENT) try: logger.info("ipmi-console start") server.run() @@ -132,9 +133,18 @@ def _free_resource(): thread.join() -def start(): +def start(instance="node-0"): + """ + Attach ipmi-console to target instance specified by + its name + :param instance: infrasim instance name + """ + daemon.daemonize("{}/{}/.ipmi_console.pid".format(config.infrasim_home, instance)) + # initialize logging common.init_logger() + # initialize environment + common.init_env(instance) # parse the sdrs and build all sensors sdr.parse_sdrs() # running thread for each threshold based sensor @@ -142,25 +152,32 @@ def start(): _start_console() -def stop(): +def stop(instance="node-0"): + """ + Stop ipmi-console of target instance specified by + its name + :param instance: infrasim instance name + """ try: - with open("{}/.ipmi_console.pid".format(config.infrasim_home), "r") as f: + file_ipmi_console_pid = "{}/{}/.ipmi_console.pid".\ + format(config.infrasim_home, instance) + with open(file_ipmi_console_pid, "r") as f: pid = f.readline().strip() os.kill(int(pid), signal.SIGTERM) + os.remove(file_ipmi_console_pid) except: pass -def console_main(): +def console_main(instance="node-0"): if len(sys.argv) < 2: print "ipmi-console [ start | stop ]" sys.exit(1) if sys.argv[1] == "start": - daemon.daemonize("{}/.ipmi_console.pid".format(config.infrasim_home)) - start() + start(instance) elif sys.argv[1] == "stop": - stop() + stop(instance) else: pass diff --git a/infrasim/ipmicons/common.py b/infrasim/ipmicons/common.py index 20fd1b1..8207b3f 100644 --- a/infrasim/ipmicons/common.py +++ b/infrasim/ipmicons/common.py @@ -5,15 +5,15 @@ ''' import subprocess import os -import sys import time import threading import telnetlib import logging - import socket import Queue import re +import env +import traceback lock = threading.Lock() @@ -39,6 +39,7 @@ def init_logger(): # create console handler with a higher log level # ch = logging.StreamHandler() # ch.setLevel(logging.ERROR) + logger.setLevel(logging.INFO) # create formatter and add it to the handlers formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") @@ -48,6 +49,82 @@ def init_logger(): logger.addHandler(fh) +def init_env(instance): + """ + This is to sync ipmi-console with runtime vBMC configuration. + Initial version capture infrasim instance name by infrasim-main status, while we + have a plan to give instance name to ipmi-console so that it can be attached to + target vBMC instance. + """ + logger.info("Init ipmi-console environment for infrasim instance: {}". + format(instance)) + + # Get runtime vbmc.conf + vbmc_conf_path = os.path.join(os.environ["HOME"], ".infrasim", instance, "data", "vbmc.conf") + if not os.path.exists(vbmc_conf_path): + msg = "{} vBMC configuration is not defined at {}".format(instance, vbmc_conf_path) + logger.error(msg) + raise Exception(msg) + else: + msg = "Target vbmc to attach is: {}".format(vbmc_conf_path) + logger.info(msg) + + # Get runtime infrasim.yml + infrasim_yml_path = os.path.join(os.environ["HOME"], ".infrasim", instance, "data", "infrasim.yml") + if not os.path.exists(infrasim_yml_path): + msg = "{} infrasim instance is not defined at {}".format(instance, infrasim_yml_path) + logger.error(msg) + raise Exception(msg) + else: + msg = "Target infrasim instance to attach is: {}".format(infrasim_yml_path) + logger.info(msg) + + # Get variable and set to ipmi-console env + # - PORT_TELNET_TO_VBMC + # - VBMC_IP + # - VBMC_PORT + with open(vbmc_conf_path, 'r') as fp: + conf = fp.read() + + p_telnet = re.compile(r"^\s*console\s*[\d:\.]+\s+(?P\d+)", + re.MULTILINE) + s_telnet = p_telnet.search(conf) + if s_telnet: + env.PORT_TELNET_TO_VBMC = int(s_telnet.group("port_telnet_to_vbmc")) + logger.info("PORT_TELNET_TO_VBMC: {}".format(env.PORT_TELNET_TO_VBMC)) + else: + raise Exception("PORT_TELNET_TO_VBMC is not found") + + p_vbmc = re.compile(r"^\s*addr\s*(?P[\d:\.]+)\s*(?P\d+)", + re.MULTILINE) + s_vbmc = p_vbmc.search(conf) + if s_vbmc: + ip = s_vbmc.group("vbmc_ip") + if ip == "::" or ip == "0.0.0.0": + env.VBMC_IP = "localhost" + else: + env.VBMC_IP = "ip" + logger.info("VBMC_IP: {}".format(env.VBMC_IP)) + env.VBMC_PORT = int(s_vbmc.group("vbmc_port")) + logger.info("VBMC_PORT: {}".format(env.VBMC_PORT)) + else: + raise Exception("VBMC_IP and VBMC_PORT is not found") + + # Get variable and set to ipmi-console env + # - PORT_SSH_FOR_CLIENT + with open(infrasim_yml_path, 'r') as fp: + conf = fp.read() + + p_port = re.compile(r"^\s*ipmi_console_ssh:\s*(?P\d+)", + re.MULTILINE) + s_port = p_port.search(conf) + if s_port: + env.PORT_SSH_FOR_CLIENT = int(s_port.group("port_ssh_for_client")) + else: + env.PORT_SSH_FOR_CLIENT = 9300 + logger.info("PORT_SSH_FOR_CLIENT: {}".format(env.PORT_SSH_FOR_CLIENT)) + + def get_logger(): return logger @@ -79,14 +156,15 @@ def send_ipmi_sim_command(command): logger.info("send IPMI SIM command: " + command.strip()) result = "" try: - tn.open('localhost', '9000') + tn.open('localhost', env.PORT_TELNET_TO_VBMC) tn.write(command) time.sleep(0.1) result = tn.read_some() tn.close() logger.info("IPMI SIM command result: " + result) except socket.error as se: - logger.error("Unable to connect lanserv at 9000: {0}".format(se)) + logger.error("Unable to connect lanserv at {0}: {1}". + format(env.PORT_TELNET_TO_VBMC, se)) finally: lock.release() @@ -115,15 +193,22 @@ def send_ipmitool_command(*cmds): lock.acquire() dst_cmd = ["ipmitool", - "-I", "lan", "-H", 'localhost', "-U", vbmc_user, "-P", vbmc_pass] + "-I", "lan", + "-H", env.VBMC_IP, + "-U", vbmc_user, + "-P", vbmc_pass, + "-p", str(env.VBMC_PORT)] for cmd in cmds: dst_cmd.append(cmd) - child = subprocess.Popen(dst_cmd, - stdout=subprocess.PIPE, - stdin=subprocess.PIPE, - stderr=subprocess.PIPE - ) - (stdout, stderr) = child.communicate() + try: + child = subprocess.Popen(dst_cmd, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.PIPE) + (stdout, stderr) = child.communicate() + except: + logger.error(traceback.format_exc()) + raise child.wait() logger.info("ipmitool command: " + ' '.join(dst_cmd)) logger.info("ipmitool command stdout: " + stdout.strip()) diff --git a/infrasim/ipmicons/env.py b/infrasim/ipmicons/env.py new file mode 100644 index 0000000..8781e77 --- /dev/null +++ b/infrasim/ipmicons/env.py @@ -0,0 +1,11 @@ +''' +********************************************************* +Copyright @ 2015 EMC Corporation All Rights Reserved +********************************************************* +''' +# -*- coding: utf-8 -*- + +PORT_TELNET_TO_VBMC = 9000 +PORT_SSH_FOR_CLIENT = 9300 +VBMC_IP = "localhost" +VBMC_PORT = 623 diff --git a/infrasim/model.py b/infrasim/model.py index fa0a51f..1e2a728 100755 --- a/infrasim/model.py +++ b/infrasim/model.py @@ -1384,8 +1384,6 @@ def __init__(self, bmc_info={}): self.__port_qemu_ipmi = 9002 self.__sol_device = "" - - def set_type(self, vendor_type): self.__vendor_type = vendor_type diff --git a/test/functional/test_ipmi_console.py b/test/functional/test_ipmi_console.py index 68b148a..4055504 100644 --- a/test/functional/test_ipmi_console.py +++ b/test/functional/test_ipmi_console.py @@ -12,15 +12,16 @@ import os import time import paramiko -from infrasim import qemu -from infrasim import ipmi -from infrasim import socat from infrasim import console from infrasim import config +from infrasim.model import CNode from test import fixtures import threading import yaml import shutil +import telnetlib +import socket +import signal def run_command(cmd="", shell=True, stdout=None, stderr=None): @@ -85,14 +86,20 @@ def setUpClass(cls): with open(cls.TMP_CONF_FILE, "w") as f: yaml.dump(node_info, f, default_flow_style=False) - socat.start_socat(conf_file=cls.TMP_CONF_FILE) - ipmi.start_ipmi(conf_file=cls.TMP_CONF_FILE) + node = CNode(node_info) + node.init() + node.precheck() + node.start() + # Wait ipmi_sim sever coming up. # FIXME: good way??? - time.sleep(5) - ipmi_console_thread = threading.Thread(target=console.start, args=()) + print "Wait ipmi-console start in about 30s..." + time.sleep(15) + + ipmi_console_thread = threading.Thread(target=console.start, args=(node_info["name"],)) ipmi_console_thread.setDaemon(True) ipmi_console_thread.start() + # Wait SSH server coming up # FIXME: Need a good way to check if SSH server is listening # on port 9300 @@ -106,9 +113,17 @@ def tearDownClass(cls): cls.channel.send('quit\n') cls.channel.close() cls.ssh.close() - qemu.stop_qemu(conf_file=cls.TMP_CONF_FILE) - ipmi.stop_ipmi(conf_file=cls.TMP_CONF_FILE) - socat.stop_socat(conf_file=cls.TMP_CONF_FILE) + + with open(cls.TMP_CONF_FILE, "r") as yml_file: + node_info = yaml.load(yml_file) + + console.stop(node_info["name"]) + + node = CNode(node_info) + node.init() + node.precheck() + node.stop() + if os.path.exists(cls.TMP_CONF_FILE): os.unlink(cls.TMP_CONF_FILE) @@ -142,6 +157,9 @@ def test_help_accessibility(self): assert 'Available' in str_output def test_sel_accessibility(self): + ipmi_sel_cmd = 'ipmitool -H 127.0.0.1 -U admin -P admin sel clear' + run_command(ipmi_sel_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + self.channel.send('sel get ' + self.sensor_id + '\n') time.sleep(0.1) str_output = read_buffer(self.channel) @@ -150,7 +168,7 @@ def test_sel_accessibility(self): self.channel.send('sel set ' + self.sensor_id + ' ' + self.event_id + ' assert\n') time.sleep(0.1) self.channel.send('sel set ' + self.sensor_id + ' ' + self.event_id + ' deassert\n') - time.sleep(0.1) + time.sleep(2) ipmi_sel_cmd = 'ipmitool -H 127.0.0.1 -U admin -P admin sel list' returncode, output = run_command(ipmi_sel_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -160,6 +178,7 @@ def test_sel_accessibility(self): deassert_line = lines[-1] except IndexError: assert False + assert 'Fan #0xc0 | Upper Non-critical going low | Asserted' in assert_line assert 'Fan #0xc0 | Upper Non-critical going low | Deasserted' in deassert_line @@ -265,3 +284,131 @@ def test_sensor_value_set_discrete_state_invalid_value(self): str_output = read_buffer(self.channel) self.assertTrue("PSU1 Status : 0x0100" in str_output) + + +class test_ipmi_console_config_change(unittest.TestCase): + + ssh = paramiko.SSHClient() + channel = None + # Just for quanta_d51 + sensor_id = '0xc0' + sensor_value = '1000.00' + event_id = '6' + TMP_CONF_FILE = "/tmp/test.yml" + bmc_conf = "" + + @classmethod + def setUpClass(cls): + node_info = {} + fake_config = fixtures.FakeConfig() + node_info = fake_config.get_node_info() + node_info["ipmi_console_port"] = 9100 + node_info["ipmi_console_ssh"] = 9400 + cls.bmc_conf = os.path.join(os.environ["HOME"], ".infrasim", + node_info["name"], "data", "vbmc.conf") + + with open(cls.TMP_CONF_FILE, "w") as f: + yaml.dump(node_info, f, default_flow_style=False) + + node = CNode(node_info) + node.init() + node.precheck() + node.start() + + # Wait ipmi_sim sever coming up. + # FIXME: good way??? + print "Wait ipmi-console start in about 30s..." + time.sleep(15) + + ipmi_console_thread = threading.Thread(target=console.start, args=(node_info["name"],)) + ipmi_console_thread.setDaemon(True) + ipmi_console_thread.start() + + # console.start(node_info["name"]) + + # Wait SSH server coming up + # FIXME: Need a good way to check if SSH server is listening + # on port 9300 + time.sleep(20) + + @classmethod + def tearDownClass(cls): + + with open(cls.TMP_CONF_FILE, "r") as yml_file: + node_info = yaml.load(yml_file) + + console.stop(node_info["name"]) + + node = CNode(node_info) + node.init() + node.precheck() + node.stop() + + if os.path.exists(cls.TMP_CONF_FILE): + os.unlink(cls.TMP_CONF_FILE) + + workspace = os.path.join(config.infrasim_home, "test") + if os.path.exists(workspace): + shutil.rmtree(workspace) + + def test_ipmi_console_valid_port(self): + """ + Verify vBMC console is listening on port 9100 + """ + with open(self.bmc_conf, "r") as fp: + bmc_conf = fp.read() + assert 'console 0.0.0.0 9100' in bmc_conf + + tn = telnetlib.Telnet(host="127.0.0.1", port=9100, timeout=5) + tn.read_until(">") + tn.write("get_user_password 0x20 admin\n") + tn.read_until("\nadmin", timeout=5) + tn.close() + + def test_ipmi_console_invalid_port(self): + """ + Verify vBMC console is not listening on default port 9000 + """ + try: + tn = telnetlib.Telnet(host="127.0.0.1", port=9000, timeout=5) + except socket.error: + pass + else: + raise self.fail("Default port 9000 for ipmi-console is still " + "in use after changed to 9100.") + + def test_ipmi_console_valid_ssh(self): + """ + Verify port 9400 is valid + """ + self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + self.ssh.connect('127.0.0.1', username='', password='', port=9400) + channel = self.ssh.invoke_shell() + + try: + channel.send("\n") + time.sleep(0.1) + str_output = read_buffer(channel) + self.assertTrue("IPMI_SIM>" in str_output) + except: + channel.send("quit\n") + channel.close() + self.ssh.close() + raise + else: + channel.send("quit\n") + channel.close() + self.ssh.close() + + def test_ipmi_console_invalid_ssh(self): + """ + Verify default port 9300 is invalid now + """ + self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + try: + self.ssh.connect('127.0.0.1', username='', password='', port=9300) + except socket.error: + pass + else: + self.fail("Expect server refuse connection to port 9300, " + "but test fail") From c232c4a6585fcf25e06b7e205401bfa683704417 Mon Sep 17 00:00:00 2001 From: "Gu, Forrest" Date: Tue, 15 Nov 2016 19:15:11 +0800 Subject: [PATCH 2/2] bugfix/sync_ipmi_console_interface Enhance `daemonize()` If peripheral program close stdout already, daemonize won't try to copy fd to /dev/null. Previously, it cause an exception with empty stdout. --- infrasim/daemon.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/infrasim/daemon.py b/infrasim/daemon.py index 0bdbcfd..bd46923 100644 --- a/infrasim/daemon.py +++ b/infrasim/daemon.py @@ -56,6 +56,9 @@ def atexit_cb(): si = file(stdin, 'r') so = file(stdout, 'a+') se = file(stderr, 'a+', 0) - os.dup2(si.fileno(), sys.stdin.fileno()) - os.dup2(so.fileno(), sys.stdout.fileno()) - os.dup2(se.fileno(), sys.stderr.fileno()) + if hasattr(sys.stdin, "fileno"): + os.dup2(si.fileno(), sys.stdin.fileno()) + if hasattr(sys.stdout, "fileno"): + os.dup2(so.fileno(), sys.stdout.fileno()) + if hasattr(sys.stderr, "fileno"): + os.dup2(se.fileno(), sys.stderr.fileno())