/** * Associates cost analytics project with the farm * * It does not perform any actions if cost analytics is disabled * * @param ProjectEntity|string $project The project entity or its identifier * @return string Returns identifier of the associated project * @throws InvalidArgumentException * @throws AnalyticsException */ public function setProject($project) { if (Scalr::getContainer()->analytics->enabled) { if ($project instanceof ProjectEntity) { $projectId = $project->projectId; } else { $projectId = $project; unset($project); } $analytics = Scalr::getContainer()->analytics; if ($projectId === null) { $ccId = $this->GetEnvironmentObject()->getPlatformConfigValue(Scalr_Environment::SETTING_CC_ID); if (!empty($ccId)) { //Assigns Project automatically only if it is the one withing the Cost Center $projects = ProjectEntity::findByCcId($ccId); if (count($projects) == 1) { $project = $projects->getArrayCopy()[0]; $projectId = $project->projectId; } } } elseif (!empty($projectId)) { //Validates specified project's identifier if (!preg_match('/^[[:xdigit:]-]{36}$/', $projectId)) { throw new InvalidArgumentException(sprintf("Identifier of the cost analytics Project must have valid UUID format. '%s' given.", strip_tags($projectId))); } $project = isset($project) ? $project : $analytics->projects->get($projectId); if (!$project) { throw new AnalyticsException(sprintf("Could not find Project with specified identifier %s.", strip_tags($projectId))); } else { if ($project->ccId !== $this->GetEnvironmentObject()->getPlatformConfigValue(Scalr_Environment::SETTING_CC_ID)) { throw new AnalyticsException(sprintf("Invalid project identifier. Parent Cost center of the Project should correspond to the Environment's cost center.")); } } } else { $projectId = null; } //Sets project to the farm object only if it has been provided if (isset($projectId)) { $project = isset($project) ? $project : $analytics->projects->get($projectId); $oldProjectId = $this->GetSetting(Entity\FarmSetting::PROJECT_ID); $this->SetSetting(Entity\FarmSetting::PROJECT_ID, $project->projectId); //Server property SERVER_PROPERTIES::FARM_PROJECT_ID should be updated //for all running servers associated with the farm. $this->DB->Execute("\n INSERT `server_properties` (`server_id`, `name`, `value`)\n SELECT s.`server_id`, ? AS `name`, ? AS `value`\n FROM `servers` s\n WHERE s.`farm_id` = ?\n ON DUPLICATE KEY UPDATE `value` = ?\n ", [SERVER_PROPERTIES::FARM_PROJECT_ID, $project->projectId, $this->ID, $project->projectId]); //Cost centre should correspond to Project's CC $this->DB->Execute("\n INSERT `server_properties` (`server_id`, `name`, `value`)\n SELECT s.`server_id`, ? AS `name`, ? AS `value`\n FROM `servers` s\n WHERE s.`farm_id` = ?\n ON DUPLICATE KEY UPDATE `value` = ?\n ", [SERVER_PROPERTIES::ENV_CC_ID, $project->ccId, $this->ID, $project->ccId]); if (empty($oldProjectId)) { $analytics->events->fireAssignProjectEvent($this, $project->projectId); } elseif ($oldProjectId !== $projectId) { $analytics->events->fireReplaceProjectEvent($this, $project->projectId, $oldProjectId); } } } return $projectId; }
/** * Gets the list of the cost centres * * @return array Returns the list of the cost centres */ private function getNodesList($period, $ccId = null, $query = null) { $nodes = []; $projectItems = $this->getContainer()->analytics->projects->findByKey($query, ['accountId' => $this->user->getAccountId()], true); foreach ($projectItems as $item) { /* @var $item ProjectEntity */ if (!isset($nodes[$item->ccId])) { $nodes[$item->ccId] = $this->getCostCenterData($this->getContainer()->analytics->ccs->get($item->ccId), $period); $nodes[$item->ccId]['nodes'] = []; } $nodes[$item->ccId]['nodes'][] = $this->getProjectData($item, $period); } $ccsItems = $this->getContainer()->analytics->ccs->findByKey($query, ['accountId' => $this->user->getAccountId()], true); foreach ($ccsItems as $item) { /* @var $item CostCentreEntity */ if (!isset($nodes[$item->ccId])) { $nodes[$item->ccId] = $this->getCostCenterData($item, $period); $nodes[$item->ccId]['nodes'] = []; foreach (ProjectEntity::findByCcId($item->ccId) as $projectItem) { /* @var $projectItem ProjectEntity */ if ($projectItem->shared != ProjectEntity::SHARED_WITHIN_ACCOUNT || $projectItem->accountId != $this->user->getAccountId()) { continue; } $nodes[$item->ccId]['nodes'][] = $this->getProjectData($projectItem, $period); } } } return array_values($nodes); }
/** * Gets relation dependent budget * * Another words it will return total budgeted amount for all projects * which have relations to the cost center. * * If we use this method for the project it will return 0 * * @return float Returns relation dependent budget amount according to current state in the database */ public function getRelationDependentBudget() { if (empty($this->subjectId)) { throw new InvalidArgumentException(sprintf("Identifier of the subject has not been provided for the %s", get_class($this))); } //Projects have no descendants if ($this->subjectType == self::SUBJECT_TYPE_PROJECT) { return 0; } $params = [$this->year, self::SUBJECT_TYPE_PROJECT]; $stmt = ''; foreach (ProjectEntity::findByCcId($this->subjectId) as $project) { $stmt .= ", " . $this->qstr('subjectId', $project->projectId); } if ($stmt != '') { $ret = $this->db()->GetOne("\n SELECT SUM(q.`budget`) AS `budget`\n FROM " . $this->table('q') . "\n WHERE q.`year` = ?\n AND q.`subject_type` = ?\n " . (is_numeric($this->quarter) ? " AND q.`quarter` =" . intval($this->quarter) : "") . "\n AND q.`subject_id` IN (" . ltrim($stmt, ',') . ")\n ", $params); } else { $ret = 0.0; } return $ret; }
/** * Gets the list of the cost centres * * @return array Returns the list of the cost centres */ private function getNodesList($period, $ccId = null, $query = null) { $nodes = array(); if (!$ccId) { $criteria = null; if ($query) { $criteria = array('name' => array('$like' => array('%' . $query . '%'))); foreach (ProjectEntity::find($criteria) as $item) { /* @var $item ProjectEntity */ if (!isset($nodes[$item->ccId])) { $nodes[$item->ccId] = $this->getCostCenterData($this->getContainer()->analytics->ccs->get($item->ccId), $period); $nodes[$item->ccId]['nodes'] = array(); } $nodes[$item->ccId]['nodes'][] = $this->getProjectData($item, $period); } foreach (CostCentreEntity::find($criteria) as $item) { /* @var $item CostCentreEntity */ if (!isset($nodes[$item->ccId])) { $nodes[$item->ccId] = $this->getCostCenterData($item, $period); $nodes[$item->ccId]['nodes'] = array(); } $projectItems = ProjectEntity::findByCcId($item->ccId); foreach ($projectItems as $projectItem) { $nodes[$item->ccId]['nodes'][] = $this->getProjectData($projectItem, $period); } } } else { foreach (CostCentreEntity::all() as $item) { /* @var $item CostCentreEntity */ $nodes[$item->ccId] = $this->getCostCenterData($item, $period); } } } else { foreach ($this->getContainer()->analytics->ccs->get($ccId)->getProjects() as $item) { $nodes[$item->projectId] = $this->getProjectData($item, $period); } } return array_values($nodes); }
/** * Gets analytics data for the cost center * * @param string $ccId The identifier of the cost center (UUID) * @param string $mode Mode (week, month, quarter, year, custom) * @param string $startDate Start date in UTC (Y-m-d) * @param string $endDate End date in UTC (Y-m-d) * @return array Returns analytics data for the specified cost center */ public function getCostCenterPeriodData($ccId, $mode, $startDate, $endDate) { $analytics = $this->getContainer()->analytics; $utcTz = new DateTimeZone('UTC'); $iterator = ChartPeriodIterator::create($mode, new DateTime($startDate, $utcTz), new DateTime($endDate, $utcTz), 'UTC'); $timelineEvents = $analytics->events->count($iterator->getInterval(), $iterator->getStart(), $iterator->getEnd(), ['ccId' => $ccId]); //Interval which is used in the database query for grouping $queryInterval = preg_replace('/^1 /', '', $iterator->getInterval()); //Current period data $collectionSet = (new AggregationCollectionSet(['byPlatformDetailed' => new AggregationCollection(['period', 'platform', 'projectId'], ['cost' => 'sum']), 'byProjectDetailed' => new AggregationCollection(['period', 'projectId', 'platform'], ['cost' => 'sum']), 'byPlatform' => new AggregationCollection(['platform', 'projectId'], ['cost' => 'sum']), 'byProject' => new AggregationCollection(['projectId', 'platform'], ['cost' => 'sum'])]))->load($this->get(['ccId' => $ccId], $iterator->getStart(), $iterator->getEnd(), [$queryInterval, TagEntity::TAG_ID_PLATFORM, TagEntity::TAG_ID_PROJECT], true))->calculatePercentage(); //Previous period data $collectionSetPrev = (new AggregationCollectionSet(['byPlatformDetailed' => new AggregationCollection(['period', 'platform', 'projectId'], ['cost' => 'sum']), 'byProjectDetailed' => new AggregationCollection(['period', 'projectId', 'platform'], ['cost' => 'sum']), 'byPlatform' => new AggregationCollection(['platform', 'projectId'], ['cost' => 'sum']), 'byProject' => new AggregationCollection(['projectId', 'platform'], ['cost' => 'sum'])]))->load($this->get(['ccId' => $ccId], $iterator->getPreviousStart(), $iterator->getPreviousEnd(), [$queryInterval, TagEntity::TAG_ID_PLATFORM, TagEntity::TAG_ID_PROJECT], true))->calculatePercentage(); $quarterIterator = $this->getCurrentQuarterIterator(); $queryQuarterInterval = preg_replace('/^1 /', '', $quarterIterator->getInterval()); $rawQuarterUsage = $this->get(['ccId' => $ccId], $quarterIterator->getStart(), $quarterIterator->getEnd(), [TagEntity::TAG_ID_PLATFORM, TagEntity::TAG_ID_PROJECT], true); $itemsRollingAvg = $this->getRollingAvg(['ccId' => $ccId], $queryQuarterInterval, $quarterIterator->getEnd(), null, $rawQuarterUsage, ['projectId' => 'projects', 'platform' => 'clouds']); //Gets the list of the projects which is assigned to cost center at current moment to optimize //retrieving its names with one query $projects = new ArrayCollection(); foreach (ProjectEntity::findByCcId($ccId) as $i) { $projects[$i->projectId] = $i; } //Function retrieves the name of the project by specified identifier $fnGetProjectName = function ($projectId) use($projects) { if (empty($projectId)) { $projectName = 'Unassigned resources'; } else { if (!isset($projects[$projectId])) { //Trying to find the name of the project in the tag values history if (null === ($pe = AccountTagEntity::findOne([['tagId' => TagEntity::TAG_ID_PROJECT], ['valueId' => $projectId]]))) { $projectName = $projectId; } else { $projectName = $pe->valueName; unset($pe); } } else { $projectName = $projects[$projectId]->name; } } return $projectName; }; $cloudsData = []; $projectsData = []; $timeline = []; $prevPointKey = null; foreach ($iterator as $chartPoint) { /* @var $chartPoint \Scalr\Stats\CostAnalytics\ChartPointInfo */ $i = $chartPoint->i; $timeline[] = array('datetime' => $chartPoint->dt->format('Y-m-d H:00'), 'label' => $chartPoint->label, 'onchart' => $chartPoint->show, 'cost' => round(isset($collectionSet['byPlatformDetailed']['data'][$chartPoint->key]['cost']) ? $collectionSet['byPlatformDetailed']['data'][$chartPoint->key]['cost'] : 0, 2), 'events' => isset($timelineEvents[$chartPoint->key]) ? $timelineEvents[$chartPoint->key] : null); //Period - Platform - Projects subtotals if (!isset($collectionSet['byPlatformDetailed']['data'][$chartPoint->key]['data'])) { foreach ($cloudsData as $platform => $v) { if (!$iterator->isFuture()) { //Previous period details if (isset($collectionSetPrev['byPlatformDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$platform])) { $pp = $collectionSetPrev['byPlatformDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$platform]; } else { $pp = null; } //Previous point details if (isset($collectionSet['byPlatformDetailed']['data'][$prevPointKey]['data'][$platform])) { $ppt = $collectionSet['byPlatformDetailed']['data'][$prevPointKey]['data'][$platform]; } else { $ppt = null; } $r = $this->getPointDataArray(null, $pp, $ppt); //Projects data is empty $r['projects'] = []; $cloudsData[$platform]['data'][] = $r; } else { $cloudsData[$platform]['data'][] = null; } } } else { //Initializes with empty values to prevent data shifts on charts. if (!isset($collectionSet['byPlatformDetailed']['data'][$chartPoint->key]['data'])) { $collectionSet['byPlatformDetailed']['data'][$chartPoint->key]['data'] = []; } $combined =& $collectionSet['byPlatformDetailed']['data'][$chartPoint->key]['data']; if (!empty($cloudsData)) { foreach ($cloudsData as $platform => $t) { if (!array_key_exists($platform, $combined)) { $combined[$platform] = []; } } } foreach ($combined as $platform => $v) { //Previous period details if (isset($collectionSetPrev['byPlatformDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$platform])) { $pp = $collectionSetPrev['byPlatformDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$platform]; } else { $pp = null; } //Previous point details if (isset($collectionSet['byPlatformDetailed']['data'][$prevPointKey]['data'][$platform])) { $ppt = $collectionSet['byPlatformDetailed']['data'][$prevPointKey]['data'][$platform]; } else { $ppt = null; } if (!isset($cloudsData[$platform]) && $i > 0) { $cloudsData[$platform]['name'] = $platform; //initializes platfrorm legend for the not filled period $cloudsData[$platform]['data'] = array_fill(0, $i, null); } if (!$iterator->isFuture()) { $r = $this->getPointDataArray($v, $pp, $ppt); // projects data $cloudProjectData = []; if (!empty($v['data'])) { foreach ($v['data'] as $projectId => $pv) { if (isset($pp['data'][$projectId])) { $ppp = $pp['data'][$projectId]; } else { $ppp = null; } if (isset($ppt['data'][$projectId])) { $pppt = $ppt['data'][$projectId]; } else { $pppt = null; } $cloudProjectData[] = $this->getDetailedPointDataArray($projectId, $fnGetProjectName($projectId), $pv, $ppp, $pppt); } } $r['projects'] = $cloudProjectData; $cloudsData[$platform]['name'] = $platform; $cloudsData[$platform]['data'][] = $r; } else { $cloudsData[$platform]['data'][] = null; } } } //Period - Project - Platform subtotal if (!isset($collectionSet['byProjectDetailed']['data'][$chartPoint->key]['data'])) { foreach ($projectsData as $projectId => $v) { if (!$iterator->isFuture()) { //Previous period details if (isset($collectionSetPrev['byProjectDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$projectId])) { $pp = $collectionSetPrev['byProjectDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$projectId]; } else { $pp = null; } //Previous point details if (isset($collectionSet['byProjectDetailed']['data'][$prevPointKey]['data'][$projectId])) { $ppt = $collectionSet['byProjectDetailed']['data'][$prevPointKey]['data'][$projectId]; } else { $ppt = null; } $r = $this->getPointDataArray(null, $pp, $ppt); //Projects data is empty $r['clouds'] = []; $projectsData[$projectId]['data'][] = $r; } else { $projectsData[$projectId]['data'][] = null; } } } else { //Initializes with empty values to prevent data shifts on charts. if (!isset($collectionSet['byProjectDetailed']['data'][$chartPoint->key]['data'])) { $collectionSet['byProjectDetailed']['data'][$chartPoint->key]['data'] = []; } $combined =& $collectionSet['byProjectDetailed']['data'][$chartPoint->key]['data']; if (!empty($projectsData)) { foreach ($projectsData as $projectId => $t) { if (!array_key_exists($projectId, $combined)) { $combined[$projectId] = []; } } } foreach ($combined as $projectId => $v) { //Previous period details if (isset($collectionSetPrev['byProjectDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$projectId])) { $pp = $collectionSetPrev['byProjectDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$projectId]; } else { $pp = null; } //Previous point details if (isset($collectionSet['byProjectDetailed']['data'][$prevPointKey]['data'][$projectId])) { $ppt = $collectionSet['byProjectDetailed']['data'][$prevPointKey]['data'][$projectId]; } else { $ppt = null; } if (!isset($projectsData[$projectId]) && $i > 0) { $projectsData[$projectId]['name'] = $fnGetProjectName($projectId); //initializes project legend for the not filled period $projectsData[$projectId]['data'] = array_fill(0, $i, null); } if (!$iterator->isFuture()) { $r = $this->getPointDataArray($v, $pp, $ppt); // platform data $cloudPlatformData = []; if (!empty($v['data'])) { foreach ($v['data'] as $platform => $pv) { if (isset($pp['data'][$platform])) { $ppp = $pp['data'][$platform]; } else { $ppp = null; } if (isset($ppt['data'][$platform])) { $pppt = $ppt['data'][$platform]; } else { $pppt = null; } $cloudPlatformData[] = $this->getDetailedPointDataArray($platform, $platform, $pv, $ppp, $pppt); } } $r['clouds'] = $cloudPlatformData; $projectsData[$projectId]['name'] = $fnGetProjectName($projectId); $projectsData[$projectId]['data'][] = $r; } else { $projectsData[$projectId]['data'][] = null; } } } $prevPointKey = $chartPoint->key; } //complete arrays for cloud data and project data $cntpoints = count($timeline); foreach ($cloudsData as $platform => $v) { if (($j = count($v['data'])) < $cntpoints) { while ($j < $cntpoints) { $cloudsData[$platform]['data'][] = null; $j++; } } } foreach ($projectsData as $projectId => $v) { if (($j = count($v['data'])) < $cntpoints) { while ($j < $cntpoints) { $projectsData[$projectId]['data'][] = null; $j++; } } } //Spending trends uses daily usage precalculated data $trends = $this->calculateSpendingTrends(['ccId' => $ccId], $timeline, $queryInterval, $iterator->getEnd()); if ($iterator->getWholePreviousPeriodEnd() != $iterator->getPreviousEnd()) { $rawPrevUsageWhole = $this->get(['ccId' => $ccId], $iterator->getPreviousStart(), $iterator->getWholePreviousPeriodEnd(), [TagEntity::TAG_ID_PLATFORM, TagEntity::TAG_ID_PROJECT], true); //Previous whole period usage subtotals by platform $prevUsageWhole = (new AggregationCollection(['platform'], ['cost' => 'sum']))->load($rawPrevUsageWhole); //Previous whole period usage subtotals by project $prevUsageWhole2 = (new AggregationCollection(['projectId'], ['cost' => 'sum']))->load($rawPrevUsageWhole); } else { $prevUsageWhole = $collectionSetPrev['byPlatform']; $prevUsageWhole2 = $collectionSetPrev['byProject']; } //Build cloud platforms total $cloudsTotal = []; $it = $collectionSet['byPlatform']->getIterator(); foreach ($it as $platform => $p) { $pp = isset($collectionSetPrev['byPlatform']['data'][$platform]) ? $collectionSetPrev['byPlatform']['data'][$platform] : null; $pw = isset($prevUsageWhole['data'][$platform]) ? $prevUsageWhole['data'][$platform] : null; $cl = $this->getTotalDataArray($platform, $platform, $p, $pp, $pw, $cloudsData, $iterator); if ($it->hasChildren()) { $clProjects = []; foreach ($it->getChildren() as $projectId => $c) { $cp = isset($collectionSetPrev['byPlatform']['data'][$platform]['data'][$projectId]) ? $collectionSetPrev['byPlatform']['data'][$platform]['data'][$projectId] : null; $clProjects[] = $this->getTotalDataArray($projectId, $fnGetProjectName($projectId), $c, $cp, null, $projectsData, $iterator, true); } $cl['projects'] = $clProjects; } else { $cl['projects'] = []; } $cloudsTotal[] = $cl; } //Build projects total $projectsTotal = []; $it = $collectionSet['byProject']->getIterator(); //For each assigned project wich is not archived we should display //zero dollar spend even if there are not any spend for //the selected period. $projectsWithoutSpend = []; foreach ($projects as $projectEntity) { /* @var $projectEntity \Scalr\Stats\CostAnalytics\Entity\ProjectEntity */ if ($projectEntity->archived) { continue; } if (!isset($collectionSet['byProject']['data'][$projectEntity->projectId])) { $projectsWithoutSpend[$projectEntity->projectId] = ['cost' => 0, 'cost_percentage' => 0, 'id' => $projectEntity->projectId]; } } //Passing projects with spend and then assigned projects without spend for the selected period foreach ([$it, $projectsWithoutSpend] as $internalIterator) { foreach ($internalIterator as $projectId => $p) { $pp = isset($collectionSetPrev['byProject']['data'][$projectId]) ? $collectionSetPrev['byProject']['data'][$projectId] : null; $pw = isset($prevUsageWhole2['data'][$projectId]) ? $prevUsageWhole2['data'][$projectId] : null; $cl = $this->getTotalDataArray($projectId, $fnGetProjectName($projectId), $p, $pp, $pw, $projectsData, $iterator); if ($internalIterator instanceof ArrayIterator && $internalIterator->hasChildren()) { $clPlatforms = []; foreach ($internalIterator->getChildren() as $platform => $c) { $cp = isset($collectionSetPrev['byProject']['data'][$projectId]['data'][$platform]) ? $collectionSetPrev['byProject']['data'][$projectId]['data'][$platform] : null; $clPlatforms[] = $this->getTotalDataArray($platform, $platform, $c, $cp, null, $cloudsData, $iterator, true); } $cl['clouds'] = $clPlatforms; } else { $cl['clouds'] = []; } $projectsTotal[] = $cl; } } $data = ['reportVersion' => '0.1.0', 'totals' => ['cost' => round($collectionSet['byPlatform']['cost'], 2), 'prevCost' => round($collectionSetPrev['byPlatform']['cost'], 2), 'growth' => round($collectionSet['byPlatform']['cost'] - $collectionSetPrev['byPlatform']['cost'], 2), 'growthPct' => $collectionSetPrev['byPlatform']['cost'] == 0 ? null : round(abs(($collectionSet['byPlatform']['cost'] - $collectionSetPrev['byPlatform']['cost']) / $collectionSetPrev['byPlatform']['cost'] * 100), 0), 'clouds' => $cloudsTotal, 'projects' => $projectsTotal, 'trends' => $trends, 'forecastCost' => null], 'timeline' => $timeline, 'clouds' => $cloudsData, 'projects' => $projectsData, 'interval' => $queryInterval, 'startDate' => $iterator->getStart()->format('Y-m-d'), 'endDate' => $iterator->getEnd()->format('Y-m-d'), 'previousStartDate' => $iterator->getPreviousStart()->format('Y-m-d'), 'previousEndDate' => $iterator->getPreviousEnd()->format('Y-m-d')]; if ($iterator->getTodayDate() < $iterator->getEnd()) { $data['totals']['forecastCost'] = self::calculateForecast($data['totals']['cost'], $iterator->getStart(), $iterator->getEnd(), $prevUsageWhole['cost'], ($data['totals']['growth'] >= 0 ? 1 : -1) * $data['totals']['growthPct'], isset($itemsRollingAvg['rollingAverageDaily']) ? $itemsRollingAvg['rollingAverageDaily'] : null); } $budgetRequest = ['ccId' => $ccId, 'usage' => $data['totals']['cost']]; if ($mode != 'custom') { //We need to get budget for the appropriate quarter $budgetRequest['period'] = $iterator->getQuarterPeriod(); } $budget = $this->getBudgetUsedPercentage($budgetRequest); $this->calculateBudgetEstimateOverspend($budget); $data['totals']['budget'] = $budget; return $data; }
/** * Gets all projects associated with the cost centre * * @return \ArrayObject Returns collection of the ProjectEntity objects */ public function getProjects() { return ProjectEntity::findByCcId($this->ccId); }
public function xBuildAction() { $this->request->defineParams(array('farmId' => array('type' => 'int'), 'roles' => array('type' => 'json'), 'rolesToRemove' => array('type' => 'json'), 'farm' => array('type' => 'json'), 'launch' => array('type' => 'bool'))); if (!$this->isFarmConfigurationValid($this->getParam('farmId'), $this->getParam('farm'), (array) $this->getParam('roles'))) { if ($this->errors['error_count'] != 0) { $this->response->failure(); $this->response->data(array('errors' => $this->errors)); return; } } $farm = $this->getParam('farm'); $client = Client::Load($this->user->getAccountId()); if ($this->getParam('farmId')) { $dbFarm = DBFarm::LoadByID($this->getParam('farmId')); $this->user->getPermissions()->validate($dbFarm); $this->request->checkPermissions($dbFarm->__getNewFarmObject(), Acl::PERM_FARMS_UPDATE); $dbFarm->isLocked(); if ($this->getParam('changed') && $dbFarm->changedTime && $this->getParam('changed') != $dbFarm->changedTime) { $userName = '******'; $changed = explode(' ', $this->getParam('changed')); $changedTime = intval($changed[1]); try { $user = new Scalr_Account_User(); $user->loadById($dbFarm->changedByUserId); $userName = $user->getEmail(); } catch (Exception $e) { } $this->response->failure(); $this->response->data(array('changedFailure' => sprintf('%s changed this farm at %s', $userName, Scalr_Util_DateTime::convertTz($changedTime)))); return; } else { if ($this->getParam('changed')) { $this->checkFarmConfigurationIntegrity($this->getParam('farmId'), $this->getParam('farm'), (array) $this->getParam('roles'), (array) $this->getParam('rolesToRemove')); } } $dbFarm->changedByUserId = $this->user->getId(); $dbFarm->changedTime = microtime(); if ($this->getContainer()->analytics->enabled) { $projectId = $farm['projectId']; if (empty($projectId)) { $ccId = $dbFarm->GetEnvironmentObject()->getPlatformConfigValue(Scalr_Environment::SETTING_CC_ID); if (!empty($ccId)) { //Assigns Project automatically only if it is the one withing the Cost Center $projects = ProjectEntity::findByCcId($ccId); if (count($projects) == 1) { $projectId = $projects->getArrayCopy()[0]->projectId; } } } if (!empty($projectId) && $dbFarm->GetSetting(Entity\FarmSetting::PROJECT_ID) != $projectId) { $this->request->checkPermissions($dbFarm->__getNewFarmObject(), Acl::PERM_FARMS_PROJECTS); } } $bNew = false; } else { $this->request->restrictAccess(Acl::RESOURCE_OWN_FARMS, Acl::PERM_FARMS_CREATE); $this->user->getAccount()->validateLimit(Scalr_Limits::ACCOUNT_FARMS, 1); $dbFarm = new DBFarm(); $dbFarm->ClientID = $this->user->getAccountId(); $dbFarm->EnvID = $this->getEnvironmentId(); $dbFarm->Status = FARM_STATUS::TERMINATED; $dbFarm->ownerId = $this->user->getId(); $dbFarm->changedByUserId = $this->user->getId(); $dbFarm->changedTime = microtime(); $bNew = true; } if ($this->getParam('farm')) { $dbFarm->Name = $this->request->stripValue($farm['name']); $dbFarm->RolesLaunchOrder = $farm['rolesLaunchOrder']; $dbFarm->Comments = $this->request->stripValue($farm['description']); } if (empty($dbFarm->Name)) { throw new Exception(_("Farm name required")); } $setFarmTeams = false; if ($bNew) { $setFarmTeams = true; } else { if ($dbFarm->ownerId == $this->user->getId() || $this->request->hasPermissions($dbFarm->__getNewFarmObject(), Acl::PERM_FARMS_CHANGE_OWNERSHIP)) { if (is_numeric($farm['owner']) && $farm['owner'] != $dbFarm->ownerId) { $dbFarm->ownerId = $farm['owner']; $f = Entity\Farm::findPk($dbFarm->ID); Entity\FarmSetting::addOwnerHistory($f, User::findPk($farm['owner']), User::findPk($this->user->getId())); $f->save(); } $setFarmTeams = true; } } $dbFarm->save(); if ($setFarmTeams && is_array($farm['teamOwner'])) { /* @var $f Entity\Farm */ $f = Entity\Farm::findPk($dbFarm->ID); $f->setTeams(empty($farm['teamOwner']) ? [] : Entity\Account\Team::find([['name' => ['$in' => $farm['teamOwner']]], ['accountId' => $this->getUser()->accountId]])); $f->save(); } if ($bNew) { $dbFarm->SetSetting(Entity\FarmSetting::CREATED_BY_ID, $this->user->getId()); $dbFarm->SetSetting(Entity\FarmSetting::CREATED_BY_EMAIL, $this->user->getEmail()); } $governance = new Scalr_Governance($this->getEnvironmentId()); if (!$this->getParam('farmId') && $governance->isEnabled(Scalr_Governance::CATEGORY_GENERAL, Scalr_Governance::GENERAL_LEASE)) { $dbFarm->SetSetting(Entity\FarmSetting::LEASE_STATUS, 'Active'); // for created farm } if (isset($farm['variables'])) { $variables = new Scalr_Scripting_GlobalVariables($this->user->getAccountId(), $this->getEnvironmentId(), ScopeInterface::SCOPE_FARM); $variables->setValues(is_array($farm['variables']) ? $farm['variables'] : [], 0, $dbFarm->ID, 0, '', false, true); } if (!$farm['timezone']) { $farm['timezone'] = date_default_timezone_get(); } $dbFarm->SetSetting(Entity\FarmSetting::TIMEZONE, $farm['timezone']); $dbFarm->SetSetting(Entity\FarmSetting::EC2_VPC_ID, isset($farm["vpc_id"]) ? $farm['vpc_id'] : null); $dbFarm->SetSetting(Entity\FarmSetting::EC2_VPC_REGION, isset($farm["vpc_id"]) ? $farm['vpc_region'] : null); $dbFarm->SetSetting(Entity\FarmSetting::SZR_UPD_REPOSITORY, $farm[Entity\FarmSetting::SZR_UPD_REPOSITORY]); $dbFarm->SetSetting(Entity\FarmSetting::SZR_UPD_SCHEDULE, $farm[Entity\FarmSetting::SZR_UPD_SCHEDULE]); if (!$dbFarm->GetSetting(Entity\FarmSetting::CRYPTO_KEY)) { $dbFarm->SetSetting(Entity\FarmSetting::CRYPTO_KEY, Scalr::GenerateRandomKey(40)); } if ($this->getContainer()->analytics->enabled) { //Cost analytics project must be set for the Farm object $dbFarm->setProject(!empty($farm['projectId']) ? $farm['projectId'] : null); } $virtualFarmRoles = array(); $roles = $this->getParam('roles'); if (!empty($roles)) { foreach ($roles as $role) { if (strpos($role['farm_role_id'], "virtual_") !== false) { $dbRole = DBRole::loadById($role['role_id']); $dbFarmRole = $dbFarm->AddRole($dbRole, $role['platform'], $role['cloud_location'], (int) $role['launch_index'], $role['alias']); $virtualFarmRoles[$role['farm_role_id']] = $dbFarmRole->ID; } } } $usedPlatforms = array(); $farmRoleVariables = new Scalr_Scripting_GlobalVariables($this->user->getAccountId(), $this->getEnvironmentId(), ScopeInterface::SCOPE_FARMROLE); if (!empty($roles)) { foreach ($roles as $role) { if ($role['farm_role_id']) { if (isset($virtualFarmRoles[$role['farm_role_id']])) { $role['farm_role_id'] = $virtualFarmRoles[$role['farm_role_id']]; } $update = true; $dbFarmRole = DBFarmRole::LoadByID($role['farm_role_id']); $dbRole = DBRole::loadById($dbFarmRole->RoleID); $role['role_id'] = $dbFarmRole->RoleID; if ($dbFarmRole->Platform == SERVER_PLATFORMS::GCE) { $dbFarmRole->CloudLocation = $role['cloud_location']; } } else { /** TODO: Remove because will be handled with virtual_ **/ $update = false; $dbRole = DBRole::loadById($role['role_id']); $dbFarmRole = $dbFarm->AddRole($dbRole, $role['platform'], $role['cloud_location'], (int) $role['launch_index']); } if ($dbRole->hasBehavior(ROLE_BEHAVIORS::RABBITMQ)) { $role['settings'][Entity\FarmRoleSetting::SCALING_MAX_INSTANCES] = $role['settings'][Entity\FarmRoleSetting::SCALING_MIN_INSTANCES]; } if ($update) { $dbFarmRole->LaunchIndex = (int) $role['launch_index']; $dbFarmRole->Alias = $role['alias']; $dbFarmRole->Save(); } $usedPlatforms[$role['platform']] = 1; $oldRoleSettings = $dbFarmRole->GetAllSettings(); // Update virtual farm_role_id with actual value $scripts = (array) $role['scripting']; if (!empty($virtualFarmRoles)) { array_walk_recursive($scripts, function (&$v, $k) use($virtualFarmRoles) { if (is_string($v)) { $v = str_replace(array_keys($virtualFarmRoles), array_values($virtualFarmRoles), $v); } }); array_walk_recursive($role['settings'], function (&$v, $k) use($virtualFarmRoles) { if (is_string($v)) { $v = str_replace(array_keys($virtualFarmRoles), array_values($virtualFarmRoles), $v); } }); } $dbFarmRole->ClearSettings("chef."); if (!empty($role['scaling_settings']) && is_array($role['scaling_settings'])) { foreach ($role['scaling_settings'] as $k => $v) { $dbFarmRole->SetSetting($k, $v, Entity\FarmRoleSetting::TYPE_CFG); } } foreach ($role['settings'] as $k => $v) { $dbFarmRole->SetSetting($k, $v, Entity\FarmRoleSetting::TYPE_CFG); } /****** Scaling settings ******/ $scalingManager = new Scalr_Scaling_Manager($dbFarmRole); $scalingManager->setFarmRoleMetrics(is_array($role['scaling']) ? $role['scaling'] : array()); //TODO: optimize this code... $this->db->Execute("DELETE FROM farm_role_scaling_times WHERE farm_roleid=?", array($dbFarmRole->ID)); // 5 = Time based scaling -> move to constants if (!empty($role['scaling'][Entity\ScalingMetric::METRIC_DATE_AND_TIME_ID])) { foreach ($role['scaling'][Entity\ScalingMetric::METRIC_DATE_AND_TIME_ID] as $scal_period) { $chunks = explode(":", $scal_period['id']); $this->db->Execute("INSERT INTO farm_role_scaling_times SET\n farm_roleid\t\t= ?,\n start_time\t\t= ?,\n end_time\t\t= ?,\n days_of_week\t= ?,\n instances_count\t= ?\n ", array($dbFarmRole->ID, $chunks[0], $chunks[1], $chunks[2], $chunks[3])); } } /*****************/ /* Add script options to databse */ $dbFarmRole->SetScripts($scripts, (array) $role['scripting_params']); /* End of scripting section */ /* Add storage configuration */ if (isset($role['storages']['configs'])) { $dbFarmRole->getStorage()->setConfigs($role['storages']['configs'], false); } $farmRoleVariables->setValues(is_array($role['variables']) ? $role['variables'] : [], $dbFarmRole->GetRoleID(), $dbFarm->ID, $dbFarmRole->ID, '', false, true); foreach (Scalr_Role_Behavior::getListForFarmRole($dbFarmRole) as $behavior) { $behavior->onFarmSave($dbFarm, $dbFarmRole); } /** * Platform specified updates */ if ($dbFarmRole->Platform == SERVER_PLATFORMS::EC2) { \Scalr\Modules\Platforms\Ec2\Helpers\EbsHelper::farmUpdateRoleSettings($dbFarmRole, $oldRoleSettings, $role['settings']); \Scalr\Modules\Platforms\Ec2\Helpers\EipHelper::farmUpdateRoleSettings($dbFarmRole, $oldRoleSettings, $role['settings']); if ($role['settings']['aws.elb.remove']) { $this->request->restrictAccess(Acl::RESOURCE_AWS_ELB, Acl::PERM_AWS_ELB_MANAGE); } \Scalr\Modules\Platforms\Ec2\Helpers\ElbHelper::farmUpdateRoleSettings($dbFarmRole, $oldRoleSettings, $role['settings']); } if (in_array($dbFarmRole->Platform, array(SERVER_PLATFORMS::IDCF, SERVER_PLATFORMS::CLOUDSTACK))) { Scalr\Modules\Platforms\Cloudstack\Helpers\CloudstackHelper::farmUpdateRoleSettings($dbFarmRole, $oldRoleSettings, $role['settings']); } } } $rolesToRemove = $this->getParam('rolesToRemove'); if (!empty($rolesToRemove)) { $currentFarmRoles = Entity\FarmRole::find([['farmId' => $dbFarm->ID], ['id' => ['$in' => $rolesToRemove]]]); /* @var $farmRole Entity\FarmRole */ foreach ($currentFarmRoles as $farmRole) { $farmRole->delete(); } } $dbFarm->save(); if (!$client->GetSettingValue(CLIENT_SETTINGS::DATE_FARM_CREATED)) { $client->SetSettingValue(CLIENT_SETTINGS::DATE_FARM_CREATED, time()); } if ($this->request->hasPermissions($dbFarm->__getNewFarmObject(), Acl::PERM_FARMS_LAUNCH_TERMINATE) && $this->getParam('launch')) { $this->user->getPermissions()->validate($dbFarm); $dbFarm->isLocked(); Scalr::FireEvent($dbFarm->ID, new FarmLaunchedEvent(true, $this->user->id)); $this->response->success('Farm successfully saved and launched'); } else { $this->response->success('Farm successfully saved'); } $this->response->data(array('farmId' => $dbFarm->ID, 'isNewFarm' => $bNew)); }