diff --git a/locales/en/plugin__kubevirt-plugin.json b/locales/en/plugin__kubevirt-plugin.json index 4492aecfb..a28d0f0fc 100644 --- a/locales/en/plugin__kubevirt-plugin.json +++ b/locales/en/plugin__kubevirt-plugin.json @@ -1396,6 +1396,7 @@ "View pod logs": "View pod logs", "View virtualization dashboard": "View virtualization dashboard", "View YAML & CLI": "View YAML & CLI", + "Virtctl is disabled for this namespace as it's managed by": "Virtctl is disabled for this namespace as it's managed by", "virtio": "virtio", "VirtIO": "VirtIO", "Virtual machine is not running": "Virtual machine is not running", diff --git a/package.json b/package.json index b577d6f7f..f62644404 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ }, "devDependencies": { "@cypress/webpack-preprocessor": "^6.0.2", - "@kubevirt-ui/kubevirt-api": "^1.3.3", + "@kubevirt-ui/kubevirt-api": "^1.3.5", "@openshift-console/dynamic-plugin-sdk": "1.8.0", "@openshift-console/dynamic-plugin-sdk-internal": "1.0.0", "@openshift-console/dynamic-plugin-sdk-webpack": "^1.3.0", diff --git a/src/utils/components/SSHAccess/components/ConsoleOverVirtctl.tsx b/src/utils/components/SSHAccess/components/ConsoleOverVirtctl.tsx index 5b2d5a614..36eee382d 100644 --- a/src/utils/components/SSHAccess/components/ConsoleOverVirtctl.tsx +++ b/src/utils/components/SSHAccess/components/ConsoleOverVirtctl.tsx @@ -2,10 +2,18 @@ import React, { FC } from 'react'; import { Trans } from 'react-i18next'; import { Link } from 'react-router-dom-v5-compat'; +import UserDefinedNetworkModel from '@kubevirt-ui/kubevirt-api/console/models/UserDefinedNetworkModel'; import { V1VirtualMachine } from '@kubevirt-ui/kubevirt-api/kubevirt'; import { getConsoleVirtctlCommand } from '@kubevirt-utils/components/SSHAccess/utils'; import { documentationURL } from '@kubevirt-utils/constants/documentation'; import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import { + ClusterUserDefinedNetworkModelGroupVersionKind, + UserDefinedNetworkModelGroupVersionKind, +} from '@kubevirt-utils/models'; +import { getName, getNamespace } from '@kubevirt-utils/resources/shared'; +import useNamespaceUDN from '@kubevirt-utils/resources/udn/hooks/useNamespaceUDN'; +import { ResourceLink } from '@openshift-console/dynamic-plugin-sdk'; import { DescriptionListDescription, DescriptionListGroup, @@ -13,6 +21,7 @@ import { Grid, GridItem, Popover, + Text, } from '@patternfly/react-core'; import { HelpIcon } from '@patternfly/react-icons'; @@ -27,43 +36,63 @@ type ConsoleOverVirtctlProps = { const ConsoleOverVirtctl: FC = ({ vm }) => { const { t } = useKubevirtTranslation(); + const [isNamespaceManagedByUDN, udn] = useNamespaceUDN(getNamespace(vm)); + return ( - {t('SSH using virtctl')}{' '} - - -
- Open an SSH connection with the VM using the cluster API server. You must be able - to access the API server and have virtctl command line tool installed. -
+ + {t('SSH using virtctl')}{' '} + + +
+ Open an SSH connection with the VM using the cluster API server. You must be + able to access the API server and have virtctl command line tool installed. +
+
+
+ For more details, see{' '} + Installing virtctl in Getting + started with OpenShift Virtualization. +
+

-
- For more details, see{' '} - Installing virtctl in Getting started - with OpenShift Virtualization. -
-
-
- - {t('Example: ')} - - {getConsoleVirtctlCommand(vm)} - - - - } - aria-label="Help" - className="virtctl-popover" - position="right" - > - -
+ + {t('Example: ')} + + {getConsoleVirtctlCommand(vm)} + + + + } + aria-label="Help" + className="virtctl-popover" + position="right" + > + + +
- + {isNamespaceManagedByUDN ? ( + <> + {t("Virtctl is disabled for this namespace as it's managed by")}{' '} + {' '} + + ) : ( + + )}
); diff --git a/src/utils/constants/documentation.ts b/src/utils/constants/documentation.ts index 4fd81bd93..2064054a6 100644 --- a/src/utils/constants/documentation.ts +++ b/src/utils/constants/documentation.ts @@ -9,8 +9,8 @@ export const documentationURL = { CDI_UPLOAD_SUPPORTED_TYPES: `${REDHAT_DOC_URL}/openshift_container_platform/4.9/html/virtualization/virtual-machines#virt-cdi-supported-operations-matrix_virt-importing-virtual-machine-images-datavolumes`, CHECKUPS: `${OPENSHIFT_DOC_URL}/4.16/virt/monitoring/virt-running-cluster-checkups.html`, CHECKUPS_LATENCY: `${OPENSHIFT_DOC_URL}/4.15/virt/monitoring/virt-running-cluster-checkups.html#virt-measuring-latency-vm-secondary-network_virt-running-cluster-checkups`, - CLOUDINIT_INFO: 'https://cloudinit.readthedocs.io/en/latest/index.html', + CREATING_VMS_FROM_TEMPLATES: `${OPENSHIFT_DOC_URL}/4.15/virt/virtual_machines/creating_vms_rh/virt-creating-vms-from-templates.html`, CRON_INFO: `${REDHAT_BASE_URL}/sysadmin/automate-linux-tasks-cron`, DATA_FOUNDATION_OPERATOR: @@ -55,6 +55,8 @@ export const documentationURL = { VIRT_CTL: `${OPENSHIFT_DOC_URL}/4.15/virt/getting_started/virt-using-the-cli-tools.html`, VIRT_MANAGER_DOWNLOAD: 'https://virt-manager.org/download.html', VIRT_SECONDARY_NETWORK: `${OPENSHIFT_DOC_URL}/4.15/virt/vm_networking/virt-networking-overview.html#secondary-network-config`, + VIRTCTL_CLI: + 'https://docs.openshift.com/container-platform/4.15/virt/getting_started/virt-using-the-cli-tools.html', VIRTUALIZATION_BLOG: `https://cloud.redhat.com/learn/topics/virtualization/`, VIRTUALIZATION_WHAT_YOU_CAN_DO: 'https://docs.redhat.com/en/documentation/openshift_container_platform/4.16/html/virtualization/about#virt-what-you-can-do-with-virt_about-virt', diff --git a/src/utils/resources/udn/hooks/useNamespaceUDN.ts b/src/utils/resources/udn/hooks/useNamespaceUDN.ts new file mode 100644 index 000000000..4d4fb9330 --- /dev/null +++ b/src/utils/resources/udn/hooks/useNamespaceUDN.ts @@ -0,0 +1,51 @@ +import { + ClusterUserDefinedNetworkModelGroupVersionKind, + modelToGroupVersionKind, + ProjectModel, + UserDefinedNetworkModelGroupVersionKind, +} from '@kubevirt-utils/models'; +import { + ClusterUserDefinedNetworkKind, + UserDefinedNetworkKind, + UserDefinedNetworkRole, +} from '@kubevirt-utils/resources/udn/types'; +import { matchSelector } from '@kubevirt-utils/utils/matchSelector'; +import { isEmpty } from '@kubevirt-utils/utils/utils'; +import { K8sResourceCommon, useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; + +const useNamespaceUDN = ( + namespace: string, +): [ + isNamespaceManagedByUDN: boolean, + udn: ClusterUserDefinedNetworkKind | UserDefinedNetworkKind, +] => { + const [project] = useK8sWatchResource({ + groupVersionKind: modelToGroupVersionKind(ProjectModel), + name: namespace, + }); + + const [udns] = useK8sWatchResource({ + groupVersionKind: UserDefinedNetworkModelGroupVersionKind, + isList: true, + namespace, + }); + + const [clusterUDNs] = useK8sWatchResource({ + groupVersionKind: ClusterUserDefinedNetworkModelGroupVersionKind, + isList: true, + }); + + const primaryUDN = udns?.find( + (udn) => udn?.spec?.layer2?.role === UserDefinedNetworkRole.Primary, + ); + + const primaryClustersUDNs = clusterUDNs + ?.filter((clusterUDN) => matchSelector(project, clusterUDN?.spec?.namespaceSelector)) + ?.find((udn) => udn?.spec?.network?.layer2?.role === UserDefinedNetworkRole.Primary); + + const isNamespaceManagedByUDN = !isEmpty(primaryUDN || primaryClustersUDNs); + + return [isNamespaceManagedByUDN, primaryUDN || primaryClustersUDNs]; +}; + +export default useNamespaceUDN; diff --git a/src/utils/resources/udn/types.ts b/src/utils/resources/udn/types.ts new file mode 100644 index 000000000..9caa3a705 --- /dev/null +++ b/src/utils/resources/udn/types.ts @@ -0,0 +1,51 @@ +import { K8sResourceKind, Selector } from '@openshift-console/dynamic-plugin-sdk'; + +export type UserDefinedNetworkAnnotations = { + description?: string; +}; + +export enum UserDefinedNetworkRole { + Primary = 'Primary', + Secondary = 'Secondary', +} + +export type UserDefinedNetworkLayer2 = { + ipamLifecycle?: string; + joinSubnets?: string[]; + mtu?: number; + role: UserDefinedNetworkRole; + subnets?: string[]; +}; + +export type UserDefinedNetworkLayer3Subnet = { + cidr: string; + hostSubnet?: number; +}; + +export type UserDefinedNetworkSubnet = string | UserDefinedNetworkLayer3Subnet; + +export type UserDefinedNetworkLayer3 = { + joinSubnets?: string[]; + mtu?: number; + role: string; + subnets?: UserDefinedNetworkLayer3Subnet[]; +}; + +export type ClusterUserDefinedNetworkSpec = { + namespaceSelector?: Selector; + network?: UserDefinedNetworkSpec; +}; + +export type UserDefinedNetworkSpec = { + layer2?: UserDefinedNetworkLayer2; + layer3?: UserDefinedNetworkLayer3; + topology: string; +}; + +export type ClusterUserDefinedNetworkKind = { + spec?: ClusterUserDefinedNetworkSpec; +} & K8sResourceKind; + +export type UserDefinedNetworkKind = { + spec?: UserDefinedNetworkSpec; +} & K8sResourceKind; diff --git a/src/utils/utils/matchSelector.ts b/src/utils/utils/matchSelector.ts new file mode 100644 index 000000000..de0f0391f --- /dev/null +++ b/src/utils/utils/matchSelector.ts @@ -0,0 +1,54 @@ +import { + K8sResourceCommon, + MatchExpression, + Operator, + Selector, +} from '@openshift-console/dynamic-plugin-sdk'; + +import { isEmpty } from './utils'; + +export const createEquals = (key: string, value: string): MatchExpression => ({ + key, + operator: Operator.Equals, + values: [value], +}); + +export const matchExpressionSatisfied = ( + expression: MatchExpression, + labels: { [key in string]: string }, +): boolean => { + switch (expression.operator) { + case Operator.Equals: + return labels?.[expression.key] === expression.values?.[0]; + case Operator.NotEqual: + return labels?.[expression.key] !== expression.values?.[0]; + case Operator.NotEquals: + return labels?.[expression.key] !== expression.values?.[0]; + case Operator.Exists: + return !isEmpty(labels?.[expression.key]); + case Operator.DoesNotExist: + return isEmpty(labels?.[expression.key]); + case Operator.GreaterThan: + return labels?.[expression.key] > expression.values?.[0]; + case Operator.LessThan: + return labels?.[expression.key] < expression.values?.[0]; + case Operator.In: + return expression.values?.includes(labels?.[expression.key]); + case Operator.NotIn: + return !expression.values?.includes(labels?.[expression.key]); + } +}; + +export const matchSelector = (resource: K8sResourceCommon, selector: Selector) => { + const { matchExpressions, matchLabels } = selector || {}; + + const requirements = Object.keys(matchLabels || {}) + .sort() + .map((matchLabel) => createEquals(matchLabel, matchLabels[matchLabel])); + + const allExpressions = [...requirements, ...(matchExpressions || [])]; + + return allExpressions.every((expression) => + matchExpressionSatisfied(expression, resource?.metadata?.labels), + ); +}; diff --git a/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabNetworkInterfaces/VirtualMachinesOverviewTabNetworkInterfaces.tsx b/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabNetworkInterfaces/VirtualMachinesOverviewTabNetworkInterfaces.tsx index ae056d535..d3fc6beea 100644 --- a/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabNetworkInterfaces/VirtualMachinesOverviewTabNetworkInterfaces.tsx +++ b/src/views/virtualmachines/details/tabs/overview/components/VirtualMachinesOverviewTabNetworkInterfaces/VirtualMachinesOverviewTabNetworkInterfaces.tsx @@ -4,6 +4,8 @@ import { Link } from 'react-router-dom-v5-compat'; import { V1VirtualMachine, V1VirtualMachineInstance } from '@kubevirt-ui/kubevirt-api/kubevirt'; import { VirtualMachineDetailsTab } from '@kubevirt-utils/constants/tabs-constants'; import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation'; +import { getNamespace } from '@kubevirt-utils/resources/shared'; +import useNamespaceUDN from '@kubevirt-utils/resources/udn/hooks/useNamespaceUDN'; import { VirtualizedTable } from '@openshift-console/dynamic-plugin-sdk'; import { Card, CardBody, CardTitle, Divider } from '@patternfly/react-core'; @@ -29,6 +31,8 @@ const VirtualMachinesOverviewTabInterfaces: FC @@ -57,7 +61,7 @@ const VirtualMachinesOverviewTabInterfaces: FC - + {isNamespaceManagedByUDN && } diff --git a/yarn.lock b/yarn.lock index f99cb41d7..cdb268541 100644 --- a/yarn.lock +++ b/yarn.lock @@ -871,10 +871,10 @@ resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.5.0.tgz#6008e35b9d9d8ee27bc4bfaa70c8cbf33a537b4c" integrity sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA== -"@kubevirt-ui/kubevirt-api@^1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@kubevirt-ui/kubevirt-api/-/kubevirt-api-1.3.3.tgz#c43ac3c392365c5a5651fd8b60f89c0f7ac5b4c2" - integrity sha512-YqZo/KKq9ndSq6YW052x9GzXMudQV8MSddgX4URO6UAKfEhVDrpKItspX97/dvBFv/qy0hMN7J5CXHDiTG3lMg== +"@kubevirt-ui/kubevirt-api@^1.3.5": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@kubevirt-ui/kubevirt-api/-/kubevirt-api-1.3.5.tgz#c9c10b30d8cc9770fbc6b08077b9a997fdc3ca58" + integrity sha512-FI9RXYwVl3/INr4z7uemuJk3D218DzxOSm4iiVjJIiYp5DHj+r5EqwTaaWi9NQQYj+aZUw3a1akd8p/JO8lMZw== "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4"