Skip to content

Commit

Permalink
Handle k8s edge cases in the web console overview
Browse files Browse the repository at this point in the history
  • Loading branch information
jwforres committed Feb 18, 2015
1 parent f87744e commit 641d1c2
Show file tree
Hide file tree
Showing 11 changed files with 678 additions and 195 deletions.
195 changes: 173 additions & 22 deletions assets/app/scripts/controllers/overview.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,30 @@ angular.module('openshiftConsole')
$scope.pods = {};
$scope.services = {};
$scope.unfilteredServices = {};
$scope.podsByLabel = {};
$scope.deployments = {};
$scope.deploymentsByConfig = {};
$scope.deploymentConfigs = {"": null}; // when we have deployments that were not created from a deploymentConfig
// the explicit assignment of the "" key is needed so that the null depConfig is
// iterated over during the ng-repeat in the template
$scope.deploymentConfigs = {};
$scope.builds = {};
$scope.images = {};
$scope.imagesByDockerReference = {};
$scope.podsByServiceByLabel = {};

// All pods under a service (no "" service key)
$scope.podsByService = {};
// All pods under a deployment (no "" deployment key)
$scope.podsByDeployment = {};
// Pods not in a deployment
// "" service key for pods not under any service
$scope.monopodsByService = {};
// All deployments
// "" service key for deployments not under any service
// "" deployment config for deployments not created from a deployment config
$scope.deploymentsByServiceByDeploymentConfig = {};
// All deployments
// "" service key for deployments not under any service
// Only being built to improve efficiency in the podRelationships method, not used by the view
$scope.deploymentsByService = {};
// All deployment configs
// "" service key for deployment configs not under any service
$scope.deploymentConfigsByService = {};

$scope.labelSuggestions = {};
$scope.alerts = $scope.alerts || {};
Expand All @@ -30,32 +44,165 @@ angular.module('openshiftConsole')

watches.push(DataService.watch("pods", $scope, function(pods) {
$scope.pods = pods.by("metadata.name");
$scope.podsByLabel = pods.by("metadata.labels", "metadata.name");
podsByServiceByLabel();
console.log("podsByLabel (list)", $scope.podsByLabel);
podRelationships();
console.log("pods", $scope.pods);
}, {poll: true}));

watches.push(DataService.watch("services", $scope, function(services) {
$scope.unfilteredServices = services.by("metadata.name");
LabelFilter.addLabelSuggestionsFromResources($scope.unfilteredServices, $scope.labelSuggestions);
LabelFilter.setLabelSuggestions($scope.labelSuggestions);
$scope.services = LabelFilter.getLabelSelector().select($scope.unfilteredServices);
podsByServiceByLabel();

// Order is important here since podRelationships expects deploymentsByServiceByDeploymentConfig to be up to date
deploymentsByService();
deploymentConfigsByService();
podRelationships();

$scope.emptyMessage = "No services to show";
updateFilterWarning();
console.log("services (list)", $scope.services);
}));

var podsByServiceByLabel = function() {
$scope.podsByServiceByLabel = {};
angular.forEach($scope.services, function(service, name) {
var ls = new LabelSelector(service.spec.selector);
var servicePods = ls.select($scope.pods)
$scope.podsByServiceByLabel[name] = {};
DataService.objectsByAttribute(servicePods, "metadata.labels", $scope.podsByServiceByLabel[name], null, "metadata.name");
// Expects deploymentsByServiceByDeploymentConfig to be up to date
var podRelationships = function() {
$scope.monopodsByService = {"": {}};
$scope.podsByService = {};
$scope.podsByDeployment = {};

// Initialize all the label selectors upfront
var depSelectors = {};
angular.forEach($scope.deployments, function(deployment, depName){
depSelectors[depName] = new LabelSelector(deployment.spec.selector);
$scope.podsByDeployment[depName] = {};
});
var svcSelectors = {};
angular.forEach($scope.unfilteredServices, function(service, svcName){
svcSelectors[svcName] = new LabelSelector(service.spec.selector);
$scope.podsByService[svcName] = {};
});


angular.forEach($scope.pods, function(pod, name){
var deployments = [];
var services = [];
angular.forEach($scope.deployments, function(deployment, depName){
var ls = depSelectors[depName];
if (ls.matches(pod)) {
deployments.push(depName);
$scope.podsByDeployment[depName][name] = pod;
}
});
angular.forEach($scope.unfilteredServices, function(service, svcName){
var ls = svcSelectors[svcName];
if (ls.matches(pod)) {
services.push(svcName);
$scope.podsByService[svcName][name] = pod;

var isInDepInSvc = false;
// Not using angular.forEach so that we can break out early
for (var i = 0; !isInDepInSvc && i < deployments.length; i++) {
var depName = deployments[i];
if ($scope.deploymentsByService[svcName][depName]) {
isInDepInSvc = true;
}
}

if (!isInDepInSvc) {
$scope.monopodsByService[svcName] = $scope.monopodsByService[svcName] || {};
$scope.monopodsByService[svcName][name] = pod;
}
}
});
if (deployments.length == 0 && services.length == 0 && showMonopod(pod)) {
$scope.monopodsByService[""][name] = pod;
}
});

console.log("podsByDeployment", $scope.podsByDeployment);
console.log("podsByService", $scope.podsByService);
console.log("monopodsByService", $scope.monopodsByService);
};

// Filter out monopods we know we don't want to see
var showMonopod = function(pod) {
// Hide pods in the Succeeded or Terminated phase since these are run once pods
// that are done
if (pod.status.phase == 'Succeeded' || pod.status.phase == 'Terminated') {
return false;
}
// Hide our build pods since we are already showing details for currently running or recently
// run builds under the appropriate areas
for (var id in $scope.builds) {
if ($scope.builds[id].podName == pod.metadata.name) {
return false;;
}
}
// Hide our deployer pods since it is obvious the deployment is happening when the new deployment
// appears.
if (pod.metadata.annotations && pod.metadata.annotations.deployment) {
return false;
}

return true;
};

var deploymentConfigsByService = function() {
$scope.deploymentConfigsByService = {"": {}};
angular.forEach($scope.deploymentConfigs, function(deploymentConfig, depName){
var foundMatch = false;
var depConfigSelector = new LabelSelector(deploymentConfig.template.controllerTemplate.replicaSelector);
angular.forEach($scope.services, function(service, name){
$scope.deploymentConfigsByService[name] = $scope.deploymentConfigsByService[name] || {};

var serviceSelector = new LabelSelector(service.spec.selector);
if (serviceSelector.covers(depConfigSelector)) {
$scope.deploymentConfigsByService[name][depName] = deploymentConfig;
foundMatch = true;
}
});
if (!foundMatch) {
$scope.deploymentConfigsByService[""][depName] = deploymentConfig;
}
});
};

var deploymentsByService = function() {
$scope.deploymentsByService = {"": {}};
$scope.deploymentsByServiceByDeploymentConfig = {"": {}};

angular.forEach($scope.deployments, function(deployment, depName){
var foundMatch = false;
var deploymentSelector = new LabelSelector(deployment.spec.selector);

console.log("podsByServiceByLabel", $scope.podsByServiceByLabel);
angular.forEach($scope.services, function(service, name){
$scope.deploymentsByService[name] = $scope.deploymentsByService[name] || {};
$scope.deploymentsByServiceByDeploymentConfig[name] = $scope.deploymentsByServiceByDeploymentConfig[name] || {};

var serviceSelector = new LabelSelector(service.spec.selector);
if (serviceSelector.covers(deploymentSelector)) {
$scope.deploymentsByService[name][depName] = deployment;

var depConfigName = "";
if (deployment.metadata && deployment.metadata.annotations && deployment.metadata.annotations.deploymentConfig) {
depConfigName = deployment.metadata.annotations.deploymentConfig;
}
$scope.deploymentsByServiceByDeploymentConfig[name][depConfigName] = $scope.deploymentsByServiceByDeploymentConfig[name][depConfigName] || {};
$scope.deploymentsByServiceByDeploymentConfig[name][depConfigName][depName] = deployment;
foundMatch = true;
}
});
if (!foundMatch) {
$scope.deploymentsByService[""][depName] = deployment;

var depConfigName = "";
if (deployment.metadata && deployment.metadata.annotations && deployment.metadata.annotations.deploymentConfig) {
depConfigName = deployment.metadata.annotations.deploymentConfig;
}
$scope.deploymentsByServiceByDeploymentConfig[""][depConfigName] = $scope.deploymentsByServiceByDeploymentConfig[""][depConfigName] || {};
$scope.deploymentsByServiceByDeploymentConfig[""][depConfigName][depName] = deployment;
}
});
};

function parseEncodedDeploymentConfig(deployment) {
Expand All @@ -70,10 +217,9 @@ angular.module('openshiftConsole')
}
}

// Sets up subscription for deployments and deploymentsByConfig
// Sets up subscription for deployments
watches.push(DataService.watch("replicationcontrollers", $scope, function(deployments, action, deployment) {
$scope.deployments = deployments.by("metadata.name");
$scope.deploymentsByConfig = deployments.by("metadata.annotations.deploymentConfig", "metadata.name");
if (deployment) {
if (action !== "DELETED") {
parseEncodedDeploymentConfig(deployment);
Expand All @@ -84,8 +230,11 @@ angular.module('openshiftConsole')
parseEncodedDeploymentConfig(dep);
});
}

// Order is important here since podRelationships expects deploymentsByServiceByDeploymentConfig to be up to date
deploymentsByService();
podRelationships();
console.log("deployments (subscribe)", $scope.deployments);
console.log("deploymentsByConfig (subscribe)", $scope.deploymentsByConfig);
}));

// Sets up subscription for images and imagesByDockerReference
Expand Down Expand Up @@ -131,6 +280,9 @@ angular.module('openshiftConsole')
associateDeploymentConfigTriggersToBuild(deploymentConfig, build);
});
}

deploymentConfigsByService();

console.log("deploymentConfigs (subscribe)", $scope.deploymentConfigs);
}));

Expand Down Expand Up @@ -171,7 +323,6 @@ angular.module('openshiftConsole')
// trigger a digest loop
$scope.$apply(function() {
$scope.services = labelSelector.select($scope.unfilteredServices);
podsByServiceByLabel();
updateFilterWarning();
});
});
Expand Down
20 changes: 20 additions & 0 deletions assets/app/scripts/directives/resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,24 @@ angular.module('openshiftConsole')
},
templateUrl: 'views/_pods.html'
};
})
.directive('triggers', function() {
return {
restrict: 'E',
scope: {
triggers: '='
},
templateUrl: 'views/_triggers.html'
};
})
.directive('deploymentConfigMetadata', function() {
return {
restrict: 'E',
scope: {
deploymentConfigId: '=',
exists: '=',
differentService: '='
},
templateUrl: 'views/_deployment-config-metadata.html'
};
});
16 changes: 16 additions & 0 deletions assets/app/scripts/filters/date.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,20 @@ angular.module('openshiftConsole')
return function(timestamp, amt, unit) {
return moment().subtract(amt, unit).diff(moment(timestamp)) < 0;
}
})
.filter('orderObjectsByDate', function() {
return function(items, reverse) {
var filtered = [];
angular.forEach(items, function(item) {
filtered.push(item);
});
filtered.sort(function (a, b) {
if (!a.metadata || !a.metadata.creationTimestamp || !b.metadata || !b.metadata.creationTimestamp) {
throw "orderObjectsByDate expects all objects to have the field metadata.creationTimestamp";
}
return moment(a.metadata.creationTimestamp).diff(moment(b.metadata.creationTimestamp));
});
if(reverse) filtered.reverse();
return filtered;
}
});
17 changes: 17 additions & 0 deletions assets/app/scripts/misc/labelSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,23 @@ LabelSelector.prototype.matches = function(resource) {
return true;
};

LabelSelector.prototype.hasConjunct = function(conjunct) {
return this._conjuncts[this._getIdForConjunct(conjunct)] ? true : false;
};

// Test whether this label selector covers the given selector
LabelSelector.prototype.covers = function(selector) {
// TODO - currently k8s only returns key: value
// which represents 'key in (value)' So we only handle
// the IN operator for now
for (var id in this._conjuncts) {
if (!selector.hasConjunct(this._conjuncts[id])) {
return false;
}
}
return true;
};

// We assume label values have no whitespace, commas, parens, etc. based
// on k8s def for label values
LabelSelector.prototype._getStringForConjunct = function(conjunct) {
Expand Down
Loading

0 comments on commit 641d1c2

Please sign in to comment.