support/package-builder/PackageManager.py
87815216
 import os
 import threading
326d5ca8
 import copy
2820c61a
 from PackageBuildDataGenerator import PackageBuildDataGenerator
 from Logger import Logger
 from constants import constants
7418d2bf
 import docker
 from CommandUtils import CommandUtils
2820c61a
 from PackageUtils import PackageUtils
518d6a6f
 from ToolChainUtils import ToolChainUtils
d024e640
 from Scheduler import Scheduler
 from ThreadPool import ThreadPool
45c9260c
 from SpecData import SPECS
f93ef2b0
 from StringUtils import StringUtils
8f56b626
 from Sandbox import Chroot, Container
2820c61a
 
 class PackageManager(object):
6c9b4b2f
 
87815216
     def __init__(self, logName=None, logPath=None, pkgBuildType="chroot"):
2820c61a
         if logName is None:
             logName = "PackageManager"
         if logPath is None:
             logPath = constants.logPath
87815216
         self.logName = logName
         self.logPath = logPath
26b55679
         self.logLevel = constants.logLevel
         self.logger = Logger.getLogger(logName, logPath, constants.logLevel)
87815216
         self.mapCyclesToPackageList = {}
         self.mapPackageToCycle = {}
         self.sortedPackageList = []
326d5ca8
         self.listOfPackagesAlreadyBuilt = set()
87815216
         self.pkgBuildType = pkgBuildType
a2ee40ce
         if self.pkgBuildType == "container":
             self.dockerClient = docker.from_env(version="auto")
6c9b4b2f
 
326d5ca8
     def buildToolChain(self):
         pkgCount = 0
         try:
             tUtils = ToolChainUtils()
             pkgCount = tUtils.buildCoreToolChainPackages()
         except Exception as e:
             self.logger.error("Unable to build tool chain")
             self.logger.error(e)
             raise e
         return pkgCount
 
     def buildToolChainPackages(self, buildThreads):
         pkgCount = self.buildToolChain()
         if self.pkgBuildType == "container":
             # Stage 1 build container
             #TODO image name constants.buildContainerImageName
8f56b626
             if pkgCount > 0 or not self.dockerClient.images.list(constants.buildContainerImage):
326d5ca8
                 self._createBuildContainer()
9bc3518e
         self.logger.info("Step 2 : Building stage 2 of the toolchain...")
         self.logger.info(constants.listToolChainPackages)
         self.logger.info("")
326d5ca8
         self._buildGivenPackages(constants.listToolChainPackages, buildThreads)
9bc3518e
         self.logger.info("The entire toolchain is now available")
         self.logger.info(45 * '-')
         self.logger.info("")
326d5ca8
         if self.pkgBuildType == "container":
             # Stage 2 build container
             #TODO: rebuild container only if anything in listToolChainPackages was built
             self._createBuildContainer()
 
4b1a41cb
     def buildPackages(self, listPackages, buildThreads):
326d5ca8
         if constants.rpmCheck:
             constants.rpmCheck = False
             self.buildToolChainPackages(buildThreads)
             self._buildTestPackages(buildThreads)
             constants.rpmCheck = True
             self._buildGivenPackages(listPackages, buildThreads)
         else:
             self.buildToolChainPackages(buildThreads)
9bc3518e
             self.logger.info("Step 3 : Building the following package(s) and dependencies...")
             self.logger.info(listPackages)
             self.logger.info("")
326d5ca8
             self._buildGivenPackages(listPackages, buildThreads)
26b55679
         self.logger.info("Package build has been completed")
         self.logger.info("")
326d5ca8
 
     def _readPackageBuildData(self, listPackages):
518d6a6f
         try:
87815216
             pkgBuildDataGen = PackageBuildDataGenerator(self.logName, self.logPath)
             self.mapCyclesToPackageList, self.mapPackageToCycle, self.sortedPackageList = (
                 pkgBuildDataGen.getPackageBuildData(listPackages))
 
         except Exception as e:
             self.logger.exception(e)
2820c61a
             self.logger.error("unable to get sorted list")
             return False
         return True
6c9b4b2f
 
f93ef2b0
     # Returns list of package names which spec file has all subpackages built
     # Returns set of package name and version like
     # ["name1-vers1", "name2-vers2",..]
326d5ca8
     def _readAlreadyAvailablePackages(self):
         listAvailablePackages = set()
87815216
         pkgUtils = PackageUtils(self.logName, self.logPath)
f93ef2b0
         listPackages = SPECS.getData().getListPackages()
         for package in listPackages:
             for version in SPECS.getData().getVersions(package):
                 # Mark package available only if all subpackages are available
                 packageIsAlreadyBuilt=True
                 listRPMPackages = SPECS.getData().getRPMPackages(package, version)
                 for rpmPkg in listRPMPackages:
6e3ba406
                     if pkgUtils.findRPMFileForGivenPackage(rpmPkg, version) is None:
f93ef2b0
                         packageIsAlreadyBuilt=False
                         break;
                 if packageIsAlreadyBuilt:
                     listAvailablePackages.add(package+"-"+version)
 
26b55679
         self.logger.debug("List of Already built packages")
         self.logger.debug(listAvailablePackages)
2820c61a
         return listAvailablePackages
adf248d5
 
326d5ca8
     def _calculateParams(self, listPackages):
2820c61a
         self.mapCyclesToPackageList.clear()
         self.mapPackageToCycle.clear()
87815216
         self.sortedPackageList = []
6c9b4b2f
 
f93ef2b0
         self.listOfPackagesAlreadyBuilt = list(self._readAlreadyAvailablePackages())
c04feb5d
 
         updateBuiltRPMSList = False
         while not updateBuiltRPMSList:
             updateBuiltRPMSList = True
f93ef2b0
             listOfPackagesAlreadyBuilt = self.listOfPackagesAlreadyBuilt
c04feb5d
             for pkg in listOfPackagesAlreadyBuilt:
f93ef2b0
                 packageName, packageVersion = StringUtils.splitPackageNameAndVersion(pkg)
                 listDependentRpmPackages = SPECS.getData().getRequiresAllForPackage(packageName, packageVersion)
c04feb5d
                 needToRebuild = False
                 for dependentPkg in listDependentRpmPackages:
                     if dependentPkg not in self.listOfPackagesAlreadyBuilt:
                         needToRebuild = True
                         updateBuiltRPMSList = False
                 if needToRebuild:
                     self.listOfPackagesAlreadyBuilt.remove(pkg)
b5e09fac
 
326d5ca8
         listPackagesToBuild = copy.copy(listPackages)
fe747196
         for pkg in listPackages:
87815216
             if (pkg in self.listOfPackagesAlreadyBuilt and
                     not constants.rpmCheck):
fe747196
                 listPackagesToBuild.remove(pkg)
326d5ca8
         if not self._readPackageBuildData(listPackagesToBuild):
fe747196
             return False
2820c61a
         return True
b5e09fac
 
326d5ca8
     def _buildTestPackages(self, buildThreads):
b5e09fac
         self.buildToolChain()
326d5ca8
         self._buildGivenPackages(constants.listMakeCheckRPMPkgtoInstall, buildThreads)
b5e09fac
 
326d5ca8
     def _initializeThreadPool(self, statusEvent):
a5e4be9f
         ThreadPool.clear()
87815216
         ThreadPool.mapPackageToCycle = self.mapPackageToCycle
         ThreadPool.logger = self.logger
         ThreadPool.statusEvent = statusEvent
         ThreadPool.pkgBuildType = self.pkgBuildType
b5e09fac
 
326d5ca8
     def _initializeScheduler(self, statusEvent):
26b55679
         Scheduler.setLog(self.logName, self.logPath, self.logLevel)
f93ef2b0
         Scheduler.setParams(self.sortedPackageList, set(self.listOfPackagesAlreadyBuilt))
d024e640
         Scheduler.setEvent(statusEvent)
87815216
         Scheduler.stopScheduling = False
b5e09fac
 
326d5ca8
     def _buildGivenPackages(self, listPackages, buildThreads):
f93ef2b0
         # Extend listPackages from ["name1", "name2",..] to ["name1-vers1", "name2-vers2",..]
         listPackageNamesAndVersions=[]
         for pkg in listPackages:
             for version in SPECS.getData().getVersions(pkg):
                 listPackageNamesAndVersions.append(pkg+"-"+version)
26b55679
         alreadyBuiltRPMS = self._readAlreadyAvailablePackages()
         if alreadyBuiltRPMS:
             self.logger.debug("List of already available packages:")
             self.logger.debug(alreadyBuiltRPMS)
 
b5e09fac
         if constants.rpmCheck:
f93ef2b0
             listMakeCheckPackages=set()
             for pkg in listPackages:
                 version = SPECS.getData().getHighestVersion(pkg)
                 listMakeCheckPackages.add(pkg+"-"+version)
             listPackageNamesAndVersions = (list(set(listPackageNamesAndVersions)|(listMakeCheckPackages-alreadyBuiltRPMS)))
b5e09fac
 
f93ef2b0
         returnVal = self._calculateParams(listPackageNamesAndVersions)
a5e4be9f
         if not returnVal:
             self.logger.error("Unable to set paramaters. Terminating the package manager.")
895c821e
             raise Exception("Unable to set paramaters")
b5e09fac
 
9bc3518e
         listBasePackageNamesAndVersions = list(map(lambda x:SPECS.getData().getBasePkg(x), listPackageNamesAndVersions))
         listPackagesToBuild = list((set(listBasePackageNamesAndVersions) - set(alreadyBuiltRPMS)))
         if listPackagesToBuild:
             self.logger.info("List of packages yet to be built...")
             self.logger.info(listPackagesToBuild)
             self.logger.info("")
87815216
         statusEvent = threading.Event()
326d5ca8
         self._initializeScheduler(statusEvent)
         self._initializeThreadPool(statusEvent)
b5e09fac
 
326d5ca8
         for i in range(0, buildThreads):
87815216
             workerName = "WorkerThread" + str(i)
b37b4c63
             ThreadPool.addWorkerThread(workerName)
             ThreadPool.startWorkerThread(workerName)
b5e09fac
 
d024e640
         statusEvent.wait()
87815216
         Scheduler.stopScheduling = True
26b55679
         self.logger.debug("Waiting for all remaining worker threads")
326d5ca8
         ThreadPool.join_all()
b5e09fac
 
87815216
         setFailFlag = False
         allPackagesBuilt = False
d024e640
         if Scheduler.isAnyPackagesFailedToBuild():
87815216
             setFailFlag = True
b5e09fac
 
d024e640
         if Scheduler.isAllPackagesBuilt():
87815216
             allPackagesBuilt = True
b5e09fac
 
d024e640
         if setFailFlag:
             self.logger.error("Some of the packages failed:")
             self.logger.error(Scheduler.listOfFailedPackages)
895c821e
             raise Exception("Failed during building package")
6c9b4b2f
 
d024e640
         if not setFailFlag:
             if allPackagesBuilt:
26b55679
                 self.logger.debug("All packages built successfully")
d024e640
             else:
                 self.logger.error("Build stopped unexpectedly.Unknown error.")
895c821e
                 raise Exception("Unknown error")
b5e09fac
 
326d5ca8
     def _createBuildContainer(self):
26b55679
         self.logger.debug("Generating photon build container..")
7418d2bf
         try:
             #TODO image name constants.buildContainerImageName
8f56b626
             self.dockerClient.images.remove(constants.buildContainerImage, force=True)
7418d2bf
         except Exception as e:
             #TODO - better handling
             self.logger.debug("Photon build container image not found.")
 
         # Create toolchain chroot and install toolchain RPMs
8f56b626
         chroot = None
7418d2bf
         try:
             #TODO: constants.tcrootname
8f56b626
             chroot = Chroot(self.logger)
             chroot.create("toolchain-chroot")
7418d2bf
             tcUtils = ToolChainUtils("toolchain-chroot", self.logPath)
8f56b626
             tcUtils.installToolChainRPMS(chroot)
7418d2bf
         except Exception as e:
8f56b626
             if chroot:
                 chroot.destroy()
7418d2bf
             raise e
8f56b626
         self.logger.debug("createBuildContainer: " + chroot.getPath())
7418d2bf
 
         # Create photon build container using toolchain chroot
8f56b626
         chroot.unmountAll()
7418d2bf
         #TODO: Coalesce logging
         cmdUtils = CommandUtils()
8f56b626
         cmd = "cd " + chroot.getPath() + " && tar -czf ../tcroot.tar.gz ."
         cmdUtils.runCommandInShell(cmd, logfn=self.logger.debug)
         cmd = "mv " + chroot.getPath() + "/../tcroot.tar.gz ."
         cmdUtils.runCommandInShell(cmd, logfn=self.logger.debug)
7418d2bf
         #TODO: Container name, docker file name from constants.
8f56b626
         self.dockerClient.images.build(tag=constants.buildContainerImage,
7418d2bf
                                        path=".",
                                        rm=True,
                                        dockerfile="Dockerfile.photon_build_container")
 
         # Cleanup
         cmd = "rm -f ./tcroot.tar.gz"
8f56b626
         cmdUtils.runCommandInShell(cmd, logfn=self.logger.debug)
         chroot.destroy()
26b55679
         self.logger.debug("Photon build container successfully created.")