You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
aggregation-platform/plugins/kubernetes/ts/kubernetesModel.ts

784 lines
28 KiB

/// <reference path="../../includes.ts"/>
/// <reference path="kubernetesPlugin.ts"/>
module Kubernetes {
export var FABRIC8_PROJECT_JSON = "fabric8ProjectJson";
function byId(thing) {
return thing.id;
}
function createKey(namespace, id, kind) {
return (namespace || "") + "-" + (kind || 'undefined').toLowerCase() + '-' + (id || 'undefined').replace(/\./g, '-');
}
function populateKey(item) {
var result = item;
result['_key'] = createKey(getNamespace(item), getName(item), getKind(item));
return result;
}
function populateKeys(items:Array<any>) {
var result = [];
angular.forEach(items, (item) => {
result.push(populateKey(item));
});
return result;
}
function selectPods(pods, namespace, labels) {
return pods.filter((pod) => {
return getNamespace(pod) === namespace && selectorMatches(labels, getLabels(pod));
});
}
/**
* The object which keeps track of all the pods, replication controllers, services and their associations
*/
export class KubernetesModelService {
public kubernetes = <KubernetesState> null;
public apps = [];
public services = [];
public replicationcontrollers = [];
public filterReplicationcontrollers = [];
/*public get filterReplicationcontrollers():Array<any> {
return this.filterReplicationcontrollers;
}
public set filterReplicationcontrollers(filterReplicationcontrollers:Array<any>) {
this.filterReplicationcontrollers = filterReplicationcontrollers;
}*/
public get replicationControllers():Array<any> {
return this.replicationcontrollers;
}
public set replicationControllers(replicationControllers:Array<any>) {
this.replicationcontrollers = replicationControllers;
}
public pods = [];
public hosts = [];
public get namespaces():Array<string> {
return this.kubernetes.namespaces;
}
//public namespaces = [];
public routes = [];
public templates = [];
public redraw = false;
public resourceVersions = {};
// various views on the data
public podsByHost = {};
public servicesByKey = {};
public replicationControllersByKey = {};
public podsByKey = {};
public appInfos = [];
public appViews = [];
public appFolders = [];
public fetched = false;
public get showRunButton():boolean {
if (isOpenShift) {
return true;
}
return _.any(this.services, (service) => {
var name = getName(service);
if (name === "templates") {
var podCounters = service.$podCounters;
return podCounters && (podCounters.valid || podCounters.ready);
} else {
return false;
}
});
}
public buildconfigs = [];
public events = [];
public workspaces = [];
public projects = [];
public project = null;
public get serviceApps():Array<any> {
return _.filter(this.services, (s) => {
return s.$host && s.$serviceUrl && s.$podCount
});
}
public $keepPolling() {
return keepPollingModel;
}
public orRedraw(flag) {
this.redraw = this.redraw || flag;
}
public getService(namespace, id) {
return this.servicesByKey[createKey(namespace, id, 'service')];
}
public getReplicationController(namespace, id) {
return this.replicationControllersByKey[createKey(namespace, id, 'replicationController')];
}
public getPod(namespace, id) {
return this.podsByKey[createKey(namespace, id, 'pod')];
}
public podsForNamespace(namespace = this.currentNamespace()) {
return _.filter(this.pods, { namespace: namespace });
}
public getBuildConfig(name) {
return _.find(this.buildconfigs, { $name: name });
}
public getProject(name, ns = this.currentNamespace()) {
var buildConfig = this.project;
if (!buildConfig) {
var text = localStorage[FABRIC8_PROJECT_JSON];
if (text) {
try {
buildConfig = angular.fromJson(text);
} catch (e) {
log.warn("Could not parse json for " + FABRIC8_PROJECT_JSON + ". Was: " + text + ". " + e, e);
}
}
}
if (buildConfig && ns != getNamespace(buildConfig) && name != buildConfig.$name) {
buildConfig = this.getBuildConfig(name);
}
return buildConfig;
}
public setProject(buildConfig) {
this.project = buildConfig;
if (buildConfig) {
// lets store in local storage
var localStorage = inject("localStorage");
if (localStorage) {
localStorage[FABRIC8_PROJECT_JSON] = angular.toJson(buildConfig);
}
}
}
/**
* Returns the current selected namespace or the default namespace
*/
public currentNamespace() {
var answer = null;
if (this.kubernetes) {
answer = this.kubernetes.selectedNamespace;
}
return answer || defaultNamespace;
}
protected updateIconUrlAndAppInfo(entity, nameField: string) {
var answer = null;
var id = getName(entity);
entity.$iconUrl = Core.pathGet(entity, ['metadata', 'annotations', 'fabric8.' + id + '/iconUrl']);
entity.$info = Core.pathGet(entity, ['metadata', 'annotations', 'fabric8.' + id + '/summary']);
if (entity.$iconUrl) {
return;
}
if (id && nameField) {
(this.templates || []).forEach((template) => {
var metadata = template.metadata;
if (metadata) {
var annotations = metadata.annotations || {};
var iconUrl = annotations["fabric8." + id + "/iconUrl"] || annotations["fabric8/iconUrl"];
if (iconUrl) {
(template.objects || []).forEach((item) => {
var entityName = getName(item);
if (id === entityName) {
entity.$iconUrl = iconUrl;
}
});
}
}
});
(this.appInfos || []).forEach((appInfo) => {
var iconPath = appInfo.iconPath;
if (iconPath && !answer && iconPath !== "null") {
var iconUrl = gitPathToUrl(iconPath);
var ids = Core.pathGet(appInfo, ["names", nameField]);
angular.forEach(ids, (appId) => {
if (appId === id) {
entity.$iconUrl = iconUrl;
entity.appPath = appInfo.appPath;
entity.$info = appInfo;
}
});
}
});
}
if (!entity.$iconUrl) {
entity.$iconUrl = defaultIconUrl;
}
}
public maybeInit() {
this.fetched = true;
this.servicesByKey = {};
this.podsByKey = {};
this.replicationControllersByKey = {};
this.pods.forEach((pod) => {
if (!pod.kind) pod.kind = "Pod";
this.podsByKey[pod._key] = pod;
var host = getHost(pod);
pod.$labelsText = Kubernetes.labelsToString(getLabels(pod));
if (host) {
pod.$labelsText += labelFilterTextSeparator + "host=" + host;
}
pod.$iconUrl = defaultIconUrl;
this.discoverPodConnections(pod);
pod.$containerPorts = [];
var podStatus = pod.status || {};
var startTime = podStatus.startTime;
pod.$startTime = null;
if (startTime) {
pod.$startTime = new Date(startTime);
}
var createdTime = getCreationTimestamp(pod);
pod.$createdTime = null;
pod.$age = null;
if (createdTime) {
pod.$createdTime = new Date(createdTime);
pod.$age = humandate.relativeTime(pod.$createdTime);
}
var ready = isReady(pod);
pod.$ready = ready;
pod.$statusCss = statusTextToCssClass(podStatus.phase, ready);
var maxRestartCount = 0;
angular.forEach(Core.pathGet(pod, ["status", "containerStatuses"]), (status) => {
var restartCount = status.restartCount;
if (restartCount) {
if (restartCount > maxRestartCount) {
maxRestartCount = restartCount;
}
}
});
if (maxRestartCount ) {
pod.$restartCount = maxRestartCount;
}
var imageNames = "";
angular.forEach(Core.pathGet(pod, ["spec", "containers"]), (container) => {
var image = container.image;
if (image) {
if (!imageNames) {
imageNames = image;
} else {
imageNames = imageNames + " " + image;
}
var idx = image.lastIndexOf(":");
if (idx > 0) {
image = image.substring(0, idx);
}
var paths = image.split("/", 3);
if (paths.length) {
var answer = null;
if (paths.length == 3) {
answer = paths[1] + "/" + paths[2];
} else if (paths.length == 2) {
answer = paths[0] + "/" + paths[1];
} else {
answer = paths[0] + "/" + paths[1];
}
container.$imageLink = UrlHelpers.join("https://registry.hub.docker.com/u/", answer);
}
}
angular.forEach(container.ports, (port) => {
var containerPort = port.containerPort;
if (containerPort) {
pod.$containerPorts.push(containerPort);
}
});
});
pod.$imageNames = imageNames;
var podStatus = podStatus;
var podSpec = (pod.spec || {});
pod.$podIP = podStatus.podIP;
pod.$host = podSpec.host || podSpec.nodeName || podStatus.hostIP;
});
this.services.forEach((service) => {
if (!service.kind) service.kind = "Service";
this.servicesByKey[service._key] = service;
var selector = getSelector(service);
service.$pods = [];
if (!service.$podCounters) {
service.$podCounters = {};
}
var podLinkUrl = UrlHelpers.join("/kubernetes/namespace", service.metadata.namespace, "pods");
_.assign(service.$podCounters, selector ? createPodCounters(selector, this.pods, service.$pods, Kubernetes.labelsToString(selector, ","), podLinkUrl) : {});
service.$podCount = service.$pods.length;
var selectedPods = service.$pods;
service.connectTo = selectedPods.map((pod) => {
return pod._key;
}).join(',');
service.$labelsText = Kubernetes.labelsToString(getLabels(service));
this.updateIconUrlAndAppInfo(service, "serviceNames");
var spec = service.spec || {};
service.$portalIP = spec.portalIP;
service.$selectorText = Kubernetes.labelsToString(spec.selector);
var ports = _.map(spec.ports || [], "port");
service.$ports = ports;
service.$portsText = ports.join(", ");
var iconUrl = service.$iconUrl;
if (iconUrl && selectedPods) {
selectedPods.forEach((pod) => {
pod.$iconUrl = iconUrl;
});
}
service.$serviceUrl = serviceLinkUrl(service);
});
this.replicationControllers.forEach((replicationController) => {
if (!replicationController.kind) replicationController.kind = "ReplicationController";
this.replicationControllersByKey[replicationController._key] = replicationController
var selector = getSelector(replicationController);
replicationController.$pods = [];
if(isFilterRC(replicationController) && !isInclude(this.filterReplicationcontrollers, replicationController))
this.filterReplicationcontrollers.push(replicationController);
replicationController.$podCounters = selector ? createPodCounters(selector, this.pods, replicationController.$pods) : null;
replicationController.$podCount = replicationController.$pods.length;
replicationController.$replicas = (replicationController.spec || {}).replicas;
replicationController.$oracleName = getOracleName(getName(replicationController));
//console.log(getName(replicationController));
replicationController.$oracleStatus = getOracleStatus(getLabels(replicationController));
replicationController.$extractStatus = getExtractStatus(getLabels(replicationController));
var selectedPods = replicationController.$pods;
replicationController.connectTo = selectedPods.map((pod) => {
return pod._key;
}).join(',');
replicationController.$labelsText = Kubernetes.labelsToString(getLabels(replicationController));
replicationController.metadata.labels = Kubernetes.labelToChinese(getLabels(replicationController));
this.updateIconUrlAndAppInfo(replicationController, "replicationControllerNames");
var iconUrl = replicationController.$iconUrl;
if (iconUrl && selectedPods) {
selectedPods.forEach((pod) => {
pod.$iconUrl = iconUrl;
});
}
});
// services may not map to an icon but their pods may do via the RC
// so lets default it...
this.services.forEach((service) => {
var iconUrl = service.$iconUrl;
var selectedPods = service.$pods;
if (selectedPods) {
if (!iconUrl || iconUrl === defaultIconUrl) {
iconUrl = null;
selectedPods.forEach((pod) => {
if (!iconUrl) {
iconUrl = pod.$iconUrl;
if (iconUrl) {
service.$iconUrl = iconUrl;
}
}
});
}
}
});
this.updateApps();
var podsByHost = {};
this.pods.forEach((pod) => {
var host = getHost(pod);
var podsForHost = podsByHost[host];
if (!podsForHost) {
podsForHost = [];
podsByHost[host] = podsForHost;
}
podsForHost.push(pod);
});
this.podsByHost = podsByHost;
var tmpHosts = [];
for (var hostKey in podsByHost) {
var hostPods = [];
var podCounters = createPodCounters((pod) => getHost(pod) === hostKey, this.pods, hostPods, "host=" + hostKey);
var hostIP = null;
if (hostPods.length) {
var pod = hostPods[0];
var currentState = pod.status;
if (currentState) {
hostIP = currentState.hostIP;
}
}
var hostDetails = {
name: hostKey,
id: hostKey,
elementId: hostKey.replace(/\./g, '_'),
hostIP: hostIP,
pods: hostPods,
kind: "Host",
$podCounters: podCounters,
$iconUrl: hostIconUrl
};
tmpHosts.push(hostDetails);
}
this.hosts = tmpHosts;
enrichBuildConfigs(this.buildconfigs);
enrichEvents(this.events, this);
}
protected updateApps() {
try {
// lets create the app views by trying to join controllers / services / pods that are related
var appViews = [];
this.replicationControllers.forEach((replicationController) => {
var name = getName(replicationController);
var $iconUrl = replicationController.$iconUrl;
appViews.push({
appPath: "/dummyPath/" + name,
$name: name,
$info: {
$iconUrl: $iconUrl
},
$iconUrl: $iconUrl,
replicationControllers: [replicationController],
pods: replicationController.$pods || [],
services: []
});
});
var noMatches = [];
this.services.forEach((service) => {
// now lets see if we can find an app with an RC of the same selector
var matchesApp = null;
appViews.forEach((appView) => {
appView.replicationControllers.forEach((replicationController) => {
var repSelector = getSelector(replicationController);
if (repSelector &&
selectorMatches(repSelector, getSelector(service)) &&
getNamespace(service) === getNamespace(replicationController)) {
matchesApp = appView;
}
});
});
if (matchesApp) {
matchesApp.services.push(service);
} else {
noMatches.push(service);
}
});
log.debug("no matches: ", noMatches);
noMatches.forEach((service) => {
var appView = _.find(appViews, (appView) => {
return _.any(appView.replicationControllers, (rc) => {
return _.startsWith(getName(rc), getName(service));
});
});
if (appView) {
appView.services.push(service);
} else {
var $iconUrl = service.$iconUrl;
appViews.push({
appPath: "/dummyPath/" + name,
$name: name,
$info: {
$iconUrl: $iconUrl
},
$iconUrl: $iconUrl,
replicationControllers: [],
pods: service.$pods || [],
services: [service]
});
}
});
angular.forEach(this.routes, (route) => {
var metadata = route.metadata || {};
var spec = route.spec || {};
var serviceName = Core.pathGet(spec, ["to", "name"]);
var host = spec.host;
var namespace = getNamespace(route);
if (serviceName && host) {
var service = this.getService(namespace, serviceName);
if (service) {
service.$host = host;
// TODO we could use some annotations / metadata to deduce what URL we should use to open this
// service in the console. For now just assume its http:
if (host) {
var hostUrl = host;
if (hostUrl.indexOf("://") < 0) {
hostUrl = "http://" + host;
}
service.$connectUrl = UrlHelpers.join(hostUrl, "/");
}
// TODO definitely need that annotation, temp hack for apiman link
if (getName(service) === 'apiman' && host) {
service.$connectUrl = (<any> new URI().host(service.$host)
.path('apimanui/index.html'))
.query({})
.hash(URI.encode(angular.toJson({
backTo: new URI().toString(),
token: HawtioOAuth.getOAuthToken()
}))).toString();
}
} else {
log.debug("Could not find service " + serviceName + " namespace " + namespace + " for route: " + metadata.name);
}
}
});
appViews = _.sortBy(populateKeys(appViews), (appView) => appView._key);
ArrayHelpers.sync(this.appViews, appViews, '$name');
if (this.appInfos && this.appViews) {
var folderMap = {};
var folders = [];
var appMap = {};
angular.forEach(this.appInfos, (appInfo) => {
if (!appInfo.$iconUrl && appInfo.iconPath && appInfo.iconPath !== "null") {
appInfo.$iconUrl = gitPathToUrl(appInfo.iconPath);
}
var appPath = appInfo.appPath;
if (appPath) {
appMap[appPath] = appInfo;
var idx = appPath.lastIndexOf("/");
var folderPath = "";
if (idx >= 0) {
folderPath = appPath.substring(0, idx);
}
folderPath = Core.trimLeading(folderPath, "/");
var folder = folderMap[folderPath];
if (!folder) {
folder = {
path: folderPath,
expanded: true,
apps: []
};
folders.push(folder);
folderMap[folderPath] = folder;
}
folder.apps.push(appInfo);
}
});
this.appFolders = _.sortBy(folders, "path");
var apps = [];
var defaultInfo = {
$iconUrl: defaultIconUrl
};
angular.forEach(this.appViews, (appView:any) => {
try {
var appPath = appView.appPath;
/*
TODO
appView.$select = () => {
Kubernetes.setJson($scope, appView.id, $scope.model.apps);
};
*/
var appInfo:any = angular.copy(defaultInfo);
if (appPath) {
appInfo = appMap[appPath] || appInfo;
}
if (!appView.$info) {
appView.$info = defaultInfo;
appView.$info = appInfo;
}
appView.id = appPath;
if (!appView.$name) {
appView.$name = appInfo.name || appView.$name;
}
if (!appView.$iconUrl) {
appView.$iconUrl = appInfo.$iconUrl;
}
apps.push(appView);
appView.$podCounters = createAppViewPodCounters(appView);
appView.$podCount = (appView.pods || []).length;
appView.$replicationControllersText = (appView.replicationControllers || []).map((i) => i["_key"]).join(" ");
appView.$servicesText= (appView.services || []).map((i) => i["_key"]).join(" ");
appView.$serviceViews = createAppViewServiceViews(appView);
} catch (e) {
log.warn("Failed to update appViews: " + e);
}
});
//this.apps = apps;
this.apps = this.appViews;
}
} catch (e) {
log.warn("Caught error: " + e);
}
}
protected discoverPodConnections(entity) {
var info = Core.pathGet(entity, ["status", "info"]);
var hostPort = null;
var currentState = entity.status || {};
var desiredState = entity.spec || {};
var podId = getName(entity);
var host = currentState["hostIP"];
var podIP = currentState["podIP"];
var hasDocker = false;
var foundContainerPort = null;
if (desiredState) {
var containers = desiredState.containers;
angular.forEach(containers, (container) => {
if (!hostPort) {
var ports = container.ports;
angular.forEach(ports, (port) => {
if (!hostPort) {
var containerPort = port.containerPort;
var portName = port.name;
var containerHostPort = port.hostPort;
if (containerPort === 8778 || "jolokia" === portName) {
if (containerPort) {
if (podIP) {
foundContainerPort = containerPort;
}
if (containerHostPort) {
hostPort = containerHostPort;
}
}
}
}
});
}
});
}
if (foundContainerPort && podId && isRunning(currentState)) {
if (!Kubernetes.isOpenShift) {
// TODO temp workaround for k8s on GKE https://github.com/kubernetes/kubernetes/issues/17172
entity.$jolokiaUrl = UrlHelpers.join(Kubernetes.masterApiUrl(),
"api",
Kubernetes.defaultApiVersion,
"proxy",
"namespaces",
entity.metadata.namespace ,
"pods",
//"https:" + podId + ":" + foundContainerPort,
podId + ":" + foundContainerPort,
"jolokia/");
} else {
entity.$jolokiaUrl = UrlHelpers.join(Kubernetes.masterApiUrl(),
"api",
Kubernetes.defaultApiVersion,
"namespaces",
entity.metadata.namespace ,
"pods",
"https:" + podId + ":" + foundContainerPort,
"proxy/jolokia/");
}
}
}
}
function getTemplateService(model) {
var key = createKey('default', 'templates', 'service');
var answer = model.servicesByKey[key];
log.debug("found template service: ", answer);
return answer;
}
/**
* Creates a model service which keeps track of all the pods, replication controllers and services along
* with their associations and status
*/
_module.factory('KubernetesModel', ['$rootScope', '$http', 'KubernetesApiURL', 'KubernetesState', 'WatcherService', '$location', '$resource', ($rootScope, $http, AppLibraryURL, KubernetesState, watcher:WatcherService, $location:ng.ILocationService, $resource:ng.resource.IResourceService) => {
var $scope = new KubernetesModelService();
$scope.kubernetes = KubernetesState;
// create all of our resource classes
var typeNames = watcher.getTypes();
_.forEach(typeNames, (type:string) => {
var urlTemplate = uriTemplateForKubernetesKind(type);
$scope[type + 'Resource'] = createResource(type, urlTemplate, $resource, $scope);
});
if (!isOpenShift) {
// register custom URL factories for templates/projects
watcher.registerCustomUrlFunction(KubernetesAPI.WatchTypes.BUILD_CONFIGS, (options:KubernetesAPI.K8SOptions) => {
var templateService = getTemplateService($scope);
if (templateService) {
return UrlHelpers.join(templateService.proxyUrl, '/oapi/v1/namespaces/default/buildconfigs/');
}
return null;
});
// register custom URL factories for templates/projects
watcher.registerCustomUrlFunction(KubernetesAPI.WatchTypes.TEMPLATES, (options:KubernetesAPI.K8SOptions) => {
var templateService = getTemplateService($scope);
if (templateService) {
return UrlHelpers.join(templateService.proxyUrl, '/oapi/v1/namespaces/default/templates/');
}
return null;
});
}
// register for all updates on objects
watcher.registerListener((objects:ObjectMap) => {
var types = watcher.getTypes();
_.forEach(types, (type:string) => {
switch (type) {
case WatchTypes.SERVICES:
var items = populateKeys(objects[type]);
angular.forEach(items, (item) => {
item.proxyUrl = kubernetesProxyUrlForService(kubernetesApiUrl(), item);
});
$scope[type] = items;
break;
case WatchTypes.TEMPLATES:
case WatchTypes.ROUTES:
case WatchTypes.BUILDS:
case WatchTypes.BUILD_CONFIGS:
case WatchTypes.IMAGE_STREAMS:
// don't put a break here :-)
default:
$scope[type] = populateKeys(objects[type]);
}
log.debug("Type: ", type, " object: ", $scope[type]);
});
$scope.maybeInit();
$rootScope.$broadcast('kubernetesModelUpdated', $scope);
Core.$apply($rootScope);
});
// set the selected namespace if set in the location bar
// otherwise use whatever previously selected namespace is
// available
var search = $location.search();
if ('namespace' in search) {
watcher.setNamespace(search['namespace']);
}
function selectPods(pods, namespace, labels) {
return pods.filter((pod) => {
return getNamespace(pod) === namespace && selectorMatches(labels, getLabels(pod));
});
}
return $scope;
}]);
}