config/controllerconfig/controllerconfig/controllerconfig/utils.py

899 lines
27 KiB
Python

#
# Copyright (c) 2014-2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Utilities
"""
import collections
import errno
import glob
import os
import shutil
import socket
import subprocess
import time
import sys
import yaml
import netaddr
from tsconfig import tsconfig
from configutilities.common.utils import is_valid_mac
from sysinv.common import constants as sysinv_constants
from common import constants
from common import log
LOOPBACK_IFNAME = 'lo'
NETWORK_SCRIPTS_PATH = '/etc/sysconfig/network-scripts'
NETWORK_SCRIPTS_PREFIX = 'ifcfg'
NETWORK_SCRIPTS_LOOPBACK = '%s-%s' % (NETWORK_SCRIPTS_PREFIX, LOOPBACK_IFNAME)
BOND_MIIMON_DEFAULT = 100
LOG = log.get_logger(__name__)
DEVNULL = open(os.devnull, 'w')
def filesystem_get_free_space(path):
""" Get Free space of directory """
statvfs = os.statvfs(path)
return (statvfs.f_frsize * statvfs.f_bavail)
def directory_get_size(start_dir, regex=None):
"""
Get total size of a directory tree in bytes
:param start_dir: top of tree
:param regex: only include files matching this regex (if provided)
:return: size in bytes
"""
total_size = 0
for dirpath, _, filenames in os.walk(start_dir):
for filename in filenames:
if regex is None or regex.match(filename):
filep = os.path.join(dirpath, filename)
try:
total_size += os.path.getsize(filep)
except OSError, e:
if e.errno != errno.ENOENT:
raise e
return total_size
def print_bytes(sizeof):
""" Pretty print bytes """
for size in ['Bytes', 'KB', 'MB', 'GB', 'TB']:
if abs(sizeof) < 1024.0:
return "%3.1f %s" % (sizeof, size)
sizeof /= 1024.0
def modprobe_drbd():
"""Load DRBD module"""
try:
mod_parms = subprocess.check_output(['drbdadm', 'sh-mod-parms'],
close_fds=True).rstrip()
subprocess.call(["modprobe", "-s", "drbd", mod_parms], stdout=DEVNULL)
except subprocess.CalledProcessError:
LOG.error("Failed to load drbd module")
raise
def drbd_start(resource):
"""Start drbd resource"""
try:
subprocess.check_call(["drbdadm", "up", resource],
stdout=DEVNULL)
subprocess.check_call(["drbdadm", "primary", resource],
stdout=DEVNULL)
except subprocess.CalledProcessError:
LOG.error("Failed to start drbd %s" % resource)
raise
def drbd_stop(resource):
"""Stop drbd resource"""
try:
subprocess.check_call(["drbdadm", "secondary", resource],
stdout=DEVNULL)
# Allow time for demotion to be processed
time.sleep(1)
subprocess.check_call(["drbdadm", "down", resource], stdout=DEVNULL)
except subprocess.CalledProcessError:
LOG.error("Failed to stop drbd %s" % resource)
raise
def mount(device, directory):
"""Mount a directory"""
try:
subprocess.check_call(["mount", device, directory], stdout=DEVNULL)
except subprocess.CalledProcessError:
LOG.error("Failed to mount %s filesystem" % directory)
raise
def umount(directory):
"""Unmount a directory"""
try:
subprocess.check_call(["umount", directory], stdout=DEVNULL)
except subprocess.CalledProcessError:
LOG.error("Failed to umount %s filesystem" % directory)
raise
def start_service(name):
""" Start a systemd service """
try:
subprocess.check_call(["systemctl", "start", name], stdout=DEVNULL)
except subprocess.CalledProcessError:
LOG.error("Failed to start %s service" % name)
raise
def stop_service(name):
""" Stop a systemd service """
try:
subprocess.check_call(["systemctl", "stop", name], stdout=DEVNULL)
except subprocess.CalledProcessError:
LOG.error("Failed to stop %s service" % name)
raise
def restart_service(name):
""" Restart a systemd service """
try:
subprocess.check_call(["systemctl", "restart", name], stdout=DEVNULL)
except subprocess.CalledProcessError:
LOG.error("Failed to restart %s service" % name)
raise
def start_lsb_service(name):
""" Start a Linux Standard Base service """
try:
script = os.path.join("/etc/init.d", name)
# Call the script with SYSTEMCTL_SKIP_REDIRECT=1 in the environment
subprocess.check_call([script, "start"],
env=dict(os.environ,
**{"SYSTEMCTL_SKIP_REDIRECT": "1"}),
stdout=DEVNULL)
except subprocess.CalledProcessError:
LOG.error("Failed to start %s service" % name)
raise
def stop_lsb_service(name):
""" Stop a Linux Standard Base service """
try:
script = os.path.join("/etc/init.d", name)
# Call the script with SYSTEMCTL_SKIP_REDIRECT=1 in the environment
subprocess.check_call([script, "stop"],
env=dict(os.environ,
**{"SYSTEMCTL_SKIP_REDIRECT": "1"}),
stdout=DEVNULL)
except subprocess.CalledProcessError:
LOG.error("Failed to stop %s service" % name)
raise
def restart_lsb_service(name):
""" Restart a Linux Standard Base service """
try:
script = os.path.join("/etc/init.d", name)
# Call the script with SYSTEMCTL_SKIP_REDIRECT=1 in the environment
subprocess.check_call([script, "restart"],
env=dict(os.environ,
**{"SYSTEMCTL_SKIP_REDIRECT": "1"}),
stdout=DEVNULL)
except subprocess.CalledProcessError:
LOG.error("Failed to restart %s service" % name)
raise
def check_sm_service(service, state):
""" Check whether an SM service has the supplied state """
try:
output = subprocess.check_output(["sm-query", "service", service])
return state in output
except subprocess.CalledProcessError:
return False
def wait_sm_service(service, timeout=180):
""" Check whether an SM service has been enabled.
:param service: SM service name
:param timeout: timeout in seconds
:return True if the service is enabled, False otherwise
"""
for _ in range(timeout):
if check_sm_service(service, 'enabled-active'):
return True
time.sleep(1)
return False
def is_active(service):
""" Check whether an SM service is active """
return check_sm_service(service, 'enabled-active')
def get_controller_hostname():
"""
Get the hostname for this controller
:return: controller hostname
"""
return socket.gethostname()
def get_mate_controller_hostname():
"""
Get the hostname for the mate controller
:return: mate controller hostname
"""
my_hostname = socket.gethostname()
if my_hostname.endswith('-0'):
postfix = '-1'
elif my_hostname.endswith('-1'):
postfix = '-0'
else:
raise Exception("Invalid controller hostname")
return my_hostname.rsplit('-', 1)[0] + postfix
def get_address_from_hosts_file(hostname):
"""
Get the IP address of a host from the /etc/hosts file
:param hostname: hostname to look up
:return: IP address of host
"""
hosts = open('/etc/hosts')
for line in hosts:
if line.strip() and line.split()[1] == hostname:
return line.split()[0]
raise Exception("Hostname %s not found in /etc/hosts" % hostname)
def validate_and_normalize_mac(address):
"""Validate a MAC address and return normalized form.
Checks whether the supplied MAC address is formally correct and
normalize it to all lower case.
:param address: MAC address to be validated and normalized.
:returns: Normalized and validated MAC address.
:raises: InvalidMAC If the MAC address is not valid.
"""
if not is_valid_mac(address):
raise Exception("InvalidMAC %s" % address)
return address.lower()
def is_valid_ipv4(address):
"""Verify that address represents a valid IPv4 address."""
try:
return netaddr.valid_ipv4(address)
except Exception:
return False
def is_valid_ipv6(address):
try:
return netaddr.valid_ipv6(address)
except Exception:
return False
def is_valid_ip(address):
if not is_valid_ipv4(address):
return is_valid_ipv6(address)
return True
def lag_mode_to_str(lag_mode):
if lag_mode == 0:
return "balance-rr"
if lag_mode == 1:
return "active-backup"
elif lag_mode == 2:
return "balance-xor"
elif lag_mode == 3:
return "broadcast"
elif lag_mode == 4:
return "802.3ad"
elif lag_mode == 5:
return "balance-tlb"
elif lag_mode == 6:
return "balance-alb"
else:
raise Exception(
"Invalid LAG_MODE value of %d. Valid values: 0-6" % lag_mode)
def is_combined_load():
return 'worker' in tsconfig.subfunctions
def get_system_type():
if is_combined_load():
return sysinv_constants.TIS_AIO_BUILD
return sysinv_constants.TIS_STD_BUILD
def get_security_profile():
eprofile = sysinv_constants.SYSTEM_SECURITY_PROFILE_EXTENDED
if tsconfig.security_profile == eprofile:
return eprofile
return sysinv_constants.SYSTEM_SECURITY_PROFILE_STANDARD
def is_cpe():
return get_system_type() == sysinv_constants.TIS_AIO_BUILD
def get_interface_config_common(device, mtu=None):
"""
Return the interface configuration parameters that is common to all
device types.
"""
parameters = collections.OrderedDict()
parameters['BOOTPROTO'] = 'none'
parameters['ONBOOT'] = 'yes'
parameters['DEVICE'] = device
# Increased to accommodate devices that require more time to
# complete link auto-negotiation
parameters['LINKDELAY'] = '20'
if mtu:
parameters['MTU'] = mtu
return parameters
def get_interface_config_ipv4(ip_address, ip_subnet, ip_gateway):
"""
Return the interface configuration parameters for all IPv4 static
addressing.
"""
parameters = collections.OrderedDict()
parameters['IPADDR'] = ip_address
parameters['NETMASK'] = ip_subnet.netmask
parameters['BROADCAST'] = ip_subnet.broadcast
if ip_gateway:
parameters['GATEWAY'] = ip_gateway
return parameters
def get_interface_config_ipv6(ip_address, ip_subnet, ip_gateway):
"""
Return the interface configuration parameters for all IPv6 static
addressing.
"""
parameters = collections.OrderedDict()
parameters['IPV6INIT'] = 'yes'
parameters['IPV6ADDR'] = netaddr.IPNetwork('%s/%u' % (ip_address,
ip_subnet.prefixlen))
if ip_gateway:
parameters['IPV6_DEFAULTGW'] = ip_gateway
return parameters
def get_interface_config_static(ip_address, ip_subnet, ip_gateway=None):
"""
Return the interface configuration parameters for all IP static
addressing.
"""
if netaddr.IPAddress(ip_address).version == 4:
return get_interface_config_ipv4(ip_address, ip_subnet, ip_gateway)
else:
return get_interface_config_ipv6(ip_address, ip_subnet, ip_gateway)
def write_interface_config_file(device, parameters):
"""
Write interface configuration parameters to the network scripts
directory named after the supplied device.
:param device device name as str
:param parameters dict of parameters
"""
filename = os.path.join(NETWORK_SCRIPTS_PATH, "%s-%s" %
(NETWORK_SCRIPTS_PREFIX, device))
try:
with open(filename, 'w') as f:
for parameter, value in parameters.items():
f.write("%s=%s\n" % (parameter, str(value)))
except IOError:
LOG.error("Failed to create file: %s" % filename)
raise
def write_interface_config_ethernet(device, mtu=None, parameters=None):
"""Write the interface configuration for an Ethernet device."""
config = get_interface_config_common(device, mtu)
if parameters:
config.update(parameters)
write_interface_config_file(device, config)
def write_interface_config_vlan(device, mtu, parameters=None):
"""Write the interface configuration for a VLAN device."""
config = get_interface_config_vlan()
if parameters:
config.update(parameters)
write_interface_config_ethernet(device, mtu, parameters=config)
def write_interface_config_slave(device, master, parameters=None):
"""Write the interface configuration for a bond slave device."""
config = get_interface_config_slave(master)
if parameters:
config.update(parameters)
write_interface_config_ethernet(device, parameters=config)
def write_interface_config_bond(device, mtu, mode, txhash, miimon,
member1, member2, parameters=None):
"""Write the interface configuration for a bond master device."""
config = get_interface_config_bond(mode, txhash, miimon)
if parameters:
config.update(parameters)
write_interface_config_ethernet(device, mtu, parameters=config)
# create slave device configuration files
if member1:
write_interface_config_slave(member1, device)
if member2:
write_interface_config_slave(member2, device)
def get_interface_config_vlan():
"""
Return the interface configuration parameters for all IP static
addressing.
"""
parameters = collections.OrderedDict()
parameters['VLAN'] = 'yes'
return parameters
def get_interface_config_slave(master):
"""
Return the interface configuration parameters for bond interface
slave devices.
"""
parameters = collections.OrderedDict()
parameters['MASTER'] = master
parameters['SLAVE'] = 'yes'
parameters['PROMISC'] = 'yes'
return parameters
def get_interface_config_bond(mode, txhash, miimon):
"""
Return the interface configuration parameters for bond interface
master devices.
"""
options = "mode=%s miimon=%s" % (mode, miimon)
if txhash:
options += " xmit_hash_policy=%s" % txhash
if mode == constants.LAG_MODE_8023AD:
options += " lacp_rate=fast"
parameters = collections.OrderedDict()
parameters['BONDING_OPTS'] = "\"%s\"" % options
return parameters
def remove_interface_config_files(stdout=None, stderr=None):
"""
Remove all existing interface configuration files.
"""
files = glob.glob1(NETWORK_SCRIPTS_PATH, "%s-*" % NETWORK_SCRIPTS_PREFIX)
for file in [f for f in files if f != NETWORK_SCRIPTS_LOOPBACK]:
ifname = file[len(NETWORK_SCRIPTS_PREFIX) + 1:] # remove prefix
subprocess.check_call(["ifdown", ifname],
stdout=stdout, stderr=stderr)
os.remove(os.path.join(NETWORK_SCRIPTS_PATH, file))
def remove_interface_ip_address(device, ip_address, ip_subnet,
stdout=None, stderr=None):
"""Remove an IP address from an interface"""
subprocess.check_call(
["ip", "addr", "del",
str(ip_address) + "/" + str(ip_subnet.prefixlen),
"dev", device],
stdout=stdout, stderr=stderr)
def send_interface_garp(device, ip_address, stdout=None, stderr=None):
"""Send a GARP message for the supplied address"""
subprocess.call(
["arping", "-c", "3", "-A", "-q", "-I",
device, str(ip_address)],
stdout=stdout, stderr=stderr)
def restart_networking(stdout=None, stderr=None):
"""
Restart networking services.
"""
# Kill any leftover dhclient process from the boot
subprocess.call(["pkill", "dhclient"])
# remove any existing IP addresses
ifs = glob.glob1('/sys/class/net', "*")
for i in [i for i in ifs if i != LOOPBACK_IFNAME]:
subprocess.call(
["ip", "link", "set", "dev", i, "down"])
subprocess.call(
["ip", "addr", "flush", "dev", i])
subprocess.call(
["ip", "-6", "addr", "flush", "dev", i])
subprocess.check_call(["systemctl", "restart", "network"],
stdout=stdout, stderr=stderr)
def output_to_dict(output):
dict = {}
output = filter(None, output.split('\n'))
for row in output:
values = row.split()
if len(values) != 2:
raise Exception("The following output does not respect the "
"format: %s" % row)
dict[values[1]] = values[0]
return dict
def get_install_uuid():
""" Get the install uuid from the feed directory. """
uuid_fname = None
try:
uuid_dir = '/www/pages/feed/rel-' + tsconfig.SW_VERSION
uuid_fname = os.path.join(uuid_dir, 'install_uuid')
with open(uuid_fname, 'r') as uuid_file:
install_uuid = uuid_file.readline().rstrip()
except IOError:
LOG.error("Failed to open file: %s", uuid_fname)
raise Exception("Failed to retrieve install UUID")
return install_uuid
def write_simplex_flag():
""" Write simplex flag. """
simplex_flag = "/etc/platform/simplex"
try:
open(simplex_flag, 'w')
except IOError:
LOG.error("Failed to open file: %s", simplex_flag)
raise Exception("Failed to write configuration file")
def create_manifest_runtime_config(filename, config):
"""Write the runtime Puppet configuration to a runtime file."""
if not config:
return
try:
with open(filename, 'w') as f:
yaml.dump(config, f, default_flow_style=False)
except Exception:
LOG.exception("failed to write config file: %s" % filename)
raise
def apply_manifest(controller_address_0, personality, manifest, hieradata,
stdout_progress=False, runtime_filename=None):
"""Apply puppet manifest files."""
# FIXME(mpeters): remove once manifests and modules are not dependent
# on checking the primary config condition
os.environ["INITIAL_CONFIG_PRIMARY"] = "true"
cmd = [
"/usr/local/bin/puppet-manifest-apply.sh",
hieradata,
str(controller_address_0),
personality,
manifest
]
if runtime_filename:
cmd.append((runtime_filename))
logfile = "/tmp/apply_manifest.log"
try:
with open(logfile, "w") as flog:
subprocess.check_call(cmd, stdout=flog, stderr=flog)
except subprocess.CalledProcessError:
msg = "Failed to execute %s manifest" % manifest
print(msg)
raise Exception(msg)
def create_system_controller_config(filename):
""" Create any additional parameters needed for system controller"""
# set keystone endpoint region name and sysinv keystone authtoken
# region name
config = {
'keystone::endpoint::region':
sysinv_constants.SYSTEM_CONTROLLER_REGION,
'sysinv::region_name':
sysinv_constants.SYSTEM_CONTROLLER_REGION,
}
try:
with open(filename, 'w') as f:
yaml.dump(config, f, default_flow_style=False)
except Exception:
LOG.exception("failed to write config file: %s" % filename)
raise
def create_static_config():
cmd = ["/usr/bin/sysinv-puppet",
"create-static-config",
constants.HIERADATA_WORKDIR]
try:
os.makedirs(constants.HIERADATA_WORKDIR)
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
msg = "Failed to create puppet hiera static config"
print(msg)
raise Exception(msg)
def create_system_config():
cmd = ["/usr/bin/sysinv-puppet",
"create-system-config",
constants.HIERADATA_PERMDIR]
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
msg = "Failed to update puppet hiera system config"
print(msg)
raise Exception(msg)
def create_host_config(hostname=None):
cmd = ["/usr/bin/sysinv-puppet",
"create-host-config",
constants.HIERADATA_PERMDIR]
if hostname:
cmd.append(hostname)
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
msg = "Failed to update puppet hiera host config"
print(msg)
raise Exception(msg)
def shutdown_file_systems():
""" Shutdown filesystems """
umount("/var/lib/postgresql")
drbd_stop("drbd-pgsql")
umount("/opt/platform")
drbd_stop("drbd-platform")
umount("/opt/cgcs")
drbd_stop("drbd-cgcs")
umount("/opt/extension")
drbd_stop("drbd-extension")
if os.path.exists("/opt/patch-vault"):
umount("/opt/patch-vault")
drbd_stop("drbd-patch-vault")
def persist_config():
"""Copy temporary config files into new DRBD filesystem"""
# Persist temporary keyring
try:
if os.path.isdir(constants.KEYRING_WORKDIR):
shutil.move(constants.KEYRING_WORKDIR, constants.KEYRING_PERMDIR)
except IOError:
LOG.error("Failed to persist temporary keyring")
raise Exception("Failed to persist temporary keyring")
# Move puppet working files into permanent directory
try:
# ensure parent directory is present
subprocess.call(["mkdir", "-p", tsconfig.PUPPET_PATH])
# move hiera data to puppet directory
if os.path.isdir(constants.HIERADATA_WORKDIR):
subprocess.check_call(["mv", constants.HIERADATA_WORKDIR,
tsconfig.PUPPET_PATH])
except subprocess.CalledProcessError:
LOG.error("Failed to persist puppet config files")
raise Exception("Failed to persist puppet config files")
# Move config working files into permanent directory
try:
# ensure parent directory is present
subprocess.call(["mkdir", "-p",
os.path.dirname(constants.CONFIG_PERMDIR)])
if os.path.isdir(constants.CONFIG_WORKDIR):
# Remove destination directory in case it was created previously
subprocess.call(["rm", "-rf", constants.CONFIG_PERMDIR])
# move working data to config directory
subprocess.check_call(["mv", constants.CONFIG_WORKDIR,
constants.CONFIG_PERMDIR])
except subprocess.CalledProcessError:
LOG.error("Failed to persist config files")
raise Exception("Failed to persist config files")
# Copy postgres config files for mate
try:
subprocess.check_call(["mkdir",
constants.CONFIG_PERMDIR + "/postgresql"])
except subprocess.CalledProcessError:
LOG.error("Failed to create postgresql dir")
raise Exception("Failed to persist config files")
try:
for f in glob.glob("/etc/postgresql/*.conf"):
subprocess.check_call([
"cp", "-p", f, constants.CONFIG_PERMDIR + "/postgresql/"])
except IOError:
LOG.error("Failed to persist postgresql config files")
raise Exception("Failed to persist config files")
# Set up replicated directory for PXE config files
try:
subprocess.check_call([
"mkdir", "-p", constants.CONFIG_PERMDIR + "/pxelinux.cfg"])
except subprocess.CalledProcessError:
LOG.error("Failed to create persistent pxelinux.cfg directory")
raise Exception("Failed to persist config files")
try:
subprocess.check_call(["ln", "-s", constants.CONFIG_PERMDIR +
"/pxelinux.cfg", "/pxeboot/pxelinux.cfg"])
except subprocess.CalledProcessError:
LOG.error("Failed to create pxelinux.cfg symlink")
raise Exception("Failed to persist config files")
# Copy branding tarball for mate
if os.listdir('/opt/branding'):
try:
subprocess.check_call([
"mkdir", constants.CONFIG_PERMDIR + "/branding"])
except subprocess.CalledProcessError:
LOG.error("Failed to create branding dir")
raise Exception("Failed to persist config files")
try:
if os.path.isfile(
'/opt/branding/horizon-region-exclusions.csv'):
subprocess.check_call(
["cp", "-p",
'/opt/branding/horizon-region-exclusions.csv',
constants.CONFIG_PERMDIR + "/branding/"])
except IOError:
LOG.error("Failed to persist horizon exclusion file")
raise Exception("Failed to persist config files")
try:
for f in glob.glob("/opt/branding/*.tgz"):
subprocess.check_call([
"cp", "-p", f, constants.CONFIG_PERMDIR + "/branding/"])
break
except IOError:
LOG.error("Failed to persist branding config files")
raise Exception("Failed to persist config files")
def apply_banner_customization():
""" Apply and Install banners provided by the user """
""" execute: /usr/sbin/apply_banner_customization """
logfile = "/tmp/apply_banner_customization.log"
try:
with open(logfile, "w") as blog:
subprocess.check_call(["/usr/sbin/apply_banner_customization",
"/opt/banner"],
stdout=blog, stderr=blog)
except subprocess.CalledProcessError:
error_text = "Failed to apply banner customization"
print("%s; see %s for detail" % (error_text, logfile))
def mtce_restart():
"""Restart maintenance processes to handle interface changes"""
restart_service("mtcClient")
restart_service("hbsClient")
restart_service("rmon")
restart_service("pmon")
def mark_config_complete():
"""Signal initial configuration has been completed"""
try:
subprocess.check_call(["touch",
constants.INITIAL_CONFIG_COMPLETE_FILE])
subprocess.call(["rm", "-rf", constants.KEYRING_WORKDIR])
except subprocess.CalledProcessError:
LOG.error("Failed to mark initial config complete")
raise Exception("Failed to mark initial config complete")
def configure_hostname(hostname):
"""Configure hostname for this host."""
hostname_file = '/etc/hostname'
try:
with open(hostname_file, 'w') as f:
f.write(hostname + "\n")
except IOError:
LOG.error("Failed to update file: %s", hostname_file)
raise Exception("Failed to configure hostname")
try:
subprocess.check_call(["hostname", hostname])
except subprocess.CalledProcessError:
LOG.error("Failed to update hostname %s" % hostname)
raise Exception("Failed to configure hostname")
def progress(steps, step, action, result, newline=False):
"""Display progress."""
if steps == 0:
hashes = 45
percentage = 100
else:
hashes = (step * 45) / steps
percentage = (step * 100) / steps
sys.stdout.write("\rStep {0:{width}d} of {1:d} [{2:45s}] "
"[{3:d}%]".format(min(step, steps), steps,
'#' * hashes, percentage,
width=len(str(steps))))
if step == steps or newline:
sys.stdout.write("\n")
sys.stdout.flush()
def touch(fname):
with open(fname, 'a'):
os.utime(fname, None)
def is_ssh_parent():
"""Determine if current process is started from a ssh session"""
command = ('pstree -s %d' % (os.getpid()))
try:
cmd_output = subprocess.check_output(command, shell=True)
if "ssh" in cmd_output:
return True
else:
return False
except subprocess.CalledProcessError:
return False