Skip to content

Commit

Permalink
Merge pull request kubevm-io#45 from hexiaodai/jianmin/valid
Browse files Browse the repository at this point in the history
Add CRD type validation
  • Loading branch information
hexiaodai authored Jan 8, 2025
2 parents e14c07c + 1233cda commit 5a42b5b
Show file tree
Hide file tree
Showing 38 changed files with 40,990 additions and 706 deletions.
10 changes: 10 additions & 0 deletions src/clients/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ import { VirtualMachinePowerStateRequest_PowerState } from "@/clients/ts/managem
import { ListOptions } from "./ts/management/resource/v1alpha1/resource"
import { virtualMachine } from "@/utils/parse-summary"

export const transport = new GrpcWebFetchTransport({
baseUrl: window.location.origin
})

export const defaultTimeout = 1500

export const resourceClient = new ResourceManagementClient(transport)
export const virtualMachineClient = new VirtualMachineManagementClient(transport)
export const resourceWatchClient = new ResourceWatchManagementClient(transport)

export const getResourceName = (type: ResourceType) => {
return ResourceType[type]
}
Expand Down
155 changes: 155 additions & 0 deletions src/clients/data-volume.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { defaultTimeout, resourceClient, resourceWatchClient } from "./clients"
import { ListOptions } from "./ts/management/resource/v1alpha1/resource"
import { components } from "./ts/openapi/openapi-schema"
import { NamespaceName, ResourceType } from "./ts/types/types"
import { EventType, WatchOptions } from "./ts/management/resource/v1alpha1/watch"
import { namespaceNameKey } from "@/utils/k8s"
import { isAbortedError } from "@/utils/utils"
import { VirtualMachine } from "./virtual-machine"

export type DataVolume = components["schemas"]["v1beta1DataVolume"]

export const getDataVolume = async (ns: NamespaceName): Promise<DataVolume> => {
return new Promise((resolve, reject) => {
const call = resourceClient.get({
resourceType: ResourceType.DATA_VOLUME,
namespaceName: ns
})
call.then((result) => {
resolve(JSON.parse(result.response.data) as DataVolume)
})
call.response.catch((err: Error) => {
reject(new Error(`Failed to get data volume [Namespace: ${ns.namespace}, Name: ${ns.name}]: ${err.message}`))
})
})
}

export const listDataVolumes = async (opts?: ListOptions): Promise<DataVolume[]> => {
return new Promise((resolve, reject) => {
const call = resourceClient.list({
resourceType: ResourceType.DATA_VOLUME,
options: ListOptions.create(opts)
})
call.then((result) => {
let items: DataVolume[] = []
result.response.items.forEach((item: string) => {
items.push(JSON.parse(item) as DataVolume)
})
resolve(items)
})
call.response.catch((err: Error) => {
reject(new Error(`Failed to list data volume: ${err.message}`))
})
})
}

export const watchDataVolumes = (setDataVolumes: React.Dispatch<React.SetStateAction<DataVolume[] | undefined>>, setLoading: React.Dispatch<React.SetStateAction<boolean>>, abortSignal: AbortSignal, opts?: WatchOptions): Promise<void> => {
return new Promise((resolve, reject) => {
setLoading(true)

const map = new Map<string, DataVolume>()

const call = resourceWatchClient.watch({
resourceType: ResourceType.DATA_VOLUME,
options: WatchOptions.create(opts)
}, { abort: abortSignal })

let timeoutId: NodeJS.Timeout | null = null
const updateVirtualMachineSummarys = () => {
if (map.size === 0 && timeoutId === null) {
timeoutId = setTimeout(() => {
const items = Array.from(map.values())
setDataVolumes(items.length > 0 ? items : undefined)
timeoutId = null
}, defaultTimeout)
} else {
const items = Array.from(map.values())
setDataVolumes(items.length > 0 ? items : undefined)
if (timeoutId !== null) {
clearTimeout(timeoutId)
timeoutId = null
}
}
}

call.responses.onMessage((response) => {
switch (response.eventType) {
case EventType.READY: {
setLoading(false)
break
}
case EventType.ADDED:
case EventType.MODIFIED: {
response.items.forEach((data) => {
const vm = JSON.parse(data) as DataVolume
map.set(namespaceNameKey(vm), vm)
})
break
}
case EventType.DELETED: {
response.items.forEach((data) => {
const vm = JSON.parse(data) as DataVolume
map.delete(namespaceNameKey(vm))
})
break
}
}
updateVirtualMachineSummarys()
})

call.responses.onError((err: Error) => {
setLoading(false)
if (isAbortedError(err)) {
resolve()
} else {
reject(new Error(`Error in watch stream for DataVolume: ${err.message}`))
}
})

call.responses.onComplete(() => {
setLoading(false)
resolve()
})
})
}

export const getRootDisk = (setLoading: React.Dispatch<React.SetStateAction<boolean>>, vm: VirtualMachine): Promise<DataVolume> => {
return new Promise((resolve, reject) => {
const disks = vm.spec?.template?.spec?.domain?.devices?.disks
if (!disks || disks.length === 0) {
setLoading(false)
reject(new Error(`Failed to get root disk for VirtualMachine [Namespace: ${vm.metadata!.namespace}, Name: ${vm.metadata!.name}]: No disks found`))
return
}

let disk = disks.find((disk: any) => {
return disk.bootOrder === 1
})
if (!disk) {
disk = disks[0]
}

const volumes = vm.spec?.template?.spec?.volumes
if (!volumes || volumes.length === 0) {
setLoading(false)
reject(new Error(`Failed to get root disk for VirtualMachine [Namespace: ${vm.metadata!.namespace}, Name: ${vm.metadata!.name}]: No volumes found`))
return
}

const vol = volumes.find((vol: any) => {
return vol.name == disk.name
})
if (!vol) {
setLoading(false)
reject(new Error(`Failed to get root disk for VirtualMachine [Namespace: ${vm.metadata!.namespace}, Name: ${vm.metadata!.name}]: No volume found`))
return
}
getDataVolume({ namespace: vm.metadata!.namespace, name: vol.dataVolume!.name }).then(dv => {
resolve(dv)
}).catch(err => {
reject(new Error(`Failed to get root disk for VirtualMachine [Namespace: ${vm.metadata!.namespace}, Name: ${vm.metadata!.name}]: ${err.message}`))
}).finally(() => {
setLoading(false)
})
})
}
100 changes: 100 additions & 0 deletions src/clients/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { defaultTimeout, resourceWatchClient } from "./clients"
import { NamespaceName, ResourceType } from "./ts/types/types"
import { EventType, WatchOptions } from "./ts/management/resource/v1alpha1/watch"
import { namespaceNameKey } from "@/utils/k8s"
import { isAbortedError } from "@/utils/utils"

export const watchVirtualMachineEvents = (ns: NamespaceName, setEvents: React.Dispatch<React.SetStateAction<any[] | undefined>>, setLoading: React.Dispatch<React.SetStateAction<boolean>>, abortSignal: AbortSignal): Promise<void> => {
return new Promise((resolve, reject) => {
setLoading(true)

const map = new Map<string, any>()

const call = resourceWatchClient.watch({
resourceType: ResourceType.EVENT,
options: WatchOptions.create({
fieldSelectorGroup: {
operator: "&&",
fieldSelectors: [
{
fieldPath: "involvedObject.namespace",
operator: "=",
values: [ns.namespace]
},
{
fieldPath: "involvedObject.kind",
operator: "~=",
values: ["VirtualMachine", "VirtualMachineInstance", "Pod"]
},
{
fieldPath: "involvedObject.name",
operator: "=",
values: [ns.name]
}
]
}
})
}, { abort: abortSignal })

let timeoutId: NodeJS.Timeout | null = null
const updateEvents = () => {
if (map.size === 0 && timeoutId === null) {
timeoutId = setTimeout(() => {
const items = Array.from(map.values()).sort((a: any, b: any) => {
return new Date(b.lastTimestamp).getTime() - new Date(a.lastTimestamp).getTime()
})
setEvents(items.length > 0 ? items : undefined)
timeoutId = null
}, defaultTimeout)
} else {
const items = Array.from(map.values()).sort((a: any, b: any) => {
return new Date(b.lastTimestamp).getTime() - new Date(a.lastTimestamp).getTime()
})
setEvents(items.length > 0 ? items : undefined)
if (timeoutId !== null) {
clearTimeout(timeoutId)
timeoutId = null
}
}
}

call.responses.onMessage((response) => {
switch (response.eventType) {
case EventType.READY: {
setLoading(false)
break
}
case EventType.ADDED:
case EventType.MODIFIED: {
response.items.forEach((data) => {
const e = JSON.parse(data)
map.set(namespaceNameKey(e), e)
})
break
}
case EventType.DELETED: {
response.items.forEach((data) => {
const vm = JSON.parse(data)
map.delete(namespaceNameKey(vm))
})
break
}
}
updateEvents()
})

call.responses.onError((err: Error) => {
setLoading(false)
if (isAbortedError(err)) {
resolve()
} else {
reject(new Error(`Error in watch stream for Event: ${err.message}`))
}
})

call.responses.onComplete(() => {
setLoading(false)
resolve()
})
})
}
3 changes: 3 additions & 0 deletions src/clients/ip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { components } from "./ts/openapi/openapi-schema"

export type IP = components["schemas"]["v1IP"]
3 changes: 3 additions & 0 deletions src/clients/ippool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { components } from "./ts/openapi/openapi-schema"

export type IPPool = components["schemas"]["v1IPPool"]
3 changes: 3 additions & 0 deletions src/clients/network-attachment-definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { components } from "./ts/openapi/openapi-schema"

export type NetworkAttachmentDefinition = components["schemas"]["v1NetworkAttachmentDefinition"]
Loading

0 comments on commit 5a42b5b

Please sign in to comment.