fuel-devops/devops/driver/ipmi/ipmi_driver.py

265 lines
10 KiB
Python

# Copyright 2013 Mirantis, Inc.
#
# 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.
from contextlib import contextmanager
import os
import shutil
import subprocess
import time
from devops import error
from devops.helpers.retry import retry
from devops import logger as LOGGER
class DevopsDriver(object):
def __getattr__(self, name):
"""Default method for all unimplemented functions."""
def default_method(*args, **kwargs):
LOGGER.debug('Call of unimplemented method detected. '
'Method is {0}{1}{2}'.format(name, args, kwargs))
return default_method
def __init__(self,
ipmi_user, ipmi_password, ipmi_host,
ipmi_driver_root_dir='/tmp/devops_net_install',
ip_install_server='10.20.0.1',
ip_admin_node='10.20.0.2',
fuel_iso_path='/tmp/fuel.iso',
interface_install_server='eth0',
syslinux_dir='/usr/share/syslinux/',
system_init='not_systemd',
**driver_parameters):
self.ipmi_cmd = ['/usr/bin/ipmitool', '-l', 'lan',
'-H', ipmi_host, '-U', ipmi_user, '-P', ipmi_password]
self.ipmi_driver_root_dir = ipmi_driver_root_dir
self.syslinux_dir = syslinux_dir
self.ip_install_server = ip_install_server
self.ip_node_admin = ip_admin_node
self.fuel_iso_path = fuel_iso_path
self.interface_install_server = interface_install_server
self.system_init = system_init
self._check_system_ready()
def _check_system_ready(self):
must_have_commands = [
'/usr/bin/ipmitool',
'/usr/sbin/dnsmasq',
'/usr/sbin/rpc.nfsd']
for command in must_have_commands:
if not os.path.isfile(command):
raise error.DevopsEnvironmentError(command)
return True
def node_reset(self):
LOGGER.debug('Resetting server via IPMI')
cmd = self.ipmi_cmd + ['power', 'reset']
output = subprocess.check_output(cmd)
LOGGER.debug('Reset output: %s' % output)
return True
def node_reboot(self):
LOGGER.debug('Reboot server via IPMI')
cmd = self.ipmi_cmd + ['power', 'cycle']
output = subprocess.check_output(cmd)
LOGGER.debug('Reboot server output: {0}'.format(output))
return True
def node_shutdown(self):
LOGGER.debug('Off server via IPMI')
cmd = self.ipmi_cmd + ['power', 'off']
output = subprocess.check_output(cmd)
LOGGER.debug('Off server output: {0}'.format(output))
return True
@retry(20, 30)
def set_node_boot(self, device):
"""Set boot device
Valid are:
pxe,
disk,
"""
LOGGER.debug('Set boot device to %s' % device)
cmd = self.ipmi_cmd + ['chassis', 'bootdev', device]
output = subprocess.check_output(cmd)
LOGGER.debug('Set boot server output: {0}'.format(output))
return True
def get_node_power(self):
LOGGER.debug('Get server power')
cmd = self.ipmi_cmd + ['power', 'status']
output = subprocess.check_output(cmd)
LOGGER.debug('Set boot server output: {0}'.format(output))
return True
def admin_node_create(self):
self._prepare_files()
self._create_boot_menu()
self._add_self_temp_ip()
self._start_dhcp_tftp()
self._start_nfs()
self.set_node_boot('pxe')
self.node_reset()
time.sleep(60)
self.set_node_boot('disk')
self._stop_dhcp_tftp()
def _clean_driver_file(self):
self._stop_dhcp_tftp()
self._stop_nfs()
if subprocess.check_output('mount').find(
'{0}/tftpboot/fuel'.format(
self.ipmi_driver_root_dir)) > -1:
cmd = ['sudo', 'umount',
'{0}/tftpboot/fuel'.format(self.ipmi_driver_root_dir)]
subprocess.check_output(cmd)
if os.path.isdir(self.ipmi_driver_root_dir):
shutil.rmtree(self.ipmi_driver_root_dir)
def _prepare_files(self):
self._clean_driver_file()
os.mkdir(self.ipmi_driver_root_dir)
os.mkdir('{0}/tftpboot'.format(self.ipmi_driver_root_dir))
os.mkdir('{0}/tftpboot/pxelinux.cfg'.format(self.ipmi_driver_root_dir))
os.mkdir('{0}/tftpboot/fuel'.format(self.ipmi_driver_root_dir))
syslinux_files = ['memdisk', 'menu.c32', 'poweroff.c32',
'pxelinux.0', 'reboot.c32', 'ldlinux.c32',
'libutil.c32']
for i in syslinux_files:
shutil.copy('{0}/{1}'.format(self.syslinux_dir, i),
'{0}/tftpboot/'.format(self.ipmi_driver_root_dir))
cmd = ['sudo', 'mount', '-o', 'loop', self.fuel_iso_path,
'{0}/tftpboot/fuel'.format(self.ipmi_driver_root_dir)]
subprocess.call(cmd)
return True
@contextmanager
def _create_boot_menu(self, interface='eth0',
ks_script='tftpboot/fuel/ks.cfg'):
LOGGER.debug('Create PXE boot menu for booting from network')
menu_boot = ("DEFAULT menu.c32\n"
"prompt 0\n"
"MENU TITLE Fuel Installer\n"
"TIMEOUT 20\n"
"LABEL LABEL fuel\n"
"MENU LABEL Install ^FUEL\n"
"MENU DEFAULT\n"
"KERNEL /fuel/isolinux/vmlinuz\n"
"INITRD /fuel/isolinux/initrd.img\n"
"APPEND ks=nfs:{0}:{1}/{4} "
"ks.device='{3}' "
"repo=nfs:{0}:{1}/tftpboot/fuel/ ip={2} "
"netmask=255.255.255.0 dns1={0} "
"hostname=fuel.mirantis.com ".
format(self.ip_install_server,
self.ipmi_driver_root_dir,
self.ip_node_admin,
interface,
ks_script))
with open('{0}/tftpboot/pxelinux.cfg/default'.
format(self.ipmi_driver_root_dir, 'w')) as f:
f.write(menu_boot)
return True
def _add_self_temp_ip(self):
# Check all IPs
cmd = ['sudo', 'ip', 'addr', 'sh']
output = subprocess.check_output(cmd)
if output.find(self.ip_install_server) > -1:
cmd = ['sudo', 'ip', 'addr', 'show', 'dev',
self.interface_install_server]
output = subprocess.check_output(cmd)
if output.find(self.ip_install_server) > -1:
LOGGER.debug('ip {0} already set on the interface {1}'.
format((self.ip_install_server,
self.interface_install_server)))
return
else:
LOGGER.debug('ip {0} already set on the another interface'.
format(self.ip_install_server))
cmd = ['sudo', 'ip', 'addr', 'add', '{0}/24'.
format(self.ip_install_server),
'dev', self.interface_install_server]
output = subprocess.check_output(cmd)
LOGGER.debug('Added ip address {0} to {0} . Output is :{1}'.
format(self.ip_install_server,
self.interface_install_server,
output))
return True
def _start_dhcp_tftp(self):
cmd = ['sudo', 'dnsmasq',
'--enable-tftp',
'--tftp-root={0}/tftpboot'.format(self.ipmi_driver_root_dir),
'--dhcp-range={0},{0}'.format(self.ip_node_admin),
'--port=0', '--interface={0}'.
format(self.interface_install_server),
"--dhcp-boot=pxelinux.0",
'--pid-file={0}/dnsmasq.pid'.format(self.ipmi_driver_root_dir)]
subprocess.call(cmd)
return True
def _stop_dhcp_tftp(self):
if os.path.isfile('{0}/dnsmasq.pid'.format(self.ipmi_driver_root_dir)):
try:
pid_file = open('{0}/dnsmasq.pid'.
format(self.ipmi_driver_root_dir), 'r')
for line in pid_file:
pid = line.strip().lower()
cmd = ['sudo', 'kill', pid]
subprocess.call(cmd)
pid_file.close()
LOGGER.debug('dnsmasq killed')
except subprocess.CalledProcessError as e:
LOGGER.warning("Can't stop dnsmasq: {0}".format(e.output))
return True
def _start_nfs(self):
if os.path.isfile('/etc/exports'):
cmd = ['sudo', 'mv', '/etc/exports', '/etc/exports-devops-last']
output = subprocess.check_output(cmd)
LOGGER.info(output)
cmd = ['sudo', 'touch', '/etc/exports']
subprocess.call(cmd)
cmd = ['sudo', 'chown', os.getlogin(), '/etc/exports']
subprocess.call(cmd)
f = open('/etc/exports', 'w+')
f.write('{0}/tftpboot/fuel/ {1}(ro,async,no_subtree_check,fsid=1,'
'no_root_squash)'.format(self.ipmi_driver_root_dir,
self.ip_node_admin))
f.close()
if self.system_init.find('systemd') == 1:
cmd = ['sudo', 'systemctl', 'restart', 'nfsd']
else:
cmd = ['sudo', 'service', 'nfs-kernel-server', 'restart']
output = subprocess.check_output(cmd)
LOGGER.debug('NFS server started, output is {0}'.format(output))
return True
def _stop_nfs(self):
if os.path.isfile('/etc/exports-devops-last'):
cmd = ['sudo', 'mv', '/etc/exports-devops-last', '/etc/exports']
subprocess.call(cmd)
if self.system_init.find('systemd') == 1:
cmd = ['sudo', 'systemctl', 'stop', 'nfsd']
else:
cmd = ['sudo', 'service', 'nfs-kernel-server', 'stop']
output = subprocess.check_output(cmd)
LOGGER.debug('NFS server stopped, output is {0}'.format(output))
return True