提交 31d469eb 编写于 作者: M Marcin Maciaszczyk

Implemented initial version of events view (replica set detail page) with @floreks

上级 fc489d21
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// TODO(floreks): Find appropriate place for global color definitions.
$primary: #326DE6;
$line: #AAAAAA;
$subline: #888888;
......@@ -82,7 +82,7 @@ func CreateHttpApiHandler(client *client.Client) http.Handler {
eventsWs.Path("/api/events").
Produces(restful.MIME_JSON)
eventsWs.Route(
eventsWs.GET("/{namespace}").
eventsWs.GET("/{namespace}/{replicaSet}").
To(apiHandler.handleEvents).
Writes(Events{}))
wsContainer.Add(eventsWs)
......@@ -197,7 +197,8 @@ func (apiHandler *ApiHandler) handleLogs(request *restful.Request, response *res
// Handles event API call.
func (apiHandler *ApiHandler) handleEvents(request *restful.Request, response *restful.Response) {
namespace := request.PathParameter("namespace")
result, err := GetEvents(apiHandler.client, namespace)
replicaSet := request.PathParameter("replicaSet")
result, err := GetEvents(apiHandler.client, namespace, replicaSet)
if err != nil {
handleInternalError(response, err)
return
......
......@@ -15,6 +15,7 @@
package main
import (
api "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/fields"
......@@ -60,10 +61,65 @@ type Event struct {
Reason string `json:"reason"`
}
// Return events for particular namespace or error if occurred.
func GetEvents(client *client.Client, namespace string) (*Events, error) {
// Return events for particular namespace and replica set or error if occurred.
func GetEvents(client *client.Client, namespace string, replicaSetName string) (*Events, error) {
events := &Events{
Namespace: namespace,
}
// Get events for replica set.
rsEvents, err := GetReplicaSetEvents(client, namespace, replicaSetName)
if err != nil {
return nil, err
}
AppendEvents(rsEvents, events)
// Get events for pods in replica set.
podEvents, err := GetReplicaSetPodsEvents(client, namespace, replicaSetName)
if err != nil {
return nil, err
}
AppendEvents(podEvents, events)
return events, nil
}
// Gets events associated to replica set.
func GetReplicaSetEvents(client *client.Client, namespace, replicaSetName string) ([]api.Event,
error) {
fieldSelector, err := fields.ParseSelector("involvedObject.name=" + replicaSetName)
if err != nil {
return nil, err
}
list, err := client.Events(namespace).List(unversioned.ListOptions{
LabelSelector: unversioned.LabelSelector{labels.Everything()},
FieldSelector: unversioned.FieldSelector{fieldSelector},
})
if err != nil {
return nil, err
}
return list.Items, nil
}
// Gets events associated to pods in replica set.
func GetReplicaSetPodsEvents(client *client.Client, namespace, replicaSetName string) ([]api.Event,
error) {
replicaSet, err := client.ReplicationControllers(namespace).Get(replicaSetName)
if err != nil {
return nil, err
}
pods, err := client.Pods(namespace).List(unversioned.ListOptions{
LabelSelector: unversioned.LabelSelector{labels.SelectorFromSet(replicaSet.Spec.Selector)},
FieldSelector: unversioned.FieldSelector{fields.Everything()},
})
......@@ -71,12 +127,37 @@ func GetEvents(client *client.Client, namespace string) (*Events, error) {
return nil, err
}
events := &Events{
Namespace: namespace,
events := make([]api.Event, 0, 0)
for _, pod := range pods.Items {
fieldSelector, err := fields.ParseSelector("involvedObject.name=" + pod.Name)
if err != nil {
return nil, err
}
list, err := client.Events(namespace).List(unversioned.ListOptions{
LabelSelector: unversioned.LabelSelector{labels.Everything()},
FieldSelector: unversioned.FieldSelector{fieldSelector},
})
if err != nil {
return nil, err
}
for _, event := range list.Items {
events = append(events, event)
}
}
for _, element := range list.Items {
events.Events = append(events.Events, Event{
return events, nil
}
// Appends events from source slice to target events representation.
func AppendEvents(source []api.Event, target *Events) {
for _, element := range source {
target.Events = append(target.Events, Event{
Message: element.Message,
SourceComponent: element.Source.Component,
SourceHost: element.Source.Host,
......@@ -87,6 +168,4 @@ func GetEvents(client *client.Client, namespace string) (*Events, error) {
Reason: element.Reason,
})
}
return events, nil
}
......@@ -15,8 +15,11 @@
package main
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
)
// Detailed information about a Replica Set.
......@@ -44,6 +47,9 @@ type ReplicaSetDetail struct {
// Detailed information about Pods belonging to this Replica Set.
Pods []ReplicaSetPod `json:"pods"`
// Detailed information about service related to Replica Set.
Services []ServiceDetail `json:"services"`
}
// Detailed information about a Pod that belongs to a Replica Set.
......@@ -61,6 +67,22 @@ type ReplicaSetPod struct {
NodeName string `json:"nodeName"`
}
// Detailed information about a Service connected to Replica Set.
type ServiceDetail struct {
// Internal endpoints of all Kubernetes services that have the same label selector as connected
// Replica Set.
// Endpoint is DNS name merged with ports.
InternalEndpoint string `json:"internalEndpoint"`
// External endpoints of all Kubernetes services that have the same label selector as connected
// Replica Set.
// Endpoint is external IP address name merged with ports.
ExternalEndpoints []string `json:"externalEndpoints"`
// Label selector of the service.
Selector map[string]string `json:"selector"`
}
// Returns detailed information about the given replica set in the given namespace.
func GetReplicaSetDetail(client *client.Client, namespace string, name string) (
*ReplicaSetDetail, error) {
......@@ -72,6 +94,15 @@ func GetReplicaSetDetail(client *client.Client, namespace string, name string) (
replicaSet := replicaSetWithPods.ReplicaSet
pods := replicaSetWithPods.Pods
services, err := client.Services(namespace).List(unversioned.ListOptions{
LabelSelector: unversioned.LabelSelector{labels.Everything()},
FieldSelector: unversioned.FieldSelector{fields.Everything()},
})
if err != nil {
return nil, err
}
replicaSetDetail := &ReplicaSetDetail{
Name: replicaSet.Name,
Namespace: replicaSet.Namespace,
......@@ -81,6 +112,12 @@ func GetReplicaSetDetail(client *client.Client, namespace string, name string) (
PodsDesired: replicaSet.Spec.Replicas,
}
matchingServices := getMatchingServices(services.Items, replicaSet)
for _, service := range matchingServices {
replicaSetDetail.Services = append(replicaSetDetail.Services, getServiceDetail(service))
}
for _, container := range replicaSet.Spec.Template.Spec.Containers {
replicaSetDetail.ContainerImages = append(replicaSetDetail.ContainerImages, container.Image)
}
......@@ -97,3 +134,20 @@ func GetReplicaSetDetail(client *client.Client, namespace string, name string) (
return replicaSetDetail, nil
}
func getServiceDetail(service api.Service) ServiceDetail {
var externalEndpoints []string
for _, externalIp := range service.Status.LoadBalancer.Ingress {
externalEndpoints = append(externalEndpoints,
getExternalEndpoint(externalIp.Hostname, service.Spec.Ports))
}
serviceDetail := ServiceDetail{
InternalEndpoint: getInternalEndpoint(service.Name, service.Namespace,
service.Spec.Ports),
ExternalEndpoints: externalEndpoints,
Selector: service.Spec.Selector,
}
return serviceDetail
}
......@@ -47,6 +47,28 @@ backendApi.PortMapping;
*/
backendApi.AppDeploymentSpec;
/**
* @typedef {{
* namespace: string,
* events: !Array<!backendApi.Event>
* }}
*/
backendApi.Events;
/**
* @typedef {{
* message: string,
* sourceComponent: string,
* sourceHost: string,
* object: string,
* count: number,
* firstSeen: string,
* lastSeen: string,
* reason: string
* }}
*/
backendApi.Event;
/**
* @typedef {{
* replicaSets: !Array<!backendApi.ReplicaSet>
......@@ -79,7 +101,8 @@ backendApi.ReplicaSet;
* containerImages: !Array<string>,
* podsDesired: number,
* podsRunning: number,
* pods: !Array<!backendApi.ReplicaSetPod>
* pods: !Array<!backendApi.ReplicaSetPod>,
* services: !Array<!backendApi.ServiceDetail>
* }}
*/
backendApi.ReplicaSetDetail;
......@@ -94,6 +117,15 @@ backendApi.ReplicaSetDetail;
*/
backendApi.ReplicaSetPod;
/**
* @typedef {{
* internalEndpoint: string,
* externalEndpoints: !Array<string>,
* selector: !Object<string, string>
* }}
*/
backendApi.ServiceDetail;
/**
* @typedef {{
* name: string
......
......@@ -19,11 +19,15 @@
export default function config($mdThemingProvider) {
// Create a color palette that uses Kubernetes colors.
let kubernetesColorPaletteName = 'kubernetesColorPalette';
let kubernetesAccentPaletteName = 'kubernetesAccentPallete';
let kubernetesColorPalette = $mdThemingProvider.extendPalette('blue', {
'500': '326de6',
});
// Use the palette as default one.
$mdThemingProvider.definePalette(kubernetesColorPaletteName, kubernetesColorPalette);
$mdThemingProvider.theme('default').primaryPalette(kubernetesColorPaletteName);
$mdThemingProvider.definePalette(kubernetesAccentPaletteName, kubernetesColorPalette);
$mdThemingProvider.theme('default')
.primaryPalette(kubernetesColorPaletteName)
.accentPalette(kubernetesAccentPaletteName);
}
......@@ -14,27 +14,177 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<div layout="column" layout-margin layout-align="center center">
Displaying details for replica set {{ctrl.replicaSetDetail.name}} in namespace
{{ctrl.replicaSetDetail.namespace}}.
<div>
<table>
<thead>
<tr>
<th>Pod</th>
<th>Start time</th>
<th>IP</th>
<th>Node</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="pod in ctrl.replicaSetDetail.pods">
<td>{{pod.name}}</td>
<td>{{pod.startTime}}</td>
<td>{{pod.podIP}}</td>
<td>{{pod.nodeName}}</td>
</tr>
</tbody>
</table>
<md-content layout="row" layout-fill flex>
<div class="kd-replicasetdetail-sidebar" layout="column">
<div class="kd-replicasetdetail-sidebar-header" flex layout-align="start center" layout="row">
<a flex-offset="10" ui-sref="replicasets">
<i class="material-icons kd-replicasetdetail-sidebar-hedaer-icon">keyboard_backspace</i>
</a>
<span flex-offset="5" flex class="md-title">
{{ctrl.replicaSetDetail.name}}
</span>
</div>
<div flex-offset="5" flex layout-align="start start" layout-padding layout="row">
<div flex layout="column">
<div layout="row" class="kd-replicasetdetail-sidebar-buttons">
<span flex="55">
<i class="material-icons kd-replicasetdetail-sidebar-icon">refresh</i>
ROLLING UPDATE
</span>
<span flex-offset="5" flex>
<i class="material-icons kd-replicasetdetail-sidebar-icon">delete</i>
DELETE
</span>
</div>
<div flex layout="column" class="kd-replicasetdetail-sidebar-info">
<span class="kd-replicasetdetail-sidebar-title">
{{ctrl.replicaSetDetail.pods.length}} pods
<i class="material-icons kd-replicasetdetail-sidebar-icon">
mode_edit
</i>
</span>
<span class="kd-replicasetdetail-sidebar-line">Label selector</span>
<span class="kd-replicasetdetail-sidebar-subline">
{{ctrl.formatLabelString(ctrl.replicaSetDetail.labelSelector)}}
</span>
<span class="kd-replicasetdetail-sidebar-title">Replica Sets</span>
<span class="kd-replicasetdetail-sidebar-line">Labels</span>
<span class="kd-replicasetdetail-sidebar-subline">
{{ctrl.formatLabelString(ctrl.replicaSetDetail.labels)}}
</span>
<span class="kd-replicasetdetail-sidebar-line">Images</span>
<span class="kd-replicasetdetail-sidebar-subline"
ng-repeat="image in ctrl.replicaSetDetail.containerImages">
{{image}}
</span>
<span class="kd-replicasetdetail-sidebar-title">Service</span>
<span class="kd-replicasetdetail-sidebar-line">Label selector</span>
<span class="kd-replicasetdetail-sidebar-subline"
ng-repeat="service in ctrl.replicaSetDetail.services">
{{ctrl.formatLabelString(service.selector)}}
</span>
<span class="kd-replicasetdetail-sidebar-line">Internal endpoint</span>
<div class="kd-replicasetdetail-sidebar-subline"
ng-repeat="service in ctrl.replicaSetDetail.services">
<span ng-show="service.internalEndpoint">
{{service.internalEndpoint}}
</span>
<i ng-show="service.internalEndpoint"
class="material-icons kd-replicasetdetail-sidebar-service-icon">
help
</i>
<span ng-hide="service.internalEndpoint">none</span>
</div>
<span class="kd-replicasetdetail-sidebar-line">External endpoint</span>
<div class="kd-replicasetdetail-sidebar-subline"
ng-repeat="service in ctrl.replicaSetDetail.services">
<span ng-show="service.externalEndpoints">
{{service.externalEndpoints}}
</span>
<i ng-show="service.externalEndpoints"
class="material-icons kd-replicasetdetail-sidebar-service-icon">open_in_new</i>
<span class="kd-replicasetdetail-sidebar-subline" ng-hide="service.externalEndpoints">
none
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<md-tabs flex md-border-bottom md-dynamic-height>
<md-tab label="Pods">
<md-content>
<table class="kd-replicasetdetail-table" cellspacing="0" cellpadding="15">
<thead>
<tr>
<th class="kd-replicasetdetail-table-cell">
Pod<i class="material-icons kd-replicasetdetail-table-icon">help</i>
</th>
<th class="kd-replicasetdetail-table-cell">
Start time<i class="material-icons kd-replicasetdetail-table-icon">help</i>
</th>
<th class="kd-replicasetdetail-table-cell">
<i class="material-icons kd-replicasetdetail-table-icon">arrow_downward</i>
IP
<i class="material-icons kd-replicasetdetail-table-icon">help</i>
</th>
<th class="kd-replicasetdetail-table-cell">
Node<i class="material-icons kd-replicasetdetail-table-icon">help</i>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="pod in ctrl.replicaSetDetail.pods">
<td class="kd-replicasetdetail-table-cell">{{pod.name}}</td>
<td class="kd-replicasetdetail-table-cell">{{pod.startTime | date:'short'}}</td>
<td class="kd-replicasetdetail-table-cell">{{pod.podIP}}</td>
<td class="kd-replicasetdetail-table-cell">{{pod.nodeName}}</td>
</tr>
</tbody>
</table>
</md-content>
</md-tab>
<md-tab label="Events">
<md-content flex>
<div class="kd-replicasetdetail-options" layout="row">
<md-input-container class="kd-replicasetdetail-option-picker">
<label>Type</label>
<!-- TODO(maciaszczykm): Handle value change. -->
<md-select ng-model="ctrl.eventType" required>
<md-option ng-repeat="type in ctrl.eventTypeFilter" ng-value="type">
{{type}}
</md-option>
</md-select>
</md-input-container>
<md-input-container class="kd-replicasetdetail-option-picker">
<label>Source</label>
<!-- TODO(maciaszczykm): Handle value change. -->
<md-select ng-model="ctrl.eventSource" required>
<md-option ng-repeat="source in ctrl.eventSourceFilter" ng-value="source">
{{source}}
</md-option>
</md-select>
</md-input-container>
</div>
<table class="kd-replicasetdetail-table" cellspacing="0" cellpadding="15">
<thead>
<tr>
<th class="kd-replicasetdetail-table-cell">
Message<i class="material-icons kd-replicasetdetail-table-icon">help</i>
</th>
<th class="kd-replicasetdetail-table-cell">
Source<i class="material-icons kd-replicasetdetail-table-icon">help</i>
</th>
<th class="kd-replicasetdetail-table-cell">
Sub-object<i class="material-icons kd-replicasetdetail-table-icon">help</i>
</th>
<th class="kd-replicasetdetail-table-cell">
Count<i class="material-icons kd-replicasetdetail-table-icon">help</i></th>
<th class="kd-replicasetdetail-table-cell">
First seen<i class="material-icons kd-replicasetdetail-table-icon">help</i>
</th>
<th class="kd-replicasetdetail-table-cell">
<i class="material-icons kd-replicasetdetail-table-icon">arrow_downward</i>
Last seen
<i class="material-icons kd-replicasetdetail-table-icon">help</i>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="event in ctrl.replicaSetEvents.events">
<td class="kd-replicasetdetail-table-cell kd-replicasetdetail-table-message">
{{event.message}}
</td>
<td class="kd-replicasetdetail-table-cell">
{<!>{{event.sourceComponent}} {{event.sourceHost}}<!>}
</td>
<td class="kd-replicasetdetail-table-cell">{{event.object}}</td>
<td class="kd-replicasetdetail-table-cell">{{event.count}}</td>
<td class="kd-replicasetdetail-table-cell">{{event.firstSeen | date:'short'}}</td>
<td class="kd-replicasetdetail-table-cell">{{event.lastSeen | date:'short'}}</td>
</tr>
</tbody>
</table>
</md-tab>
</md-tabs>
</md-content>
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
@import "../../assets/styles/color_variables";
$replicasetdetails-sidebar-bg: #FAFAFA;
$replicasetdetails-border: #DDDDDD;
$replicasetdetails-table-message: black;
$replicasetdetails-table-cell: #777777;
.kd-replicasetdetail-sidebar {
background-color: $replicasetdetails-sidebar-bg;
min-width: 315px;
}
.kd-replicasetdetail-table {
width: 100%;
}
.kd-replicasetdetail-sidebar-header {
color: dimgray;
border-bottom: 1px solid $replicasetdetails-border;
min-height: 49px;
max-height: 49px;
}
.kd-replicasetdetail-sidebar-hedaer-icon {
font-size: 1.7em;
vertical-align: middle;
}
.kd-replicasetdetail-table-message {
color: #000000;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.kd-replicasetdetail-sidebar-title {
padding-bottom: 10px;
padding-top: 10px;
font-size: 15px;
}
.kd-replicasetdetail-sidebar-line {
padding-bottom: 6px;
font-size: 11px;
color: $line
}
.kd-replicasetdetail-sidebar-subline {
padding-bottom: 8px;
font-size: 13px;
color: $subline
}
.kd-replicasetdetail-sidebar-icon {
color: $primary;
padding: 0 3px 0 3px;
vertical-align: top;
font-size: 20px;
}
.kd-replicasetdetail-sidebar-service-icon {
color: $subline;
padding: 0 3px 0 3px;
vertical-align: top;
font-size: 14px;
}
.kd-replicasetdetail-sidebar-buttons {
color: $primary;
font-size: 0.85em;
}
.kd-replicasetdetail-options {
margin: 15px;
}
.kd-replicasetdetail-sidebar-info {
padding-left: 5px;
}
.kd-replicasetdetail-option-picker {
min-width: 150px;
padding-right: 25px;
}
.kd-replicasetdetail-table-cell {
text-align: left;
color: $replicasetdetails-table-cell;
font-size: 13px;
border-bottom: 1px solid $replicasetdetails-border;
}
.kd-replicasetdetail-table-message {
color: $replicasetdetails-table-message;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.kd-replicasetdetail-table-icon {
font-size: 14px;
padding: 0 3px 0 3px;
vertical-align: top;
}
\ No newline at end of file
......@@ -20,12 +20,39 @@
export default class ReplicaSetDetailController {
/**
* @param {!backendApi.ReplicaSetDetail} replicaSetDetail
* @param {!backendApi.Events} replicaSetEvents
* @ngInject
*/
constructor(replicaSetDetail) {
/**
* @export {!backendApi.ReplicaSetDetail}
*/
constructor(replicaSetDetail, replicaSetEvents) {
/** @export {!backendApi.ReplicaSetDetail} */
this.replicaSetDetail = replicaSetDetail;
/** @export {!backendApi.Events} */
this.replicaSetEvents = replicaSetEvents;
/** @const @export {!Array<string>} */
this.eventTypeFilter = ['All', 'Warning'];
/** @export {string} */
this.eventType = this.eventTypeFilter[0];
/** @const @export {!Array<string>} */
this.eventSourceFilter = ['All', 'User', 'System'];
/** @export {string} */
this.eventSource = this.eventSourceFilter[0];
}
/**
* TODO(floreks): Reuse this in replicasetlist controller.
* Formats labels object to readable string.
* @param {!Object} object
* @return {string}
* @export
*/
formatLabelString(object) {
let result = '';
angular.forEach(object, function(value, key) { result += `,${key}=${value}`; });
return result.substring(1);
}
}
......@@ -51,6 +51,7 @@ export default function stateConfig($stateProvider) {
templateUrl: 'replicasetdetail/replicasetdetail.html',
resolve: {
replicaSetDetail: resolveReplicaSetDetails,
replicaSetEvents: resolveReplicaSetEvents,
},
});
}
......@@ -67,3 +68,16 @@ export function resolveReplicaSetDetails($stateParams, $resource) {
return resource.get().$promise;
}
/**
* @param {!StateParams} $stateParams
* @param {!angular.$resource} $resource
* @return {!angular.$q.Promise}
* @ngInject
*/
export function resolveReplicaSetEvents($stateParams, $resource) {
/** @type {!angular.Resource<!backendApi.Events>} */
let resource = $resource('/api/events/:namespace/:replicaSet', $stateParams);
return resource.get().$promise;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册