Example #1
0
class BaseKitInstaller(KitInstallerBase):
    puppet_modules = ['univa-tortuga_kit_base']

    def __init__(self):
        self._global_param_db_api = GlobalParameterDbApi()
        super().__init__()

    def get_db_parameter_value(self, key, default='__throw_exception__'):
        try:
            return self._global_param_db_api.getParameter(key).getValue()
        except ParameterNotFound:
            if default == '__throw_exception__':
                raise
        return default

    def get_db_parameter_bool(self, key, default='__throw_exception__'):
        value = self.get_db_parameter_value(key, default)
        return bool(value[0] == '1')

    def set_db_parameter_value(self, key, value):
        self._global_param_db_api.addParameter(Parameter(name=key,
                                                         value=value))

    def action_post_install(self):
        super().action_post_install()
        return PostInstallAction(self)()
Example #2
0
class ParameterManager(TortugaObjectManager, Singleton):
    def __init__(self):
        super(ParameterManager, self).__init__()

        self._globalParameterDbApi = GlobalParameterDbApi()

    def getParameter(self, name):
        """
        Raises:
            ParameterNotFound
        """

        return self._globalParameterDbApi.getParameter(name)

    def getBoolParameter(self, name, default=None):
        """
        Raises:
            ParameterNotFound
            TortugaException
        """

        try:
            param = self.getParameter(name)
        except ParameterNotFound:
            if default is not None:
                return default

            raise

        return param.getValue() and \
            param.getValue()[0].lower() in ('1', 'y', 't')

    def getIntParameter(self, name, default=None):
        """
        Raises:
            ParameterNotFound
            TortugaException
        """

        try:
            param = self.getParameter(name)
        except ParameterNotFound:
            if default is not None:
                return default

            raise

        return int(param.getValue())

    def getParameterList(self):
        return self._globalParameterDbApi.getParameterList()

    def upsertParameter(self, parameter):
        return self._globalParameterDbApi.upsertParameter(parameter)

    def deleteParameter(self, name):
        return self._globalParameterDbApi.deleteParameter(name)
Example #3
0
class ParameterManager(TortugaObjectManager):
    def __init__(self):
        super(ParameterManager, self).__init__()

        self._globalParameterDbApi = GlobalParameterDbApi()

    def getParameter(self, session: Session, name: str) -> Parameter:
        """
        Raises:
            ParameterNotFound
        """

        return self._globalParameterDbApi.getParameter(session, name)

    def getBoolParameter(self,
                         session: Session,
                         name: str,
                         default: Optional[bool] = None) -> bool:
        """
        Raises:
            ParameterNotFound
            TortugaException
        """

        try:
            param = self.getParameter(session, name)
        except ParameterNotFound:
            if default is not None:
                return default

            raise

        return param.getValue() and \
            param.getValue()[0].lower() in ('1', 'y', 't')

    def getIntParameter(self,
                        session: Session,
                        name: str,
                        default: Optional[int] = None) -> int:
        """
        Raises:
            ParameterNotFound
            TortugaException
        """

        try:
            param = self.getParameter(name)
        except ParameterNotFound:
            if default is not None:
                return default

            raise

        return int(param.getValue())

    def getParameterList(self, session: Session) -> TortugaObjectList:
        return self._globalParameterDbApi.getParameterList(session)

    def upsertParameter(self, session: Session, parameter: Parameter) -> None:
        self._globalParameterDbApi.upsertParameter(session, parameter)

    def deleteParameter(self, session: Session, name: str) -> None:
        self._globalParameterDbApi.deleteParameter(session, name)
Example #4
0
class OSSupport(OsSupportBase):
    def __init__(self, osFamilyInfo):
        super(OSSupport, self).__init__(osFamilyInfo)

        self._cm = ConfigManager()
        self._globalParameterDbApi = GlobalParameterDbApi()

        try:
            depot_dir = \
                self._globalParameterDbApi.getParameter('depot').getValue()
        except ParameterNotFound:
            # Fallback to legacy default
            depot_dir = '/depot'

        self._cm.setDepotDir(depot_dir)

    def getPXEReinstallSnippet(self, ksurl, node, hardwareprofile=None,
                               softwareprofile=None): \
            # pylint: disable=no-self-use
        # General kickstart/kernel parameters

        # Find the first nic marked as bootable
        nics = [nic for nic in node.nics if nic.boot]

        if not nics:
            raise NicNotFound(
                'Node [%s] does not have a bootable NIC' % (node.name))

        # Choose the first one
        nic = nics[0]

        if hardwareprofile is None:
            hardwareprofile = node.hardwareprofile

        if softwareprofile is None:
            softwareprofile = node.softwareprofile

        # Use settings from software profile, if defined, otherwise use
        # settings from hardware profile.
        bootParams = getBootParameters(hardwareprofile, softwareprofile)

        kernel = bootParams['kernel']
        kernelParams = bootParams['kernelParams']
        initrd = bootParams['initrd']

        bootargs = [
        ]

        if softwareprofile.os.family.version == '7':
            # RHEL 7.x
            bootargs.append('inst.ks=%s' % (ksurl))
        else:
            # RHEL 5.x and 6.x
            bootargs.append('ks=%s' % (ksurl))

            bootargs.append('ksdevice=%s' % (nic.networkdevice.name))

        # Append kernel parameters, if defined.
        if kernelParams:
            bootargs.append(kernelParams)

        result = '''\
    kernel %s
    append initrd=%s %s''' % (kernel, initrd, ' '.join(bootargs))

        return result

    def __get_kickstart_network_entry(self, dbNode, hardwareprofile, nic): \
            # pylint: disable=no-self-use
        bProvisioningNic = nic.network == hardwareprofile.nics[0].network

        installer_private_ip = hardwareprofile.nics[0].ip

        if not bProvisioningNic and not nic.network.usingDhcp and not nic.ip:
            # Unconfigured public static IP network
            return None

        bActivate = False

        # By default, all interfaces are enabled at on boot
        bOnBoot = True

        # Use the network device name, as specified in the hardware profile
        netargs = [
            'network --device %s' % (nic.networkdevice.name)
        ]

        if bProvisioningNic:
            netargs.append(
                '--bootproto %s' % (
                    'static' if bProvisioningNic or
                    not nic.network.usingDhcp else 'dhcp'))

            netargs.append('--ip=%s' % (nic.ip))
            netargs.append('--netmask=%s' % (nic.network.netmask))
            netargs.append('--nameserver=%s' % (installer_private_ip))

            bActivate = True
        else:
            if nic.network and nic.network.usingDhcp:
                netargs.append('--bootproto dhcp')
            else:
                netargs.append('--bootproto static')

                if nic.ip:
                    netargs.append('--ip=%s' % (nic.ip))
                    netargs.append('--netmask=%s' % (nic.network.netmask))
                else:
                    # Do not enable interface if it's not configured
                    netargs.append('--onboot=no')

                    bOnBoot = False

        # Store provisioning network interface device name for
        # later reference in the template

        # Ensure all interfaces are activated
        if bActivate:
            netargs.append('--activate')

        bDefaultRoute = True

        if bProvisioningNic:
            # This is the nic connected to the provisioning network.

            if len(dbNode.nics) > 1:
                # Disable the default route on the management network.
                netargs.append('--nodefroute')

                bDefaultRoute = False
        else:
            # Disable DNS for all interfaces other than the
            # provisioning network
            if bOnBoot:
                netargs.append('--nodns')

        if nic.network.gateway and bDefaultRoute:
            netargs.append('--gateway %s' % (nic.network.gateway))

        return ' '.join(netargs)

    def __validate_node(self, node): \
            # pylint: disable=no-self-use
        """
        Raises:
            NodeNotFound
            NicNotFound
        """

        if not node.name:
            raise NodeNotFound('Node must have a name')

        if not node.nics:
            raise NicNotFound('Node [%s] has no associated nics' % (
                node.name))

    def __kickstart_get_timezone(self):
        tz = self._globalParameterDbApi.getParameter(
            'Timezone_zone').getValue()

        # Ensure timezone does not contain any spaces
        return tz.replace(' ', '_')

    def __kickstart_get_network_section(self, node, hardwareprofile):
        # Ensure nics are processed in order (ie. eth0, eth1, eth2...)
        nics = node.nics
        nics.sort(key=lambda nic: nic.networkdevice.name)

        network_entries = []
        hostname_set = False

        # Iterate over nics, adding 'network' Kickstart entries for each
        for nic in nics:
            networkString = self.__get_kickstart_network_entry(
                node, hardwareprofile, nic)

            if not networkString:
                continue

            if not hostname_set and nic.boot and \
                    nic.network.type == 'provision':
                networkString += ' --hostname=%s' % (node.name)

                hostname_set = True

            network_entries.append(networkString)

        return '\n'.join(network_entries)

    def __kickstart_get_repos(self, dbSwProfile, installer_private_ip):
        repo_entries = []

        for dbComponent in dbSwProfile.components:
            dbKit = dbComponent.kit
            if dbKit.isOs or dbKit.name != 'base':
                # Do not add repos for OS kits
                continue

            kitVer = '%s-%s' % (dbKit.version, dbKit.iteration)
            kitArch = 'noarch'

            subpath = '%s/%s/%s' % (dbKit.name, kitVer, kitArch)

            # Check if repository actually exists
            if not os.path.exists(os.path.join(self._cm.getDepotDir(),
                                               'kits',
                                               subpath,
                                               'repodata',
                                               'repomd.xml')):
                # Repository for specified kit is empty. Nothing to do...
                continue

            url = self._cm.getYumRootUrl(installer_private_ip) + \
                '/' + subpath

            repo_entries.append(
                'repo --name %s --baseurl=%s' % (dbKit.name, url))

        subpath = '3rdparty/%s/%s/%s' % (dbSwProfile.os.family.name,
                                         dbSwProfile.os.family.version,
                                         dbSwProfile.os.arch)

        if os.path.exists(os.path.join(self._cm.getRoot(),
                                       'repos',
                                       subpath,
                                       'repodata/repomd.xml')):
            # Third-party repository contains packages, include it in
            # Kickstart
            url = '%s/%s' % (
                self._cm.getYumRootUrl(installer_private_ip), subpath)

            repo_entries.append(
                'repo --name tortuga-third-party --baseurl=%s' % (url))

        return repo_entries

    def __get_kickstart_template(self, swprofile):
        ksTemplate = os.path.join(
            self._cm.getKitConfigBase(),
            'kickstart-%s.tmpl' % (swprofile.os.family.name.encode('ascii')))

        if not os.path.exists(ksTemplate):
            ksTemplate = os.path.join(
                self._cm.getKitConfigBase(),
                'kickstart-%s.tmpl' % (swprofile.name.encode('ascii')))

            if not os.path.exists(ksTemplate):
                ksTemplate = os.path.join(
                    self._cm.getKitConfigBase(), 'kickstart.tmpl')

        return ksTemplate

    def __kickstart_get_partition_section(self, softwareprofile):
        buf = """\
#!/bin/sh
# Determine how many drives we have
"""

        # Temporary workaround for RHEL 5.7 based distros
        # https://bugzilla.redhat.com/show_bug.cgi?format=multiple&id=709880
        if softwareprofile.os.version == '5.7':
            buf += 'set $(PYTHONPATH=/usr/lib/booty list-harddrives)\n'
        else:
            buf += 'set $(list-harddrives)\n'

        buf += """
d1=$1
d2=$3
d3=$5
d4=$7
"""

        clearpartstr = '''
cat >/tmp/partinfo << __PARTINFO__
zerombr
'''

        disksToPreserve = []

        # Need to get the drives to clear
        clearpartstr += 'clearpart '
        driveNumbers = []

        for dbPartition in softwareprofile.partitions:
            disk = dbPartition.device.split('.')[0]

            if disk not in driveNumbers:
                driveNumbers.append(disk)

                if not dbPartition.preserve:
                    # This is a partition to clear
                    if len(driveNumbers) == 1:
                        # First drive
                        clearpartstr += ('--all --initlabel'
                                         ' --drives="${d%s:-nodisk}' % (
                                             disk))
                    else:
                        clearpartstr += ',${d%s:-nodisk}' % (disk)
                else:
                    disksToPreserve.append(disk)

        clearpartstr += "--none" if not driveNumbers else '"'
        clearpartstr += '\n'

        for diskNum in driveNumbers:
            if diskNum in disksToPreserve:
                continue

            buf += '''
dd if=/dev/zero of=$d%s bs=512 count=1
''' % (diskNum)

        buf += clearpartstr

        bootloaderLocation = "mbr"

        # Now create partitions
        for dbPartition in softwareprofile.partitions:
            if dbPartition.bootLoader:
                # Can't control the partition in anaconda...it will be on
                # the drive with the boot partition
                bootloaderLocation = 'partition'

            buf += self._processPartition(dbPartition)

        # now do the bootloader
        buf += (
            'bootloader --location=%s --driveorder=${d1:-nodisk}\n' % (
                bootloaderLocation))

        buf += '__PARTINFO__\n'

        return buf

    def __get_template_subst_dict(self, node, hardwareprofile,
                                  softwareprofile):
        hardwareprofile = hardwareprofile \
            if hardwareprofile else node.hardwareprofile
        softwareprofile = softwareprofile \
            if softwareprofile else node.softwareprofile

        installer_public_fqdn = socket.getfqdn()
        installer_hostname = installer_public_fqdn.split('.')[0]

        installer_private_ip = hardwareprofile.nics[0].ip

        try:
            private_domain = self._globalParameterDbApi.\
                getParameter('DNSZone').getValue()
        except ParameterNotFound:
            private_domain = None

        installer_private_fqdn = '%s%s%s' % (
            installer_hostname,
            get_installer_hostname_suffix(
                hardwareprofile.nics[0], enable_interface_aliases=None),
            '.%s' % (private_domain) if private_domain else '')

        vals = node.name.split('.', 1)
        domain = vals[1].lower() if len(vals) == 2 else ''

        d = {
            'fqdn': node.name,
            'domain': domain,
            'hostname': installer_hostname,
            'installer_private_fqdn': installer_private_fqdn,
            'installer_private_domain': private_domain,
            'installer_private_ip': installer_private_ip,
            'puppet_master_fqdn': installer_public_fqdn,
            'installer_public_fqdn': installer_public_fqdn,
            'ntpserver': installer_private_ip,
            'os': softwareprofile.os.name,
            'osfamily': softwareprofile.os.family.name,
            'osfamilyvers': int(softwareprofile.os.family.version),
            # These are deprecated and included for backwards compatibility
            # only. Do not reference them in any new kickstart templates.
            'primaryinstaller': installer_private_fqdn,
            'puppetserver': installer_public_fqdn,
            'installerip': installer_private_ip,
        }

        # Add entry for install package source
        d['url'] = '%s/%s/%s/%s' % (
            self._cm.getYumRootUrl(installer_private_fqdn),
            softwareprofile.os.name,
            softwareprofile.os.version,
            softwareprofile.os.arch)

        d['lang'] = 'en_US.UTF-8'

        d['keyboard'] = 'us'

        d['networkcfg'] = self.__kickstart_get_network_section(
            node, hardwareprofile)

        d['rootpw'] = self._generatePassword()

        d['timezone'] = self.__kickstart_get_timezone()

        d['includes'] = '%include /tmp/partinfo'

        d['repos'] = '\n'.join(
            self.__kickstart_get_repos(
                softwareprofile, installer_private_fqdn))

        # Retain this for backwards compatibility with legacy Kickstart
        # templates
        d['packages'] = '\n'.join([])

        d['prescript'] = self.__kickstart_get_partition_section(
            softwareprofile)

        d['installer_url'] = self._cm.getInstallerUrl(installer_private_fqdn)

        d['cfmstring'] = self._cm.getCfmPassword()

        return d

    def getKickstartFileContents(self, node, hardwareprofile,
                                 softwareprofile):
        # Perform basic sanity checking before proceeding
        self.__validate_node(node)

        template_subst_dict = self.__get_template_subst_dict(
            node, hardwareprofile, softwareprofile)

        with open(self.__get_kickstart_template(softwareprofile)) as fp:
            tmpl = fp.read()

        return Template(tmpl).render(template_subst_dict)

    def _generatePassword(self): \
            # pylint: disable=no-self-use
        # Generate a random password, used when creating a Kickstart file
        # for package-based node provisioning.
        strlength = 8
        strchars = string.ascii_letters + string.digits
        rootpw = ''.join([choice(strchars) for _ in range(strlength)])
        rootpw = crypt.crypt(str(rootpw), str(time.time()))
        return rootpw

    def __get_partition_mountpoint(self, dbPartition): \
            # pylint: disable=no-self-use
        if not dbPartition.mountPoint:
            if dbPartition.fsType == 'swap':
                mountPoint = 'swap'
            else:
                # Any partition that does not have a mountpoint defined
                # is ignored.
                return None
        else:
            mountPoint = dbPartition.mountPoint

        return mountPoint

    def _processPartition(self, dbPartition): \
            # pylint: disable=no-self-use
        mountPoint = dbPartition.mountPoint \
            if dbPartition.mountPoint else \
            self.__get_partition_mountpoint(dbPartition)

        if not mountPoint:
            return ''

        result = ''

        # All partitions must have a mount point and partition type
        result = 'part %s --fstype %s' % (mountPoint, dbPartition.fsType)

        # This will throw an exception if the size stored in the
        # partition settings is not an integer.
        if dbPartition.size:
            result += ' --size=%d' % (dbPartition.size)
        else:
            # If partition size is not set or is zero, use '--recommended' flag
            if mountPoint == 'swap':
                result += ' --recommended'

        disk, part = dbPartition.device.split('.')

        optionsList = dbPartition.options.split(',') \
            if dbPartition.options else []

        if dbPartition.grow is not None:
            result += ' --grow'

            if dbPartition.maxSize is not None:
                result += ' --maxsize %d' % (dbPartition.maxSize)

        if optionsList:
            # Add the fs options...
            result += ' --fsoptions="%s"' % (','.join(optionsList))

        result += ' --noformat --onpart=${d%s:-nodisk}%s' % (disk, part) \
            if dbPartition.preserve else \
            ' --ondisk=${d%s:-nodisk}' % str(disk)

        result += '\n'

        return result