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 ChrootUtils import ChrootUtils
 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
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
         self.logger = Logger.getLogger(logName, logPath)
         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
             if pkgCount > 0 or not self.dockerClient.images.list("photon_build_container:latest"):
                 self._createBuildContainer()
         self._buildGivenPackages(constants.listToolChainPackages, buildThreads)
         if self.pkgBuildType == "container":
             # Stage 2 build container
             #TODO: rebuild container only if anything in listToolChainPackages was built
             self._createBuildContainer()
 
     def buildPackages(self, listPackages, buildThreads, pkgBuildType):
         self.pkgBuildType = pkgBuildType
         if constants.rpmCheck:
             constants.rpmCheck = False
             self.buildToolChainPackages(buildThreads)
             self._buildTestPackages(buildThreads)
             constants.rpmCheck = True
             self._buildGivenPackages(listPackages, buildThreads)
         else:
             self.buildToolChainPackages(buildThreads)
             self._buildGivenPackages(listPackages, buildThreads)
 
     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:
                     if pkgUtils.findRPMFileForGivenPackage(rpmPkg) is None:
                         packageIsAlreadyBuilt=False
                         break;
                 if packageIsAlreadyBuilt:
                     listAvailablePackages.add(package+"-"+version)
 
af3575c9
         self.logger.info("List of Already built packages")
         self.logger.info(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)
b5e09fac
 
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):
d024e640
         Scheduler.setLog(self.logName, self.logPath)
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)
             
b5e09fac
         if constants.rpmCheck:
f93ef2b0
             listMakeCheckPackages=set()
             for pkg in listPackages:
                 version = SPECS.getData().getHighestVersion(pkg)
                 listMakeCheckPackages.add(pkg+"-"+version)
326d5ca8
             alreadyBuiltRPMS = self._readAlreadyAvailablePackages()
f93ef2b0
             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
 
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
a5e4be9f
         self.logger.info("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:
                 self.logger.info("All packages built successfully")
             else:
                 self.logger.error("Build stopped unexpectedly.Unknown error.")
895c821e
                 raise Exception("Unknown error")
b5e09fac
 
d024e640
         self.logger.info("Terminated")
895c821e
 
326d5ca8
     def _createBuildContainer(self):
7418d2bf
         self.logger.info("Generating photon build container..")
         try:
             #TODO image name constants.buildContainerImageName
             self.dockerClient.images.remove("photon_build_container:latest", force=True)
         except Exception as e:
             #TODO - better handling
             self.logger.debug("Photon build container image not found.")
 
         # Create toolchain chroot and install toolchain RPMs
87815216
         chrootID = None
7418d2bf
         try:
             #TODO: constants.tcrootname
             chrUtils = ChrootUtils("toolchain-chroot", self.logPath)
             returnVal, chrootID = chrUtils.createChroot("toolchain-chroot")
             self.logger.debug("Created tool-chain chroot: " + chrootID)
             if not returnVal:
                 raise Exception("Unable to prepare tool-chain chroot")
             tcUtils = ToolChainUtils("toolchain-chroot", self.logPath)
             tcUtils.installToolChainRPMS(chrootID, "dummy")
         except Exception as e:
             if chrootID is not None:
                 self.logger.debug("Deleting chroot: " + chrootID)
                 chrUtils.destroyChroot(chrootID)
             raise e
         self.logger.info("VDBG-PU-createBuildContainer: chrootID: " + chrootID)
 
         # Create photon build container using toolchain chroot
         #TODO: Coalesce logging
         cmdUtils = CommandUtils()
         cmd = "./umount-build-root.sh " + chrootID
         cmdUtils.runCommandInShell(cmd, self.logPath + "/toolchain-chroot1.log")
         cmd = "cd " + chrootID + " && tar -czvf ../tcroot.tar.gz ."
         cmdUtils.runCommandInShell(cmd, self.logPath + "/toolchain-chroot2.log")
         cmd = "mv " + chrootID + "/../tcroot.tar.gz ."
         cmdUtils.runCommandInShell(cmd, self.logPath + "/toolchain-chroot3.log")
         #TODO: Container name, docker file name from constants.
         self.dockerClient.images.build(tag="photon_build_container:latest",
                                        path=".",
                                        rm=True,
                                        dockerfile="Dockerfile.photon_build_container")
 
         # Cleanup
         cmd = "rm -f ./tcroot.tar.gz"
         cmdUtils.runCommandInShell(cmd, self.logPath + "/toolchain-chroot4.log")
         chrUtils.destroyChroot(chrootID)
         self.logger.info("Photon build container successfully created.")