* executor basics added to repo

This commit is contained in:
Dávid Danyi
2017-03-17 16:30:19 +01:00
parent 2814f4f98e
commit 8bd0b827bf
14 changed files with 1537 additions and 3 deletions

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Action;
use App\Response\JsonCorsResponse;
use App\Service\CiExecutorService;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class CiExecutorAction extends AbstractAction
{
/**
* @var CiExecutorService
*/
private $ciExecutorService;
public function __construct(CiExecutorService $ciExecutorService)
{
$this->ciExecutorService = $ciExecutorService;
}
public function getList(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
{
return new JsonCorsResponse($this->ciExecutorService->debug());
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Action;
use App\Service\CiExecutorService;
use Interop\Container\ContainerInterface;
class CiExecutorFactory
{
public function __invoke(ContainerInterface $container)
{
$service = $container->get(CiExecutorService::class);
return new CiExecutorAction($service);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Action;
use App\Response\JsonCorsResponse;
use App\Service\CiExecutorService;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class CiStreamAction extends AbstractAction
{
/**
* @var CiExecutorService
*/
private $ciExecutorService;
public function __construct(CiExecutorService $ciExecutorService)
{
$this->ciExecutorService = $ciExecutorService;
}
public function getList(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
{
return new JsonCorsResponse($this->ciExecutorService->getCiStreams());
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Action;
use App\Service\CiExecutorService;
use Interop\Container\ContainerInterface;
class CiStreamFactory
{
public function __invoke(ContainerInterface $container)
{
$service = $container->get(CiExecutorService::class);
return new CiStreamAction($service);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Action;
use App\Response\JsonCorsResponse;
use App\Service\CiExecutorService;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class JcatPackageAction extends AbstractAction
{
/**
* @var CiExecutorService
*/
private $ciExecutorService;
public function __construct(CiExecutorService $ciExecutorService)
{
$this->ciExecutorService = $ciExecutorService;
}
public function getList(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
{
return new JsonCorsResponse($this->ciExecutorService->getJcatPackages());
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Action;
use App\Service\CiExecutorService;
use Interop\Container\ContainerInterface;
class JcatPackageFactory
{
public function __invoke(ContainerInterface $container)
{
$service = $container->get(CiExecutorService::class);
return new JcatPackageAction($service);
}
}

View File

@@ -21,6 +21,7 @@ class CiConfig implements JsonSerializable
/**
* @ORM\OneToMany(targetEntity="CiConfigItem", mappedBy="dailyConfig", cascade={"persist", "remove"}, orphanRemoval=true)
* @var CiConfigItem[]
*/
private $configItems;
@@ -51,7 +52,7 @@ class CiConfig implements JsonSerializable
}
/**
* @return mixed
* @return CiConfigItem[]|ArrayCollection
*/
public function getConfigItems()
{

View File

@@ -0,0 +1,783 @@
<?php
namespace App\Service;
use App\Entity\CiConfig;
use App\Entity\CiConfigItem;
use Doctrine\ORM\EntityManager;
use Symfony\Component\CssSelector\CssSelectorConverter;
use Zend\Cache\Storage\Adapter\Filesystem;
use Zend\Http\Client;
use Zend\Json\Json;
class CiExecutorService
{
const JENKINS_REDIRECT_URL = "http://jvirtu1.tsp.eth.ericsson.se/jenkins";
const NODE_STATUS_PATH = '/computer';
const CONFIG_1_PATH = '/me/my-views/view/Configuration/job/Config_1_edvidan';
const CONFIG_2_PATH = '/me/my-views/view/Configuration/job/manual_config_2_edvidan';
const ADD_JCAT_PATH = '/view/Tools/job/JCAT_deploy_handler_tool';
const JCAT_DEPLOY_SOURCE_URL = 'http://jcat.tsp.eth.ericsson.se/www/autotest/deploy';
const JCAT_CACHE_KEY = "jcat";
const PACKAGE_CACHE_KEY = "packages";
const STREAM_CACHE_KEY = "stream";
private $testTypeMap = [
'isDoRollbackBefore' => 'rollback_before',
'isDoForcedRollback' => 'ForcedRollback',
'isDoUpgrade' => 'upgrade',
'isDoDummyUpgrade' => 'SystemDummy',
'isDoLrt' => 'lrt',
'isDoDsv' => 'cwosdsv',
// 'isDoNmDsv' => '',
'isDoForcedRollbackDummy' => 'ForcedRollbackDummy',
'isDoRollbackAfter' => 'rollback_after',
];
private $jenkinsBaseUrl = null;
/**
* @var EntityManager
*/
private $entityManager;
/**
* @var Client
*/
private $httpClient;
/**
* @var CiNodeService
*/
private $nodeService;
/**
* CiExecutorService constructor.
* @param EntityManager $entityManager
* @param Client $httpClient
* @param CiNodeService $nodeService
*/
public function __construct(EntityManager $entityManager, Client $httpClient, CiNodeService $nodeService)
{
$this->entityManager = $entityManager;
$this->httpClient = $httpClient;
$this->nodeService = $nodeService;
$this->jenkinsBaseUrl = $this->getActiveJenkinsUrl();
}
public function debug()
{
/** @var CiConfig $config */
$config = $this->entityManager->find(CiConfig::class, 20170317);
return $this->runTest($config->getConfigItems()->first());
}
/**
* Get available jcat versions from config2 page
*
* @param bool $cached
* @return array
*/
public function getJcatPackages($cached = true)
{
if (!$cached) {
$config2Html = $this->getConfig2Html();
$staplerTokens = $this->getStaplerTokens($config2Html, "JCAT_TYPE");
return $this->getJcatVersions($staplerTokens['url'], $staplerTokens['token']);
// return $this->getJcatVersions($config2Html);
}
$cache = $this->getCache(self::JCAT_CACHE_KEY);
$result = $cache->getItem(self::PACKAGE_CACHE_KEY, $success);
if(!$success) {
$config2Html = $this->getConfig2Html();
$staplerTokens = $this->getStaplerTokens($config2Html, "JCAT_TYPE");
$packages =$this->getJcatVersions($staplerTokens['url'], $staplerTokens['token']);
// $packages =$this->getJcatVersions($config2Html);
$cache->setItem(self::PACKAGE_CACHE_KEY, serialize($packages));
} else {
$packages = unserialize($result);
}
return $packages;
}
/**
* @param bool $cached
* @return array|mixed
*/
public function getCiStreams($cached = true)
{
if (!$cached) {
$config2Html = $this->getConfig2Html();
return $this->getStreams($config2Html);
}
$cache = $this->getCache(self::STREAM_CACHE_KEY);
$result = $cache->getItem(self::STREAM_CACHE_KEY, $success);
if(!$success) {
$config2Html = $this->getConfig2Html();
$streams =$this->getStreams($config2Html);
$cache->setItem(self::STREAM_CACHE_KEY, serialize($streams));
} else {
$streams = unserialize($result);
}
return $streams;
}
/**
* Executes a daily configuration from the database.
* This method is meant to be called periodically from cron
*
* @todo implement locking mechanism, so cli task won't start if there is a job already running
* @param CiConfig $ciConfig
*/
public function executePendingTests(CiConfig $ciConfig)
{
foreach ($ciConfig->getConfigItems() as $configItem) {
$this->runTest($configItem);
}
}
/**
* Run a single test configuration item in a daily job
*
* @param CiConfigItem $configItem
* @return array|bool
*/
public function runTest(CiConfigItem $configItem)
{
$nodes = $this->nodeService->getTestNodeList($configItem->getNodes());
// @todo implement marking of "sub-job" to track if a node already has the tests going
// $availableNodes = array_filter($nodes, function($node) {
// return $this->isNodeOnline($node) && $this->isNodeIdle($node);
// });
//
// if(0 == count($availableNodes)) {
// return false;
// }
// @todo remove this part, and check for sub-job success that also needs to be saved, if any node fails, all nodes fail now
foreach($nodes as $node) {
if(!$this->isNodeOnline($node) || !$this->isNodeIdle($node)) {
return false;
}
}
// Build Config1 and wait till it's finished
// @todo reorganize this into config1build?
$config1Status = $this->buildConfigOne($nodes);
$config1JobId = $this->getConfig1JobId();
while(!$this->isConfig1Built($config1JobId)) {
sleep(2);
set_time_limit(30);
}
// Get the generated config2 page and parse it for required data
$config2Html = $this->getConfig2Html();
$streams = $this->getStreams($config2Html);
$staplerTokens = $this->getStaplerTokens($config2Html, "STREAM");
if(true === ($streamExists = in_array($configItem->getStream(), $streams))) {
$package = $this->getPackage($configItem->getStream(), $staplerTokens['url'], $staplerTokens['token']);
} else {
$package = false;
}
$jcatVersions = $this->getJcatVersions($staplerTokens['url'], $staplerTokens['token']);
// Check if the desired JCAT version is available, try to create it if it's not
if(!in_array($configItem->getJcat(), $jcatVersions)) {
// wait for jcat package to become available
$this->addJcatPackageToJenkins($configItem->getJcat());
$jcatDeployJobId = $this->getJcatDeployJobId();
while(!$this->isJcatPackageDeployed($jcatDeployJobId)) {
sleep(2);
set_time_limit(30);
}
// @todo check if we have to rebuild config1 or can just proceed with knowing the right parameters
}
$nodeBases = [];
$nodeTraffics = [];
foreach($nodes as $type => $typeNodes) {
foreach($typeNodes as $node => $backupPostfix) {
if(false === strpos($node, "-")) {
$nodeBaseName = sprintf("%s_%s", $node, $backupPostfix);
$nodeBases[$node] = in_array($nodeBaseName, $this->getNodeBaseNames($config2Html, $node)) ? $nodeBaseName : false;
$nodeTraffics[$node] = $this->getNodeTrafficConfigs($config2Html, $node);
} else {
list($zone1, $zone2) = explode("-", $node);
$zone1BaseName = sprintf("%s_%s", $zone1, $backupPostfix);
$zone2BaseName = sprintf("%s_%s", $zone2, $backupPostfix);
$nodeBases[$zone1] = in_array($zone1BaseName, $this->getNodeBaseNames($config2Html, $zone1)) ? $zone1BaseName : false;
$nodeBases[$zone2] = in_array($zone2BaseName, $this->getNodeBaseNames($config2Html, $zone2)) ? $zone2BaseName : false;
$nodeTraffics[$node] = $this->getNodeTrafficConfigs($config2Html, str_replace("-", "_", $node));
}
}
}
$this->buildConfigTwo($configItem, $package, $nodes, $nodeBases, $nodeTraffics);
return [
"config1ok"=> $config1Status,
// "stapler" => $staplerTokens,
// "packages" => $package,
"jcat" => $jcatVersions,
"bases" => $nodeBases,
"traffic" => $nodeTraffics,
"configsOk" => $this->config1PreCheck(),
"active.jenkins" => $this->getActiveJenkinsUrl(),
"jcat.exists" => $this->getJcatDeployUrl("TSP_JCAT-138.0_CORE"),
"jcat.nodes" => $this->getAvailableJcatTargetNodes(),
"cwis8.on" => $this->isNodeOnline("cwis8"),
];
}
/**
* Checks is both Config1 and Config2 jobs are idle.
* We can only start building new configs if there is no job running at the moment.
*
* @return bool
*/
private function config1PreCheck(): bool
{
$config1JobId = $this->getConfig1JobId();
$this->httpClient->setUri($this->jenkinsBaseUrl . self::CONFIG_1_PATH . "/${config1JobId}/api/json");
$this->httpClient->resetParameters(false, false);
$request = $this->httpClient->getRequest();
$request->setMethod('GET');
$response = $this->httpClient->send();
$body = $response->getBody();
$parsedResponse = Json::decode($body);
if($parsedResponse->building == true) {
return false;
}
$config2JobId = $this->getConfig2JobId();
$this->httpClient->setUri($this->jenkinsBaseUrl . self::CONFIG_1_PATH . "/${config2JobId}/api/json");
$this->httpClient->resetParameters(false, false);
$request = $this->httpClient->getRequest();
$request->setMethod('GET');
$response = $this->httpClient->send();
$body = $response->getBody();
$parsedResponse = Json::decode($body);
if($parsedResponse->building == true) {
return false;
}
return true;
}
/**
* @param $nodes
* @return bool
* @todo build uri parameters OR switch to the other uri for api stuff
*/
private function buildConfigOne($nodes): bool
{
$appendParams = [];
foreach($nodes as $type => $typeNodes) {
foreach($typeNodes as $node => $basePostfix) {
$appendParams[] = sprintf("%s=%s", $type, $node);
}
}
$this->httpClient->setUri($this->jenkinsBaseUrl . self::CONFIG_1_PATH . '/buildWithParameters?delay=0sec&' . implode("&", $appendParams));
$this->httpClient->resetParameters(false, false);
$request = $this->httpClient->getRequest();
$request->setMethod('POST');
$response = $this->httpClient->send();
sleep(2);
return $response->isSuccess();
}
private function getConfig1JobId()
{
return $this->getJobId($this->jenkinsBaseUrl . self::CONFIG_1_PATH);
}
private function getConfig2JobId()
{
return $this->getJobId($this->jenkinsBaseUrl . self::CONFIG_2_PATH);
}
private function getJobId($uri)
{
$this->httpClient->setUri($uri . '/api/json');
$this->httpClient->resetParameters(false, false);
$request = $this->httpClient->getRequest();
$request->setMethod('GET');
$response = $this->httpClient->send();
$body = $response->getBody();
$parsedResponse = Json::decode($body);
return $parsedResponse->lastBuild->number;
}
private function isConfig1Built(int $jobId): bool
{
$this->httpClient->setUri($this->jenkinsBaseUrl . self::CONFIG_1_PATH . "/${jobId}/api/json");
$this->httpClient->resetParameters(false, false);
$request = $this->httpClient->getRequest();
$request->setMethod('GET');
$response = $this->httpClient->send();
$body = $response->getBody();
$parsedResponse = Json::decode($body);
if($parsedResponse->building == false) {
if ($parsedResponse->result == 'SUCCESS') {
return true;
}
throw new \Exception("Build failed with result: " . $parsedResponse->result);
}
return false;
}
/**
* @return string
* @throws \Exception
* @todo investigate why GET method on the config page returns HTTP405
*/
private function getConfig2Html(): string
{
$this->httpClient->setUri($this->jenkinsBaseUrl . self::CONFIG_2_PATH . "/build");
$this->httpClient->resetParameters(false, false);
$request = $this->httpClient->getRequest();
$request->setMethod('GET');
$response = $this->httpClient->send();
if($response->getStatusCode() == 401) {
throw new \Exception("Not authenticated");
}
return $response->getBody();
}
private function getStaplerTokens($config2Body, $blockName = "STREAM")
{
if(0 === ($didMatch = preg_match(sprintf('#referencedParameters\.push\("%s"\);.*?makeStaplerProxy\(\'(.*?)\',\'(.*?)\'.*?// Create Jenkins proxy#msi', $blockName), $config2Body, $matches))) {
throw new \Exception("Couldn't match stapler tokens in response body");
}
return [
'url' => $matches[1],
'token' => $matches[2],
];
}
private function getStreams($config2Body)
{
$cssToXpathConverter = new CssSelectorConverter();
$xpathQuery = $cssToXpathConverter->toXPath('input[value=STREAM]');
$domDocument = new \DOMDocument();
$domDocument->loadHTML($config2Body);
$xpath = new \DOMXPath($domDocument);
$element = $xpath->query($xpathQuery);
if($element->length == 0) {
throw new \Exception("Can't find STREAM");
}
if($element[0]->nextSibling->nodeName != "select") {
throw new \Exception("Can't find STREAM dropdown");
}
$streams = [];
foreach ($element[0]->nextSibling->childNodes as $option) {
$streams[] = $option->textContent;
}
sort($streams, SORT_NATURAL);
return array_values(array_filter(array_unique($streams), function($item){
return $item != "" && $item != "none";
}));
}
private function getPackage($stream, $url, $token): string
{
$this->httpClient->setUri($this->jenkinsBaseUrl . $url . '/doUpdate');
$this->httpClient->resetParameters(false, false);
$headers = $this->httpClient->getRequest()->getHeaders();
$headers->addHeaderLine("Crumb", $token);
$headers->addHeaderLine("Content-type", "application/x-stapler-method-invocation;charset=UTF-8");
$request = $this->httpClient->getRequest();
$request->setMethod('POST');
$request->setContent(sprintf('["STREAM=%s"]', $stream));
$updateResponse = $this->httpClient->send();
if(!$updateResponse->isSuccess()) {
throw new \Exception("Stapler doUpdate error");
}
sleep(1);
$this->httpClient->setUri($this->jenkinsBaseUrl . $url . '/getChoicesForUI');
$request->setContent("[]");
$choicesResponse = $this->httpClient->send();
if(!$choicesResponse->isSuccess()) {
throw new \Exception("Stapler getChoicesForUI error");
}
$packages = Json::decode($choicesResponse->getBody());
if($packages[0][0] == "Script error") {
throw new \Exception("STREAM stapler script error");
}
return array_shift($packages[0]);
}
/**
* @param $url
* @param $token
* @return mixed
* @throws \Exception
*/
private function getJcatVersions($url, $token)
{
// v1
// $cssToXpathConverter = new CssSelectorConverter();
// $xpathQuery = $cssToXpathConverter->toXPath('input[value=JCAT_DIR__MAIN]');
//
// $domDocument = new \DOMDocument();
// $domDocument->loadHTML($config2Body);
//
// $xpath = new \DOMXPath($domDocument);
// $element = $xpath->query($xpathQuery);
//
// if($element->length == 0) {
// throw new \Exception("Can't find JCAT_DIR__MAIN");
// }
//
// if($element[0]->nextSibling->nodeName != "select") {
// throw new \Exception("Can't find JCAT dropdown");
// }
//
// $jcatVersions = [];
// foreach ($element[0]->nextSibling->childNodes as $option) {
// $jcatVersions[] = $option->textContent;
// }
//
// rsort($jcatVersions, SORT_NATURAL);
// return array_values(array_filter(array_unique($jcatVersions), function($item){return $item != "";}));
// v2
$this->httpClient->setUri($this->jenkinsBaseUrl . $url . '/doUpdate');
$this->httpClient->resetParameters(false, false);
$headers = $this->httpClient->getRequest()->getHeaders();
$headers->addHeaderLine("Crumb", $token);
$headers->addHeaderLine("Content-type", "application/x-stapler-method-invocation;charset=UTF-8");
$request = $this->httpClient->getRequest();
$request->setMethod('POST');
$request->setContent('["JCAT_TYPE=INSTALLED"]');
$updateResponse = $this->httpClient->send();
if(!$updateResponse->isSuccess()) {
throw new \Exception("Stapler doUpdate error");
}
sleep(1);
$this->httpClient->setUri($this->jenkinsBaseUrl . $url . '/getChoicesForUI');
$request->setContent("[]");
$choicesResponse = $this->httpClient->send();
if(!$choicesResponse->isSuccess()) {
throw new \Exception("Stapler getChoicesForUI error");
}
$jcats = Json::decode($choicesResponse->getBody());
rsort($jcats[0]);
if($jcats[0][0] == "Script error") {
throw new \Exception("JCAT stapler script error");
}
return $jcats[0];
}
private function getNodeBaseNames($config2Body, $nodeName)
{
$cssToXpathConverter = new CssSelectorConverter();
$xpathQuery = $cssToXpathConverter->toXPath(sprintf('input[value=%s_BASE_BACKUP]', $nodeName));
$domDocument = new \DOMDocument();
$domDocument->loadHTML($config2Body);
$xpath = new \DOMXPath($domDocument);
$element = $xpath->query($xpathQuery);
if($element->length == 0) {
throw new \Exception(sprintf("Can't find %s_BASE_BACKUP", $nodeName));
}
if($element[0]->nextSibling->nodeName != "select") {
throw new \Exception(sprintf("Can't find %s_BASE_BACKUP dropdown", $nodeName));
}
$baseNames = [];
foreach ($element[0]->nextSibling->childNodes as $option) {
$baseNames[] = $option->textContent;
}
rsort($baseNames, SORT_NATURAL);
return array_filter($baseNames, function($item){return $item != "";});
}
private function getNodeTrafficConfigs($config2Body, $nodeName)
{
$cssToXpathConverter = new CssSelectorConverter();
$xpathQuery = $cssToXpathConverter->toXPath(sprintf('input[value=%s_TRAFFIC]', $nodeName));
$domDocument = new \DOMDocument();
$domDocument->loadHTML($config2Body);
$xpath = new \DOMXPath($domDocument);
$element = $xpath->query($xpathQuery);
if($element->length == 0) {
throw new \Exception(sprintf("Can't find %s_TRAFFIC", $nodeName));
}
if($element[0]->nextSibling->nodeName != "select") {
throw new \Exception(sprintf("Can't find %s_TRAFFIC dropdown", $nodeName));
}
$trafficConfigs = [];
foreach ($element[0]->nextSibling->childNodes as $option) {
$trafficConfigs[] = $option->textContent;
}
rsort($trafficConfigs, SORT_NATURAL);
return array_filter($trafficConfigs, function($item){return $item != "";});
}
/**
* @param CiConfigItem $configItem
* @param bool|string $package
* @param array $nodes
* @param array $nodeBases
* @param array $nodeTraffics
* @return bool
*/
private function buildConfigTwo(CiConfigItem $configItem, $package, array $nodes, array $nodeBases, array $nodeTraffics)
{
$params = [];
if(false === $package) {
// stream doesn't exist, have to be created as new. enforces test to be nightly
$params[] = "MODE=nightly";
$params[] = "NEW_STREAM=" . $configItem->getStream();
} else {
$params[] = "MODE=" . ($configItem->isNightly() ? 'nightly' : 'manual');
$params[] = "STREAM=" . $configItem->getStream();
$params[] = "PACKAGE_NAME=" . $configItem->getStream();
}
$params[] = "RECIPIENTS=PDLSPCORED@pdl.internal.ericsson.com";
$params[] = "JCAT_DIR__MAIN=" . $configItem->getJcat();
$params[] = "JCAT_DIR__UPGRADE=" . $configItem->getJcat();
foreach($nodes as $node) {
if(false === strpos($node, "-")) {
$params[] = sprintf("%s_BASE_BACKUP=%s_%s", $node, $node, $nodeBases[$node]);
} else {
list($zone1, $zone2) = explode("-", $node);
$params[] = sprintf("%s_BASE_BACKUP=%s_%s",$zone1, $zone1, $nodeBases[$zone1]);
$params[] = sprintf("%s_BASE_BACKUP=%s_%s",$zone2, $zone2, $nodeBases[$zone2]);
}
// todo create suites parameters
foreach ($this->testTypeMap as $callable => $paramName) {
if($configItem->$callable()) {
$params[] = sprintf("%s_SUITES=%s", $node, $paramName);
}
}
$params[] = sprintf("%s_TRAFFIC=%s", $node, $nodeTraffics[$node]);
}
$this->httpClient->setUri($this->jenkinsBaseUrl . self::CONFIG_2_PATH . '/buildWithParameters?delay=0sec&' . implode("&", $params));
$this->httpClient->resetParameters(false, false);
$request = $this->httpClient->getRequest();
$request->setMethod('POST');
$response = $this->httpClient->send();
sleep(2);
return $response->isSuccess();
}
/**
* @param string $namespace
* @return Filesystem
*/
private function getCache(string $namespace): Filesystem
{
$cache = new Filesystem();
$cache->getOptions()
->setCacheDir("data/cache")
->setTtl(3600)
->setNamespace($namespace)
;
return $cache;
}
/**
* Returns uri of jcat deploy or boolean FALSE, if it can't find/guess it.
*
* @param string $jcatVersion The jcat version in the format of TSP-JCATxxx.y[type]
* @return bool|string
*/
private function getJcatDeployUrl(string $jcatVersion)
{
preg_match('#TSP_JCAT-([0-9]{3,}\.[0-9]+)(.*)?#', $jcatVersion, $matches);
$version = $matches[1];
$deployType = trim($matches[2],"_");
unset($matches);
// package has no type specified, or type is COMMON
if($deployType == "" || $deployType == "COMMON") {
$uri = self::JCAT_DEPLOY_SOURCE_URL . sprintf("/JCAT-%s/TSP_JCAT-%s.tar.gz", $version, $version);
$this->httpClient->setUri($uri);
$this->httpClient->resetParameters(false, false);
$request = $this->httpClient->getRequest();
$request->setMethod('HEAD');
$response = $this->httpClient->send();
if($response->isOk()) {
return $uri;
}
return false;
}
// package type specified exists on jcat server
$uri = self::JCAT_DEPLOY_SOURCE_URL . sprintf("/JCAT-%s-%s/TSP_JCAT-%s-%s.tar.gz", $deployType, $version, $deployType, $version);
$this->httpClient->setUri($uri);
$this->httpClient->resetParameters(false, false);
$request = $this->httpClient->getRequest();
$request->setMethod('HEAD');
$response = $this->httpClient->send();
if($response->isOk()) {
return $uri;
}
// package type specified exists with 'TSP' prefix on jcat server
$uri = self::JCAT_DEPLOY_SOURCE_URL . sprintf("/JCAT-TSP%s-%s/TSP_JCAT-TSP%s-%s.tar.gz", $deployType, $version, $deployType, $version);
$this->httpClient->setUri($uri);
$this->httpClient->resetParameters(false, false);
$request = $this->httpClient->getRequest();
$request->setMethod('HEAD');
$response = $this->httpClient->send();
if($response->isOk()) {
return $uri;
}
return false;
}
private function addJcatPackageToJenkins($jcatVersion)
{
$availableJcatTargetNodes = $this->getAvailableJcatTargetNodes();
$jcatDeployUri = $this->getJcatDeployUrl($jcatVersion);
$appendParams = [
"NEW_DEPLOY_NAME=$jcatVersion",
"NEW_DEPLOY_URL=$jcatDeployUri",
"ACTION=add",
];
foreach($availableJcatTargetNodes as $node) {
$appendParams[] = "NODES=$node";
}
$this->httpClient->setUri($this->jenkinsBaseUrl . self::ADD_JCAT_PATH . '/buildWithParameters?delay=0sec&' . implode("&", $appendParams));
$this->httpClient->resetParameters(false, false);
$request = $this->httpClient->getRequest();
$request->setMethod('POST');
$response = $this->httpClient->send();
return $response->isSuccess();
}
/**
* Returns last started jcat deploy creator jobId
*
* @return int
*/
private function getJcatDeployJobId(): ?int
{
return $this->getJobId($this->jenkinsBaseUrl . self::ADD_JCAT_PATH);
}
/**
* @todo check if build result is instant or 'inQueue' reflects build state at all
* @param int $jobId
* @return bool
* @throws \Exception
*/
private function isJcatPackageDeployed(int $jobId): bool
{
$this->httpClient->setUri($this->jenkinsBaseUrl . self::ADD_JCAT_PATH . "/${jobId}/api/json");
$this->httpClient->resetParameters(false, false);
$request = $this->httpClient->getRequest();
$request->setMethod('GET');
$response = $this->httpClient->send();
$body = $response->getBody();
$parsedResponse = Json::decode($body);
if($parsedResponse->inQueue == false) {
if ($parsedResponse->lastBuild->number == $parsedResponse->lastSuccessfulBuild->number) {
return true;
}
throw new \Exception("Build failed.");
}
return false;
}
private function getAvailableJcatTargetNodes()
{
$this->httpClient->setUri($this->jenkinsBaseUrl . self::ADD_JCAT_PATH . "/build");
$this->httpClient->resetParameters(false, false);
$response = $this->httpClient->send();
$jcatHandlerPageBody = $response->getBody();
$cssToXpathConverter = new CssSelectorConverter();
$xpathQuery = $cssToXpathConverter->toXPath('input[value=NODES]');
$domDocument = new \DOMDocument();
$domDocument->loadHTML($jcatHandlerPageBody);
$xpath = new \DOMXPath($domDocument);
$element = $xpath->query($xpathQuery);
if($element->length == 0) {
throw new \Exception("Can't find NODES");
}
if($element[0]->nextSibling->nodeName != "select") {
throw new \Exception("Can't find NODES dropdown");
}
$trafficConfigs = [];
foreach ($element[0]->nextSibling->childNodes as $option) {
$trafficConfigs[] = $option->textContent;
}
sort($trafficConfigs, SORT_NATURAL);
return $trafficConfigs;
}
private function getActiveJenkinsUrl(): string
{
$this->httpClient->setUri(self::JENKINS_REDIRECT_URL);
$this->httpClient->resetParameters(false, false);
$this->httpClient->setOptions(['maxredirects' => 0]);
$request = $this->httpClient->getRequest();
$request->setMethod('HEAD');
$response = $this->httpClient->send();
if($response->isRedirect()) {
$this->httpClient->setOptions(['maxredirects' => 5]);
return rtrim($response->getHeaders()->get("location")->getFieldValue(), "/");
}
throw new \Exception("Jenkins redirect url is not working, return code was: " . $response->getStatusCode());
}
public function isNodeOnline(string $node): bool
{
$this->httpClient->setUri($this->jenkinsBaseUrl . self::NODE_STATUS_PATH . "/${node}/api/json");
$this->httpClient->resetParameters(false, false);
$request = $this->httpClient->getRequest();
$request->setMethod('GET');
$response = $this->httpClient->send();
$body = $response->getBody();
$parsedResponse = Json::decode($body);
return !$parsedResponse->offline;
}
public function isNodeIdle(string $node): bool
{
$this->httpClient->setUri($this->jenkinsBaseUrl . self::NODE_STATUS_PATH . "/${node}/api/json");
$this->httpClient->resetParameters(false, false);
$request = $this->httpClient->getRequest();
$request->setMethod('GET');
$response = $this->httpClient->send();
$body = $response->getBody();
$parsedResponse = Json::decode($body);
return $parsedResponse->idle;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Service;
use Interop\Container\ContainerInterface;
use Zend\Http\Client;
class CiExecutorServiceFactory
{
public function __invoke(ContainerInterface $container)
{
$nodeService = $container->get(CiNodeService::class);
$entityManager = $container->get('doctrine.entity_manager.orm_default');
$config = $container->get('config');
$httpClient = new Client();
$httpClient->setAuth($config['ci.auth']['user'], $config['ci.auth']['pass']);
return new CiExecutorService($entityManager, $httpClient, $nodeService);
}
}

View File

@@ -0,0 +1,155 @@
<?php
namespace App\Service;
class CiNodeService
{
private $nodes = [
[
"name" => 'esx4b', "type" => 'esx', "size" => 1, "team" => "detoqs",
"bases" => ["7101_base_Jenkins_CI_20161006"], "defaultBase" => "7101_base_Jenkins_CI_20161006", "canAutoStart" => true,
],
[
"name" => 'esx10a', "type" => 'esx', "size" => 1, "team" => "detoqs",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'esx10b', "type" => 'esx', "size" => 1, "team" => "detoqs",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'asumi', "type" => 'esx', "size" => 1, "team" => "detoqs",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'michiyo', "type" => 'esx', "size" => 1, "team" => "detoqs",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'sakura', "type" => 'esx', "size" => 1, "team" => "detoqs",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'takumi', "type" => 'esx', "size" => 1, "team" => "detoqs",
"bases" => ["7000_base_Jenkins_CI_20160701"], "defaultBase" => "7000_base_Jenkins_CI_20160701", "canAutoStart" => true,
],
[
"name" => 'cwsled', "type" => 'maia', "size" => 1, "team" => "taurus",
"bases" => [], "defaultBase" => "", "canAutoStart" => false,
],
[
"name" => 'g8z1', "type" => 'nsp5', "size" => 1, "team" => "detoqs",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'to22', "type" => 'nsp5', "size" => 1, "team" => "detoqs",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'cwis8', "type" => 'nsp6', "size" => 3, "team" => "vikinx",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'cwis9', "type" => 'nsp6', "size" => 3, "team" => "oorio",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'ivis1', "type" => 'nsp6', "size" => 1, "team" => "vikinx",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'ivis2', "type" => 'nsp6', "size" => 1, "team" => "oorio",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'nmis1', "type" => 'nsp6', "size" => 2, "team" => "unknown",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'tina', "type" => 'nsp6', "size" => 1, "team" => "oorio",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'alla', "type" => 'nsp6.1', "size" => 3, "team" => "unknown",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'katyusa', "type" => 'nsp6.1', "size" => 2, "team" => "unknown",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'elena', "type" => 'nsp6.1', "size" => 2, "team" => "oorio",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'natasha', "type" => 'nsp6.1', "size" => 2, "team" => "trex",
"bases" => ["7101_base_Jenkins_CI_20161217"], "defaultBase" => "7101_base_Jenkins_CI_20161217", "canAutoStart" => true,
],
[
"name" => 'mazsola-tade', "type" => 'nsp6.1', "size" => 1, "team" => "vikinx",
"bases" => ["7111_base_Jenkins_CI_20170130"], "defaultBase" => "7111_base_Jenkins_CI_20170130", "canAutoStart" => true,
],
[
"name" => 'pikkolo', "type" => 'nsp6.1', "size" => 1, "team" => "taurus",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'sonia', "type" => 'nsp6.1', "size" => 1, "team" => "trex",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'tatiana', "type" => 'nsp6.1', "size" => 2, "team" => "taurus",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'opossum', "type" => 'gep5', "size" => 1, "team" => "taurus",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'petymeg', "type" => 'gep5', "size" => 2, "team" => "vikinx",
"bases" => [], "defaultBase" => "", "canAutoStart" => true,
],
[
"name" => 'gicnode4', "type" => 'gep5', "size" => 2, "team" => "oorio",
"bases" => [], "defaultBase" => "", "canAutoStart" => false,
],
[
"name" => 'gicnode10', "type" => 'gep5', "size" => 1, "team" => "trex",
"bases" => [], "defaultBase" => "", "canAutoStart" => false,
],
[
"name" => 'gicnode19', "type" => 'gep5', "size" => 2, "team" => "trex",
"bases" => [], "defaultBase" => "", "canAutoStart" => false,
],
];
public function getNodes(): array
{
return $this->nodes;
}
public function getNode($node): ?array
{
return array_pop(array_filter($this->nodes, function($item) use ($node) {
return $item['name'] == $node;
}));
}
public function getTestNodeList(array $nodes): ?array
{
$filteredNodes = array_filter($this->nodes, function($item) use ($nodes) {
return in_array($item['name'], $nodes);
});
$result = [];
foreach ($filteredNodes as $filteredNode) {
$type = strtoupper($filteredNode['type']);
if(!isset($result[$type])) {
$result[$type] = [];
}
$result[$type][$filteredNode['name']] = $filteredNode['defaultBase'];
}
return $result;
}
}