kubernetes: Add Virtual machines side tab

It lists VMs of Kubevirt. The tab is only shown if connected cluster
has Kubevirt installed. It uses standard React + Redux + Saga approach.
Nested in angular environment. Redux store is garbage collected when
Virtual Machines view is left.

Changed dependencies:
  * react-redux, redux-saga - React state management dependencies
  * babel-plugin-transform-regenerator - support for JS generator
    functions
  * eslint-plugin-flowtype, flow-bin, flow-webpack-plugin - JS type
    annotation

Closes #7830
This commit is contained in:
jniederm 2018-01-22 08:00:50 +01:00 committed by Martin Pitt
parent 0218d776ca
commit 2a7622d28a
23 changed files with 630 additions and 9 deletions

View File

@ -4,6 +4,7 @@
"es6": true
},
"extends": "eslint:recommended",
"parser": "babel-eslint",
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
@ -11,7 +12,7 @@
},
"sourceType": "module"
},
"plugins": ["react"],
"plugins": ["flowtype", "react"],
"rules": {
"react/jsx-uses-vars": "error",
"no-console": "off",

12
.flowconfig Normal file
View File

@ -0,0 +1,12 @@
[ignore]
[include]
[libs]
[lints]
[options]
module.name_mapper='.*cockpit$' -> '<PROJECT_ROOT>/src/base1/cockpit.js'
[strict]

1
.gitignore vendored
View File

@ -165,6 +165,7 @@ po*.js.gz
/pkg/*/manifest.json
.idea
.vscode/
# Test output
Test*.log

View File

@ -25,7 +25,9 @@
"qunit-tap": "1.5.1",
"qunitjs": "1.23.1",
"react-lite": "0.15.39",
"react-redux": "5.0.6",
"redux": "3.7.2",
"redux-saga": "0.16.0",
"registry-image-widgets": "0.0.16",
"requirejs": "2.1.22",
"term.js-cockpit": "0.0.10"
@ -34,6 +36,7 @@
"babel-core": "^6.26.0",
"babel-eslint": "~7.1.1",
"babel-loader": "^6.4.1",
"babel-plugin-transform-regenerator": "6.26.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"chrome-remote-interface": "^0.25.2",
@ -42,13 +45,14 @@
"css-loader": "~0.23.1",
"eslint": "^3.0.0",
"eslint-loader": "~1.6.1",
"eslint-plugin-flowtype": "2.39.1",
"eslint-plugin-react": "~6.9.0",
"exports-loader": "~0.6.3",
"extend": "~3.0.0",
"extract-text-webpack-plugin": "~1.0.1",
"htmlparser": "~1.7.7",
"html-minifier": "~0.7.2",
"html-webpack-plugin": "~2.22.0",
"htmlparser": "~1.7.7",
"imports-loader": "~0.6.5",
"jed": "~1.1.0",
"jshint": "~2.9.1",

View File

@ -36,7 +36,7 @@ along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
<div class="nav-item-pf-header">
<span translate>Cluster</span>
</div>
<ul class="list-group">
<ul class="list-group" id="kubernetes-navigation">
<li class="list-group-item" ng-class="{ active: viewActive(null)}">
<a ng-href="#{{ viewUrl(null) }}">
<i class="fa fa-dashboard fa-fw" title="Overview"></i>
@ -55,6 +55,12 @@ along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
<span class="list-group-item-value" translate>Containers</span>
</a>
</li>
<li class="list-group-item" ng-class="{ active: viewActive('vms')}" ng-if="settings.kubevirtEnabled">
<a ng-href="#{{ viewUrl('vms') }}" id="vms-menu-link">
<i class="fa pficon-virtual-machine fa-fw" title="Virtual Machines"></i>
<span class="list-group-item-value" translate>Virtual Machines</span>
</a>
</li>
<li class="list-group-item" ng-class="{ active: viewActive('topology')}">
<a ng-href="#{{ viewUrl('topology') }}">
<i class="pficon pficon-topology fa-fw" title="Topology"></i>

View File

@ -1110,8 +1110,9 @@
"CockpitEnvironment",
'kubeLoader',
'cockpitConnectionInfo',
'KubevirtPrefix',
function($q, CockpitKubeRequest, cockpitKubeDiscover,
CockpitEnvironment, loader, info) {
CockpitEnvironment, loader, info, kubevirtPrefix) {
var promise = null;
return function kubeDiscoverSettings(force) {
if (!force && promise)
@ -1126,6 +1127,7 @@
isAdmin: false,
currentUser: null,
canChangeConnection: false,
kubevirtEnabled: false,
};
var env_p = CockpitEnvironment()
@ -1171,7 +1173,18 @@
return $q.all([watch, req]);
});
promise = $q.all([discover_p, env_p])
var kubevirt_p = new CockpitKubeRequest('GET', kubevirtPrefix)
.then(function(response) {
settings.kubevirtEnabled = true;
}, function(errorResponse) {
if (errorResponse.status === 404) {
settings.kubevirtEnabled = false;
return;
}
debug('Kubevirt check request failed:', errorResponse);
});
promise = $q.all([discover_p, env_p, kubevirt_p])
.then(function() {
settings.canChangeConnection = info.type == "kubectl";
return settings;

View File

@ -38,6 +38,7 @@
var KUBE = "/api/v1";
var OPENSHIFT = "/oapi/v1";
var KUBEVIRT = "/apis/kubevirt.io/v1alpha1";
var DEFAULT = { api: KUBE, create: 0 };
var SCHEMA = flatSchema([
{ kind: "DeploymentConfig", type: "deploymentconfigs", api: OPENSHIFT },
@ -62,6 +63,7 @@
{ kind: "Service", type: "services", api: KUBE, create: -80 },
{ kind: "SubjectAccessReview", type: "subjectaccessreviews", api: OPENSHIFT },
{ kind: "User", type: "users", api: OPENSHIFT, global: true },
{ kind: "VirtualMachine", type: "virtualmachines", api: KUBEVIRT },
]);
var NAME_RE = /^[a-z0-9]([-a-z0-9_.]*[a-z0-9])?$/;
@ -306,6 +308,8 @@
.value("KUBE_SCHEMA", SCHEMA)
.constant("KubevirtPrefix", KUBEVIRT)
/**
* KUBE_NAME_RE
*

View File

@ -44,6 +44,7 @@
require('./nodes');
require('./topology');
require('./volumes');
require('./virtual-machines.js');
/* And the actual application */
require('./app');
@ -63,6 +64,7 @@
'kubernetes.volumes',
'kubernetes.nodes',
'kubernetes.date',
'kubernetes.virtualMachines',
'registry.images',
'registry.policy',
'registry.projects',

View File

@ -0,0 +1,79 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2018 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
(function() {
"use strict";
var angular = require('angular');
require('angular-route');
require('angular-dialog.js');
require('./kube-client');
require('./listing');
var vmsReact = require('./virtual-machines/index.jsx');
require('../views/virtual-machines-page.html');
angular.module('kubernetes.virtualMachines', [
'ngRoute',
'ui.cockpit',
'kubernetesUI',
'kubeClient',
'kubernetes.listing'
])
.config([
'$routeProvider',
'$locationProvider',
function($routeProvider, $locationProvider) {
$routeProvider
.when('/vms', {
templateUrl: 'views/virtual-machines-page.html',
controller: 'VirtualMachinesCtrl'
});
/*
Links rewriting is enabled by default. It does two things:
* It changes links href in older browsers.
* It handles the navigation instead of the browser.
The link rewriting code runs in 'click' event handler registered
to the `document` element to bubbling phase. It calls `preventDefault()`
event method and instructs browser to go to the destination.
Such behavior breaks event handling in React since:
* React always gets event with flag `defaultPrevented` set.
* Navigation is performed no matter if `event.preventDefault()` is called
in React handler.
@see https://docs.angularjs.org/api/ng/provider/$locationProvider
@see https://docs.angularjs.org/guide/$location#html5-mode
*/
$locationProvider.html5Mode({ rewriteLinks: false });
}
])
.controller('VirtualMachinesCtrl', [
'$scope',
'kubeLoader',
'kubeSelect',
function($scope, loader, select) {
vmsReact.init($scope, loader, select);
}]
);
}());

View File

@ -0,0 +1,34 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2017 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
import * as actionConstants from './action-types.jsx'
export function setVms(vms) {
return {
type: actionConstants.SET_VMS,
payload: vms
}
}
export function setSettings(settings) {
return {
type: actionConstants.SET_SETTINGS,
payload: settings
}
}

View File

@ -0,0 +1,21 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2017 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
export const SET_VMS = 'SET_VMS'
export const SET_SETTINGS = 'SET_SETTINGS'

View File

@ -0,0 +1,50 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2017 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
// @flow
import React from 'react'
import { connect } from 'react-redux'
import { gettext as _ } from 'cockpit'
import { Listing } from '../../../../lib/cockpit-components-listing.jsx'
import VmsListingRow from './VmsListingRow.jsx'
const VmsListing = ({ vms, settings }) => {
const isOpenshift = settings.flavor === 'openshift'
const namespaceLabel = isOpenshift ? _("Project") : _("Namespace")
const rows = vms.map(vm => (<VmsListingRow vm={vm} key={vm.metadata.uid} />))
return (
<Listing title={_("Virtual Machines")}
emptyCaption={_("No virtual machines")}
columnTitles={[_("Name"), namespaceLabel, _("Node"), _("State")]}>
{rows}
</Listing>
)
}
VmsListing.propTypes = {
vms: React.PropTypes.object.isRequired,
setting: React.PropTypes.object.isRequired,
}
export default connect(({ vms, settings }) => ({
vms,
settings
}))(VmsListing)

View File

@ -0,0 +1,84 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2017 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
// @flow
import React from 'react'
import { gettext as _ } from 'cockpit'
import { ListingRow } from '../../../../lib/cockpit-components-listing.jsx'
import type { Vm } from '../types.jsx'
import { getPairs } from '../utils.jsx'
const NODE_LABEL = 'kubevirt.io/nodeName'
function getNodeName(vm: Vm) {
return (vm.metadata.labels && vm.metadata.labels[NODE_LABEL]) || null
}
const GeneralTab = ({ vm }: { vm: Vm }) => {
const nodeName = getNodeName(vm)
const nodeLink = nodeName ? (<a href={`#/nodes/${nodeName}`}>{nodeName}</a>) : '-'
return (
<div className="row">
<div className="col-xs-12 col-md-6">
<dl>
<dt>{_("Node")}</dt>
<dd className="vm-node">{nodeLink}</dd>
</dl>
</div>
<div className="col-xs-12 col-md-6">
<dl className="full-width">
<dt>{_("Labels")}</dt>
{vm.metadata.labels && getPairs(vm.metadata.labels).map(pair => {
const printablePair = pair.key + '=' + pair.value
return (<dd key={printablePair}>{printablePair}</dd>)
})}
</dl>
</div>
</div>
)
}
const VmsListingRow = ({ vm }: { vm: Vm }) => {
const node = (vm.metadata.labels && vm.metadata.labels[NODE_LABEL]) || '-'
const phase = (vm.status && vm.status.phase) || _("n/a")
const generalTabRenderer = {
name: _("General"),
renderer: GeneralTab,
data: { vm },
presence: 'always',
}
return (
<ListingRow
rowId={`vm-${vm.metadata.name}`}
columns={[
{name: vm.metadata.name, 'header': true},
vm.metadata.namespace,
node,
phase // phases description https://github.com/kubevirt/kubevirt/blob/master/pkg/api/v1/types.go
]}
tabRenderers={[generalTabRenderer]}/>
)
}
VmsListingRow.propTypes = {
vm: React.PropTypes.object.isRequired
}
export default VmsListingRow

View File

@ -0,0 +1,82 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2017 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
import 'regenerator-runtime/runtime' // required for library initialization
import React from 'react'
import { createStore, applyMiddleware, compose } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { Provider } from 'react-redux'
import reducers from './reducers.jsx'
import rootSaga from './sagas.jsx'
import * as actionCreators from './action-creators.jsx'
import VmsListing from './components/VmsListing.jsx'
const sagaMiddleware = createSagaMiddleware()
let reduxStore
function initReduxStore() {
const initialState = {
vms: []
}
const middleware = [ sagaMiddleware ]
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const storeEnhancers = composeEnhancers(applyMiddleware(...middleware))
reduxStore = createStore(reducers, initialState, storeEnhancers)
}
function addKubeLoaderListener ($scope, kubeLoader, kubeSelect) {
// register load callback( callback, until )
kubeLoader.listen(function() {
const vms = kubeSelect().kind('VirtualMachine')
reduxStore.dispatch(actionCreators.setVms(Object.values(vms)))
}, $scope);
// enable watching( watched-entity-type, until )
kubeLoader.watch('VirtualMachine', $scope);
}
const VmsPlugin = () => (
<Provider store={reduxStore} >
<VmsListing />
</Provider>
)
function addScopeVarsToStore ($scope) {
$scope.$watch(
(scope => scope.settings),
(newSettings => reduxStore.dispatch(actionCreators.setSettings(newSettings))))
}
/**
*
* @param {$rootScope.Scope} $scope 'VirtualMachinesCtrl' controller scope
* @param {kubeLoader} kubeLoader
* @param {kubeSelect} kubeSelect
*/
function init($scope, kubeLoader, kubeSelect) {
initReduxStore()
sagaMiddleware.run(rootSaga)
addKubeLoaderListener($scope, kubeLoader, kubeSelect)
addScopeVarsToStore($scope)
const rootElement = document.querySelector('#kubernetes-virtual-machines-root')
React.render(<VmsPlugin />, rootElement)
}
export { init };

View File

@ -0,0 +1,44 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2017 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
import { combineReducers } from 'redux'
import * as actionTypes from './action-types.jsx'
const createReducer = (initialState, actionHandlerMap) => (state = initialState, action) => {
if (actionHandlerMap[action.type]) {
return actionHandlerMap[action.type](state, action)
}
return state
}
const vmsReducer = createReducer([], {
[actionTypes.SET_VMS]: (state = [], { payload }) => payload ? payload : []
})
const settingsReducer = createReducer([], {
[actionTypes.SET_SETTINGS]: (state = [], { payload }) => payload ? payload : {}
})
const rootReducer = combineReducers({
vms: vmsReducer,
settings: settingsReducer
})
export default rootReducer

View File

@ -0,0 +1,23 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2017 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
function* rootSaga() {
}
export default rootSaga;

View File

@ -0,0 +1,62 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2017 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
// @flow
export type Vm = {
apiVersion: string,
kind: 'VirtualMachine',
metadata: {
clusterName: string,
creationTimestamp: string,
generation: number,
labels: {[string]: string},
name: string,
namespace: string,
resourceVersion: string,
selfLink: string,
uid: string
},
spec: {
domain: {
devices: {
console: Array<Object>,
disks: Array<Object>,
graphics: Array<Object>,
interfaces: Array<Object>,
video: Array<Object>,
[string]: any
},
memory: {
unit: string,
value: number
},
os: {
bootOrder?: mixed,
type: Object
},
type: string
}
},
status: ?{
graphics?: mixed,
nodeName: string,
phase: ?string
}
}

View File

@ -0,0 +1,28 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2018 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @return {Array<{key: *, value: *}>} all own enumerable key-value pairs
*/
export function getPairs(object) {
return Object.keys(object).map(key => ({
key,
value: object[key]
}))
}

View File

@ -23,7 +23,8 @@
width: unit(@sidebar-width-xl, px) !important;
}
.list-group-item-value {
max-width: unit(@sidebar-width-xl - 80, px) !important;
max-width: unit(@sidebar-width-xl - 60, px) !important;
padding-right: 10px !important;
}
}

View File

@ -1,7 +1,7 @@
@import "/variables.less";
@sidebar-width-sm: 40;
@sidebar-width-md: 115;
@sidebar-width-xl: 180;
@sidebar-width-md: 145;
@sidebar-width-xl: 185;
@command-bg-color: #eee;

View File

@ -0,0 +1,4 @@
<filter-bar class="content-filter">
</filter-bar>
<div id="kubernetes-virtual-machines-root"></div>

View File

@ -61,6 +61,8 @@ class TestOpenshift(MachineCase, OpenshiftCommonTests):
def setUp(self):
super(TestOpenshift, self).setUp()
print "======= done setUp"
self.openshift = self.machines['openshift']
self.openshift.upload(["verify/files/mock-app-openshift.json"], "/tmp")
self.kubeconfig = os.path.join(self.tmpdir, "config")
@ -406,6 +408,70 @@ LABEL io.projectatomic.nulecule.atomicappversion="0.1.11" \
wait(lambda: "virt-launcher-testvm" in o.execute("oc get pods"))
wait(lambda: "Running" in o.execute("oc get pods | grep virt-launcher-testvm"))
def testVirtualMachinesTabPresent(self):
self.bootstrapKubevirt()
b = self.browser
self.login_and_go("/kubernetes")
b.wait_present("#kubernetes-navigation")
self.assertTrue(b.is_present("#vms-menu-link"))
def testVirtualMachinesListed(self):
self.bootstrapKubevirt()
o = self.openshift
# *.yaml files comes from kubevirt distribution, see tarball extraction in bots/images/script/lib/kubevirt.setup
o.execute("oc create -f /kubevirt/iscsi-demo-target.yaml") # disk for test VM
o.execute("oc create -f /kubevirt/vm.yaml") # let's create a VM
wait(lambda: "testvm" in o.execute("oc get vm"), msg='Deployment of virtual machine "testvm" failed.')
b = self.browser
self.login_and_go("/kubernetes")
b.wait_present("#vms-menu-link")
b.click("#vms-menu-link")
b.wait_present("tr[data-row-id='vm-testvm']")
self.assertTrue(b.text("tr[data-row-id='vm-testvm'] th") == 'testvm')
def testVirtualMachineRowExpansion(self):
self.bootstrapKubevirt()
o = self.openshift
o.execute("oc create -f /kubevirt/iscsi-demo-target.yaml") # disk for test VM
o.execute("oc create -f /kubevirt/vm.yaml") # let's create a VM
wait(lambda: "testvm" in o.execute("oc get vm"), msg='Deployment of virtual machine "testvm" failed.')
b = self.browser
self.login_and_go("/kubernetes")
b.wait_present("#vms-menu-link")
b.click("#vms-menu-link")
b.wait_present("tr[data-row-id='vm-testvm']")
b.click("tr[data-row-id='vm-testvm']")
b.wait_present("tr[data-row-id='vm-testvm'] + tr.listing-ct-panel")
self.assertTrue('Node' in b.text("tr[data-row-id='vm-testvm'] + tr.listing-ct-panel"))
def testVirtualMachineLinkToNode(self):
print 'start'
self.bootstrapKubevirt()
print 'kubevirt installed'
o = self.openshift
o.execute("oc create -f /kubevirt/iscsi-demo-target.yaml") # disk for test VM
o.execute("oc create -f /kubevirt/vm.yaml") # let's create a VM
wait(lambda: "testvm" in o.execute("oc get vm"), msg='Deployment of virtual machine "testvm" failed.')
b = self.browser
self.login_and_go("/kubernetes")
b.wait_present("#vms-menu-link")
b.click("#vms-menu-link")
b.wait_present("tr[data-row-id='vm-testvm']")
b.click("tr[data-row-id='vm-testvm']")
b.wait_present("tr[data-row-id='vm-testvm'] + tr.listing-ct-panel .vm-node")
node_not_assigned = '-'
node_name = b.text("tr[data-row-id='vm-testvm'] + tr.listing-ct-panel .vm-node").strip()
if node_name != node_not_assigned:
b.click("tr[data-row-id='vm-testvm'] + tr.listing-ct-panel .vm-node a")
b.wait_js_cond('window.location.hash === "#/nodes/%s"' % (node_name,))
@skipImage("Kubernetes not packaged", "debian-stable", "debian-testing", "ubuntu-1604", "ubuntu-stable", "fedora-i386")
@skipImage("No cockpit-kubernetes packaged", "continuous-atomic", "fedora-atomic", "rhel-atomic")

View File

@ -356,7 +356,7 @@ var plugins = [
}
}),
new copy(info.files),
new extract("[name].css")
new extract("[name].css"),
];
var output = {