Can attach defined networks to domains

This commit is contained in:
Pierre HUBERT 2023-12-08 13:10:53 +01:00
parent 54a3013c59
commit f05ae9fd52
5 changed files with 77 additions and 13 deletions

View File

@ -63,11 +63,20 @@ pub struct FeaturesXML {
#[serde(rename = "acpi")] #[serde(rename = "acpi")]
pub struct ACPIXML {} pub struct ACPIXML {}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename = "source")]
pub struct NetIntSourceXML {
#[serde(rename(serialize = "@network"))]
pub network: String,
}
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename = "interface")] #[serde(rename = "interface")]
pub struct DomainNetInterfaceXML { pub struct DomainNetInterfaceXML {
#[serde(rename(serialize = "@type"))] #[serde(rename(serialize = "@type"))]
pub r#type: String, pub r#type: String,
pub source: Option<NetIntSourceXML>,
} }
/// Devices information /// Devices information

View File

@ -3,7 +3,7 @@ use crate::constants;
use crate::libvirt_lib_structures::{ use crate::libvirt_lib_structures::{
DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML, DevicesXML, DiskBootXML, DiskDriverXML, DiskReadOnlyXML, DiskSourceXML, DiskTargetXML, DiskXML,
DomainCPUTopology, DomainCPUXML, DomainMemoryXML, DomainNetInterfaceXML, DomainVCPUXML, DomainCPUTopology, DomainCPUXML, DomainMemoryXML, DomainNetInterfaceXML, DomainVCPUXML,
DomainXML, FeaturesXML, GraphicsXML, NetworkDHCPRangeXML, NetworkDHCPXML, DomainXML, FeaturesXML, GraphicsXML, NetIntSourceXML, NetworkDHCPRangeXML, NetworkDHCPXML,
NetworkDNSForwarderXML, NetworkDNSXML, NetworkDomainXML, NetworkForwardXML, NetworkIPXML, NetworkDNSForwarderXML, NetworkDNSXML, NetworkDomainXML, NetworkForwardXML, NetworkIPXML,
NetworkXML, OSLoaderXML, OSTypeXML, XMLUuid, ACPIXML, OSXML, NetworkXML, OSLoaderXML, OSTypeXML, XMLUuid, ACPIXML, OSXML,
}; };
@ -68,7 +68,7 @@ pub enum VMArchitecture {
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum Network { pub enum Network {
UserspaceSLIRPStack, UserspaceSLIRPStack,
// TODO : complete network types DefinedNetwork { network: String }, // TODO : complete network types
} }
#[derive(serde::Serialize, serde::Deserialize)] #[derive(serde::Serialize, serde::Deserialize)]
@ -226,6 +226,11 @@ impl VMInfo {
networks.push(match n { networks.push(match n {
Network::UserspaceSLIRPStack => DomainNetInterfaceXML { Network::UserspaceSLIRPStack => DomainNetInterfaceXML {
r#type: "user".to_string(), r#type: "user".to_string(),
source: None,
},
Network::DefinedNetwork { network } => DomainNetInterfaceXML {
r#type: "network".to_string(),
source: Some(NetIntSourceXML { network }),
}, },
}) })
} }
@ -344,6 +349,9 @@ impl VMInfo {
.iter() .iter()
.map(|d| match d.r#type.as_str() { .map(|d| match d.r#type.as_str() {
"user" => Ok(Network::UserspaceSLIRPStack), "user" => Ok(Network::UserspaceSLIRPStack),
"network" => Ok(Network::DefinedNetwork {
network: d.source.as_ref().unwrap().network.to_string(),
}),
a => Err(LibVirtStructError::DomainExtraction(format!( a => Err(LibVirtStructError::DomainExtraction(format!(
"Unknown network interface type: {a}! " "Unknown network interface type: {a}! "
))), ))),

View File

@ -30,12 +30,17 @@ export interface VMDisk {
deleteType?: "keepfile" | "deletefile"; deleteType?: "keepfile" | "deletefile";
} }
export type VMNetInterface = VMNetUserspaceSLIRPStack; export type VMNetInterface = VMNetUserspaceSLIRPStack | VMNetDefinedNetwork;
export interface VMNetUserspaceSLIRPStack { export interface VMNetUserspaceSLIRPStack {
type: "UserspaceSLIRPStack"; type: "UserspaceSLIRPStack";
} }
export interface VMNetDefinedNetwork {
type: "DefinedNetwork";
network: string;
}
interface VMInfoInterface { interface VMInfoInterface {
name: string; name: string;
uuid?: string; uuid?: string;
@ -65,7 +70,7 @@ export class VMInfo implements VMInfoInterface {
vnc_access: boolean; vnc_access: boolean;
iso_file?: string; iso_file?: string;
disks: VMDisk[]; disks: VMDisk[];
networks: VMNetUserspaceSLIRPStack[]; networks: VMNetInterface[];
constructor(int: VMInfoInterface) { constructor(int: VMInfoInterface) {
this.name = int.name; this.name = int.name;

View File

@ -13,11 +13,13 @@ import {
import { VMInfo, VMNetInterface } from "../../api/VMApi"; import { VMInfo, VMNetInterface } from "../../api/VMApi";
import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider"; import { useConfirm } from "../../hooks/providers/ConfirmDialogProvider";
import { SelectInput } from "./SelectInput"; import { SelectInput } from "./SelectInput";
import { NetworkInfo } from "../../api/NetworksApi";
export function VMNetworksList(p: { export function VMNetworksList(p: {
vm: VMInfo; vm: VMInfo;
onChange?: () => void; onChange?: () => void;
editable: boolean; editable: boolean;
networksList: NetworkInfo[];
}): React.ReactElement { }): React.ReactElement {
const addNew = () => { const addNew = () => {
p.vm.networks.push({ type: "UserspaceSLIRPStack" }); p.vm.networks.push({ type: "UserspaceSLIRPStack" });
@ -28,15 +30,14 @@ export function VMNetworksList(p: {
<> <>
{/* networks list */} {/* networks list */}
{p.vm.networks.map((n, num) => ( {p.vm.networks.map((n, num) => (
<NetworkInfo <NetworkInfoWidget
key={num} key={num}
editable={p.editable}
network={n} network={n}
onChange={p.onChange}
removeFromList={() => { removeFromList={() => {
p.vm.networks.splice(num, 1); p.vm.networks.splice(num, 1);
p.onChange?.(); p.onChange?.();
}} }}
{...p}
/> />
))} ))}
@ -47,11 +48,12 @@ export function VMNetworksList(p: {
); );
} }
function NetworkInfo(p: { function NetworkInfoWidget(p: {
editable: boolean; editable: boolean;
network: VMNetInterface; network: VMNetInterface;
onChange?: () => void; onChange?: () => void;
removeFromList: () => void; removeFromList: () => void;
networksList: NetworkInfo[];
}): React.ReactElement { }): React.ReactElement {
const confirm = useConfirm(); const confirm = useConfirm();
const deleteNetwork = async () => { const deleteNetwork = async () => {
@ -65,7 +67,7 @@ function NetworkInfo(p: {
}; };
return ( return (
<div> <>
<ListItem <ListItem
secondaryAction={ secondaryAction={
p.editable && ( p.editable && (
@ -95,6 +97,7 @@ function NetworkInfo(p: {
value={p.network.type} value={p.network.type}
onValueChange={(v) => { onValueChange={(v) => {
p.network.type = v as any; p.network.type = v as any;
p.onChange?.();
}} }}
options={[ options={[
{ {
@ -103,6 +106,11 @@ function NetworkInfo(p: {
description: description:
"Provides a virtual LAN with NAT to the outside world. The virtual network has DHCP & DNS services", "Provides a virtual LAN with NAT to the outside world. The virtual network has DHCP & DNS services",
}, },
{
label: "Defined network",
value: "DefinedNetwork",
description: "Attach to a defined network",
},
]} ]}
/> />
) : ( ) : (
@ -111,6 +119,32 @@ function NetworkInfo(p: {
} }
/> />
</ListItem> </ListItem>
<div style={{ marginLeft: "70px" }}>
{p.network.type === "DefinedNetwork" && (
<SelectInput
editable={p.editable}
label="Defined network"
options={p.networksList.map((n) => {
const chars = [n.forward_mode.toString()];
if (n.ip_v4) chars.push("IPv4");
if (n.ip_v6) chars.push("IPv6");
if (n.description) chars.push(n.description);
return {
label: n.name,
value: n.name,
description: chars.join(" - "),
};
})}
value={p.network.network}
onValueChange={(v) => {
if (p.network.type === "DefinedNetwork")
p.network.network = v as any;
p.onChange?.();
}}
/>
)}
</div> </div>
</>
); );
} }

View File

@ -14,6 +14,7 @@ import { VMSelectIsoInput } from "../forms/VMSelectIsoInput";
import { VMScreenshot } from "./VMScreenshot"; import { VMScreenshot } from "./VMScreenshot";
import { ResAutostartInput } from "../forms/ResAutostartInput"; import { ResAutostartInput } from "../forms/ResAutostartInput";
import { VMNetworksList } from "../forms/VMNetworksList"; import { VMNetworksList } from "../forms/VMNetworksList";
import { NetworkApi, NetworkInfo } from "../../api/NetworksApi";
interface DetailsProps { interface DetailsProps {
vm: VMInfo; vm: VMInfo;
@ -27,10 +28,12 @@ export function VMDetails(p: DetailsProps): React.ReactElement {
const [vcpuCombinations, setVCPUCombinations] = React.useState< const [vcpuCombinations, setVCPUCombinations] = React.useState<
number[] | any number[] | any
>(); >();
const [networksList, setNetworksList] = React.useState<NetworkInfo[] | any>();
const load = async () => { const load = async () => {
setIsoList(await IsoFilesApi.GetList()); setIsoList(await IsoFilesApi.GetList());
setVCPUCombinations(await ServerApi.NumberVCPUs()); setVCPUCombinations(await ServerApi.NumberVCPUs());
setNetworksList(await NetworkApi.GetList());
}; };
return ( return (
@ -42,6 +45,7 @@ export function VMDetails(p: DetailsProps): React.ReactElement {
<VMDetailsInner <VMDetailsInner
isoList={isoList} isoList={isoList}
vcpuCombinations={vcpuCombinations} vcpuCombinations={vcpuCombinations}
networksList={networksList}
{...p} {...p}
/> />
)} )}
@ -50,7 +54,11 @@ export function VMDetails(p: DetailsProps): React.ReactElement {
} }
function VMDetailsInner( function VMDetailsInner(
p: DetailsProps & { isoList: IsoFile[]; vcpuCombinations: number[] } p: DetailsProps & {
isoList: IsoFile[];
vcpuCombinations: number[];
networksList: NetworkInfo[];
}
): React.ReactElement { ): React.ReactElement {
return ( return (
<Grid container spacing={2}> <Grid container spacing={2}>
@ -201,12 +209,12 @@ function VMDetailsInner(
p.onChange?.(); p.onChange?.();
}} }}
/> />
<VMDisksList vm={p.vm} editable={p.editable} onChange={p.onChange} /> <VMDisksList {...p} />
</EditSection> </EditSection>
{/* Networks section */} {/* Networks section */}
<EditSection title="Networks"> <EditSection title="Networks">
<VMNetworksList vm={p.vm} editable={p.editable} onChange={p.onChange} /> <VMNetworksList {...p} />
</EditSection> </EditSection>
</Grid> </Grid>
); );