# Copyright 2015 IBM Corp. # # 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 abc from nova.i18n import _ from oslo_config import cfg from oslo_log import log as logging import six from nova_zvm.virt.zvm import exception CONF = cfg.CONF LOG = logging.getLogger(__name__) @six.add_metaclass(abc.ABCMeta) class LinuxDist(object): """Linux distribution base class Due to we need to interact with linux dist and inject different files according to the dist version. Currently only RHEL and SLES are supported """ def create_network_configuration_files(self, file_path, network_info, base_vdev): """Generate network configuration files to instance.""" device_num = 0 cfg_files = [] cmd_strings = '' udev_cfg_str = '' dns_cfg_str = '' route_cfg_str = '' cmd_str = None file_path = self._get_network_file_path() file_name_route = file_path + 'routes' file_name_dns = self._get_dns_filename() for vif in network_info: file_name = self._get_device_filename(device_num) network = vif['network'] (cfg_str, cmd_str, dns_str, route_str) = self._generate_network_configuration(network, base_vdev, device_num) target_net_conf_file_name = file_path + file_name cfg_str_for_log = cfg_str.replace('\n', ' ') LOG.debug('Network configure file[%s] content is: %s', (target_net_conf_file_name, cfg_str_for_log)) cfg_files.append((target_net_conf_file_name, cfg_str)) udev_cfg_str += self._get_udev_configuration(device_num, '0.0.' + str(base_vdev).zfill(4)) self._append_udev_rules_file(cfg_files, base_vdev) if cmd_str is not None: cmd_strings += cmd_str if len(dns_str) > 0: dns_cfg_str += dns_str if len(route_str) > 0: route_cfg_str += route_str base_vdev = str(hex(int(base_vdev, 16) + 3))[2:] device_num += 1 if len(dns_cfg_str) > 0: cfg_files.append((file_name_dns, dns_cfg_str)) self._append_udev_info(cfg_files, file_name_route, route_cfg_str, udev_cfg_str) return cfg_files, cmd_strings def _generate_network_configuration(self, network, vdev, device_num): ip_v4 = dns_str = '' subnets_v4 = [s for s in network['subnets'] if s['version'] == 4] if len(subnets_v4[0]['ips']) > 0: ip_v4 = subnets_v4[0]['ips'][0]['address'] if len(subnets_v4[0]['dns']) > 0: for dns in subnets_v4[0]['dns']: dns_str += 'nameserver ' + dns['address'] + '\n' netmask_v4 = str(subnets_v4[0].as_netaddr().netmask) gateway_v4 = subnets_v4[0]['gateway']['address'] or '' broadcast_v4 = str(subnets_v4[0].as_netaddr().broadcast) device = self._get_device_name(device_num) address_read = str(vdev).zfill(4) address_write = str(hex(int(vdev, 16) + 1))[2:].zfill(4) address_data = str(hex(int(vdev, 16) + 2))[2:].zfill(4) subchannels = '0.0.%s' % address_read.lower() subchannels += ',0.0.%s' % address_write.lower() subchannels += ',0.0.%s' % address_data.lower() cfg_str = self._get_cfg_str(device, broadcast_v4, gateway_v4, ip_v4, netmask_v4, address_read, subchannels) cmd_str = self._get_cmd_str(address_read, address_write, address_data) route_str = self._get_route_str(gateway_v4) return cfg_str, cmd_str, dns_str, route_str def assemble_zfcp_srcdev(self, fcp, wwpn, lun): path = '/dev/disk/by-path/ccw-0.0.%(fcp)s-zfcp-0x%(wwpn)s:0x%(lun)s' srcdev = path % {'fcp': fcp, 'wwpn': wwpn, 'lun': lun} return srcdev @abc.abstractmethod def _get_network_file_path(self): """Get network file configuration path.""" pass def get_change_passwd_command(self, admin_password): """construct change password command :admin_password: the password to be changed to """ return "echo 'root:%s' | chpasswd" % admin_password @abc.abstractmethod def _get_cfg_str(self, device, broadcast_v4, gateway_v4, ip_v4, netmask_v4, address_read, subchannels): """construct configuration file of network device.""" pass @abc.abstractmethod def _get_device_filename(self, device_num): """construct the name of a network device file.""" pass @abc.abstractmethod def _get_route_str(self, gateway_v4): """construct a router string.""" pass @abc.abstractmethod def _get_cmd_str(self, address_read, address_write, address_data): """construct network startup command string.""" pass @abc.abstractmethod def _get_dns_filename(self): """construct the name of dns file.""" pass @abc.abstractmethod def get_znetconfig_contents(self): """construct znetconfig file will be called during first boot.""" pass @abc.abstractmethod def _get_device_name(self, device_num): """construct the name of a network device.""" pass @abc.abstractmethod def _get_udev_configuration(self, device, dev_channel): """construct udev configuration info.""" pass @abc.abstractmethod def _get_udev_rules(self, channel_read, channel_write, channel_data): """construct udev rules info.""" pass @abc.abstractmethod def _append_udev_info(self, cfg_files, file_name_route, route_cfg_str, udev_cfg_str): pass @abc.abstractmethod def _append_udev_rules_file(self, cfg_files, base_vdev): pass @abc.abstractmethod def get_scp_string(self, root, fcp, wwpn, lun): """construct scp_data string for ipl parameter""" pass @abc.abstractmethod def get_zipl_script_lines(self, image, ramdisk, root, fcp, wwpn, lun): """construct the lines composing the script to generate the /etc/zipl.conf file """ pass class rhel(LinuxDist): def _get_network_file_path(self): return '/etc/sysconfig/network-scripts/' def _get_cfg_str(self, device, broadcast_v4, gateway_v4, ip_v4, netmask_v4, address_read, subchannels): cfg_str = 'DEVICE=\"' + device + '\"\n' cfg_str += 'BOOTPROTO=\"static\"\n' cfg_str += 'BROADCAST=\"' + broadcast_v4 + '\"\n' cfg_str += 'GATEWAY=\"' + gateway_v4 + '\"\n' cfg_str += 'IPADDR=\"' + ip_v4 + '\"\n' cfg_str += 'NETMASK=\"' + netmask_v4 + '\"\n' cfg_str += 'NETTYPE=\"qeth\"\n' cfg_str += 'ONBOOT=\"yes\"\n' cfg_str += 'PORTNAME=\"PORT' + address_read + '\"\n' cfg_str += 'OPTIONS=\"layer2=1\"\n' cfg_str += 'SUBCHANNELS=\"' + subchannels + '\"\n' return cfg_str def _get_route_str(self, gateway_v4): return '' def _get_cmd_str(self, address_read, address_write, address_data): return '' def _get_dns_filename(self): return '/etc/resolv.conf' def _get_device_name(self, device_num): return 'eth' + str(device_num) def _get_udev_configuration(self, device, dev_channel): return '' def _append_udev_info(self, cfg_files, file_name_route, route_cfg_str, udev_cfg_str): pass def _get_udev_rules(self, channel_read, channel_write, channel_data): """construct udev rules info.""" return '' def _append_udev_rules_file(self, cfg_files, base_vdev): pass class rhel6(rhel): def get_znetconfig_contents(self): return '\n'.join(('cio_ignore -R', 'znetconf -R -n', 'udevadm trigger', 'udevadm settle', 'sleep 2', 'znetconf -A', 'service network restart', 'cio_ignore -u')) def _get_device_filename(self, device_num): return 'ifcfg-eth' + str(device_num) def _get_device_name(self, device_num): return 'eth' + str(device_num) def get_scp_string(self, root, fcp, wwpn, lun): return ("=root=%(root)s selinux=0 zfcp.allow_lun_scan=0 " "rd_ZFCP=0.0.%(fcp)s,0x%(wwpn)s,0x%(lun)s") % { 'root': root, 'fcp': fcp, 'wwpn': wwpn, 'lun': lun} def get_zipl_script_lines(self, image, ramdisk, root, fcp, wwpn, lun): return ['#!/bin/bash\n', ('echo -e "[defaultboot]\\n' 'timeout=5\\n' 'default=boot-from-volume\\n' 'target=/boot/\\n' '[boot-from-volume]\\n' 'image=%(image)s\\n' 'ramdisk=%(ramdisk)s\\n' 'parameters=\\"root=%(root)s ' 'rd_ZFCP=0.0.%(fcp)s,0x%(wwpn)s,0x%(lun)s ' 'zfcp.allow_lun_scan=0 selinux=0\\""' '>/etc/zipl_volume.conf\n' 'zipl -c /etc/zipl_volume.conf') % {'image': image, 'ramdisk': ramdisk, 'root': root, 'fcp': fcp, 'wwpn': wwpn, 'lun': lun}] class rhel7(rhel): def get_znetconfig_contents(self): return '\n'.join(('cio_ignore -R', 'znetconf -R -n', 'udevadm trigger', 'udevadm settle', 'sleep 2', 'znetconf -A', 'cio_ignore -u')) def _get_device_filename(self, device_num): # Construct a device like ifcfg-enccw0.0.1000, ifcfg-enccw0.0.1003 base = int(CONF.zvm_default_nic_vdev, 16) device = str(hex(base + device_num * 3))[2:] return 'ifcfg-enccw0.0.' + str(device).zfill(4) def _get_device_name(self, device_num): # Construct a device like enccw0.0.1000, enccw0.0.1003 base = int(CONF.zvm_default_nic_vdev, 16) device = str(hex(base + device_num * 3))[2:] return 'enccw0.0.' + str(device).zfill(4) def get_scp_string(self, root, fcp, wwpn, lun): return ("=root=%(root)s selinux=0 zfcp.allow_lun_scan=0 " "rd.zfcp=0.0.%(fcp)s,0x%(wwpn)s,0x%(lun)s") % { 'root': root, 'fcp': fcp, 'wwpn': wwpn, 'lun': lun} def get_zipl_script_lines(self, image, ramdisk, root, fcp, wwpn, lun): return ['#!/bin/bash\n', ('echo -e "[defaultboot]\\n' 'timeout=5\\n' 'default=boot-from-volume\\n' 'target=/boot/\\n' '[boot-from-volume]\\n' 'image=%(image)s\\n' 'ramdisk=%(ramdisk)s\\n' 'parameters=\\"root=%(root)s ' 'rd.zfcp=0.0.%(fcp)s,0x%(wwpn)s,0x%(lun)s ' 'zfcp.allow_lun_scan=0 selinux=0\\""' '>/etc/zipl_volume.conf\n' 'zipl -c /etc/zipl_volume.conf') % {'image': image, 'ramdisk': ramdisk, 'root': root, 'fcp': fcp, 'wwpn': wwpn, 'lun': lun}] class sles(LinuxDist): def _get_network_file_path(self): return '/etc/sysconfig/network/' def _get_cidr_from_ip_netmask(self, ip, netmask): netmask_fields = netmask.split('.') bin_str = '' for octet in netmask_fields: bin_str += bin(int(octet))[2:].zfill(8) mask = str(len(bin_str.rstrip('0'))) cidr_v4 = ip + '/' + mask return cidr_v4 def _get_cfg_str(self, device, broadcast_v4, gateway_v4, ip_v4, netmask_v4, address_read, subchannels): cidr_v4 = self._get_cidr_from_ip_netmask(ip_v4, netmask_v4) cfg_str = "BOOTPROTO=\'static\'\n" cfg_str += "IPADDR=\'%s\'\n" % cidr_v4 cfg_str += "BROADCAST=\'%s\'\n" % broadcast_v4 cfg_str += "STARTMODE=\'onboot\'\n" cfg_str += ("NAME=\'OSA Express Network card (%s)\'\n" % address_read) return cfg_str def _get_route_str(self, gateway_v4): route_str = 'default %s - -\n' % gateway_v4 return route_str def _get_cmd_str(self, address_read, address_write, address_data): cmd_str = 'qeth_configure -l 0.0.%s ' % address_read.lower() cmd_str += '0.0.%(write)s 0.0.%(data)s 1\n' % {'write': address_write.lower(), 'data': address_data.lower()} cmd_str += ('echo "0.0.%(read)s,0.0.%(write)s,0.0.%(data)s #`date`"' ' >>/boot/zipl/active_devices.txt\n' % {'read': address_read.lower(), 'write': address_write.lower(), 'data': address_data.lower()}) return cmd_str def _get_dns_filename(self): return '/etc/resolv.conf' def _get_device_filename(self, device_num): return 'ifcfg-eth' + str(device_num) def _get_device_name(self, device_num): return 'eth' + str(device_num) def _append_udev_info(self, cfg_files, file_name_route, route_cfg_str, udev_cfg_str): udev_file_name = '/etc/udev/rules.d/70-persistent-net.rules' cfg_files.append((udev_file_name, udev_cfg_str)) if len(route_cfg_str) > 0: cfg_files.append((file_name_route, route_cfg_str)) def _get_udev_configuration(self, device, dev_channel): cfg_str = 'SUBSYSTEM==\"net\", ACTION==\"add\", DRIVERS==\"qeth\",' cfg_str += ' KERNELS==\"%s\", ATTR{type}==\"1\",' % dev_channel cfg_str += ' KERNEL==\"eth*\", NAME=\"eth%s\"\n' % device return cfg_str def _append_udev_rules_file(self, cfg_files, base_vdev): rules_file_name = '/etc/udev/rules.d/51-qeth-0.0.%s.rules' % base_vdev read_ch = '0.0.' + base_vdev write_ch = '0.0.' + str(hex(int(base_vdev, 16) + 1))[2:] data_ch = '0.0.' + str(hex(int(base_vdev, 16) + 2))[2:] udev_rules_str = self._get_udev_rules(read_ch, write_ch, data_ch) cfg_files.append((rules_file_name, udev_rules_str)) def _get_udev_rules(self, channel_read, channel_write, channel_data): """construct udev rules info.""" sub_str = '%(read)s %%k %(read)s %(write)s %(data)s qeth' % { 'read': channel_read, 'read': channel_read, 'write': channel_write, 'data': channel_data} rules_str = '# Configure qeth device at' rules_str += ' %(read)s/%(write)s/%(data)s\n' % { 'read': channel_read, 'write': channel_write, 'data': channel_data} rules_str += ('ACTION==\"add\", SUBSYSTEM==\"drivers\", KERNEL==' '\"qeth\", IMPORT{program}=\"collect %s\"\n') % sub_str rules_str += ('ACTION==\"add\", SUBSYSTEM==\"ccw\", KERNEL==\"' '%(read)s\", IMPORT{program}="collect %(channel)s\"\n') % { 'read': channel_read, 'channel': sub_str} rules_str += ('ACTION==\"add\", SUBSYSTEM==\"ccw\", KERNEL==\"' '%(write)s\", IMPORT{program}=\"collect %(channel)s\"\n') % { 'write': channel_write, 'channel': sub_str} rules_str += ('ACTION==\"add\", SUBSYSTEM==\"ccw\", KERNEL==\"' '%(data)s\", IMPORT{program}=\"collect %(channel)s\"\n') % { 'data': channel_data, 'channel': sub_str} rules_str += ('ACTION==\"remove\", SUBSYSTEM==\"drivers\", KERNEL==\"' 'qeth\", IMPORT{program}=\"collect --remove %s\"\n') % sub_str rules_str += ('ACTION==\"remove\", SUBSYSTEM==\"ccw\", KERNEL==\"' '%(read)s\", IMPORT{program}=\"collect --remove %(channel)s\"\n' ) % {'read': channel_read, 'channel': sub_str} rules_str += ('ACTION==\"remove\", SUBSYSTEM==\"ccw\", KERNEL==\"' '%(write)s\", IMPORT{program}=\"collect --remove %(channel)s\"\n' ) % {'write': channel_write, 'channel': sub_str} rules_str += ('ACTION==\"remove\", SUBSYSTEM==\"ccw\", KERNEL==\"' '%(data)s\", IMPORT{program}=\"collect --remove %(channel)s\"\n' ) % {'data': channel_data, 'channel': sub_str} rules_str += ('TEST==\"[ccwgroup/%(read)s]\", GOTO=\"qeth-%(read)s' '-end\"\n') % {'read': channel_read, 'read': channel_read} rules_str += ('ACTION==\"add\", SUBSYSTEM==\"ccw\", ENV{COLLECT_' '%(read)s}==\"0\", ATTR{[drivers/ccwgroup:qeth]group}=\"' '%(read)s,%(write)s,%(data)s\"\n') % { 'read': channel_read, 'read': channel_read, 'write': channel_write, 'data': channel_data} rules_str += ('ACTION==\"add\", SUBSYSTEM==\"drivers\", KERNEL==\"qeth' '\", ENV{COLLECT_%(read)s}==\"0\", ATTR{[drivers/' 'ccwgroup:qeth]group}=\"%(read)s,%(write)s,%(data)s\"\n' 'LABEL=\"qeth-%(read)s-end\"\n') % { 'read': channel_read, 'read': channel_read, 'write': channel_write, 'data': channel_data, 'read': channel_read} rules_str += ('ACTION==\"add\", SUBSYSTEM==\"ccwgroup\", KERNEL==' '\"%s\", ATTR{layer2}=\"1\"\n') % channel_read rules_str += ('ACTION==\"add\", SUBSYSTEM==\"ccwgroup\", KERNEL==' '\"%s\", ATTR{online}=\"1\"\n') % channel_read return rules_str def get_scp_string(self, root, fcp, wwpn, lun): return ("=root=%(root)s zfcp.allow_lun_scan=0 " "zfcp.device=0.0.%(fcp)s,0x%(wwpn)s,0x%(lun)s") % { 'root': root, 'fcp': fcp, 'wwpn': wwpn, 'lun': lun} def get_zipl_script_lines(self, image, ramdisk, root, fcp, wwpn, lun): return ['#!/bin/bash\n', ('echo -e "[defaultboot]\\n' 'default=boot-from-volume\\n' '[boot-from-volume]\\n' 'image=%(image)s\\n' 'target = /boot/zipl\\n' 'ramdisk=%(ramdisk)s\\n' 'parameters=\\"root=%(root)s ' 'zfcp.device=0.0.%(fcp)s,0x%(wwpn)s,0x%(lun)s ' 'zfcp.allow_lun_scan=0\\""' '>/etc/zipl_volume.conf\n' 'mkinitrd\n' 'zipl -c /etc/zipl_volume.conf') % {'image': image, 'ramdisk': ramdisk, 'root': root, 'fcp': fcp, 'wwpn': wwpn, 'lun': lun}] class sles11(sles): def get_znetconfig_contents(self): return '\n'.join(('cio_ignore -R', 'znetconf -R -n', 'sleep 2', 'udevadm trigger', 'udevadm settle', 'sleep 2', 'znetconf -A', 'service network restart', 'cio_ignore -u')) class sles12(sles): def get_znetconfig_contents(self): remove_route = 'rm -f %s/ifroute-eth*' % self._get_network_file_path() return '\n'.join(('cio_ignore -R', 'znetconf -R -n', 'sleep 2', remove_route, 'udevadm trigger', 'udevadm settle', 'sleep 2', 'znetconf -A', 'cio_ignore -u')) class ubuntu(LinuxDist): def create_network_configuration_files(self, file_path, network_info, base_vdev): """Generate network configuration files to instance.""" cfg_files = [] cmd_strings = '' network_config_file_name = self._get_network_file() network_cfg_str = 'auto lo\n' network_cfg_str += 'iface lo inet loopback\n' for vif in network_info: network_hw_config_fname = self._get_device_filename(base_vdev) network_hw_config_str = self._get_network_hw_config_str(base_vdev) cfg_files.append((network_hw_config_fname, network_hw_config_str)) network = vif['network'] (cfg_str, dns_str) = self._generate_network_configuration(network, base_vdev) LOG.debug('Network configure file content is: %s', cfg_str) network_cfg_str += cfg_str if len(dns_str) > 0: network_cfg_str += dns_str base_vdev = str(hex(int(base_vdev, 16) + 3))[2:] cfg_files.append((network_config_file_name, network_cfg_str)) return cfg_files, cmd_strings def _get_network_file(self): return '/etc/network/interfaces' def _get_cfg_str(self, device, broadcast_v4, gateway_v4, ip_v4, netmask_v4): cfg_str = 'auto ' + device + '\n' cfg_str += 'iface ' + device + ' inet static\n' cfg_str += 'address ' + ip_v4 + '\n' cfg_str += 'netmask ' + netmask_v4 + '\n' cfg_str += 'broadcast ' + broadcast_v4 + '\n' cfg_str += 'gateway ' + gateway_v4 + '\n' return cfg_str def _generate_network_configuration(self, network, vdev): ip_v4 = dns_str = '' subnets_v4 = [s for s in network['subnets'] if s['version'] == 4] if len(subnets_v4[0]['ips']) > 0: ip_v4 = subnets_v4[0]['ips'][0]['address'] if len(subnets_v4[0]['dns']) > 0: for dns in subnets_v4[0]['dns']: dns_str += 'dns-nameservers ' + dns['address'] + '\n' netmask_v4 = str(subnets_v4[0].as_netaddr().netmask) gateway_v4 = subnets_v4[0]['gateway']['address'] or '' broadcast_v4 = str(subnets_v4[0].as_netaddr().broadcast) device = self._get_device_name(vdev) cfg_str = self._get_cfg_str(device, broadcast_v4, gateway_v4, ip_v4, netmask_v4) return cfg_str, dns_str def _get_route_str(self, gateway_v4): return '' def _get_cmd_str(self, address_read, address_write, address_data): return '' def _get_device_name(self, device_num): return 'enc' + str(device_num) def _get_dns_filename(self): return '' def _get_device_filename(self, device_num): return '/etc/sysconfig/hardware/config-ccw-0.0.' + str(device_num) def _get_network_hw_config_str(self, base_vdev): ccwgroup_chans_str = ' '.join(( '0.0.' + str(hex(int(base_vdev, 16)))[2:], '0.0.' + str(hex(int(base_vdev, 16) + 1))[2:], '0.0.' + str(hex(int(base_vdev, 16) + 2))[2:])) return '\n'.join(('CCWGROUP_CHANS=(' + ccwgroup_chans_str + ')', 'QETH_OPTIONS=layer2')) def _get_network_file_path(self): pass def get_znetconfig_contents(self): return '\n'.join(('cio_ignore -R', 'znetconf -R -n', 'sleep 2', 'udevadm trigger', 'udevadm settle', 'sleep 2', 'znetconf -A', '/etc/init.d/networking restart', 'cio_ignore -u')) def _get_udev_configuration(self, device, dev_channel): return '' def _append_udev_info(self, cfg_files, file_name_route, route_cfg_str, udev_cfg_str): pass def get_scp_string(self, root, fcp, wwpn, lun): # Although the content is the same as SLES, I don't want to merge them. # Because they are NOT logically related, and it's very likely that # they evolve separately in the future. return ("=root=%(root)s zfcp.allow_lun_scan=0 " "zfcp.device=0.0.%(fcp)s,0x%(wwpn)s,0x%(lun)s") % { 'root': root, 'fcp': fcp, 'wwpn': wwpn, 'lun': lun} def get_zipl_script_lines(self, image, ramdisk, root, fcp, wwpn, lun): return ['#!/bin/bash\n', ('echo -e "[defaultboot]\\n' 'default=boot-from-volume\\n' '[boot-from-volume]\\n' 'image=%(image)s\\n' 'target = /boot/zipl\\n' 'ramdisk=%(ramdisk)s\\n' 'parameters=\\"root=%(root)s ' 'zfcp.device=0.0.%(fcp)s,0x%(wwpn)s,0x%(lun)s ' 'zfcp.allow_lun_scan=0\\""' '>/etc/zipl_volume.conf\n' 'mkinitrd\n' 'zipl -c /etc/zipl_volume.conf') % {'image': image, 'ramdisk': ramdisk, 'root': root, 'fcp': fcp, 'wwpn': wwpn, 'lun': lun}] def assemble_zfcp_srcdev(self, fcp, wwpn, lun): path = '/dev/disk/by-path/ccw-0.0.%(fcp)s-fc-0x%(wwpn)s-lun-%(lun)s' lun = int(lun[0:4], 16) srcdev = path % {'fcp': fcp, 'wwpn': wwpn, 'lun': lun} return srcdev def _get_udev_rules(self, channel_read, channel_write, channel_data): """construct udev rules info.""" return '' def _append_udev_rules_file(self, cfg_files, base_vdev): pass class ubuntu16(ubuntu): pass class ListDistManager(object): def get_linux_dist(self, os_version): distro, release = self.parse_dist(os_version) return globals()[distro + release] def _parse_release(self, os_version, distro, remain): supported = {'rhel': ['6', '7'], 'sles': ['11', '12'], 'ubuntu': ['16']} releases = supported[distro] for r in releases: if remain.startswith(r): return r else: msg = _('Can not handle os: %s') % os_version raise exception.ZVMImageError(msg=msg) def parse_dist(self, os_version): """Separate os and version from os_version. Possible return value are only: ('rhel', x.y) and ('sles', x.y) where x.y may not be digits """ supported = {'rhel': ['rhel', 'redhat', 'red hat'], 'sles': ['suse', 'sles'], 'ubuntu': ['ubuntu']} os_version = os_version.lower() for distro, patterns in supported.items(): for i in patterns: if os_version.startswith(i): # Not guarrentee the version is digital remain = os_version.split(i, 2)[1] release = self._parse_release(os_version, distro, remain) return distro, release msg = _('Can not handle os: %s') % os_version raise exception.ZVMImageError(msg=msg)