Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions locales/en/plugin__networking-console-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@
"View logs": "View logs",
"View naming information": "View naming information",
"Virtual Machines": "Virtual Machines",
"Visualize, scale, and manage your cluster topology. Right-click the canvas for additional actions.": "Visualize, scale, and manage your cluster topology. Right-click the canvas for additional actions.",
"VLAN": "VLAN",
"VLAN tag number": "VLAN tag number",
"Weight": "Weight",
Expand Down
945 changes: 473 additions & 472 deletions locales/es/plugin__networking-console-plugin.json

Large diffs are not rendered by default.

939 changes: 470 additions & 469 deletions locales/fr/plugin__networking-console-plugin.json

Large diffs are not rendered by default.

928 changes: 463 additions & 465 deletions locales/ja/plugin__networking-console-plugin.json

Large diffs are not rendered by default.

938 changes: 468 additions & 470 deletions locales/ko/plugin__networking-console-plugin.json

Large diffs are not rendered by default.

940 changes: 469 additions & 471 deletions locales/zh/plugin__networking-console-plugin.json

Large diffs are not rendered by default.

702 changes: 700 additions & 2 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@patternfly/react-styles": "6.6.1",
"@patternfly/react-table": "6.6.2",
"@patternfly/react-tokens": "6.6.1",
"@patternfly/react-topology": "^6.6.0",
"classnames": "^2.5.1",
"js-yaml": "^4.3.2",
"linkify-it": "6.1.0",
Expand Down
3 changes: 3 additions & 0 deletions plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from './src/views/networkpolicies/manifest';
import { RoutesExposedModules, RoutesExtensions } from './src/views/routes/manifest';
import { ServicesExposedModules, ServicesExtensions } from './src/views/services/manifest';
import { TopologyExposedModules, TopologyExtensions } from './src/views/topology/manifest';
import {
UserDefinedNetworksExposedModules,
UserDefinedNetworksExtensions,
Expand All @@ -31,6 +32,7 @@ export const pluginMetadata: ConsolePluginBuildMetadata = {
...FlagsExposedModules,
...RoutesExposedModules,
...UserDefinedNetworksExposedModules,
...TopologyExposedModules,
yamlTemplates: './templates/index.ts',
},
name: 'networking-console-plugin',
Expand All @@ -45,4 +47,5 @@ export const extensions: EncodedExtension[] = [
...NADsExtensions,
...FlagsExtensions,
...UserDefinedNetworksExtensions,
...TopologyExtensions,
];
2 changes: 2 additions & 0 deletions src/utils/resources/pods/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const VIRT_LAUNCHER_POD_LABEL_KEY = 'kubevirt.io';
export const VIRT_LAUNCHER_POD_LABEL_VALUE = 'virt-launcher';
3 changes: 3 additions & 0 deletions src/utils/resources/udns/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ export const LAYER2_TOPOLOGY = 'Layer2';
export const LAYER3_TOPOLOGY = 'Layer3';

export const CLUSTER_UDN_NAD_NAME_PREFIX = 'cluster.udn.';

export const NETWORK_CREATED = 'NetworkCreated';
export const NETWORK_ALLOCATION_SUCCEEDED = 'NetworkAllocationSucceeded';
63 changes: 58 additions & 5 deletions src/utils/resources/udns/helper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import {
K8sResourceCommon,
MatchExpression,
Operator,
} from '@openshift-console/dynamic-plugin-sdk';
import { ClusterUserDefinedNetworkModel } from '@utils/models';
import { getName } from '@utils/resources/shared';
import { getUDNConditions } from '@utils/resources/udns/selectors';
import { verifyMatchExpressions } from '@utils/utils';

import { CLUSTER_UDN_NAD_NAME_PREFIX } from './constants';
import {
CLUSTER_UDN_NAD_NAME_PREFIX,
NETWORK_ALLOCATION_SUCCEEDED,
NETWORK_CREATED,
} from './constants';
import {
ClusterUserDefinedNetworkKind,
UserDefinedNetworkKind,
Expand All @@ -10,13 +21,18 @@ import {

export type UDNResource = ClusterUserDefinedNetworkKind | UserDefinedNetworkKind;

export const isPrimaryUDN = (udn: ClusterUserDefinedNetworkKind | UserDefinedNetworkKind) =>
udn?.spec?.layer2?.role === UserDefinedNetworkRole.Primary ||
udn?.spec?.network?.layer2?.role === UserDefinedNetworkRole.Primary;

export const isClusterUDN = (udn: UDNResource): udn is ClusterUserDefinedNetworkKind =>
udn.kind === ClusterUserDefinedNetworkModel.kind;

export const isPrimaryUDN = (udn: ClusterUserDefinedNetworkKind | UserDefinedNetworkKind) => {
const spec = isClusterUDN(udn) ? udn?.spec?.network : udn?.spec;
return (
spec?.layer2?.role === UserDefinedNetworkRole.Primary ||
spec?.layer3?.role === UserDefinedNetworkRole.Primary ||
spec?.localnet?.role === UserDefinedNetworkRole.Primary
);
};

export const getUDNNADNameCandidates = (udn: UDNResource): string[] => {
const udnName = getName(udn);

Expand All @@ -30,3 +46,40 @@ export const getUDNNADNameCandidates = (udn: UDNResource): string[] => {

return [udnName];
};

export const udnIsReady = (udn: UDNResource) => {
const conditions = getUDNConditions(udn);
const networkCreationSuccessful =
conditions?.find((condition) => condition.type === NETWORK_CREATED)?.status === 'True';
const networkAllocationSuccessful =
conditions?.find((condition) => condition.type === NETWORK_ALLOCATION_SUCCEEDED)?.status ===
'True';

return networkCreationSuccessful && networkAllocationSuccessful;
};

export const getCUDNProjects = (
cudn: ClusterUserDefinedNetworkKind,
projects: K8sResourceCommon[],
): K8sResourceCommon[] => {
const namespaceSelector = cudn?.spec?.namespaceSelector || {};

const matchLabelsToExpressions = Object.entries(namespaceSelector.matchLabels || {}).map(
([key, value]): MatchExpression => ({
key,
operator: Operator.Equals,
values: [value],
}),
);

const matchExpressions = namespaceSelector.matchExpressions || [];

const combinedExpressions = [...matchLabelsToExpressions, ...matchExpressions];

return projects?.filter((project) => verifyMatchExpressions(project, combinedExpressions)) || [];
};

export const getCUDNNamespaceNames = (
cudn: ClusterUserDefinedNetworkKind,
projects: K8sResourceCommon[],
) => getCUDNProjects(cudn, projects).map((project) => getName(project)) || [];
31 changes: 31 additions & 0 deletions src/utils/resources/vm/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
export const NETWORK_PATH = '/spec/template/spec/networks';
export const INTERFACE_PATH = '/spec/template/spec/domain/devices/interfaces';
export const INTERFACE_STATE_ABSENT = 'absent';

// https://github.com/kubevirt/api/blob/9689e71fe2bed9e7da5f165760bbbf6981cc1087/core/v1/types.go#L1277
export const PrintableVMStatus = {
Deleting: 'Deleting',
Migrating: 'Migrating',
Paused: 'Paused',
Provisioning: 'Provisioning',
Running: 'Running',
Starting: 'Starting',
Stopped: 'Stopped',
Stopping: 'Stopping',
Terminating: 'Terminating',
Unknown: 'Unknown',
WaitingForReceiver: 'WaitingForReceiver',
WaitingForVolumeBinding: 'WaitingForVolumeBinding',
} as const;

export type PrintableVMStatus = (typeof PrintableVMStatus)[keyof typeof PrintableVMStatus];

export const PrintableVMErrorStatus = {
CrashLoopBackOff: 'CrashLoopBackOff',
DataVolumeError: 'DataVolumeError',
ErrImagePull: 'ErrImagePull',
ErrorDataVolumeNotFound: 'ErrorDataVolumeNotFound',
ErrorPvcNotFound: 'ErrorPvcNotFound',
ErrorUnschedulable: 'ErrorUnschedulable',
ImagePullBackOff: 'ImagePullBackOff',
} as const;

export type PrintableVMErrorStatus =
(typeof PrintableVMErrorStatus)[keyof typeof PrintableVMErrorStatus];
54 changes: 46 additions & 8 deletions src/utils/resources/vm/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { k8sPatch } from '@openshift-console/dynamic-plugin-sdk';
import { UserDefinedNetworkModel } from '@utils/models';
import { NADNetworkPair, NetworkAttachmentDefinitionKind } from '@utils/resources/nads/types';
import { getName, getNamespace } from '@utils/resources/shared';
import { getUDNNADNameCandidates, UDNResource } from '@utils/resources/udns/helper';
import { getUDNNADNameCandidates, isPrimaryUDN, UDNResource } from '@utils/resources/udns/helper';

import { INTERFACE_PATH, INTERFACE_STATE_ABSENT, NETWORK_PATH } from './constants';
import { getInterfaces, getNetworks, getVMStatus } from './selectors';
Expand Down Expand Up @@ -131,13 +131,8 @@ export const multusNetworkMatchesNAD = (
export const getUDNNetworkNamespace = (
udn: UDNResource,
vm: V1VirtualMachine,
): string | undefined => {
if (udn.kind === UserDefinedNetworkModel.kind) {
return getNamespace(udn);
}

return getNamespace(vm);
};
): string | undefined =>
udn.kind === UserDefinedNetworkModel.kind ? getNamespace(udn) : getNamespace(vm);

const multusNetworkNameMatchesCandidates = (
multusNetworkName: string,
Expand Down Expand Up @@ -180,6 +175,49 @@ export const multusNetworkMatchesUDN = (
);
};

export const vmUsesUDN = (
vm: V1VirtualMachine,
udn: UDNResource,
cudnNamespaces?: string[],
): boolean => {
if (!udn) return false;

const networks = getNetworks(vm);
const interfaces = getInterfaces(vm);

// Check for secondary UDN connection via multus networks
const hasSecondaryConnection = networks.some((network) => {
if (!multusNetworkMatchesUDN(network, udn, vm)) return false;

const iface = interfaces.find((item) => item.name === network.name);
return Boolean(iface && iface.state !== INTERFACE_STATE_ABSENT);
});

if (hasSecondaryConnection) return true;

// Check for primary UDN connection via pod network
// Primary UDNs replace the default pod network for VMs in the same namespace
if (isPrimaryUDN(udn)) {
const vmNamespace = getNamespace(vm);
const udnNamespace = getNamespace(udn);

// For namespace-scoped primary UDNs, VM must be in the same namespace
if (udnNamespace && vmNamespace === udnNamespace) {
// Check if VM has a pod network (which would use the primary UDN)
const hasPodNetwork = networks.some((network) => Boolean(network.pod));
if (hasPodNetwork) return true;
}

// For cluster-scoped primary CUDNs, check if VM's namespace is in the active namespaces
if (!udnNamespace && cudnNamespaces) {
const hasPodNetwork = networks.some((network) => Boolean(network.pod));
if (hasPodNetwork && cudnNamespaces.includes(vmNamespace)) return true;
}
}

return false;
};

export const buildNADReferenceForUDN = (
udn: UDNResource,
vm: V1VirtualMachine,
Expand Down
4 changes: 2 additions & 2 deletions src/views/routes/list/components/RouteStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import React, { FC } from 'react';
import Status from '@openshift-console/dynamic-plugin-sdk/lib/app/components/status/Status';
import { RouteKind } from '@utils/types';

import { routeStatus } from '../utils/utils';
import { getRouteStatus } from '../utils/utils';

type RouteStatusProps = {
route: RouteKind;
};

const RouteStatus: FC<RouteStatusProps> = ({ route }) => {
const status: string = routeStatus(route);
const status: string = getRouteStatus(route);

return <Status status={status} />;
};
Expand Down
6 changes: 3 additions & 3 deletions src/views/routes/list/hooks/useRouteFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { RoutesStatuses } from '@utils/constants/routes';
import { useNetworkingTranslation } from '@utils/hooks/useNetworkingTranslation';
import { RouteKind } from '@utils/types';

import { routeStatus } from '../utils/utils';
import { getRouteStatus } from '../utils/utils';

const useRouteFilters = (): RowFilter<RouteKind>[] => {
const { t } = useNetworkingTranslation();
Expand All @@ -13,13 +13,13 @@ const useRouteFilters = (): RowFilter<RouteKind>[] => {
filter: ({ selected }, obj) => {
if (selected?.length === 0) return true;

const status = routeStatus(obj);
const status = getRouteStatus(obj);

return selected.includes(status);
},
filterGroupName: t('Status'),
items: RoutesStatuses.map((status) => ({ id: status, title: status })),
reducer: (obj) => routeStatus(obj),
reducer: (obj) => getRouteStatus(obj),
type: 'route-status',
},
];
Expand Down
6 changes: 3 additions & 3 deletions src/views/routes/list/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { RoutesStatusesType } from '@utils/constants/routes';
import { t } from '@utils/hooks/useNetworkingTranslation';
import { RouteIngress, RouteKind } from '@utils/types';

export const routeStatus = (route: RouteKind): RoutesStatusesType => {
export const getRouteStatus = (route: RouteKind): RoutesStatusesType => {
let atLeastOneAdmitted = false;

if (!route.status || !route.status.ingress) {
Expand Down Expand Up @@ -92,8 +92,8 @@ export const sortRoutesByLocation = (direction: string) => (a: RouteKind, b: Rou
export const sortRoutesByStatus = (direction: string) => (a: RouteKind, b: RouteKind) => {
const { first, second } = direction === 'asc' ? { first: a, second: b } : { first: b, second: a };

const firstValue = routeStatus(first);
const secondValue = routeStatus(second);
const firstValue = getRouteStatus(first);
const secondValue = getRouteStatus(second);

return firstValue?.localeCompare(secondValue);
};
11 changes: 8 additions & 3 deletions src/views/services/details/tabs/pods/components/PodRow.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { FC } from 'react';
import classNames from 'classnames';
import * as _ from 'lodash';

import { modelToGroupVersionKind, PodModel } from '@kubevirt-ui-ext/kubevirt-api/console';
import type { IoK8sApiCoreV1Pod } from '@kubevirt-ui-ext/kubevirt-api/kubernetes';
Expand All @@ -18,7 +17,13 @@ import { useNetworkingTranslation } from '@utils/hooks/useNetworkingTranslation'

import { podColumnInfo } from '../hooks/usePodColumns';
import { formatBytesAsMiB, formatCores } from '../units';
import { getPodCPUUsage, getPodMemoryUsage, podPhase, podReadiness, podRestarts } from '../utils';
import {
getPodCPUUsage,
getPodMemoryUsage,
getPodPhase,
podReadiness,
podRestarts,
} from '../utils';

import { PodStatus } from './PodStatus';
import { PodTraffic } from './PodTraffic';
Expand All @@ -37,7 +42,7 @@ const PodRow: FC<PodRowType> = ({
const { creationTimestamp, labels, name, namespace } = pod.metadata;

const { readyCount, totalContainers } = podReadiness(pod);
const phase = podPhase(pod);
const phase = getPodPhase(pod);
const restarts = podRestarts(pod);
const context = { [`core~${PodModel.apiVersion}~${PodModel.kind}`]: pod };

Expand Down
4 changes: 2 additions & 2 deletions src/views/services/details/tabs/pods/components/PodStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Button, ButtonVariant, Content, Divider, Popover } from '@patternfly/re
import { useNetworkingTranslation } from '@utils/hooks/useNetworkingTranslation';
import { resourcePathFromModel } from '@utils/resources/shared';

import { isContainerCrashLoopBackOff, isWindowsPod, podPhase } from '../utils';
import { getPodPhase, isContainerCrashLoopBackOff, isWindowsPod } from '../utils';

type PodStatusPopoverProps = {
bodyContent: string;
Expand Down Expand Up @@ -40,7 +40,7 @@ const PodStatusPopover: FC<PodStatusPopoverProps> = ({
};

export const PodStatus: FC<PodStatusProps> = ({ pod }) => {
const status = podPhase(pod);
const status = getPodPhase(pod);
const unschedulableCondition = pod.status?.conditions?.find(
(condition) => condition.reason === 'Unschedulable' && condition.status === 'False',
);
Expand Down
12 changes: 12 additions & 0 deletions src/views/services/details/tabs/pods/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,15 @@ export const PROMETHEUS_TENANCY_BASE_PATH = window.SERVER_FLAGS.prometheusTenanc

export const MILLISECONDS_TO_SECONDS_MULTIPLIER = 1000;
export const MIGRATION__PROMETHEUS_DELAY = 15 * MILLISECONDS_TO_SECONDS_MULTIPLIER;

export const PodPhase = {
Evicted: 'Evicted',
Failed: 'Failed',
Pending: 'Pending',
Running: 'Running',
Succeeded: 'Succeeded',
Terminating: 'Terminating',
Unknown: 'Unknown',
} as const;

export type PodPhase = (typeof PodPhase)[keyof typeof PodPhase];
4 changes: 2 additions & 2 deletions src/views/services/details/tabs/pods/hooks/usePodColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { PROMETHEUS_BASE_PATH, PROMETHEUS_TENANCY_BASE_PATH } from '../constants
import {
getPodCPUUsage,
getPodMemoryUsage,
podPhase,
getPodPhase,
podReadiness,
podRestarts,
sortResourceByValue,
Expand Down Expand Up @@ -105,7 +105,7 @@ const usePodColumns = (
id: podColumnInfo.status.id,
props: { className: podColumnInfo.status.classes },
sort: (data, direction) =>
data.sort(sortResourceByValue<IoK8sApiCoreV1Pod>(direction, podPhase)),
data.sort(sortResourceByValue<IoK8sApiCoreV1Pod>(direction, getPodPhase)),
title: t('Status'),
transforms: [sortable],
},
Expand Down
Loading
Loading