Return-Path: X-Original-To: apmail-aurora-commits-archive@minotaur.apache.org Delivered-To: apmail-aurora-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 3C24510172 for ; Wed, 2 Apr 2014 23:24:15 +0000 (UTC) Received: (qmail 61531 invoked by uid 500); 2 Apr 2014 23:23:57 -0000 Delivered-To: apmail-aurora-commits-archive@aurora.apache.org Received: (qmail 61464 invoked by uid 500); 2 Apr 2014 23:23:56 -0000 Mailing-List: contact commits-help@aurora.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@aurora.incubator.apache.org Delivered-To: mailing list commits@aurora.incubator.apache.org Received: (qmail 61430 invoked by uid 99); 2 Apr 2014 23:23:56 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 02 Apr 2014 23:23:56 +0000 X-ASF-Spam-Status: No, hits=-2000.5 required=5.0 tests=ALL_TRUSTED,RP_MATCHES_RCVD X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO mail.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with SMTP; Wed, 02 Apr 2014 23:23:50 +0000 Received: (qmail 61088 invoked by uid 99); 2 Apr 2014 23:23:29 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 02 Apr 2014 23:23:29 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 6038B926D61; Wed, 2 Apr 2014 23:23:29 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: mansu@apache.org To: commits@aurora.incubator.apache.org Date: Wed, 02 Apr 2014 23:23:29 -0000 Message-Id: <2f7e64b4eb3c49ac8158032019b99dbe@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [01/15] Moved role and job page to angular. X-Virus-Checked: Checked by ClamAV on apache.org Repository: incubator-aurora Updated Branches: refs/heads/mansu/job_page [created] c3429fdb2 http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/schedulerzrole.st ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/schedulerzrole.st b/src/main/resources/org/apache/aurora/scheduler/http/schedulerzrole.st deleted file mode 100644 index b53f352..0000000 --- a/src/main/resources/org/apache/aurora/scheduler/http/schedulerzrole.st +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - - Aurora $cluster_name$ scheduler - Jobs for $role$ - - - - - - - - - - - -
-
-
-

Jobs for role $role$ $if(environment)$ and environment $environment$ $endif$

-
-
- -
- -
- - $if(jobs)$ - $if(environment)$ - - $else$ -
- -
- - - - - - - - - - - -
Resource - Production consumption - Quota - Non-Production consumption -
CPU - $prodResourcesUsed.numCpus$ cores - $resourceQuota.numCpus$ cores - $nonProdResourcesUsed.numCpus$ cores -
RAM - - - -
Disk - - - -
-
-
- $endif$ - -
- - - - - $jobs:{ job | - - - }$ - -
Job Type - Environment - Job - Pending Tasks - Active Tasks - Finished Tasks - Failed Tasks -
- $job.type$ - - $job.environment$ - - $job.name$ - - - - - $job.pendingTaskCount$ - - - $job.activeTaskCount$ - - - $job.finishedTaskCount$ - - - $job.failedTaskCount$ - - -
-
- $endif$ - - $if(cronJobs)$ -
-

Cron jobs

-
- - - - - $cronJobs:{ job | - - - }$ - -
Environment - Name - Tasks - Schedule - Next Run - Collision policy - Metadata -
$job.environment$ - $job.name$ - $job.pendingTaskCount$ - $job.cronSchedule$ - - $job.cronCollisionPolicy$ - $job.metadata$ -
-
-
- $endif$ -
- - $if(exception)$ - Exception: $exception$ - $endif$ - - - - http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/breadcrumb.html ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/breadcrumb.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/breadcrumb.html new file mode 100644 index 0000000..23f0cdc --- /dev/null +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/breadcrumb.html @@ -0,0 +1,17 @@ +
+ +
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/css/app.css ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/css/app.css b/src/main/resources/org/apache/aurora/scheduler/http/ui/css/app.css index ade850c..e81f561 100644 --- a/src/main/resources/org/apache/aurora/scheduler/http/ui/css/app.css +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/css/app.css @@ -15,3 +15,21 @@ .sort-descent:before { content: "\25B4"; } + +ul.breadcrumb { + margin: 0 0 0; +} + +.page-header { + margin: 0 0 0; + border-bottom: 0px; +} + +.center { + margin: 0px auto; + float: none; +} + +.schedulingStatus { + border-bottom: 1px #aaaaaa dashed; +} http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/error.html ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/error.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/error.html new file mode 100644 index 0000000..96dcf11 --- /dev/null +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/error.html @@ -0,0 +1,7 @@ +
+

+ An error occurred when querying the server. Please reload this page. +

+ +

{{error}}

+
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/home.html ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/home.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/home.html new file mode 100644 index 0000000..d367b81 --- /dev/null +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/home.html @@ -0,0 +1,23 @@ +
+
+ +
+ +
+
+ +
+ + + +
+ + +
+
+
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/index.html ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/index.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/index.html index 36225d1..817bd55 100644 --- a/src/main/resources/org/apache/aurora/scheduler/http/ui/index.html +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/index.html @@ -1,50 +1,49 @@ - + - + + + Aurora UI - -
-
-
-

{{reloadMsg}}

-

{{errorMsg}}

-
- -
- -
- - -
-
-
+
+
- - - + + + - - - - - + + + + + + + - - - + + + + + + - - + + + + + + + + + + + http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/job.html ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/job.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/job.html new file mode 100644 index 0000000..bbafb95 --- /dev/null +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/job.html @@ -0,0 +1,61 @@ +
+
+ +
+ +
+
+
+ +
+ +
+ +
+ + +
+

Active tasks + +

+ +
+
+ + +
+
+ +
+ + +
+
+ +
+

Completed tasks

+ + +
+
+
+
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/jobLink.html ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/jobLink.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/jobLink.html new file mode 100644 index 0000000..2deccf4 --- /dev/null +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/jobLink.html @@ -0,0 +1 @@ + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/js/app.js ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/app.js b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/app.js index db6ea99..8806964 100644 --- a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/app.js +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/app.js @@ -1,4 +1,24 @@ 'use strict'; // Declare app level module which depends on filters, and services -var auroraUI = angular.module('auroraUI', ['auroraUI.controllers', 'smartTable.table']); +var auroraUI = angular.module('auroraUI', ['ngRoute', 'auroraUI.controllers', 'smartTable.table']); + +auroraUI.config(function ($routeProvider, $locationProvider) { + $routeProvider.when("/scheduler", + {templateUrl: '/home.html', controller: 'RoleSummaryController'}); + + $routeProvider.when("/scheduler/:role", + {templateUrl: '/role.html', controller: 'JobSummaryController'}); + + $routeProvider.when("/scheduler/:role/:environment", + {templateUrl: '/role.html', controller: 'JobSummaryController'}); + + $routeProvider.when("/scheduler/:role/:environment/:job", + {templateUrl: '/job.html', controller: 'JobController'}); + + $routeProvider.otherwise({redirectTo: function (location, path) { + window.location.href = path; + }}); + + $locationProvider.html5Mode(true); +}); http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/js/controllers.js ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/controllers.js b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/controllers.js index 7cd5344..fef9956 100644 --- a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/controllers.js +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/controllers.js @@ -4,28 +4,40 @@ var auroraUIControllers = angular.module('auroraUI.controllers', []); -auroraUIControllers.controller('JobSummaryController', - function ($scope, $window, auroraClient) { +var infoTableConfig = { + isGlobalSearchActivated: true, + isPaginationEnabled: true, + itemsByPage: 25, + maxSize: 8, + selectionMode: 'single' +}; + +var summaryTableConfig = { + isPaginationEnabled: false, + isGlobalSearchActivated: false, + selectionMode: 'none' +}; + +auroraUIControllers.controller('RoleSummaryController', + function ($scope, auroraClient) { $scope.title = 'Scheduled Jobs Summary'; - $scope.error = false; - $scope.reloadMsg = "An error occurred when querying the server. Please reload this page."; - $scope.errorMsg = ""; + $scope.error = ''; - $scope.columnCollection = [ + $scope.roleSummaryColumns = [ {label: 'Role', map: 'role', cellTemplateUrl: 'roleLink.html'}, {label: 'Jobs', map: 'jobCount'}, {label: 'Cron Jobs', map: 'cronJobCount'} ]; - $scope.rowCollection = parseResponse(auroraClient.getRoleSummary()); + $scope.roleSummaries = parseResponse(auroraClient.getRoleSummary()); function parseResponse(response) { - $scope.error = response.error; - $scope.errorMsg = response.errorMsg; + $scope.error = response.error ? 'Error requesting role summary: ' + response.error : ''; - // Not the best way to set the page title, but this works on all browsers. - $window.document.title = response.pageTitle; + if ($scope.error) { + return []; + } // TODO(Suman Karumuri): Replace sort with defaultSortColumn once it lands // https://github.com/lorenzofox3/Smart-Table/pull/61 @@ -40,10 +52,473 @@ auroraUIControllers.controller('JobSummaryController', }); } - $scope.globalConfig = { - isGlobalSearchActivated: true, - isPaginationEnabled: true, - itemsByPage: 25, - maxSize: 8 + $scope.roleSummaryTableConfig = infoTableConfig; + }); + +auroraUIControllers.controller('JobSummaryController', + function ($scope, $routeParams, auroraClient) { + $scope.role = $routeParams.role; + $scope.environment = $routeParams.environment; + + $scope.error = ''; + + var showResourcesMsg = 'Show Resource Consumption'; + var hideResourcesMsg = 'Hide Resource Consumption'; + $scope.showResources = false; + $scope.resourceButtonText = showResourcesMsg; + + $scope.toggleResourceVisibility = function () { + $scope.showResources = !$scope.showResources; + $scope.resourceButtonText = ($scope.showResources ? showResourcesMsg : hideResourcesMsg); }; + + $scope.jobsTableColumns = [ + {label: 'Job Type', map: 'jobType'}, + {label: 'Environment', map: 'environment', cellTemplateUrl: '/roleEnvLink.html'}, + {label: 'Job', map: 'jobName', cellTemplateUrl: '/jobLink.html'}, + {label: 'production', map: 'isProduction'}, + {label: 'Pending Tasks', map: 'pendingTasks'}, + {label: 'Active Tasks', map: 'activeTasks'}, + {label: 'Finished Tasks', map: 'finishedTasks'}, + {label: 'Failed Tasks', map: 'failedTasks'} + ]; + + $scope.cronJobsTableColumns = [ + {label: 'Environment', map: 'environment', cellTemplateUrl: '/roleEnvLink.html'}, + {label: 'Job Name', map: 'jobName'}, + {label: 'Tasks', map: 'tasks'}, + {label: 'Schedule', map: 'schedule'}, + {label: 'Next Run', + map: 'nextCronRun', + formatFunction: function (value, format) { + return printDate(value); + } + }, + {label: 'Collision Policy', map: 'collisionPolicy'}, + {label: 'Metadata', map: 'metadata'} + ]; + + $scope.jobsTableConfig = infoTableConfig; + + $scope.cronJobsTableConfig = infoTableConfig; + + $scope.cronJobs = []; + + $scope.jobs = getJobs(); + + function getJobs() { + var summaries = auroraClient.getJobSummary($scope.role); + $scope.error = summaries.error ? 'Error fetching job summaries: ' + summaries.error : ''; + + if ($scope.error) { + return []; + } + + var jobSummaries = summaries.jobs; + + if ($scope.environment) { + jobSummaries = _.filter(jobSummaries, function (summary) { + return summary.job.key.environment === $scope.environment; + }); + } + + var byJobName = function (summary) { + return summary.jobName; + }; + + $scope.cronJobs = _.chain(jobSummaries) + .filter(function (summary) { + return isCronJob(summary.job); + }) + .map(function (summary) { + return { + role: $scope.role, // required for roleEnvLink directive + environment: summary.job.key.environment, + jobName: summary.job.taskConfig.jobName, + tasks: summary.job.instanceCount, + schedule: summary.job.cronSchedule, + nextCronRun: summary.nextCronRunMs, + collisionPolicy: getCronCollisionPolicy(summary.job.cronCollisionPolicy), + metadata: getMetadata(summary.job.taskConfig.metadata) + }; + }) + .sortBy(byJobName) + .value(); + + return _.chain(jobSummaries) + .map(function (summary) { + return { + role: $scope.role, // required for roleEnvLink directive + environment: summary.job.key.environment, + jobName: summary.job.taskConfig.jobName, + jobType: getJobType(summary.job), + isProduction: summary.job.taskConfig.production ? 'yes' : '', + pendingTasks: summary.stats.pendingTaskCount, + activeTasks: summary.stats.activeTaskCount, + finishedTasks: summary.stats.finishedTaskCount, + failedTasks: summary.stats.failedTaskCount + }; + }) + .sortBy(byJobName) + .value(); + } + + function getJobType(job) { + if (job.taskConfig.isService) { + return 'service'; + } + + if (isCronJob(job)) { + return 'cron'; + } + + return 'adhoc'; + } + + function isCronJob(job) { + return job.cronSchedule !== null; + } + + function getMetadata(attributes) { + return _.map(attributes,function (attribute) { + return attribute.key + ': ' + attribute.value; + }).join(', '); + } + + function getCronCollisionPolicy(cronCollisionPolicy) { + return _.keys(CronCollisionPolicy)[cronCollisionPolicy ? cronCollisionPolicy : 0]; + } + + // TODO(Suman Karumuri): Replace printDate with a more user friendly directive. + function printDate(timestamp) { + function pad(number) { + number = '' + number; + if (number.length < 2) { + return '0' + number; + } + return number; + } + + var d = new Date(timestamp); + return pad(d.getUTCMonth() + 1) + '/' + + pad(d.getUTCDate()) + ' ' + + pad(d.getUTCHours()) + ':' + + pad(d.getUTCMinutes()) + ':' + + pad(d.getUTCSeconds()) + + ' UTC (' + + pad(d.getMonth() + 1) + '/' + + pad(d.getDate()) + ' ' + + pad(d.getHours()) + ':' + + pad(d.getMinutes()) + ':' + + pad(d.getSeconds()) + + ' local)'; + } }); + +auroraUIControllers.controller('QuotaController', + function ($scope, $filter, auroraClient) { + $scope.error = ''; + + $scope.resourcesTableColumns = [ + {label: 'Resource', map: 'resource'}, + {label: 'Production Consumption', map: 'prodConsumption'}, + {label: 'Quota', map: 'quota'}, + {label: 'Non-Production Consumption', map: 'nonProdConsumption'} + ]; + + $scope.resourcesTableConfig = summaryTableConfig; + + $scope.resources = getQuota(); + + function getQuota() { + var quotaResponse = auroraClient.getQuota($scope.role); + $scope.error = quotaResponse.error ? 'Error fetching quota: ' + quotaResponse.error : ''; + + if ($scope.error) { + return []; + } + + var consumption = quotaResponse.quota; + return [ + { + resource: 'CPU', + prodConsumption: $filter('toCores')(consumption.prodConsumption.numCpus), + quota: $filter('toCores')(consumption.quota.numCpus), + nonProdConsumption: $filter('toCores')(consumption.nonProdConsumption.numCpus) + }, + { + resource: 'RAM', + prodConsumption: $filter('scaleMb')(consumption.prodConsumption.ramMb), + quota: $filter('scaleMb')(consumption.quota.ramMb), + nonProdConsumption: $filter('scaleMb')(consumption.nonProdConsumption.ramMb) + }, + { + resource: 'Disk', + prodConsumption: $filter('scaleMb')(consumption.prodConsumption.diskMb), + quota: $filter('scaleMb')(consumption.quota.diskMb), + nonProdConsumption: $filter('scaleMb')(consumption.nonProdConsumption.diskMb) + } + ]; + } + } +); + +auroraUIControllers.controller('JobController', + function ($scope, $routeParams, auroraClient) { + $scope.error = ''; + + $scope.role = $routeParams.role; + $scope.environment = $routeParams.environment; + $scope.job = $routeParams.job; + + $scope.taskSummary = []; + $scope.taskSummaryTableColumns = [ + {label: 'Instances', + map: 'range', + isSortable: false, + formatFunction: function (range) { + return range.start === range.end ? range.start : range.start + '-' + range.end; + } + }, + {label: 'Details', + map: 'schedulingDetail', + isSortable: false, + cellTemplateUrl: '/schedulingDetail.html' + } + ]; + + $scope.taskSummaryTableConfig = summaryTableConfig; + + var showSummary = 'Show Summary'; + var hideSummary = 'Hide Summary'; + $scope.summaryButtonText = showSummary; + $scope.showSummary = false; + + $scope.toggleSummaryVisibility = function () { + $scope.showSummary = !$scope.showSummary; + $scope.summaryButtonText = $scope.showSummary ? hideSummary : showSummary; + }; + + $scope.activeTasksTableConfig = infoTableConfig; + $scope.completedTasksTableConfig = infoTableConfig; + + var taskColumns = [ + {label: 'Instance', map: 'instanceId'}, + {label: 'Status', map: 'status', cellTemplateUrl: '/taskStatus.html'}, + {label: 'Last Active', + map: 'latestActivity', + formatFunction: function (date) { + return moment(date).fromNow(); + } + }, + {label: 'Host', map: 'host', cellTemplateUrl: '/taskSandbox.html'}, + {label: '', map: 'taskId', cellTemplateUrl: '/taskLink.html'} + ]; + + $scope.activeTasksTableColumns = taskColumns; + + $scope.completedTasksTableColumns = _.union( + _.first(taskColumns, 2), + [ + {label: 'Running duration', + map: 'duration', + formatFunction: function (duration) { + return moment(moment().subtract(duration)).fromNow(true); + } + } + ], + _.last(taskColumns, taskColumns.length - 2) + ); + + $scope.jobDashboardURL = ''; + + $scope.completedTasks = []; + + $scope.activeTasks = getTasksForJob($scope.role, $scope.environment, $scope.job); + + function getTasksForJob(role, environment, job) { + var response = auroraClient.getTasks(role, environment, job); + + $scope.error = response.error ? 'Error fetching tasks: ' + response.error : ''; + + if ($scope.error) { + return []; + } + + $scope.jobDashboardURL = getJobDashboardURL(response.statsURLPrefix); + + $scope.taskSummary = summarizeActiveTaskConfigs(response.tasks); + + var tasks = _.map(response.tasks, function (task) { + return summarizeTask(task); + }); + + var activeTaskPredicate = function (task) { + return task.isActive; + }; + + $scope.completedTasks = _.chain(tasks) + .reject(activeTaskPredicate) + .sortBy(function (task) { + return -task.latestActivity; //sort in descending order + }) + .value(); + + return _.chain(tasks) + .filter(activeTaskPredicate) + .sortBy(function (task) { + return task.instanceId; + }) + .value(); + } + + function summarizeTask(task) { + var isActive = isActiveTask(task); + var sortedTaskEvents = _.sortBy(task.taskEvents, function (taskEvent) { + return taskEvent.timestamp; + }); + var lastTransitionAt = _.isEmpty(sortedTaskEvents) ? 0 : _.last(sortedTaskEvents).timestamp; + + return { + instanceId: task.assignedTask.instanceId, + status: _.invert(ScheduleStatus)[task.status], + host: task.assignedTask.slaveHost || '', + latestActivity: lastTransitionAt, + duration: getDuration(sortedTaskEvents, isActive), + isActive: isActive, + taskId: task.assignedTask.taskId, + taskEvents: summarizeTaskEvents(sortedTaskEvents), + showDetails: false + }; + } + + function isActiveTask(task) { + return _.contains(ACTIVE_STATES, task.status); + } + + function getDuration(sortedTaskEvents, isActive) { + if (!isActive || _.isEmpty(sortedTaskEvents)) { + return 0; + } + + var runningTaskEvent = _.find(sortedTaskEvents, function (taskEvent) { + return taskEvent.status === ScheduleStatus['RUNNING']; + }); + + return runningTaskEvent ? _.last(sortedTaskEvents).timestamp - runningTaskEvent.timestamp : 0; + } + + function summarizeTaskEvents(taskEvents) { + return _.map(taskEvents, function (taskEvent) { + return { + date: moment(taskEvent.timestamp).format('MM DD h:mm:ss'), + status: _.invert(ScheduleStatus)[taskEvent.status], + message: taskEvent.message + }; + }); + } + + function summarizeActiveTaskConfigs(tasks) { + return _.chain(tasks) + .filter(function (task) { + return isActiveTask(task) + }) + .map(function (task) { + return { + instanceId: task.assignedTask.instanceId, + schedulingDetail: configToDetails(task.assignedTask.task) + }; + }) + .groupBy(function (task) { + return JSON.stringify(task.schedulingDetail); + }) + .map(function (tasks, schedulingDetail) { + var schedulingDetail = _.first(tasks).schedulingDetail; + return _.map(toRanges(_.pluck(tasks, 'instanceId')), function (range) { + return { + range: range, + schedulingDetail: schedulingDetail + } + }); + }) + .flatten(true) + .sortBy(function (scheduleDetail) { + return scheduleDetail.range.start; + }) + .value(); + } + + function configToDetails(task) { + return { + numCpus: task.numCpus, + ramMb: task.ramMb, + diskMb: task.diskMb, + isService: task.isService, + production: task.production, + contact: task.contactEmail || '', + ports: _.sortBy(task.requestedPorts).join(', '), + constraints: _.chain(task.constraints) + .sortBy(function (constraint) { + return constraint.name; + }) + .map(function (constraint) { + return formatConstraint(constraint); + }) + .value() + .join(', '), + metadata: _.chain(task.metadata) + .sortBy(function (metadata) { + return metadata.key; + }) + .map(function (metadata) { + return metadata.key + ':' + metadata.value; + }) + .value() + .join(', ') + }; + } + + function toRanges(instanceIds) { + instanceIds = _.sortBy(instanceIds); + var result = []; + var i = 0; + var start = instanceIds[i]; + var end = instanceIds[i]; + while (i < instanceIds.length) { + if ((i + 1 === instanceIds.length) || (instanceIds[i] + 1 !== instanceIds[i + 1])) { + result.push({start: start, end: instanceIds[i]}); + i++; + start = instanceIds[i]; + } else { + i++; + } + } + return result; + } + + function formatConstraint(constraint) { + var taskConstraint = constraint.constraint; + + var valueConstraint = ''; + if (!_.isNull(taskConstraint.value)) { + var values = taskConstraint.value.values.join(','); + valueConstraint = taskConstraint.value.negated ? "not " + values : values; + } + + var limitConstraint = taskConstraint.limit ? JSON.stringify(taskConstraint.limit) : ''; + + if (_.isEmpty(limitConstraint) && _.isEmpty(valueConstraint)) { + return ''; + } else { + return constraint.name + ':' + + (_.isEmpty(limitConstraint) ? valueConstraint : limitConstraint); + } + } + + function getJobDashboardURL(statsURLPrefix) { + return _.isEmpty(statsURLPrefix) + ? '' + : statsURLPrefix + $scope.role + '.' + $scope.environment + '.' + $scope.job; + } + } +); http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/js/directives.js ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/directives.js b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/directives.js index d2b2017..2c0634d 100644 --- a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/directives.js +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/directives.js @@ -3,6 +3,69 @@ auroraUI.directive('roleLink', function () { return { restrict: 'C', - template: "{{formatedValue}}" + template: '{{formatedValue}}' }; -}); \ No newline at end of file +}); + +auroraUI.directive('roleEnvLink', function () { + return { + restrict: 'C', + template: '{{formatedValue}}' + }; +}); + +auroraUI.directive('jobLink', function () { + return { + restrict: 'C', + template: '' + + '{{formatedValue}}' + }; +}); + +auroraUI.directive('breadcrumb', function () { + return { + restrict: 'E', + templateUrl: '/breadcrumb.html' + }; +}); + +auroraUI.directive('error', function () { + return { + restrict: 'E', + templateUrl: '/error.html' + }; +}); + +auroraUI.directive('taskSandboxLink', function () { + return { + restrict: 'C', + template: '' + + '{{formatedValue}}' + }; +}); + +auroraUI.directive('taskStatus', function () { + return { + restrict: 'E', + replace: true, + link: function (scope, element, attrs, ctrl) { + element.on('click', function (e) { + scope.showDetails = !scope.showDetails; + }); + } + }; +}); + +auroraUI.directive('taskLink', function () { + return { + restrict: 'C', + template: '' + + '' + }; +}); + +auroraUI.directive('schedulingDetail', function () { + return { + restrict: 'C' + }; +}) http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/js/filters.js ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/filters.js b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/filters.js new file mode 100644 index 0000000..0bde9fb --- /dev/null +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/filters.js @@ -0,0 +1,47 @@ +'use strict'; + +auroraUI.filter('scheduleStatusTooltip', function () { + return function (value) { + var states = { + PENDING: 'The scheduler is searching for a machine that satisfies the resources and ' + + 'constraints for this task.', + + THROTTLED: 'The task will be rescheduled, but is being throttled for restarting too ' + + 'frequently.', + + ASSIGNED: 'The scheduler has selected a machine to run the task and is instructing the ' + + 'slave to launch it.', + + STARTING: 'The executor is preparing to launch the task.', + RUNNING: 'The user process(es) are running.', + FAILED: 'The task ran, but did not exit indicating success.', + FINISHED: 'The task ran and exited successfully.', + KILLED: 'A user or cron invocation terminated the task.', + PREEMPTING: 'This task is being killed to make resources available for a production task.', + KILLING: 'A user request or cron invocation has requested the task be killed.', + LOST: 'The task cannot be accounted for, usually a result of slave process or machine ' + + 'failure.' + }; + + return states[value] ? states[value] : value; + }; +}); + +auroraUI.filter('scaleMb', function () { + return function (sizeInMb) { + var size = sizeInMb; + var SCALE = ['MiB', 'GiB', 'TiB', 'PiB', 'EiB']; + var unit = 0; + while (size >= 1024 && unit < SCALE.length) { + size = size / 1024; + unit++; + } + return size.toFixed(2).toString() + ' ' + SCALE[unit]; + }; +}); + +auroraUI.filter('toCores', function () { + return function (count) { + return count + ' cores'; + } +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/js/services.js ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/services.js b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/services.js index 8681bbe..8310171 100644 --- a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/services.js +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/services.js @@ -2,38 +2,80 @@ auroraUI.factory( 'auroraClient', - function () { - return { - getRoleSummary: function () { - var response = this.getSchedulerClient().getRoleSummary(); - return { - error: response.responseCode !== 1, - errorMsg: response.message, - summaries: response.result !== null ? response.result.roleSummaryResult.summaries : [], - pageTitle: this.getPageTitle(response.serverInfo) - } - }, - - // TODO(Suman Karumuri): Make schedulerClient a service - schedulerClient: null, - - getSchedulerClient: function () { - if (!this.schedulerClient) { - var transport = new Thrift.Transport("/api"); - var protocol = new Thrift.Protocol(transport); - this.schedulerClient = new ReadOnlySchedulerClient(protocol); - return this.schedulerClient; - } else { - return this.schedulerClient; - } - }, - - getPageTitle: function (info) { - var title = "Aurora UI"; - return info.error || typeof info.clusterName === "undefined" - ? title - : info.clusterName + " " + title; - } - }; - } -); + ['$window', + function ($window) { + return { + getRoleSummary: function () { + var response = this.getSchedulerClient().getRoleSummary(); + var result = this.processResponse(response); + result.summaries = response.result !== null + ? response.result.roleSummaryResult.summaries : []; + return result; + }, + + getJobSummary: function (role) { + var response = this.getSchedulerClient().getJobSummary(role); + var result = this.processResponse(response); + result.jobs = response.result !== null ? response.result.jobSummaryResult.summaries : []; + return result; + }, + + getQuota: function (role) { + var response = this.getSchedulerClient().getQuota(role); + var result = this.processResponse(response); + result.quota = response.result !== null ? response.result.getQuotaResult : []; + return result; + }, + + getTasks: function (role, environment, jobName) { + var id = new Identity(); + id.role = role; + var taskQuery = new TaskQuery(); + taskQuery.identity = id; + taskQuery.environment = environment; + taskQuery.jobName = jobName; + var response = this.getSchedulerClient().getTasksStatus(taskQuery); + var result = this.processResponse(response); + result.tasks = response.result !== null ? response.result.scheduleStatusResult.tasks : []; + return result; + }, + + // TODO(Suman Karumuri): Make schedulerClient a service + schedulerClient: null, + + getSchedulerClient: function () { + if (!this.schedulerClient) { + var transport = new Thrift.Transport("/api/"); + var protocol = new Thrift.Protocol(transport); + this.schedulerClient = new ReadOnlySchedulerClient(protocol); + return this.schedulerClient; + } else { + return this.schedulerClient; + } + }, + + processResponse: function (response) { + this.setPageTitle(response.serverInfo); + return { + error: response.responseCode !== 1 + ? (response.message || 'No error message returned by the scheduler') + : '', + statsURLPrefix: response.serverInfo && response.serverInfo.statsURLPrefix + ? response.serverInfo.statsURLPrefix + : '' + }; + }, + + getPageTitle: function (info) { + var title = "Aurora UI"; + return _.isNull(info) || info.error || typeof info.clusterName === "undefined" + ? title + : info.clusterName + " " + title; + }, + + setPageTitle: function (serverInfo) { + $window.document.title = this.getPageTitle(serverInfo); + } + }; + } + ]); http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/role.html ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/role.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/role.html new file mode 100644 index 0000000..5698542 --- /dev/null +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/role.html @@ -0,0 +1,65 @@ +
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+
+
+
+
+ +
+ + +
+ +
+

Cron jobs for role {{role}}

+ + +
+
+
+
+
\ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/roleEnvLink.html ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/roleEnvLink.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/roleEnvLink.html new file mode 100644 index 0000000..1db7ad1 --- /dev/null +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/roleEnvLink.html @@ -0,0 +1 @@ + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/roleLink.html ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/roleLink.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/roleLink.html index fc25526..63a1688 100644 --- a/src/main/resources/org/apache/aurora/scheduler/http/ui/roleLink.html +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/roleLink.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/schedulingDetail.html ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/schedulingDetail.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/schedulingDetail.html new file mode 100644 index 0000000..b7e3049 --- /dev/null +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/schedulingDetail.html @@ -0,0 +1,23 @@ +
+
    +
  • resources: + cpu: {{dataRow.schedulingDetail.numCpus | toCores}}, + ram: {{dataRow.schedulingDetail.ramMb | scaleMb}}, + disk: {{dataRow.schedulingDetail.diskMb | scaleMb}} +
  • +
  • + constraints : {{dataRow.schedulingDetail.constraints}} +
  • +
  • production : {{dataRow.schedulingDetail.production}}
  • +
  • service : {{dataRow.schedulingDetail.isService}}
  • +
  • + ports : {{dataRow.schedulingDetail.ports}} +
  • +
  • + metadata : {{dataRow.schedulingDetail.metadata}} +
  • +
  • + contact : {{dataRow.schedulingDetail.contact}} +
  • +
+
\ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/taskLink.html ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/taskLink.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/taskLink.html new file mode 100644 index 0000000..3779b7e --- /dev/null +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/taskLink.html @@ -0,0 +1 @@ + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/taskSandbox.html ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/taskSandbox.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/taskSandbox.html new file mode 100644 index 0000000..1c7c716 --- /dev/null +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/taskSandbox.html @@ -0,0 +1 @@ + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/resources/org/apache/aurora/scheduler/http/ui/taskStatus.html ---------------------------------------------------------------------- diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/taskStatus.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/taskStatus.html new file mode 100644 index 0000000..121dfe8 --- /dev/null +++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/taskStatus.html @@ -0,0 +1,19 @@ + +
+ + + + {{formatedValue}} + + +
    +
  • + {{taskEvent.date}} local - + + {{taskEvent.status}} + + - {{taskEvent.message}} +
  • +
+
+
\ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/main/thrift/org/apache/aurora/gen/api.thrift ---------------------------------------------------------------------- diff --git a/src/main/thrift/org/apache/aurora/gen/api.thrift b/src/main/thrift/org/apache/aurora/gen/api.thrift index c0618e4..0225330 100644 --- a/src/main/thrift/org/apache/aurora/gen/api.thrift +++ b/src/main/thrift/org/apache/aurora/gen/api.thrift @@ -436,6 +436,7 @@ struct JobSummaryResult { struct ServerInfo { 1: string clusterName 2: i32 thriftAPIVersion + 3: string statsURLPrefix // A url prefix for job container stats. } union Result { http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java b/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java index a4e9464..a48029e 100644 --- a/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java +++ b/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java @@ -116,6 +116,7 @@ public class SchedulerIT extends BaseZooKeeperTest { private static final String CLUSTER_NAME = "integration_test_cluster"; private static final String SERVERSET_PATH = "/fake/service/path"; + private static final String STATS_URL_PREFIX = "fake_url"; private static final String FRAMEWORK_ID = "integration_test_framework_id"; private ExecutorService executor = Executors.newCachedThreadPool( @@ -200,7 +201,8 @@ public class SchedulerIT extends BaseZooKeeperTest { .withCredentials(ZooKeeperClient.digestCredentials("mesos", "mesos")); injector = Guice.createInjector( ImmutableList.builder() - .addAll(SchedulerMain.getModules(CLUSTER_NAME, SERVERSET_PATH, zkClientConfig)) + .addAll(SchedulerMain.getModules( + CLUSTER_NAME, SERVERSET_PATH, zkClientConfig, STATS_URL_PREFIX)) .add(new LifecycleModule()) .add(new AppLauncherModule()) .add(new ZooKeeperClientModule(zkClientConfig)) http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java b/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java index fae2de1..b25d962 100644 --- a/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java +++ b/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java @@ -144,7 +144,10 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest { private static final IResourceAggregate CONSUMED = IResourceAggregate.build(new ResourceAggregate(0.0, 0, 0)); private static final ServerInfo SERVER_INFO = - new ServerInfo().setClusterName("test").setThriftAPIVersion(THRIFT_API_VERSION); + new ServerInfo() + .setClusterName("test") + .setThriftAPIVersion(THRIFT_API_VERSION) + .setStatsURLPrefix("fake_url"); private static final APIVersion API_VERSION = new APIVersion().setMajor(THRIFT_API_VERSION); private static final String CRON_SCHEDULE = "0 * * * *"; @@ -318,7 +321,7 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest { } private static IScheduledTask buildScheduledTask(String jobName) { - return IScheduledTask.build(new ScheduledTask() + return IScheduledTask.build(new ScheduledTask() .setAssignedTask(new AssignedTask() .setTask(new TaskConfig() .setOwner(ROLE_IDENTITY) http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/test/java/org/apache/aurora/scheduler/thrift/aop/ServerInfoInterceptorTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/aurora/scheduler/thrift/aop/ServerInfoInterceptorTest.java b/src/test/java/org/apache/aurora/scheduler/thrift/aop/ServerInfoInterceptorTest.java index dd991fb..7a8baa4 100644 --- a/src/test/java/org/apache/aurora/scheduler/thrift/aop/ServerInfoInterceptorTest.java +++ b/src/test/java/org/apache/aurora/scheduler/thrift/aop/ServerInfoInterceptorTest.java @@ -45,8 +45,11 @@ public class ServerInfoInterceptorTest extends EasyMockTest { private AuroraAdmin.Iface realThrift; private AuroraAdmin.Iface decoratedThrift; - private static final IServerInfo SERVER_INFO = - IServerInfo.build(new ServerInfo().setClusterName("test").setThriftAPIVersion(1)); + private static final IServerInfo SERVER_INFO = IServerInfo.build( + new ServerInfo() + .setClusterName("test") + .setThriftAPIVersion(1) + .setStatsURLPrefix("fake_url")); private ServerInfoInterceptor interceptor; @@ -86,9 +89,9 @@ public class ServerInfoInterceptorTest extends EasyMockTest { new ServerInfo().setClusterName("FAKECLUSTER").setThriftAPIVersion(100000); Response response = okResponse( - Result.getJobsResult( - new GetJobsResult().setConfigs(ImmutableSet.of()))) - .setServerInfo(previousServerInfo); + Result.getJobsResult( + new GetJobsResult().setConfigs(ImmutableSet.of()))) + .setServerInfo(previousServerInfo); expect(realThrift.getJobs(ROLE)).andReturn(response); http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/c3429fdb/src/test/resources/org/apache/aurora/gen/api.thrift.md5 ---------------------------------------------------------------------- diff --git a/src/test/resources/org/apache/aurora/gen/api.thrift.md5 b/src/test/resources/org/apache/aurora/gen/api.thrift.md5 index 05c6e8a..2fedcca 100644 --- a/src/test/resources/org/apache/aurora/gen/api.thrift.md5 +++ b/src/test/resources/org/apache/aurora/gen/api.thrift.md5 @@ -1 +1 @@ -f91209ac428266e87dac5430bf46ed10 +1553d6dfbed91acf42c735d19b236f69