diff --git a/pkg/kubernetes/scripts/virtual-machines.js b/pkg/kubernetes/scripts/virtual-machines.js
index 1b5319a13b0a..2199beaf9ff1 100644
--- a/pkg/kubernetes/scripts/virtual-machines.js
+++ b/pkg/kubernetes/scripts/virtual-machines.js
@@ -25,9 +25,11 @@
require('angular-dialog.js');
require('./kube-client');
require('./listing');
- var vmsReact = require('./virtual-machines/index.jsx');
+ var vmsReact = require('./virtual-machines/entry-points/virtual-machines.jsx');
+ var vmReact = require('./virtual-machines/entry-points/virtual-machine.jsx');
require('../views/virtual-machines-page.html');
+ require('../views/virtual-machine-page.html');
angular.module('kubernetes.virtualMachines', [
'ngRoute',
@@ -45,6 +47,13 @@
.when('/vms', {
templateUrl: 'views/virtual-machines-page.html',
controller: 'VirtualMachinesCtrl'
+ })
+ .when('/vms/:namespace/:name', {
+ templateUrl: 'views/virtual-machine-page.html',
+ controller: 'VirtualMachineCtrl'
+ })
+ .when('/vms/:namespace', {
+ redirectTo: '/vms'
});
/*
Links rewriting is enabled by default. It does two things:
@@ -76,6 +85,18 @@
function($scope, loader, select, methods, request) {
vmsReact.init($scope, loader, select, methods, request);
}]
+ )
+
+ .controller('VirtualMachineCtrl', [
+ '$scope',
+ '$routeParams',
+ 'kubeLoader',
+ 'kubeSelect',
+ 'kubeMethods',
+ 'KubeRequest',
+ function($scope, $routeParams, loader, select, methods, request) {
+ vmReact.init($scope, $routeParams, loader, select, methods, request);
+ }]
);
}());
diff --git a/pkg/kubernetes/scripts/virtual-machines/components/VmActions.jsx b/pkg/kubernetes/scripts/virtual-machines/components/VmActions.jsx
index f93b7a6ad6de..ce748ebf7fed 100644
--- a/pkg/kubernetes/scripts/virtual-machines/components/VmActions.jsx
+++ b/pkg/kubernetes/scripts/virtual-machines/components/VmActions.jsx
@@ -30,17 +30,18 @@ import { vmActionFailed } from '../action-creators.jsx';
// import DropdownButtons from '../../../../machines/components/dropdownButtons.jsx';
-const VmActions = ({ vm, onFailure }: { vm: Vm, onFailure: Function }) => {
+const VmActions = ({ vm, onDeleteSuccess, onDeleteFailure }: { vm: Vm, onDeleteSuccess: Function, onDeleteFailure: Function }) => {
const id = vmIdPrefx(vm);
const onDelete = () => {
- vmDelete({ vm }).catch((error) => {
- console.info('VmActions: delete failed: ', error);
- onFailure({
- message: _("VM DELETE failed."),
- detail: error,
- });
- });
+ vmDelete({ vm }).then(onDeleteSuccess)
+ .catch((error) => {
+ console.info('VmActions: delete failed: ', error);
+ onDeleteFailure({
+ message: _("VM DELETE failed."),
+ detail: error,
+ });
+ });
};
const buttonDelete = (
@@ -56,12 +57,13 @@ const VmActions = ({ vm, onFailure }: { vm: Vm, onFailure: Function }) => {
VmActions.propTypes = {
vm: PropTypes.object.isRequired,
- onFailure: PropTypes.func.isRequired,
+ onDeleteSuccess: PropTypes.func.isRequired,
+ onDeleteFailure: PropTypes.func.isRequired,
};
export default connect(
() => ({}),
(dispatch, { vm }) => ({
- onFailure: ({ message, detail }) => dispatch(vmActionFailed({ vm, message, detail })),
+ onDeleteFailure: ({ message, detail }) => dispatch(vmActionFailed({ vm, message, detail })),
}),
)(VmActions);
diff --git a/pkg/kubernetes/scripts/virtual-machines/components/VmDetail.jsx b/pkg/kubernetes/scripts/virtual-machines/components/VmDetail.jsx
new file mode 100644
index 000000000000..573d286ba687
--- /dev/null
+++ b/pkg/kubernetes/scripts/virtual-machines/components/VmDetail.jsx
@@ -0,0 +1,104 @@
+/*
+ * 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 .
+ */
+
+// @flow
+
+import React, { PropTypes } from 'react';
+import cockpit, { gettext as _ } from 'cockpit';
+import { connect } from "react-redux";
+
+import { DetailPage, DetailPageRow, DetailPageHeader } from 'cockpit-components-detail-page.jsx';
+import { getPod, getPodMetrics } from '../selectors.jsx';
+import VmOverviewTab from './VmOverviewTabKubevirt.jsx';
+import VmActions from './VmActions.jsx';
+import VmMetricsTab from './VmMetricsTab.jsx';
+import VmDisksTab from './VmDisksTabKubevirt.jsx';
+
+import type { Vm, VmMessages, PersistenVolumes, Pod } from '../types.jsx';
+import { vmIdPrefx, prefixedId } from '../utils.jsx';
+
+const navigateToVms = () => {
+ cockpit.location.go([ 'vms' ]);
+};
+
+const VmDetail = ({ vm, pageParams, vmMessages, pvs, pod, podMetrics }:
+ { vm: Vm, vmMessages: VmMessages, pageParams: Object, pvs: PersistenVolumes, pod: Pod}) => {
+ const mainTitle = vm ? `${vm.metadata.namespace}:${vm.metadata.name}` : null;
+ const actions = vm ? : null;
+ const header = ();
+
+ if (!vm) {
+ return (
+
+
+ {header}
+
+
+
+ );
+ }
+ const idPrefix = vmIdPrefx(vm);
+
+ return (
+
+
+ {header}
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+VmDetail.propTypes = {
+ vm: PropTypes.object.isRequired,
+ pageParams: PropTypes.object,
+ vmMessages: PropTypes.object,
+ pvs: PropTypes.array.isRequired,
+ pod: PropTypes.object.isRequired,
+ podMetrics: PropTypes.object,
+};
+
+export default connect(
+ ({vms, pvs, pods, vmsMessages, nodeMetrics}) => {
+ const vm = vms.length > 0 ? vms[0] : null;
+ const pod = getPod(vm, pods);
+ const podMetrics = getPodMetrics(pod, nodeMetrics);
+ return {
+ vm,
+ vmMessages: vm ? vmsMessages[vm.metadata.uid] : null,
+ pvs,
+ pod,
+ podMetrics,
+ };
+ },
+)(VmDetail);
diff --git a/pkg/kubernetes/scripts/virtual-machines/components/VmDisksTabKubevirt.jsx b/pkg/kubernetes/scripts/virtual-machines/components/VmDisksTabKubevirt.jsx
index 58312d22dbe2..95ff7e7b9c56 100644
--- a/pkg/kubernetes/scripts/virtual-machines/components/VmDisksTabKubevirt.jsx
+++ b/pkg/kubernetes/scripts/virtual-machines/components/VmDisksTabKubevirt.jsx
@@ -66,7 +66,7 @@ const prepareDiskData = (disk, vm, pvs, idPrefix) => {
if (pv) {
bus = _("iSCSI");
onNavigate = () => cockpit.jump(`/kubernetes#/volumes/${pv.metadata.name}`);
- } else if (disk.disk.bus) {
+ } else if (disk.disk && disk.disk.bus) {
bus = disk.disk.bus;
}
diff --git a/pkg/kubernetes/scripts/virtual-machines/components/VmMetricsTab.jsx b/pkg/kubernetes/scripts/virtual-machines/components/VmMetricsTab.jsx
index 10e06f61a756..3e7207d26755 100644
--- a/pkg/kubernetes/scripts/virtual-machines/components/VmMetricsTab.jsx
+++ b/pkg/kubernetes/scripts/virtual-machines/components/VmMetricsTab.jsx
@@ -21,6 +21,7 @@
import React, { PropTypes } from 'react';
import { DonutChart } from 'patternfly-react';
import cockpit, { gettext as _ } from 'cockpit';
+import { prefixedId } from '../utils.jsx';
import './VmMetricsTab.less';
@@ -38,11 +39,11 @@ const MetricColumn = ({ type, children, className }) => {
const MetricColumnContent = ({ id, title, smallTitle }) => {
return (
-
+
{title}
-
+
{smallTitle}
@@ -79,33 +80,33 @@ const CPUChart = ({ id, podMetrics }) => {
);
};
-const CpuColumn = ({ podMetrics }) => {
+const CpuColumn = ({ idPrefix, podMetrics }) => {
return (
-
+
);
};
-const MemoryColumn = ({ podMetrics }) => {
+const MemoryColumn = ({ idPrefix, podMetrics }) => {
const usage = cockpit.format_bytes(podMetrics.memory.usageBytes);
return (
-
+
);
};
-const NetworkColumn = ({ podMetrics }) => {
+const NetworkColumn = ({ idPrefix, podMetrics }) => {
const received = cockpit.format_bytes_per_sec(podMetrics.network.rxBytes);
const transmitted = cockpit.format_bytes_per_sec(podMetrics.network.txBytes);
const title = (
-
+
-
+
@@ -113,27 +114,27 @@ const NetworkColumn = ({ podMetrics }) => {
);
return (
-
+
);
};
-const PodMetrics = ({ podMetrics }) => {
+const PodMetrics = ({ idPrefix, podMetrics }) => {
return (
-
-
-
+
+
+
);
};
-const VmMetricsTab = ({podMetrics}) => {
- const content = podMetrics ? (
)
+const VmMetricsTab = ({idPrefix, podMetrics}) => {
+ const content = podMetrics ? (
)
: _("Usage metrics are available after the pod starts");
return (
-
+
{content}
);
diff --git a/pkg/kubernetes/scripts/virtual-machines/components/VmOverviewTabKubevirt.jsx b/pkg/kubernetes/scripts/virtual-machines/components/VmOverviewTabKubevirt.jsx
index ce5c0354bf44..af4e5a21b8a6 100644
--- a/pkg/kubernetes/scripts/virtual-machines/components/VmOverviewTabKubevirt.jsx
+++ b/pkg/kubernetes/scripts/virtual-machines/components/VmOverviewTabKubevirt.jsx
@@ -64,7 +64,7 @@ const PodLink = ({ pod }) => {
return (
{pod.metadata.name});
};
-const VmOverviewTabKubevirt = ({ vm, vmMessages, pod }: { vm: Vm, vmMessages: VmMessages, pod: Pod }) => {
+const VmOverviewTabKubevirt = ({ vm, vmMessages, pod, showState }: { vm: Vm, vmMessages: VmMessages, pod: Pod, showState: boolean }) => {
const idPrefix = vmIdPrefx(vm);
const message = (
);
@@ -73,12 +73,25 @@ const VmOverviewTabKubevirt = ({ vm, vmMessages, pod }: { vm: Vm, vmMessages: Vm
const nodeLink = nodeName ? (
{nodeName}) : '-';
const podLink = (
);
- const items = [
- {title: commonTitles.MEMORY, value: getMemory(vm), idPostfix: 'memory'},
- {title: _("Node:"), value: nodeLink, idPostfix: 'node'},
- {title: commonTitles.CPUS, value: _(getValueOrDefault(() => vm.spec.domain.cpu.cores, 1)), idPostfix: 'vcpus'},
- {title: _("Labels:"), value: getLabels(vm), idPostfix: 'labels'},
- {title: _("Pod:"), value: podLink, idPostfix: 'pod'},
+ const memoryItem = {title: commonTitles.MEMORY, value: getMemory(vm), idPostfix: 'memory'};
+ const vCpusItem = {title: commonTitles.CPUS, value: _(getValueOrDefault(() => vm.spec.domain.cpu.cores, 1)), idPostfix: 'vcpus'};
+ const podItem = {title: _("Pod:"), value: podLink, idPostfix: 'pod'};
+ const nodeItem = {title: _("Node:"), value: nodeLink, idPostfix: 'node'};
+ const labelsItem = {title: _("Labels:"), value: getLabels(vm), idPostfix: 'labels'};
+
+ const items = showState ? [
+ memoryItem,
+ {title: _("State"), value: getValueOrDefault(() => vm.status.phase, _("n/a")), idPostfix: 'state'},
+ vCpusItem,
+ nodeItem,
+ podItem,
+ labelsItem,
+ ] : [
+ memoryItem,
+ nodeItem,
+ vCpusItem,
+ labelsItem,
+ podItem,
];
return (
{
+ return cockpit.location.go([ 'vms', vm.metadata.namespace, vm.metadata.name ]);
+};
+
const VmsListingRow = ({ vm, vmMessages, pvs, pod, podMetrics, vmUi, onExpandChanged }:
{ vm: Vm, vmMessages: VmMessages, pvs: PersistenVolumes, pod: Pod, onExpandChanged: Function }) => {
- const node = (vm.metadata.labels && vm.metadata.labels[NODE_LABEL]) || '-';
- const phase = (vm.status && vm.status.phase) || _("n/a");
+ const node = getValueOrDefault(() => vm.metadata.labels[NODE_LABEL], '-');
+ const phase = getValueOrDefault(() => vm.status.phase, _("n/a"));
const idPrefix = vmIdPrefx(vm);
const overviewTabRenderer = {
- name: ({_("Overview")}
),
+ name: ({_("Overview")}
),
renderer: VmOverviewTab,
data: { vm, vmMessages, pod },
presence: 'always',
};
const metricsTabRenderer = {
- name: ({_("Usage")}
),
+ name: ({_("Usage")}
),
renderer: VmMetricsTab,
- data: { podMetrics },
+ data: { idPrefix, podMetrics },
presence: 'always',
};
const disksTabRenderer = {
- name: ({_("Disks")}
),
+ name: ({_("Disks")}
),
renderer: VmDisksTab,
data: { vm, pvs },
presence: 'onlyActive',
@@ -74,6 +78,7 @@ const VmsListingRow = ({ vm, vmMessages, pvs, pod, podMetrics, vmUi, onExpandCha
tabRenderers={[ overviewTabRenderer, metricsTabRenderer, disksTabRenderer ]}
listingActions={[ ]}
expandChanged={onExpandChanged(vm)}
+ navigateToItem={navigateToVm.bind(this, vm)}
initiallyExpanded={initiallyExpanded} />
);
};
@@ -83,6 +88,7 @@ VmsListingRow.propTypes = {
vmMessages: PropTypes.object,
pvs: PropTypes.array.isRequired,
pod: PropTypes.object.isRequired,
+ podMetrics: PropTypes.object,
vmUi: PropTypes.object,
onExpandChanged: PropTypes.func.isRequired,
};
diff --git a/pkg/kubernetes/scripts/virtual-machines/entry-points/util/initialize.es6 b/pkg/kubernetes/scripts/virtual-machines/entry-points/util/initialize.es6
new file mode 100644
index 000000000000..30350769a08f
--- /dev/null
+++ b/pkg/kubernetes/scripts/virtual-machines/entry-points/util/initialize.es6
@@ -0,0 +1,65 @@
+/*
+ * 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 .
+ */
+
+import { initMiddleware } from '../../kube-middleware.jsx';
+import { watchMetrics, cleanupMetricsWatch } from '../../watch-metrics.es6';
+import * as actionCreators from '../../action-creators.jsx';
+
+function addKubeLoaderListener (store, $scope, kubeLoader, kubeSelect) {
+ // register load callback( callback, until )
+ kubeLoader.listen(() => {
+ const persistentVolumes = kubeSelect().kind('PersistentVolume');
+ const pods = kubeSelect().kind('Pod');
+
+ store.dispatch(actionCreators.setPVs(Object.values(persistentVolumes)));
+ store.dispatch(actionCreators.setPods(Object.values(pods)));
+ }, $scope);
+
+ // enable watching( watched-entity-type, until )
+ kubeLoader.watch('PersistentVolume', $scope);
+ kubeLoader.watch('Pod', $scope);
+}
+
+function addScopeVarsToStore (store, $scope) {
+ $scope.$watch(
+ scope => scope.settings,
+ newSettings => store.dispatch(actionCreators.setSettings(newSettings)));
+}
+
+/**
+ *
+ * @param {$rootScope.Scope} $scope '.*Ctrl' controller scope
+ * @param {kubeLoader} kubeLoader
+ * @param {kubeSelect} kubeSelect
+ * @param {kubeMethods} kubeMethods
+ * @param {KubeRequest} KubeRequest
+ * @param {store} store
+ * @param {onDestroy} onDestroy
+ */
+export default function initialize($scope, kubeLoader, kubeSelect, kubeMethods, KubeRequest, store, onDestroy) {
+ addKubeLoaderListener(store, $scope, kubeLoader, kubeSelect);
+ initMiddleware(kubeMethods, kubeLoader, KubeRequest);
+ addScopeVarsToStore(store, $scope);
+
+ watchMetrics(store);
+ $scope.$on("$destroy", () => {
+ typeof onDestroy === 'function' && onDestroy();
+ cleanupMetricsWatch();
+ });
+}
diff --git a/pkg/kubernetes/scripts/virtual-machines/entry-points/virtual-machine.jsx b/pkg/kubernetes/scripts/virtual-machines/entry-points/virtual-machine.jsx
new file mode 100644
index 000000000000..e526f8a34b56
--- /dev/null
+++ b/pkg/kubernetes/scripts/virtual-machines/entry-points/virtual-machine.jsx
@@ -0,0 +1,96 @@
+/*
+ * 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 .
+ */
+
+import 'regenerator-runtime/runtime'; // required for library initialization
+import React from 'react';
+import { Provider } from 'react-redux';
+
+import { setVms, vmExpanded } from '../action-creators.jsx';
+import VmDetail from '../components/VmDetail.jsx';
+import { initStore, getStore } from '../store.es6';
+import initialize from './util/initialize.es6';
+
+import '../../../../machines/machines.less'; // once per component hierarchy
+
+const VmPage = ({pageParams}) => (
+
+
+
+);
+
+function addVmListener (store, $scope, kubeLoader, kubeSelect, namespace, name) {
+ kubeLoader.listen(() => {
+ const vm = kubeSelect().kind('VirtualMachine')
+ .namespace(namespace)
+ .name(name)
+ .one();
+ const result = vm ? [vm] : [];
+
+ if (vm) {
+ store.dispatch(vmExpanded({
+ vm,
+ isExpanded: true
+ }));
+ }
+
+ store.dispatch(setVms(result));
+ }, $scope);
+ kubeLoader.watch('VirtualMachine', $scope);
+}
+
+/**
+ *
+ * @param {$rootScope.Scope} $scope 'VirtualMachinesCtrl' controller scope
+ * @param {$routeParams} $routeParams
+ * @param {kubeLoader} kubeLoader
+ * @param {kubeSelect} kubeSelect
+ * @param {kubeMethods} kubeMethods
+ * @param {KubeRequest} KubeRequest
+ */
+function init ($scope, $routeParams, kubeLoader, kubeSelect, kubeMethods, KubeRequest) {
+ const store = initStore();
+ const name = $routeParams.name;
+ const namespace = $routeParams.namespace;
+
+ let onDestroy;
+ if (namespace && name) {
+ // enable metrics fetching
+ onDestroy = () => {
+ const state = store.getState();
+ if (state.vms.length > 0) {
+ store.dispatch(vmExpanded({
+ vm: state.vms[0],
+ isExpanded: false,
+ }));
+ }
+ };
+
+ // fetch only if there is a namespace and name
+ addVmListener(store, $scope, kubeLoader, kubeSelect, namespace, name);
+ } else {
+ // otherwise reset
+ store.dispatch(setVms([]));
+ }
+ initialize($scope, kubeLoader, kubeSelect, kubeMethods, KubeRequest, store, onDestroy);
+
+ const rootElement = document.querySelector('#kubernetes-virtual-machine-root');
+ React.render(, rootElement);
+}
+
+export { init };
diff --git a/pkg/kubernetes/scripts/virtual-machines/entry-points/virtual-machines.jsx b/pkg/kubernetes/scripts/virtual-machines/entry-points/virtual-machines.jsx
new file mode 100644
index 000000000000..7c563341ac5b
--- /dev/null
+++ b/pkg/kubernetes/scripts/virtual-machines/entry-points/virtual-machines.jsx
@@ -0,0 +1,62 @@
+/*
+ * 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 .
+ */
+
+import 'regenerator-runtime/runtime'; // required for library initialization
+import React from 'react';
+import { Provider } from 'react-redux';
+
+import VmsListing from '../components/VmsListing.jsx';
+import { initStore, getStore } from '../store.es6';
+import initialize from './util/initialize.es6';
+import { setVms } from '../action-creators.jsx';
+
+import '../../../../machines/machines.less'; // once per component hierarchy
+
+const VmsPage = () => (
+
+
+
+);
+
+function addVmsListener (store, $scope, kubeLoader, kubeSelect) {
+ kubeLoader.listen(() => {
+ const vms = kubeSelect().kind('VirtualMachine');
+ store.dispatch(setVms(Object.values(vms)));
+ }, $scope);
+ kubeLoader.watch('VirtualMachine', $scope);
+}
+
+/**
+ *
+ * @param {$rootScope.Scope} $scope 'VirtualMachinesCtrl' controller scope
+ * @param {kubeLoader} kubeLoader
+ * @param {kubeSelect} kubeSelect
+ * @param {kubeMethods} kubeMethods
+ * @param {KubeRequest} KubeRequest
+ */
+function init ($scope, kubeLoader, kubeSelect, kubeMethods, KubeRequest) {
+ const store = initStore();
+ addVmsListener(store, $scope, kubeLoader, kubeSelect);
+ initialize($scope, kubeLoader, kubeSelect, kubeMethods, KubeRequest, store);
+
+ const rootElement = document.querySelector('#kubernetes-virtual-machines-root');
+ React.render(, rootElement);
+}
+
+export { init };
diff --git a/pkg/kubernetes/scripts/virtual-machines/index.jsx b/pkg/kubernetes/scripts/virtual-machines/index.jsx
deleted file mode 100644
index 0637f4b6ca96..000000000000
--- a/pkg/kubernetes/scripts/virtual-machines/index.jsx
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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 .
- */
-
-import 'regenerator-runtime/runtime'; // required for library initialization
-import React from 'react';
-import { createStore } from 'redux';
-import { Provider } from 'react-redux';
-
-import { initMiddleware } from './kube-middleware.jsx';
-import reducers from './reducers.jsx';
-import * as actionCreators from './action-creators.jsx';
-import VmsListing from './components/VmsListing.jsx';
-import { logDebug } from './utils.jsx';
-import { watchMetrics, cleanupMetricsWatch } from "./watch-metrics.es6";
-
-import '../../../machines/machines.less'; // once per component hierarchy
-
-let reduxStore;
-function initReduxStore() {
- if (reduxStore) {
- logDebug('initReduxStore(): store already initialized, skipping. ', reduxStore);
- return;
- }
- logDebug('initReduxStore(): initializing empty store');
- const initialState = {
- vms: []
- };
-
- const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
- reduxStore = createStore(reducers, initialState, storeEnhancers);
-}
-
-function addKubeLoaderListener ($scope, kubeLoader, kubeSelect) {
- // register load callback( callback, until )
- kubeLoader.listen(() => {
- const vms = kubeSelect().kind('VirtualMachine');
- const persistentVolumes = kubeSelect().kind('PersistentVolume');
- const pods = kubeSelect().kind('Pod');
-
- reduxStore.dispatch(actionCreators.setVms(Object.values(vms)));
- reduxStore.dispatch(actionCreators.setPVs(Object.values(persistentVolumes)));
- reduxStore.dispatch(actionCreators.setPods(Object.values(pods)));
- }, $scope);
-
- // enable watching( watched-entity-type, until )
- kubeLoader.watch('VirtualMachine', $scope);
- kubeLoader.watch('PersistentVolume', $scope);
- kubeLoader.watch('Pod', $scope);
-}
-
-const VmsPlugin = () => (
-
-
-
-);
-
-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
- * @param {kubeMethods} kubeMethods
- * @param {KubeRequest} KubeRequest
- */
-function init($scope, kubeLoader, kubeSelect, kubeMethods, KubeRequest) {
- initReduxStore();
- addKubeLoaderListener($scope, kubeLoader, kubeSelect);
- initMiddleware(kubeMethods, kubeLoader, KubeRequest);
- addScopeVarsToStore($scope);
-
- watchMetrics(reduxStore);
- $scope.$on("$destroy", () => cleanupMetricsWatch());
-
- const rootElement = document.querySelector('#kubernetes-virtual-machines-root');
- React.render(, rootElement);
-}
-
-export { init };
diff --git a/pkg/kubernetes/scripts/virtual-machines/selectors.jsx b/pkg/kubernetes/scripts/virtual-machines/selectors.jsx
index 57560eea9888..5b2965abd021 100644
--- a/pkg/kubernetes/scripts/virtual-machines/selectors.jsx
+++ b/pkg/kubernetes/scripts/virtual-machines/selectors.jsx
@@ -24,7 +24,7 @@ import type { Vm } from './types.jsx';
* Returns pod corresponding to the given vm.
*/
export function getPod(vm, pods) {
- if (!pods) {
+ if (!vm || !pods) {
return null;
}
diff --git a/pkg/kubernetes/scripts/virtual-machines/store.es6 b/pkg/kubernetes/scripts/virtual-machines/store.es6
new file mode 100644
index 000000000000..54e03feb726f
--- /dev/null
+++ b/pkg/kubernetes/scripts/virtual-machines/store.es6
@@ -0,0 +1,47 @@
+/*
+ * 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 .
+ */
+
+import { createStore } from 'redux';
+import reducers from './reducers.jsx';
+import { logDebug } from './utils.jsx';
+
+let reduxStore;
+
+export function initStore () {
+ if (reduxStore) {
+ logDebug('initStore(): store already initialized, skipping.', reduxStore);
+ return reduxStore;
+ }
+ logDebug('initStore(): initializing empty store');
+ const initialState = {
+ vms: []
+ };
+
+ const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
+ reduxStore = createStore(reducers, initialState, storeEnhancers);
+
+ return reduxStore;
+}
+
+export function getStore () {
+ if (!reduxStore) {
+ logDebug('getStore(): store is not initialized yet.');
+ }
+ return reduxStore;
+}
diff --git a/pkg/kubernetes/scripts/virtual-machines/utils.jsx b/pkg/kubernetes/scripts/virtual-machines/utils.jsx
index 8913f1af92aa..3a358e470220 100644
--- a/pkg/kubernetes/scripts/virtual-machines/utils.jsx
+++ b/pkg/kubernetes/scripts/virtual-machines/utils.jsx
@@ -31,6 +31,10 @@ export function getPairs(object) {
}));
}
+export function prefixedId(idPrefix, id) {
+ return idPrefix ? `${idPrefix}-${id}` : null;
+}
+
export function vmIdPrefx(vm) {
return `vm-${vm.metadata.name}`;
}
diff --git a/pkg/kubernetes/views/virtual-machine-page.html b/pkg/kubernetes/views/virtual-machine-page.html
new file mode 100644
index 000000000000..3b67419505aa
--- /dev/null
+++ b/pkg/kubernetes/views/virtual-machine-page.html
@@ -0,0 +1 @@
+
diff --git a/pkg/lib/cockpit-components-detail-page.jsx b/pkg/lib/cockpit-components-detail-page.jsx
new file mode 100644
index 000000000000..7187462f4872
--- /dev/null
+++ b/pkg/lib/cockpit-components-detail-page.jsx
@@ -0,0 +1,90 @@
+/*
+ * 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 .
+ */
+
+'use strict';
+
+const React = require('react');
+
+require('./listing.less');
+// TODO remove next line and detail-page.less file as well once React 16 is merged
+require('./detail-page.less');
+
+const prefixedId = (idPrefix, id) => idPrefix ? `${idPrefix}-${id}` : null;
+
+const DetailPage = ({children}) => {
+ return (
+
+ {children}
+
+ );
+};
+
+const DetailPageRow = ({title, idPrefix, children}) => {
+ // TODO use React.Fragment instead and remove 'className="detail-row"' once React 16 is merged
+ return (
+
+
{title}
+
+ {children}
+
+
+ );
+};
+
+DetailPageRow.propTypes = {
+ title: React.PropTypes.string,
+ idPrefix: React.PropTypes.string, // row will have no elements with id if not specified
+};
+
+const DetailPageHeader = ({title, iconClass, navigateUpTitle, onNavigateUp, actions, idPrefix}) => {
+ const icon = iconClass ? () : null;
+ const navigateUp = navigateUpTitle ? (
+
+ {navigateUpTitle}
+
+ ) : null;
+
+ return (
+
+
+ {actions}
+
+
+ {icon}
+ {title}
+
+ {navigateUp}
+
+ );
+};
+
+DetailPageHeader.propTypes = {
+ title: React.PropTypes.string,
+ iconClass: React.PropTypes.string, // className used for an icon
+ navigateUpTitle: React.PropTypes.string,
+ onNavigateUp: React.PropTypes.func,
+ actions: React.PropTypes.object,
+ idPrefix: React.PropTypes.string, // header will have no elements with id if not specified
+};
+
+module.exports = {
+ DetailPage,
+ DetailPageHeader,
+ DetailPageRow,
+};
diff --git a/pkg/lib/detail-page.less b/pkg/lib/detail-page.less
new file mode 100644
index 000000000000..0bda9e65892f
--- /dev/null
+++ b/pkg/lib/detail-page.less
@@ -0,0 +1,22 @@
+/* Listing pattern */
+
+@import "./variables.less";
+
+.listing-ct-inline div > .listing-ct-body {
+ border: none;
+ padding-top: 0px;
+ padding-left: @listing-ct-padding * 2;
+ padding-bottom: 0px;
+}
+
+.listing-ct-inline .detail-row ~ .detail-row > h3 {
+ border-top: 1px solid @listing-ct-border;
+ padding-top: 20px;
+ margin-top: 30px;
+}
+
+.listing-ct-inline .detail-row > h3 {
+ border-top: none;
+ padding-top: 0px;
+ margin-top: 20px;
+}
diff --git a/test/verify/check-openshift b/test/verify/check-openshift
index 0fe14b9069a4..9e089ee971b0 100755
--- a/test/verify/check-openshift
+++ b/test/verify/check-openshift
@@ -18,16 +18,14 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see .
-import parent
-from testlib import *
-
import os
-import unittest
-import time
-import sys
import re
+import sys
+import time
+import unittest
from kubelib import *
+from testlib import *
# NOTE: Both TestOpenshift and TestRegistry are in this single file to
# prevent them from being run concurrently. Both use a 'openshift'
@@ -442,11 +440,8 @@ LABEL io.projectatomic.nulecule.atomicappversion="0.1.11" \
# kubevirt is not available (means missing in API), so link should not be present
b.wait_not_present("#vms-menu-link")
- def testKubevirtMachinesList(self):
- self.bootstrapKubevirt()
-
+ def startKubevirtVm(self):
o = self.openshift
-
# The "fedoravm" VirtualMachine is created from OfflineVirtualMachine
o.execute("oc patch offlinevirtualmachine fedoravm --type=merge -p '{\"spec\":{\"running\": true}}'") # let's start the VM
print("starting vm, might take a while")
@@ -454,56 +449,172 @@ LABEL io.projectatomic.nulecule.atomicappversion="0.1.11" \
wait(lambda: "virt-launcher-fedoravm" in o.execute("oc get pods"), delay=2)
wait(lambda: "Running" in o.execute("oc get pods | grep virt-launcher-fedoravm"), delay=2, tries=150) # Example: virt-launcher-fedoravm-----rx59t
+ def verifyBrowserLocation(self, url):
+ self.browser.wait_js_cond('window.location.href.slice({0}) === "{1}"'.format(-len(url), url))
+
+ def testKubevirtVmInstance(self):
+ self.bootstrapKubevirt()
+ self.startKubevirtVm()
+ o = self.openshift
+ b = self.browser
+ vm_instance_validator = self.VmInstanceValidator(self)
+ self.login_and_go("/kubernetes")
+
+ with self.browser.wait_timeout(120):
+ b.wait_present("#vms-menu-link")
+ b.click("#vms-menu-link")
+
+ # test navigation between the vm and vms
+ vms_url_location = "/kubernetes/index.html#/vms"
+ vm_name = "fedoravm"
+ vm_namespace = "kubevirt"
+
+ vm_namespace_hash = "#/vms/{0}".format(vm_namespace)
+ vm_url_hash = "{0}/{1}".format(vm_namespace_hash, vm_name)
+ vm_url_location = "/kubernetes/index.html{0}".format(vm_url_hash)
+
+ self.verifyBrowserLocation(vms_url_location)
+ b.wait_present("tr[data-row-id='vm-fedoravm']")
+ b.click("tr[data-row-id='vm-fedoravm']")
+
+ self.verifyBrowserLocation(vm_url_location)
+ b.wait_present("#vm-header-link-up")
+ b.click("#vm-header-link-up")
+ self.verifyBrowserLocation(vms_url_location)
+ b.go(vm_url_hash)
+ self.verifyBrowserLocation(vm_url_location)
+
+ # check all rows
+ b.wait_present("#vm-fedoravm-vm-detail-row-title")
+ b.wait_in_text("#vm-fedoravm-vm-detail-row-title", "VM")
+ vm_instance_validator.validateOverview()
+ b.wait_present("#vm-fedoravm-usage-detail-row-title")
+ b.wait_in_text("#vm-fedoravm-usage-detail-row-title", "Usage")
+ vm_instance_validator.validateUsage()
+ b.wait_present("#vm-fedoravm-disks-detail-row-title")
+ b.wait_in_text("#vm-fedoravm-disks-detail-row-title", "Disks")
+ vm_instance_validator.validateDisks()
+
+ # delete the VM (not the OVM), the VM pod should be recreated automatically
+ b.click("#vm-fedoravm-delete")
+ self.verifyBrowserLocation(vms_url_location)
+ # Since the VM is created from OVM, it will be automatically restarted (spec.running: true)
+ b.wait_not_present("tr[data-row-id='vm-fedoravm']")
+ b.wait_present("tr[data-row-id='vm-fedoravm']")
+ b.go(vm_url_hash)
+
+ # check usage still not present because vm is not running yet)
+ vm_instance_validator.validateEmptyUsage()
+
+ wait(lambda: "Running" in o.execute("oc get pods | grep virt-launcher-fedoravm"), delay=2, tries=150)
+
+ # check links are present
+ with self.browser.wait_timeout(15):
+ b.wait_present("#vm-fedoravm-node")
+ wait(lambda: b.text("#vm-fedoravm-node > a").strip() != "-")
+ wait(lambda: b.is_present("#vm-fedoravm-pod > a") and b.text("#vm-fedoravm-pod > a").strip() != "")
+ b.wait_in_text("#vm-fedoravm-pod", "virt-launcher-fedoravm")
+
+ # namespace redirect to vms
+ b.go(vm_namespace_hash)
+ self.verifyBrowserLocation(vms_url_location + "?namespace=kubevirt")
+
+ # check invalid vm
+ invalid_vm_name = "invalidname"
+ invalid_vm_namespace = "invalidns"
+ invalid_vm_hash = "#/vms/{0}/{1}".format(invalid_vm_namespace, invalid_vm_name)
+ invalid_vm_url_location = "/kubernetes/index.html{0}".format(invalid_vm_hash)
+ b.go(invalid_vm_hash)
+ self.verifyBrowserLocation(invalid_vm_url_location)
+ b.wait_present("#vm-not-found-detail-row-title")
+ b.wait_in_text("#vm-not-found-detail-row-title", "VM {0}:{1} does not exist.".format(invalid_vm_namespace, invalid_vm_name))
+
+ class VmInstanceValidator:
+ def __init__(self, test_obj):
+ self.browser = test_obj.browser
+ self.openshift = test_obj.openshift
+ self.assertFalse = test_obj.assertFalse
+ self.assertGreaterEqual = test_obj.assertGreaterEqual
+
+ def validateOverview(self):
+ b = self.browser
+ b.wait_present("#vm-fedoravm-memory")
+ b.wait_in_text("#vm-fedoravm-memory", "256M")
+ b.wait_in_text("#vm-fedoravm-labels", "kubevirt.io/nodeName=f1.cockpit.lan")
+ b.wait_present("#vm-fedoravm-pod")
+
+ def validateUsage(self):
+ b = self.browser
+
+ b.wait_present("#vm-fedoravm-cpu-metric div svg") # cannot check cpu component because it is an external dependency
+ b.wait_present("#vm-fedoravm-memory-metric")
+ b.wait_present("#vm-fedoravm-network-metric")
+
+ wait(lambda: self._get_units_number("#vm-fedoravm-memory-metric-title") > 0)
+ self.assertGreaterEqual(self._get_units_number("#vm-fedoravm-download-metric", True), 0)
+ self.assertGreaterEqual(self._get_units_number("#vm-fedoravm-upload-metric", True), 0)
+
+ def validateEmptyUsage(self):
+ b = self.browser
+
+ wait(lambda: b.is_present("#vm-fedoravm-usage-metrics") and b.text(
+ "#vm-fedoravm-usage-metrics") == "Usage metrics are available after the pod starts", tries=120)
+ self.assertFalse(b.is_present("#vm-fedoravm-cpu-metric"))
+ self.assertFalse(b.is_present("#vm-fedoravm-memory-metric"))
+ self.assertFalse(b.is_present("#vm-fedoravm-network-metric"))
+
+ def validateDisks(self):
+ b = self.browser
+
+ b.wait_present("#vm-fedoravm-disks-disk0-device") # check disk properties
+ b.wait_present("#vm-fedoravm-disks-disk1-device")
+ b.wait_in_text("#vm-fedoravm-disks-disk0-device", "disk0")
+ b.wait_in_text("#vm-fedoravm-disks-disk1-device", "disk1")
+ b.wait_in_text("#vm-fedoravm-disks-disk0-bus", "virtio")
+ b.wait_in_text("#vm-fedoravm-disks-disk1-bus", "virtio")
+ b.wait_in_text("#vm-fedoravm-disks-disk0-source", "kubevirt/fedora-cloud-registry-disk-demo:latest") # registryvolume
+ b.wait_in_text("#vm-fedoravm-disks-disk1-source", "cloudinitvolume")
+
+ # TODO: add iSCSI disk to the test VM and reenable here
+ # b.click("tr.listing-ct-item.listing-ct-noexpand") # test navigation to the "Volumes" page
+ # b.wait_js_cond('window.location.hash === "#/volumes/iscsi-disk-alpine"')
+
+ def _get_units_number(self, selector, per_second=False):
+ units_regex = "([0-9]+\.?[0-9]*) .?i?B"
+ if per_second:
+ units_regex += "/s"
+ m = re.match(units_regex, self.browser.text(selector))
+ return float(m.group(1)) if m else None
+
+
+ def testKubevirtMachinesList(self):
+ self.bootstrapKubevirt()
+ self.startKubevirtVm()
+ o = self.openshift
b = self.browser
self.login_and_go("/kubernetes")
# kubevirt is available, so link should be present
- b.wait_present("#vms-menu-link")
+ with self.browser.wait_timeout(120):
+ b.wait_present("#vms-menu-link")
b.click("#vms-menu-link")
b.wait_present("tr[data-row-id='vm-fedoravm']")
self.assertEqual(b.text("tr[data-row-id='vm-fedoravm'] th"), "fedoravm")
+ vm_instance_validator = self.VmInstanceValidator(self)
# expand row and check the Overview subtab
- b.click("tr[data-row-id='vm-fedoravm']")
+ b.click("tr[data-row-id='vm-fedoravm'] td.listing-ct-toggle")
b.wait_present("tr[data-row-id='vm-fedoravm'] + tr.listing-ct-panel")
b.wait_in_text("tr[data-row-id='vm-fedoravm'] + tr.listing-ct-panel", "Node")
- b.wait_present("#vm-fedoravm-memory")
- b.wait_in_text("#vm-fedoravm-memory", "256M")
- b.wait_in_text("#vm-fedoravm-labels", "kubevirt.io/nodeName=f1.cockpit.lan")
- b.wait_present("#vm-fedoravm-pod")
-
+ vm_instance_validator.validateOverview()
# switch to the Usage subtab
b.wait_present("#vm-fedoravm-usage-tab")
b.click("#vm-fedoravm-usage-tab")
- b.wait_present("#cpu-metric div svg") # cannot check cpu component because it is an external dependency
- b.wait_present("#memory-metric")
- b.wait_present("#network-metric")
-
- def get_units_value(selector, per_second=False):
- units_regex = "([0-9]+\.?[0-9]*) .?i?B"
- if per_second:
- units_regex += "/s"
- m = re.match(units_regex, b.text(selector))
- return float(m.group(1)) if m else None
-
- wait(lambda: get_units_value("#memory-metric-title") > 0)
- self.assertGreaterEqual(get_units_value("#download-metric", True), 0)
- self.assertGreaterEqual(get_units_value("#upload-metric", True), 0)
+ vm_instance_validator.validateUsage()
# switch to the Disks subtab
b.wait_present("#vm-fedoravm-disks-tab")
b.click("#vm-fedoravm-disks-tab")
- b.wait_present("#vm-fedoravm-disks-disk0-device") # check disk properties
- b.wait_present("#vm-fedoravm-disks-disk1-device")
- b.wait_in_text("#vm-fedoravm-disks-disk0-device", "disk0")
- b.wait_in_text("#vm-fedoravm-disks-disk1-device", "disk1")
- b.wait_in_text("#vm-fedoravm-disks-disk0-bus", "virtio")
- b.wait_in_text("#vm-fedoravm-disks-disk1-bus", "virtio")
- b.wait_in_text("#vm-fedoravm-disks-disk0-source", "kubevirt/fedora-cloud-registry-disk-demo:latest") # registryvolume
- b.wait_in_text("#vm-fedoravm-disks-disk1-source", "cloudinitvolume")
-
- # TODO: add iSCSI disk to the test VM and reenable here
- # b.click("tr.listing-ct-item.listing-ct-noexpand") # test navigation to the "Volumes" page
- # b.wait_js_cond('window.location.hash === "#/volumes/iscsi-disk-alpine"')
+ vm_instance_validator.validateDisks()
# return back to the virtual-machines
b.wait_present("#vms-menu-link") # left-side menu
@@ -521,29 +632,27 @@ LABEL io.projectatomic.nulecule.atomicappversion="0.1.11" \
b.wait_present("tr[data-row-id='vm-fedoravm']")
if not b.is_present("#vm-fedoravm-usage-tab"): # if not expanded
- b.click("tr[data-row-id='vm-fedoravm']") # expand row
+ b.click("tr[data-row-id='vm-fedoravm'] td.listing-ct-toggle") # expand row
# check usage still not present because vm is not running yet)
b.wait_present("#vm-fedoravm-usage-tab")
b.click("#vm-fedoravm-usage-tab")
- b.wait_present("#usage-metrics")
- b.wait_in_text("#usage-metrics", "Usage metrics are available after the pod starts")
- self.assertFalse(b.is_present("#cpu-metric"))
- self.assertFalse(b.is_present("#memory-metric"))
- self.assertFalse(b.is_present("#network-metric"))
+ vm_instance_validator.validateEmptyUsage()
+
+ b.wait_present("#vm-fedoravm-overview-tab")
b.click("#vm-fedoravm-overview-tab")
- b.click("tr[data-row-id='vm-fedoravm']") # hide row
+ b.click("tr[data-row-id='vm-fedoravm'] td.listing-ct-toggle") # hide row
wait(lambda: "Running" in o.execute("oc get pods | grep virt-launcher-fedoravm"), delay=2, tries=150)
# link to node
b.wait_present("tr[data-row-id='vm-fedoravm']") # the just-started VM is listed
- b.click("tr[data-row-id='vm-fedoravm']") # expand row
+ b.click("tr[data-row-id='vm-fedoravm'] td.listing-ct-toggle") # expand row
b.wait_present("tr[data-row-id='vm-fedoravm'] + tr.listing-ct-panel")
b.wait_present("#vm-fedoravm-node")
wait(lambda: b.text("#vm-fedoravm-node > a").strip() != "-") # `-` == unassigned ; so wait for a change
node_name = b.text("#vm-fedoravm-node > a").strip()
b.click("#vm-fedoravm-node > a") # jump to "node" detail
- b.wait_js_cond('window.location.hash === "#/nodes/%s"' % (node_name,))
+ self.verifyBrowserLocation('/kubernetes/index.html#/nodes/%s' % node_name)
# link to pod
b.click("#vms-menu-link")
@@ -552,7 +661,7 @@ LABEL io.projectatomic.nulecule.atomicappversion="0.1.11" \
b.wait_in_text("#vm-fedoravm-pod", "virt-launcher-fedoravm")
pod_name = b.text("#vm-fedoravm-pod > a").strip()
b.click("#vm-fedoravm-pod > a")
- b.wait_js_cond('window.location.hash === "#/l/pods/kubevirt/%s"' % (pod_name))
+ self.verifyBrowserLocation('/kubernetes/index.html#/l/pods/kubevirt/%s' % pod_name)
def testKubevirtMachinesCreate(self):
class TestCreateConfig:
@@ -582,7 +691,7 @@ LABEL io.projectatomic.nulecule.atomicappversion="0.1.11" \
self.assertEqual(b.text("{0} td:nth-of-type(2)".format(row_selector)), dialog.getNamespace())
# expand row
- b.click(row_selector)
+ b.click("{0} td.listing-ct-toggle".format(row_selector))
b.wait_present("{0} + tr.listing-ct-panel".format(row_selector))
b.wait_in_text("{0} + tr.listing-ct-panel".format(row_selector), "Node")
@@ -591,7 +700,8 @@ LABEL io.projectatomic.nulecule.atomicappversion="0.1.11" \
def deleteVm(self, dialog):
b = self.browser
self.openshift.execute("oc delete vm {0} --namespace={1}".format(dialog.name, dialog.getNamespace()))
- b.wait_not_present("tr[data-row-id='vm-{0}']".format(dialog.name))
+ with self.browser.wait_timeout(300):
+ b.wait_not_present("tr[data-row-id='vm-{0}']".format(dialog.name))
return self