X-Git-Url: https://gerrit.akraino.org/r/gitweb?p=ta%2Fipa-deployer.git;a=blobdiff_plain;f=work%2Fdib-ipa-element%2Fvirtmedia-netconf%2Fironic-virtmedia-netconfig%2Fsrc%2Fvirtmedia_netconfig%2Fmain.py;fp=work%2Fdib-ipa-element%2Fvirtmedia-netconf%2Fironic-virtmedia-netconfig%2Fsrc%2Fvirtmedia_netconfig%2Fmain.py;h=06cfb04ac6e06101f41d430e4b988a91da4b219b;hp=0000000000000000000000000000000000000000;hb=ffee968ccc39318c855aa9ac2b161c06681a6b5a;hpb=6e5eddd9cf62a88c185572465393f7d5dd7d0e11 diff --git a/work/dib-ipa-element/virtmedia-netconf/ironic-virtmedia-netconfig/src/virtmedia_netconfig/main.py b/work/dib-ipa-element/virtmedia-netconf/ironic-virtmedia-netconfig/src/virtmedia_netconfig/main.py new file mode 100644 index 0000000..06cfb04 --- /dev/null +++ b/work/dib-ipa-element/virtmedia-netconf/ironic-virtmedia-netconfig/src/virtmedia_netconfig/main.py @@ -0,0 +1,361 @@ +# Copyright 2019 Nokia +# +# 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. +# + + +import tarfile +import sys +import os +import errno +import stat +import time +import tempfile +import shutil + +import json +import logging +import subprocess + +from oslo_config import cfg +from oslo_log import log +from ironic_python_agent import utils +from ironic_lib import utils as ironic_utils +from ironic_python_agent import errors +from oslo_concurrency import processutils + +CONF = cfg.CONF +LOG = log.getLogger(__name__) +dhclient_physIfaces = [] + +def dhclient_path(): + if os.path.exists("/usr/sbin/dhclient"): + return "/usr/sbin/dhclient" + elif os.path.exists("/sbin/dhclient"): + return "/sbin/dhclient" + else: + raise RuntimeError("Could not find dhclient") + +def stop_dhclient_process(interface): + """Stop a DHCP process before running os-net-config. + + :param interface: The interface on which to stop dhclient. + """ + pid_file = '/var/run/dhclient-%s.pid' % (interface) + try: + dhclient = dhclient_path() + except RuntimeError as err: + LOG.info('Exception when stopping dhclient: %s' % err) + return + + if os.path.exists(pid_file): + LOG.info('Stopping %s on interface %s' % (dhclient, interface)) + utils.execute(dhclient, '-r', '-pf', pid_file, interface) + try: + os.unlink(pid_file) + except OSError as err: + LOG.error('Could not remove dhclient pid file \'%s\': %s' % + (pid_file, err)) + +def _poll_interface(_ifacedata): + ifacedata = json.loads(_ifacedata) + global dhclient_physIfaces + + physIfaces = [] + if "network_config" in ifacedata: + for netconfdata in ifacedata["network_config"]: + if "device" in netconfdata: + if "bond" not in netconfdata["device"]: + # Is (physical) interface + LOG.debug('Physical device %s' % netconfdata["device"]) + physIfaces.append(netconfdata["device"]) + + elif "members" in netconfdata: + # logical interface with member (f.ex bond) + for _member in netconfdata["members"]: + if "type" in _member: + if _member["type"] == 'interface': + if "name" in _member: + LOG.debug('Physical device %s' % _member["name"]) + physIfaces.append(_member["name"]) + elif "name" in netconfdata: + if "type" in netconfdata and netconfdata["type"] == 'interface': + LOG.debug('Physical device %s' % netconfdata["name"]) + physIfaces.append(netconfdata["name"]) + + LOG.info('Checking for physical device(s) "%s"' % ', '.join(physIfaces)) + dhclient_physIfaces = list(physIfaces) + wait_secs = 5 + max_wait_secs = 60 + + while len(physIfaces) > 0 and max_wait_secs >= 0: + missing_devices = [] + max_wait_secs = max_wait_secs - wait_secs + + for _device in physIfaces: + devicepath = "/sys/class/net/%s/device" % _device + LOG.debug('Check path "%s"' % devicepath ) + if os.path.exists(devicepath): + LOG.debug('Device "%s" in known by kernel' % _device) + physIfaces.remove(_device) + else: + LOG.debug('Device "%s" in not (yet) known by kernel' % _device) + missing_devices.append(_device) + + if len(physIfaces) > 0: + LOG.info('Device(s) not (yet?) known by kernel: "%s"' % ', '.join(missing_devices)) + time.sleep(wait_secs) + + + if len(physIfaces) > 0: + msg = 'Timeout, Device(s) missing: "%s"' % ', '.join(physIfaces) + LOG.error(msg) + raise errors.VirtualMediaBootError(msg) + else: + LOG.info('All physical devices found.') + + for _device in dhclient_physIfaces: + stop_dhclient_process(_device) + +def _configure_static_net(os_net_config): + """Configures network using os-net-config utility""" + global dhclient_physIfaces + LOG.debug("Configuring static network with os-net-config: %s", os_net_config) + try: + os.makedirs('/etc/os-net-config/') + except OSError as exc: + if exc.errno != errno.EEXIST: + raise + pass + with open('/etc/os-net-config/config.yaml', 'w') as fp: + fp.write(os_net_config) + + try: + _poll_interface(os_net_config) + except Exception as e: + LOG.info('Exception while checking for physical interfaces: %s' % str(e) ) + + try: + os.system('/usr/sbin/ip a > /tmp/ifaces_before_initial_netconfig') + except Exception as e: + LOG.info('Exception while logging runtime ifaces to /tmp/ifaces_before_initial_netconfig: %s' % str(e) ) + + LOG.info('Running os-net-config..') + + cmd = [ '/usr/bin/os-net-config', '--detailed-exit-codes', '-v', '-c', '/etc/os-net-config/config.yaml'] + wait_secs = 5 + retries = 3 + while retries > 0: + retries = retries - 1 + netconf_process = subprocess.Popen(cmd, stdout=subprocess.PIPE) + while True: + output = netconf_process.stdout.readline() + if output == '' and netconf_process.poll() is not None: + break + if output: + LOG.info(output.strip()) + + rc = netconf_process.poll() + LOG.info('os-net-config exit with status %d' % rc) + + # os-net-config returns: + # 0 when nothing changed, + # 1 on error, + # 2 when config was modified (assuming option "--detailed-exit-codes") + + if rc == 0 or rc == 1: + LOG.info('os-net-config modified nothing or execution error. Not what we want..') + LOG.info('Attempt removing physical interface ifcfg-files to force os-net-config to reconfigure') + + for iface in dhclient_physIfaces: + ifcfg_file = '/etc/sysconfig/network-scripts/ifcfg-' + str(iface) + try: + LOG.info('Removing "%s"' % ifcfg_file) + os.system('/usr/bin/rm -f ' + ifcfg_file) + except Exception as e: + LOG.info('Ignoring exception when removing "%s": %s' % (ifcfg_file, str(e))) + pass + time.sleep(wait_secs) + + elif rc == 2: + LOG.info('os-net-config done.') + break + else: + LOG.info('os-net-config unknown exit code??') + time.sleep(wait_secs) + + # Config should be in place assuming os-net-config above was successfull + # As additional step restart network.service + LOG.info('Restarting network.service') + try: + cmd = ['/usr/bin/systemctl', 'restart', 'network'] + subprocess.check_call(cmd) + except Exception as e: + LOG.info('Igoring exception when restarting network service: %s' % str(e)) + pass + + +def get_file_size(filename): + "Get the file size by seeking at end" + fd= os.open(filename, os.O_RDONLY) + try: + return os.lseek(fd, 0, os.SEEK_END) + finally: + os.close(fd) + +def wait_for_cd_device(): + """ This function waits for /dev/sr0 device to appear """ + inputiso = '/dev/sr0' + wait_count = 30 + while not os.path.exists(inputiso) and wait_count: + LOG.debug('Waiting for %s to appear. Time left = %d secs' %(inputiso,wait_count)) + time.sleep(1) + wait_count -= 1 + + if not wait_count: + msg = "Unable to find device %s" %(inputiso) + raise errors.VirtualMediaBootError(msg) + +def check_cd_config(): + """ This function checks for any extended 64K block in CD. + If it is available it will extract the contents for the block. + Loop mount the image for reading configuration parameters. + """ + inputiso = '/dev/sr0' + outputtgz = '/tmp/cdconf.tgz' + mode = os.stat(inputiso).st_mode + if stat.S_ISBLK(mode): + filesize = get_file_size(inputiso) + skip = filesize / 2048-32 + ironic_utils.dd(inputiso, outputtgz, 'bs=2k', 'skip=%d'%skip) + + # Check if tgz file is valid. + try: + utils.execute("/usr/bin/gzip", '-t', outputtgz) + except processutils.ProcessExecutionError as err: + if 'not in gzip format' in err.stderr: + LOG.info('File is not gzip format skipping!!') + sys.exit() + + LOG.info('Configuration file in gzip format proceeding for extraction') + tar = tarfile.open(outputtgz) + tar.extractall('/tmp/floppy') + tar.close() + + dir_list = os.listdir('/tmp/floppy') + for item in dir_list: + if item.find('.img') != -1: + os.mkdir('/tmp/floppy/mnt') + utils.execute("mount", '-o', 'loop', '/tmp/floppy/%s' %item, '/tmp/floppy/mnt') + time.sleep(1) + +def _get_vmedia_params(): + """This method returns the parameters passed through virtual media floppy. + + :returns: a partial dict of potential agent configuration parameters + :raises: VirtualMediaBootError when it cannot find the virtual media device + """ + parameters_file = "parameters.txt" + + vmedia_device_file_lower_case = "/dev/disk/by-label/ir-vfd-dev" + vmedia_device_file_upper_case = "/dev/disk/by-label/IR-VFD-DEV" + if os.path.exists(vmedia_device_file_lower_case): + vmedia_device_file = vmedia_device_file_lower_case + elif os.path.exists(vmedia_device_file_upper_case): + vmedia_device_file = vmedia_device_file_upper_case + else: + + # TODO(rameshg87): This block of code is there only for compatibility + # reasons (so that newer agent can work with older Ironic). Remove + # this after Liberty release. + vmedia_device = utils._get_vmedia_device() + if not vmedia_device: + msg = "Unable to find virtual media device" + raise errors.VirtualMediaBootError(msg) + + vmedia_device_file = os.path.join("/dev", vmedia_device) + + vmedia_mount_point = tempfile.mkdtemp() + try: + try: + stdout, stderr = utils.execute("mount", vmedia_device_file, + vmedia_mount_point) + except processutils.ProcessExecutionError as e: + msg = ("Unable to mount virtual media device %(device)s: " + "%(error)s" % {'device': vmedia_device_file, 'error': e}) + raise errors.VirtualMediaBootError(msg) + + parameters_file_path = os.path.join(vmedia_mount_point, + parameters_file) + params = _read_params_from_file(parameters_file_path, '\n') + + try: + stdout, stderr = utils.execute("umount", vmedia_mount_point) + except processutils.ProcessExecutionError as e: + pass + finally: + try: + shutil.rmtree(vmedia_mount_point) + except Exception as e: + pass + + return params + +def _read_params_from_file(filepath, seperator=None): + """Extract key=value pairs from a file. + + :param filepath: path to a file containing key=value pairs separated by + whitespace or newlines. + :returns: a dictionary representing the content of the file + """ + with open(filepath) as f: + cmdline = f.read() + + options = cmdline.split(seperator) + params = {} + for option in options: + if '=' not in option: + continue + k, v = option.split('=', 1) + params[k] = v + + return params + +def main(): + log.register_options(CONF) + CONF(args=sys.argv[1:]) + log.setup(CONF, 'virtmedia-netconfig') + LOG.info("Starting virtmedia-netconfig!!") + + params = _read_params_from_file('/proc/cmdline') + # If the node booted over virtual media, the parameters are passed + # in a text file within the virtual media floppy. + + if params.get('boot_method') == 'vmedia': + LOG.info("This node is booted with vmedia. Checking for available virtual media!!") + wait_for_cd_device() + check_cd_config() + vmedia_params = _get_vmedia_params() + params.update(vmedia_params) + LOG.debug("vmedia parameters: %r", vmedia_params) + os_net_config = params.get('os_net_config') + LOG.info("virtmedia: os_net_config=%s" %os_net_config) + if os_net_config: + _configure_static_net(os_net_config) + + LOG.debug("Erasing old filesystems") + utils.execute('/usr/bin/erase-oldfs.sh') + + +if __name__ == "__main__": + sys.exit(main())