提交 d6c3b563 编写于 作者: P Piotr Bryk

Merge pull request #99 from bryk/match-service

Replica set list: handle endpoints and descirption
...@@ -26,7 +26,7 @@ import goCommand from './gocommand'; ...@@ -26,7 +26,7 @@ import goCommand from './gocommand';
* Compiles backend application in development mode and places the binary in the serve * Compiles backend application in development mode and places the binary in the serve
* directory. * directory.
*/ */
gulp.task('backend', function(doneFn) { gulp.task('backend', ['package-backend-source'], function(doneFn) {
goCommand( goCommand(
[ [
'build', 'build',
...@@ -46,7 +46,7 @@ gulp.task('backend', function(doneFn) { ...@@ -46,7 +46,7 @@ gulp.task('backend', function(doneFn) {
* The production binary difference from development binary is only that it contains all * The production binary difference from development binary is only that it contains all
* dependencies inside it and is targeted for Linux. * dependencies inside it and is targeted for Linux.
*/ */
gulp.task('backend:prod', function(doneFn) { gulp.task('backend:prod', ['package-backend-source'], function(doneFn) {
let outputBinaryPath = path.join(conf.paths.dist, conf.backend.binaryName); let outputBinaryPath = path.join(conf.paths.dist, conf.backend.binaryName);
// Delete output binary first. This is required because prod build does not override it. // Delete output binary first. This is required because prod build does not override it.
...@@ -72,3 +72,16 @@ gulp.task('backend:prod', function(doneFn) { ...@@ -72,3 +72,16 @@ gulp.task('backend:prod', function(doneFn) {
}, },
function(error) { doneFn(error); }); function(error) { doneFn(error); });
}); });
/**
* Moves all backend source files (app and tests) to a temporary package directory where it can be
* applied go commands.
*
* This is required to consolidate test and app files into single directories and to make packaging
* work.
*/
gulp.task('package-backend-source', function() {
return gulp
.src([path.join(conf.paths.backendSrc, '**/*'), path.join(conf.paths.backendTest, '**/*')])
.pipe(gulp.dest(conf.paths.backendTmpSrc));
});
...@@ -37,11 +37,7 @@ export default { ...@@ -37,11 +37,7 @@ export default {
/** /**
* Name of the main backend package that is used in go build command. * Name of the main backend package that is used in go build command.
*/ */
packageName: 'app/backend', packageName: 'github.com/kubernetes/dashboard',
/**
* Name of the test backend package that is used in go test command.
*/
testPackageName: 'test/backend',
/** /**
* Port number of the backend server. Only used during development. * Port number of the backend server. Only used during development.
*/ */
...@@ -86,6 +82,7 @@ export default { ...@@ -86,6 +82,7 @@ export default {
backendSrc: path.join(basePath, 'src/app/backend'), backendSrc: path.join(basePath, 'src/app/backend'),
backendTest: path.join(basePath, 'src/test/backend'), backendTest: path.join(basePath, 'src/test/backend'),
backendTmp: path.join(basePath, '.tmp/backend'), backendTmp: path.join(basePath, '.tmp/backend'),
backendTmpSrc: path.join(basePath, '.tmp/backend/src/github.com/kubernetes/dashboard'),
bowerComponents: path.join(basePath, 'bower_components'), bowerComponents: path.join(basePath, 'bower_components'),
build: path.join(basePath, 'build'), build: path.join(basePath, 'build'),
deploySrc: path.join(basePath, 'src/app/deploy'), deploySrc: path.join(basePath, 'src/app/deploy'),
......
...@@ -21,7 +21,8 @@ import lodash from 'lodash'; ...@@ -21,7 +21,8 @@ import lodash from 'lodash';
import conf from './conf'; import conf from './conf';
/** /**
* Spawns Go process wrapped with the Godep command. * Spawns Go process wrapped with the Godep command. Backend source files must me packaged with
* 'package-backend-source' task before running this command.
* *
* @param {!Array<string>} args * @param {!Array<string>} args
* @param {function(?Error=)} doneFn * @param {function(?Error=)} doneFn
...@@ -33,7 +34,7 @@ export default function spawnGoProcess(args, doneFn) { ...@@ -33,7 +34,7 @@ export default function spawnGoProcess(args, doneFn) {
return; return;
} }
let sourceGopath = `${process.env.GOPATH}:${conf.paths.base}`; let sourceGopath = `${process.env.GOPATH}:${conf.paths.backendTmp}`;
let env = lodash.merge(process.env, {GOPATH: sourceGopath}); let env = lodash.merge(process.env, {GOPATH: sourceGopath});
let goTask = child.spawn('godep', ['go'].concat(args), { let goTask = child.spawn('godep', ['go'].concat(args), {
......
...@@ -28,7 +28,7 @@ import goCommand from './gocommand'; ...@@ -28,7 +28,7 @@ import goCommand from './gocommand';
* @param {boolean} singleRun * @param {boolean} singleRun
* @param {function(?Error=)} doneFn * @param {function(?Error=)} doneFn
*/ */
function runUnitTests(singleRun, doneFn) { function runFrontendUnitTests(singleRun, doneFn) {
let localConfig = { let localConfig = {
configFile: conf.paths.karmaConf, configFile: conf.paths.karmaConf,
singleRun: singleRun, singleRun: singleRun,
...@@ -41,18 +41,6 @@ function runUnitTests(singleRun, doneFn) { ...@@ -41,18 +41,6 @@ function runUnitTests(singleRun, doneFn) {
server.start(); server.start();
} }
/**
* @param {function(?Error=)} doneFn
*/
function runBackendTests(doneFn) {
goCommand(
[
'test',
conf.backend.testPackageName,
],
doneFn);
}
/** /**
* @param {function(?Error=)} doneFn * @param {function(?Error=)} doneFn
*/ */
...@@ -86,12 +74,19 @@ gulp.task('test', ['frontend-test', 'backend-test']); ...@@ -86,12 +74,19 @@ gulp.task('test', ['frontend-test', 'backend-test']);
/** /**
* Runs once all unit tests of the frontend application. * Runs once all unit tests of the frontend application.
*/ */
gulp.task('frontend-test', function(doneFn) { runUnitTests(true, doneFn); }); gulp.task('frontend-test', function(doneFn) { runFrontendUnitTests(true, doneFn); });
/** /**
* Runs once all unit tests of the backend application. * Runs once all unit tests of the backend application.
*/ */
gulp.task('backend-test', runBackendTests); gulp.task('backend-test', ['package-backend-source'], function(doneFn) {
goCommand(
[
'test',
conf.backend.packageName,
],
doneFn);
});
/** /**
* Runs all unit tests of the application. Watches for changes in the source files to rerun * Runs all unit tests of the application. Watches for changes in the source files to rerun
...@@ -103,7 +98,7 @@ gulp.task('test:watch', ['frontend-test:watch', 'backend-test:watch']); ...@@ -103,7 +98,7 @@ gulp.task('test:watch', ['frontend-test:watch', 'backend-test:watch']);
* Runs frontend backend application tests. Watches for changes in the source files to rerun * Runs frontend backend application tests. Watches for changes in the source files to rerun
* the tests. * the tests.
*/ */
gulp.task('frontend-test:watch', function(doneFn) { runUnitTests(false, doneFn); }); gulp.task('frontend-test:watch', function(doneFn) { runFrontendUnitTests(false, doneFn); });
/** /**
* Runs backend application tests. Watches for changes in the source files to rerun * Runs backend application tests. Watches for changes in the source files to rerun
......
...@@ -62,7 +62,7 @@ func CreateHttpApiHandler(client *client.Client) http.Handler { ...@@ -62,7 +62,7 @@ func CreateHttpApiHandler(client *client.Client) http.Handler {
namespacesWs.Route( namespacesWs.Route(
namespacesWs.GET(""). namespacesWs.GET("").
To(apiHandler.handleGetNamespaces). To(apiHandler.handleGetNamespaces).
Writes(NamespacesList{})) Writes(NamespaceList{}))
wsContainer.Add(namespacesWs) wsContainer.Add(namespacesWs)
logsWs := new(restful.WebService) logsWs := new(restful.WebService)
......
...@@ -20,6 +20,11 @@ import ( ...@@ -20,6 +20,11 @@ import (
"k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util"
) )
const (
DescriptionAnnotationKey = "description"
NameLabelKey = "name"
)
// Specification for an app deployment. // Specification for an app deployment.
type AppDeploymentSpec struct { type AppDeploymentSpec struct {
// Name of the application. // Name of the application.
...@@ -38,6 +43,9 @@ type AppDeploymentSpec struct { ...@@ -38,6 +43,9 @@ type AppDeploymentSpec struct {
// Whether the created service is external. // Whether the created service is external.
IsExternal bool `json:"isExternal"` IsExternal bool `json:"isExternal"`
// Description of the deployment.
Description string `json:"description"`
// Target namespace of the application. // Target namespace of the application.
Namespace string `json:"namespace"` Namespace string `json:"namespace"`
} }
...@@ -59,10 +67,16 @@ type PortMapping struct { ...@@ -59,10 +67,16 @@ type PortMapping struct {
// common labels. // common labels.
// TODO(bryk): Write tests for this function. // TODO(bryk): Write tests for this function.
func DeployApp(spec *AppDeploymentSpec, client *client.Client) error { func DeployApp(spec *AppDeploymentSpec, client *client.Client) error {
annotations := map[string]string{DescriptionAnnotationKey: spec.Description}
labels := map[string]string{NameLabelKey: spec.Name}
objectMeta := api.ObjectMeta{
Annotations: annotations,
Name: spec.Name,
Labels: labels,
}
podTemplate := &api.PodTemplateSpec{ podTemplate := &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{ ObjectMeta: objectMeta,
Labels: map[string]string{"name": spec.Name},
},
Spec: api.PodSpec{ Spec: api.PodSpec{
Containers: []api.Container{{ Containers: []api.Container{{
Name: spec.Name, Name: spec.Name,
...@@ -72,12 +86,10 @@ func DeployApp(spec *AppDeploymentSpec, client *client.Client) error { ...@@ -72,12 +86,10 @@ func DeployApp(spec *AppDeploymentSpec, client *client.Client) error {
} }
replicaSet := &api.ReplicationController{ replicaSet := &api.ReplicationController{
ObjectMeta: api.ObjectMeta{ ObjectMeta: objectMeta,
Name: spec.Name,
},
Spec: api.ReplicationControllerSpec{ Spec: api.ReplicationControllerSpec{
Replicas: spec.Replicas, Replicas: spec.Replicas,
Selector: map[string]string{"name": spec.Name}, Selector: labels,
Template: podTemplate, Template: podTemplate,
}, },
} }
...@@ -91,12 +103,9 @@ func DeployApp(spec *AppDeploymentSpec, client *client.Client) error { ...@@ -91,12 +103,9 @@ func DeployApp(spec *AppDeploymentSpec, client *client.Client) error {
if len(spec.PortMappings) > 0 { if len(spec.PortMappings) > 0 {
service := &api.Service{ service := &api.Service{
ObjectMeta: api.ObjectMeta{ ObjectMeta: objectMeta,
Name: spec.Name,
Labels: map[string]string{"name": spec.Name},
},
Spec: api.ServiceSpec{ Spec: api.ServiceSpec{
Selector: map[string]string{"name": spec.Name}, Selector: labels,
}, },
} }
......
...@@ -28,7 +28,7 @@ type NamespaceSpec struct { ...@@ -28,7 +28,7 @@ type NamespaceSpec struct {
} }
// List of Namespaces in the cluster. // List of Namespaces in the cluster.
type NamespacesList struct { type NamespaceList struct {
// Unordered list of Namespaces. // Unordered list of Namespaces.
Namespaces []string `json:"namespaces"` Namespaces []string `json:"namespaces"`
} }
...@@ -47,14 +47,14 @@ func CreateNamespace(spec *NamespaceSpec, client *client.Client) error { ...@@ -47,14 +47,14 @@ func CreateNamespace(spec *NamespaceSpec, client *client.Client) error {
} }
// Returns a list of all namespaces in the cluster. // Returns a list of all namespaces in the cluster.
func GetNamespaceList(client *client.Client) (*NamespacesList, error) { func GetNamespaceList(client *client.Client) (*NamespaceList, error) {
list, err := client.Namespaces().List(labels.Everything(), fields.Everything()) list, err := client.Namespaces().List(labels.Everything(), fields.Everything())
if err != nil { if err != nil {
return nil, err return nil, err
} }
namespaceList := &NamespacesList{} namespaceList := &NamespaceList{}
for _, element := range list.Items { for _, element := range list.Items {
namespaceList.Namespaces = append(namespaceList.Namespaces, element.ObjectMeta.Name) namespaceList.Namespaces = append(namespaceList.Namespaces, element.ObjectMeta.Name)
......
...@@ -20,6 +20,8 @@ import ( ...@@ -20,6 +20,8 @@ import (
client "k8s.io/kubernetes/pkg/client/unversioned" client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/labels"
"strconv"
"strings"
) )
// List of Replica Sets in the cluster. // List of Replica Sets in the cluster.
...@@ -56,47 +58,131 @@ type ReplicaSet struct { ...@@ -56,47 +58,131 @@ type ReplicaSet struct {
CreationTime unversioned.Time `json:"creationTime"` CreationTime unversioned.Time `json:"creationTime"`
// Internal endpoints of all Kubernetes services have the same label selector as this Replica Set. // Internal endpoints of all Kubernetes services have the same label selector as this Replica Set.
// Endpoint is DNS name merged with ports.
InternalEndpoints []string `json:"internalEndpoints"` InternalEndpoints []string `json:"internalEndpoints"`
// External endpoints of all Kubernetes services have the same label selector as this Replica Set. // External endpoints of all Kubernetes services have the same label selector as this Replica Set.
// Endpoint is external IP address name merged with ports.
ExternalEndpoints []string `json:"externalEndpoints"` ExternalEndpoints []string `json:"externalEndpoints"`
} }
// Returns a list of all Replica Sets in the cluster. // Returns a list of all Replica Sets in the cluster.
func GetReplicaSetList(client *client.Client) (*ReplicaSetList, error) { func GetReplicaSetList(client *client.Client) (*ReplicaSetList, error) {
list, err := client.ReplicationControllers(api.NamespaceAll). replicaSets, err := client.ReplicationControllers(api.NamespaceAll).
List(labels.Everything(), fields.Everything()) List(labels.Everything(), fields.Everything())
if err != nil { if err != nil {
return nil, err return nil, err
} }
services, err := client.Services(api.NamespaceAll).
List(labels.Everything(), fields.Everything())
if err != nil {
return nil, err
}
return getReplicaSetList(replicaSets.Items, services.Items), nil
}
// Returns a list of all Replica Set model objects in the cluster, based on all Kubernetes
// Replica Set and Service API objects.
// The function processes all Replica Sets API objects and finds matching Services for them.
func getReplicaSetList(
replicaSets []api.ReplicationController, services []api.Service) *ReplicaSetList {
replicaSetList := &ReplicaSetList{} replicaSetList := &ReplicaSetList{}
for _, replicaSet := range list.Items { for _, replicaSet := range replicaSets {
var containerImages []string var containerImages []string
for _, container := range replicaSet.Spec.Template.Spec.Containers { for _, container := range replicaSet.Spec.Template.Spec.Containers {
containerImages = append(containerImages, container.Image) containerImages = append(containerImages, container.Image)
} }
matchingServices := getMatchingServices(services, &replicaSet)
var internalEndpoints []string
var externalEndpoints []string
for _, service := range matchingServices {
internalEndpoints = append(internalEndpoints,
getInternalEndpoint(service.Name, service.Namespace, service.Spec.Ports))
for _, externalIp := range service.Status.LoadBalancer.Ingress {
externalEndpoints = append(externalEndpoints,
getExternalEndpoint(externalIp.Hostname, service.Spec.Ports))
}
}
replicaSetList.ReplicaSets = append(replicaSetList.ReplicaSets, ReplicaSet{ replicaSetList.ReplicaSets = append(replicaSetList.ReplicaSets, ReplicaSet{
Name: replicaSet.ObjectMeta.Name, Name: replicaSet.ObjectMeta.Name,
Namespace: replicaSet.ObjectMeta.Namespace, Namespace: replicaSet.ObjectMeta.Namespace,
// TODO(bryk): This field contains test value. Implement it. Description: replicaSet.Annotations[DescriptionAnnotationKey],
Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
"Nulla metus nibh, iaculis a consectetur vitae, imperdiet pellentesque turpis.",
Labels: replicaSet.ObjectMeta.Labels, Labels: replicaSet.ObjectMeta.Labels,
PodsRunning: replicaSet.Status.Replicas, PodsRunning: replicaSet.Status.Replicas,
PodsPending: replicaSet.Spec.Replicas - replicaSet.Status.Replicas, PodsPending: replicaSet.Spec.Replicas - replicaSet.Status.Replicas,
ContainerImages: containerImages, ContainerImages: containerImages,
CreationTime: replicaSet.ObjectMeta.CreationTimestamp, CreationTime: replicaSet.ObjectMeta.CreationTimestamp,
// TODO(bryk): This field contains test value. Implement it. InternalEndpoints: internalEndpoints,
InternalEndpoints: []string{"webapp"}, ExternalEndpoints: externalEndpoints,
// TODO(bryk): This field contains test value. Implement it.
ExternalEndpoints: []string{"81.76.02.198:80"},
}) })
} }
return replicaSetList, nil return replicaSetList
}
// Returns internal endpoint name for the given service properties, e.g.,
// "my-service.namespace 80/TCP" or "my-service 53/TCP,53/UDP".
func getInternalEndpoint(serviceName string, namespace string, ports []api.ServicePort) string {
name := serviceName
if namespace != api.NamespaceDefault {
name = name + "." + namespace
}
return name + getServicePortsName(ports)
}
// Returns external endpoint name for the given service properties.
func getExternalEndpoint(serviceIp string, ports []api.ServicePort) string {
return serviceIp + getServicePortsName(ports)
}
// Gets human readable name for the given service ports list.
func getServicePortsName(ports []api.ServicePort) string {
var portsString []string
for _, port := range ports {
portsString = append(portsString, strconv.Itoa(port.Port)+"/"+string(port.Protocol))
}
if len(portsString) > 0 {
return " " + strings.Join(portsString, ",")
} else {
return ""
}
}
// Returns all services that target the same Pods (or subset) as the given Replica Set.
func getMatchingServices(services []api.Service,
replicaSet *api.ReplicationController) []api.Service {
var matchingServices []api.Service
for _, service := range services {
if isServiceMatchingReplicaSet(service.Spec.Selector, replicaSet.Spec.Selector) {
matchingServices = append(matchingServices, service)
}
}
return matchingServices
}
// Returns true when a Service with the given selector targets the same Pods (or subset) that
// a Replica Set with the given selector.
func isServiceMatchingReplicaSet(serviceSelector map[string]string,
replicaSetSpecSelector map[string]string) bool {
// If service has no selectors, then assume it targets different Pods.
if len(serviceSelector) == 0 {
return false
}
for label, value := range serviceSelector {
if rsValue, ok := replicaSetSpecSelector[label]; !ok || rsValue != value {
return false
}
}
return true
} }
...@@ -38,6 +38,7 @@ backendApi.PortMapping; ...@@ -38,6 +38,7 @@ backendApi.PortMapping;
* containerImage: string, * containerImage: string,
* isExternal: boolean, * isExternal: boolean,
* name: string, * name: string,
* description: string,
* portMappings: !Array<!backendApi.PortMapping>, * portMappings: !Array<!backendApi.PortMapping>,
* replicas: number, * replicas: number,
* namespace: string * namespace: string
......
...@@ -18,7 +18,7 @@ limitations under the License. ...@@ -18,7 +18,7 @@ limitations under the License.
<md-whiteframe class="kd-deploy-whiteframe md-whiteframe-5dp" flex flex-gt-md> <md-whiteframe class="kd-deploy-whiteframe md-whiteframe-5dp" flex flex-gt-md>
<h3 class="md-headline">Deploy a Containerized App</h3> <h3 class="md-headline">Deploy a Containerized App</h3>
<form ng-submit="ctrl.deploy()"> <form ng-submit="ctrl.deploy()">
<md-input-container class="md-block"> <md-input-container>
<label>App name</label> <label>App name</label>
<input ng-model="ctrl.name" required> <input ng-model="ctrl.name" required>
</md-input-container> </md-input-container>
...@@ -30,24 +30,24 @@ limitations under the License. ...@@ -30,24 +30,24 @@ limitations under the License.
Upload a YAML or JSON file Upload a YAML or JSON file
</md-radio-button> </md-radio-button>
</md-radio-group> </md-radio-group>
<md-input-container class="md-block"> <md-input-container>
<label>Container image</label> <label>Container image</label>
<input ng-model="ctrl.containerImage" required> <input ng-model="ctrl.containerImage" required>
</md-input-container> </md-input-container>
<md-input-container class="md-block"> <md-input-container>
<label>Number of pods</label> <label>Number of pods</label>
<input ng-model="ctrl.replicas" type="number" required min="1"> <input ng-model="ctrl.replicas" type="number" required min="1">
</md-input-container> </md-input-container>
<div layout="row" ng-repeat="portMapping in ctrl.portMappings"> <div layout="row" ng-repeat="portMapping in ctrl.portMappings">
<md-input-container class="md-block"> <md-input-container flex>
<label>Port</label> <label>Port</label>
<input ng-model="portMapping.port" type="number" min="0"> <input ng-model="portMapping.port" type="number" min="0">
</md-input-container> </md-input-container>
<md-input-container class="md-block"> <md-input-container flex>
<label>Target port</label> <label>Target port</label>
<input ng-model="portMapping.targetPort" type="number" min="0"> <input ng-model="portMapping.targetPort" type="number" min="0">
</md-input-container> </md-input-container>
<md-input-container class="md-block"> <md-input-container flex="none">
<label>Protocol</label> <label>Protocol</label>
<md-select ng-model="portMapping.protocol" required> <md-select ng-model="portMapping.protocol" required>
<md-option ng-repeat="protocol in ctrl.protocols" ng-value="protocol"> <md-option ng-repeat="protocol in ctrl.protocols" ng-value="protocol">
...@@ -56,7 +56,7 @@ limitations under the License. ...@@ -56,7 +56,7 @@ limitations under the License.
</md-select> </md-select>
</md-input-container> </md-input-container>
</div> </div>
<md-input-container class="md-block"> <md-input-container>
<label>Namespace</label> <label>Namespace</label>
<md-select ng-model="ctrl.namespace" required> <md-select ng-model="ctrl.namespace" required>
<md-option ng-repeat="namespace in ctrl.namespaces" ng-value="namespace"> <md-option ng-repeat="namespace in ctrl.namespaces" ng-value="namespace">
...@@ -70,6 +70,10 @@ limitations under the License. ...@@ -70,6 +70,10 @@ limitations under the License.
<md-switch ng-model="ctrl.isExternal" class="md-primary"> <md-switch ng-model="ctrl.isExternal" class="md-primary">
Expose service externally Expose service externally
</md-switch> </md-switch>
<md-input-container>
<label>Description (optional)</label>
<textarea ng-model="ctrl.description"></textarea>
</md-input-container>
<md-button class="md-raised md-primary" type="submit" ng-disabled="ctrl.isDeployDisabled()"> <md-button class="md-raised md-primary" type="submit" ng-disabled="ctrl.isDeployDisabled()">
Deploy Deploy
</md-button> </md-button>
......
...@@ -40,6 +40,9 @@ export default class DeployController { ...@@ -40,6 +40,9 @@ export default class DeployController {
/** @export {number} */ /** @export {number} */
this.replicas = 1; this.replicas = 1;
/** @export {string} */
this.description = '';
/** /**
* List of supported protocols. * List of supported protocols.
* TODO(bryk): Do not hardcode the here, move to backend. * TODO(bryk): Do not hardcode the here, move to backend.
...@@ -94,6 +97,7 @@ export default class DeployController { ...@@ -94,6 +97,7 @@ export default class DeployController {
containerImage: this.containerImage, containerImage: this.containerImage,
isExternal: this.isExternal, isExternal: this.isExternal,
name: this.name, name: this.name,
description: this.description,
portMappings: this.portMappings.filter(this.isPortMappingEmpty_), portMappings: this.portMappings.filter(this.isPortMappingEmpty_),
replicas: this.replicas, replicas: this.replicas,
namespace: this.namespace, namespace: this.namespace,
......
...@@ -15,17 +15,17 @@ limitations under the License. ...@@ -15,17 +15,17 @@ limitations under the License.
--> -->
<div layout="row" layout-wrap layout-margin layout-align="center center"> <div layout="row" layout-wrap layout-margin layout-align="center center">
<md-card ng-repeat="replicaSet in ctrl.replicaSets"> <md-card ng-repeat="replicaSet in ::ctrl.replicaSets">
<md-card-content class="kd-replicaset-card"> <md-card-content class="kd-replicaset-card">
<div layout="row" layout-align="space-between center"> <div layout="row" layout-align="space-between center">
<div flex layout="column"> <div flex layout="column">
<a ng-href="{{ctrl.getReplicaSetDetailHref(replicaSet)}}" flex> <a ng-href="{{::ctrl.getReplicaSetDetailHref(replicaSet)}}" flex>
{{replicaSet.name}} {{::replicaSet.name}}
</a> </a>
<div flex class="md-caption"> <div flex class="md-caption">
<span ng-repeat="(label, value) in replicaSet.labels" <span ng-repeat="(label, value) in ::replicaSet.labels"
class="kd-replicaset-card-label"> class="kd-replicaset-card-label">
{{label}}:{{value}} {{::label}}:{{::value}}
</span> </span>
</div> </div>
</div> </div>
...@@ -36,50 +36,56 @@ limitations under the License. ...@@ -36,50 +36,56 @@ limitations under the License.
<div class="md-caption"> <div class="md-caption">
<div layout="row"> <div layout="row">
<span flex="60"> <span flex="60">
{{replicaSet.podsRunning}} pods running, {{replicaSet.podsPending}} pending {{::replicaSet.podsRunning}} pods running, {{::replicaSet.podsPending}} pending
</span> </span>
<a flex="40" href="#" class="kd-replicaset-card-logs">Logs</a> <a flex="40" href="#" class="kd-replicaset-card-logs">Logs</a>
</div> </div>
<hr class="kd-replicaset-card-divider"></hr> <hr class="kd-replicaset-card-divider"></hr>
<div layout="row" layout-wrap> <div layout="row" layout-wrap>
<div ng-if="replicaSet.description" flex="100" layout="column" <div ng-if="::replicaSet.description" flex="100" layout="column"
class="kd-replicaset-card-section"> class="kd-replicaset-card-section">
<span flex>Description</span> <span flex>Description</span>
<div flex> <div flex>
{{replicaSet.description}} {{::replicaSet.description}}
</div> </div>
</div> </div>
<div flex="60" layout="column" class="kd-replicaset-card-section"> <div flex="60" layout="column" class="kd-replicaset-card-section">
<span flex>Image</span> <span flex>Image</span>
<div flex> <div flex>
<div ng-repeat="image in replicaSet.containerImages" <div ng-repeat="image in ::replicaSet.containerImages track by $index"
class="kd-replicaset-card-section-image"> class="kd-replicaset-card-section-image">
{{image}} {{::image}}
</div> </div>
</div> </div>
</div> </div>
<div flex="40" layout="column" class="kd-replicaset-card-section"> <div flex="40" layout="column" class="kd-replicaset-card-section">
<span flex="initial">Creation time</span> <span flex="initial">Creation time</span>
<span flex>{{replicaSet.creationTime}}</span> <span flex>{{::replicaSet.creationTime}}</span>
</div> </div>
<div flex="60" layout="column" class="kd-replicaset-card-section"> <div flex="60" layout="column" class="kd-replicaset-card-section">
<span flex="initial">Internal Endpoint</span> <span flex="initial">Internal Endpoint</span>
<div flex> <div flex>
<span ng-repeat="endpoint in replicaSet.internalEndpoints"> <div ng-repeat="endpoint in ::replicaSet.internalEndpoints track by $index">
{{endpoint}} {{::endpoint}}
</span> </div>
<div ng-if="::!replicaSet.internalEndpoints.length">
none
</div>
</div> </div>
</div> </div>
<div flex="40" layout="column" class="kd-replicaset-card-section"> <div flex="40" layout="column" class="kd-replicaset-card-section">
<span flex="initial">External Endpoint</span> <span flex="initial">External Endpoint</span>
<div flex> <div flex>
<span ng-repeat="endpoint in replicaSet.externalEndpoints"> <div ng-repeat="endpoint in ::replicaSet.externalEndpoints track by $index">
{{endpoint}} <a ng-href="http://{{::endpoint}}" target="_blank">{{::endpoint}}</a>
</span> </div>
<div ng-if="::!replicaSet.externalEndpoints.length">
none
</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
package main package main
import ( import (
backend "app/backend"
client "k8s.io/kubernetes/pkg/client/unversioned" client "k8s.io/kubernetes/pkg/client/unversioned"
"testing" "testing"
) )
...@@ -34,14 +33,14 @@ func (FakeClientFactory) NewInCluster() (*client.Client, error) { ...@@ -34,14 +33,14 @@ func (FakeClientFactory) NewInCluster() (*client.Client, error) {
} }
func TestCreateApiserverClient_inCluster(t *testing.T) { func TestCreateApiserverClient_inCluster(t *testing.T) {
client, _ := backend.CreateApiserverClient("", new(FakeClientFactory)) client, _ := CreateApiserverClient("", new(FakeClientFactory))
if client != fakeInClusterClient { if client != fakeInClusterClient {
t.Fatal("Expected in cluster client to be created") t.Fatal("Expected in cluster client to be created")
} }
} }
func TestCreateApiserverClient_remote(t *testing.T) { func TestCreateApiserverClient_remote(t *testing.T) {
client, _ := backend.CreateApiserverClient("http://foo:bar", new(FakeClientFactory)) client, _ := CreateApiserverClient("http://foo:bar", new(FakeClientFactory))
if client != fakeRemoteClient { if client != fakeRemoteClient {
t.Fatal("Expected remote client to be created") t.Fatal("Expected remote client to be created")
} }
......
// 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.
package main
import (
"testing"
)
func TestFoo(t *testing.T) {
// TODO(bryk): Write tests here.
isServiceMatchingReplicaSet(nil, nil)
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册