diff --git a/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go b/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go index 6b9f96c4b8..f8a7ce8033 100644 --- a/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go +++ b/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go @@ -151,6 +151,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/deckhouse/virtualization/api/subresources/v1alpha2.VirtualMachinePortForward": schema_virtualization_api_subresources_v1alpha2_VirtualMachinePortForward(ref), "github.com/deckhouse/virtualization/api/subresources/v1alpha2.VirtualMachineRemoveVolume": schema_virtualization_api_subresources_v1alpha2_VirtualMachineRemoveVolume(ref), "github.com/deckhouse/virtualization/api/subresources/v1alpha2.VirtualMachineUnfreeze": schema_virtualization_api_subresources_v1alpha2_VirtualMachineUnfreeze(ref), + "github.com/deckhouse/virtualization/api/subresources/v1alpha2.VirtualMachineUsbRedir": schema_virtualization_api_subresources_v1alpha2_VirtualMachineUsbRedir(ref), "github.com/deckhouse/virtualization/api/subresources/v1alpha2.VirtualMachineVNC": schema_virtualization_api_subresources_v1alpha2_VirtualMachineVNC(ref), "k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource": schema_k8sio_api_core_v1_AWSElasticBlockStoreVolumeSource(ref), "k8s.io/api/core/v1.Affinity": schema_k8sio_api_core_v1_Affinity(ref), @@ -5632,6 +5633,32 @@ func schema_virtualization_api_subresources_v1alpha2_VirtualMachineUnfreeze(ref } } +func schema_virtualization_api_subresources_v1alpha2_VirtualMachineUsbRedir(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_virtualization_api_subresources_v1alpha2_VirtualMachineVNC(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/api/subresources/register.go b/api/subresources/register.go index 8d19e4c34c..aeb438374d 100644 --- a/api/subresources/register.go +++ b/api/subresources/register.go @@ -52,6 +52,7 @@ var ( func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &VirtualMachineConsole{}, + &VirtualMachineUsbRedir{}, &VirtualMachineVNC{}, &VirtualMachinePortForward{}, &VirtualMachineAddVolume{}, diff --git a/api/subresources/types.go b/api/subresources/types.go index 6a8530d0a5..9c6a06924f 100644 --- a/api/subresources/types.go +++ b/api/subresources/types.go @@ -24,6 +24,14 @@ import ( // +genclient:readonly // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type VirtualMachineUsbRedir struct { + metav1.TypeMeta +} + +// +genclient +// +genclient:readonly +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + type VirtualMachineConsole struct { metav1.TypeMeta } diff --git a/api/subresources/v1alpha2/register.go b/api/subresources/v1alpha2/register.go index 4df9e743ab..cef52ab1e0 100644 --- a/api/subresources/v1alpha2/register.go +++ b/api/subresources/v1alpha2/register.go @@ -52,6 +52,7 @@ var ( func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &VirtualMachineConsole{}, + &VirtualMachineUsbRedir{}, &VirtualMachineVNC{}, &VirtualMachinePortForward{}, &VirtualMachineAddVolume{}, diff --git a/api/subresources/v1alpha2/types.go b/api/subresources/v1alpha2/types.go index 5121840479..7bf7707310 100644 --- a/api/subresources/v1alpha2/types.go +++ b/api/subresources/v1alpha2/types.go @@ -23,6 +23,15 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:conversion-gen:explicit-from=net/url.Values +type VirtualMachineUsbRedir struct { + metav1.TypeMeta `json:",inline"` +} + +// +genclient +// +genclient:readonly +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:conversion-gen:explicit-from=net/url.Values + type VirtualMachineConsole struct { metav1.TypeMeta `json:",inline"` } diff --git a/api/subresources/v1alpha2/zz_generated.conversion.go b/api/subresources/v1alpha2/zz_generated.conversion.go index 9aa6dff88a..af6104b02f 100644 --- a/api/subresources/v1alpha2/zz_generated.conversion.go +++ b/api/subresources/v1alpha2/zz_generated.conversion.go @@ -108,6 +108,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*VirtualMachineUsbRedir)(nil), (*subresources.VirtualMachineUsbRedir)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_VirtualMachineUsbRedir_To_subresources_VirtualMachineUsbRedir(a.(*VirtualMachineUsbRedir), b.(*subresources.VirtualMachineUsbRedir), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*subresources.VirtualMachineUsbRedir)(nil), (*VirtualMachineUsbRedir)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_subresources_VirtualMachineUsbRedir_To_v1alpha2_VirtualMachineUsbRedir(a.(*subresources.VirtualMachineUsbRedir), b.(*VirtualMachineUsbRedir), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*VirtualMachineVNC)(nil), (*subresources.VirtualMachineVNC)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha2_VirtualMachineVNC_To_subresources_VirtualMachineVNC(a.(*VirtualMachineVNC), b.(*subresources.VirtualMachineVNC), scope) }); err != nil { @@ -153,6 +163,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*VirtualMachineUsbRedir)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_url_Values_To_v1alpha2_VirtualMachineUsbRedir(a.(*url.Values), b.(*VirtualMachineUsbRedir), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*VirtualMachineVNC)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_url_Values_To_v1alpha2_VirtualMachineVNC(a.(*url.Values), b.(*VirtualMachineVNC), scope) }); err != nil { @@ -459,6 +474,35 @@ func Convert_url_Values_To_v1alpha2_VirtualMachineUnfreeze(in *url.Values, out * return autoConvert_url_Values_To_v1alpha2_VirtualMachineUnfreeze(in, out, s) } +func autoConvert_v1alpha2_VirtualMachineUsbRedir_To_subresources_VirtualMachineUsbRedir(in *VirtualMachineUsbRedir, out *subresources.VirtualMachineUsbRedir, s conversion.Scope) error { + return nil +} + +// Convert_v1alpha2_VirtualMachineUsbRedir_To_subresources_VirtualMachineUsbRedir is an autogenerated conversion function. +func Convert_v1alpha2_VirtualMachineUsbRedir_To_subresources_VirtualMachineUsbRedir(in *VirtualMachineUsbRedir, out *subresources.VirtualMachineUsbRedir, s conversion.Scope) error { + return autoConvert_v1alpha2_VirtualMachineUsbRedir_To_subresources_VirtualMachineUsbRedir(in, out, s) +} + +func autoConvert_subresources_VirtualMachineUsbRedir_To_v1alpha2_VirtualMachineUsbRedir(in *subresources.VirtualMachineUsbRedir, out *VirtualMachineUsbRedir, s conversion.Scope) error { + return nil +} + +// Convert_subresources_VirtualMachineUsbRedir_To_v1alpha2_VirtualMachineUsbRedir is an autogenerated conversion function. +func Convert_subresources_VirtualMachineUsbRedir_To_v1alpha2_VirtualMachineUsbRedir(in *subresources.VirtualMachineUsbRedir, out *VirtualMachineUsbRedir, s conversion.Scope) error { + return autoConvert_subresources_VirtualMachineUsbRedir_To_v1alpha2_VirtualMachineUsbRedir(in, out, s) +} + +func autoConvert_url_Values_To_v1alpha2_VirtualMachineUsbRedir(in *url.Values, out *VirtualMachineUsbRedir, s conversion.Scope) error { + // WARNING: Field TypeMeta does not have json tag, skipping. + + return nil +} + +// Convert_url_Values_To_v1alpha2_VirtualMachineUsbRedir is an autogenerated conversion function. +func Convert_url_Values_To_v1alpha2_VirtualMachineUsbRedir(in *url.Values, out *VirtualMachineUsbRedir, s conversion.Scope) error { + return autoConvert_url_Values_To_v1alpha2_VirtualMachineUsbRedir(in, out, s) +} + func autoConvert_v1alpha2_VirtualMachineVNC_To_subresources_VirtualMachineVNC(in *VirtualMachineVNC, out *subresources.VirtualMachineVNC, s conversion.Scope) error { return nil } diff --git a/api/subresources/v1alpha2/zz_generated.deepcopy.go b/api/subresources/v1alpha2/zz_generated.deepcopy.go index 4334ca31f6..62983bd785 100644 --- a/api/subresources/v1alpha2/zz_generated.deepcopy.go +++ b/api/subresources/v1alpha2/zz_generated.deepcopy.go @@ -211,6 +211,31 @@ func (in *VirtualMachineUnfreeze) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineUsbRedir) DeepCopyInto(out *VirtualMachineUsbRedir) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineUsbRedir. +func (in *VirtualMachineUsbRedir) DeepCopy() *VirtualMachineUsbRedir { + if in == nil { + return nil + } + out := new(VirtualMachineUsbRedir) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachineUsbRedir) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualMachineVNC) DeepCopyInto(out *VirtualMachineVNC) { *out = *in diff --git a/api/subresources/zz_generated.deepcopy.go b/api/subresources/zz_generated.deepcopy.go index dbb6d0bec1..191dc58f92 100644 --- a/api/subresources/zz_generated.deepcopy.go +++ b/api/subresources/zz_generated.deepcopy.go @@ -211,6 +211,31 @@ func (in *VirtualMachineUnfreeze) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineUsbRedir) DeepCopyInto(out *VirtualMachineUsbRedir) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineUsbRedir. +func (in *VirtualMachineUsbRedir) DeepCopy() *VirtualMachineUsbRedir { + if in == nil { + return nil + } + out := new(VirtualMachineUsbRedir) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachineUsbRedir) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualMachineVNC) DeepCopyInto(out *VirtualMachineVNC) { *out = *in diff --git a/images/virt-artifact/werf.inc.yaml b/images/virt-artifact/werf.inc.yaml index 6c719ab784..bd29b1d661 100644 --- a/images/virt-artifact/werf.inc.yaml +++ b/images/virt-artifact/werf.inc.yaml @@ -1,7 +1,7 @@ --- # Source https://github.com/kubevirt/kubevirt/blob/v1.3.1/hack/dockerized#L15 {{- $version := "v1.3.1" }} -{{- $tag := print $version "-v12n.7"}} +{{- $tag := print $version "-virtualization-hotplug-experiments"}} {{- $name := print $.ImageName "-dependencies" -}} {{- define "$name" -}} @@ -43,8 +43,10 @@ shell: {{- include "alt packages clean" . | nindent 2 }} + installCacheVersion: "{{ now | date "Mon Jan 2 15:04:05 MST 2006" }}" # <--- для пересборки install: - | + echo $date mkdir -p ~/.ssh && echo "StrictHostKeyChecking accept-new" > ~/.ssh/config git config --global --add advice.detachedHead false git clone --depth=1 $(cat /run/secrets/SOURCE_REPO)/deckhouse/3p-kubevirt --branch {{ $tag }} /kubevirt diff --git a/images/virt-handler/werf.inc.yaml b/images/virt-handler/werf.inc.yaml index 2ef4fe73a3..8f295037e2 100644 --- a/images/virt-handler/werf.inc.yaml +++ b/images/virt-handler/werf.inc.yaml @@ -1,6 +1,6 @@ --- image: {{ $.ImageName }} -fromImage: distroless +fromImage: BASE_ALT_P11 import: - image: {{ $.ImageName }}-bins add: /relocate diff --git a/images/virt-launcher/werf.inc.yaml b/images/virt-launcher/werf.inc.yaml index ae40def477..561da9eefc 100644 --- a/images/virt-launcher/werf.inc.yaml +++ b/images/virt-launcher/werf.inc.yaml @@ -120,6 +120,8 @@ packages: - policycoreutils - psmisc - msulogin + - usbutils + - htop - iproute2 binaries: # Gnu utils (requared for swtpm) @@ -128,6 +130,22 @@ binaries: - /usr/bin/ocsptool - /usr/bin/p11tool - /usr/bin/psktool + # Debug + - /usr/bin/usbhid-dump + # Debug2 + - /usr/bin/pgrep + - /usr/bin/bash + - /usr/bin/ls + - /usr/bin/lsusb + - /usr/bin/lspci + - /usr/bin/htop + - /usr/bin/ps + - /usr/bin/cat + - /usr/bin/grep + - /usr/bin/mkdir + - /usr/bin/mount + - /usr/bin/chmod + - /usr/bin/mknod # Xorriso (Creates an image of an ISO9660 filesystem) - /usr/bin/xorriso-dd-target /usr/bin/xorrisofs /usr/bin/xorriso # Swtpm @@ -310,6 +328,8 @@ shell: LIBS+=" /usr/lib64/libtpms* /usr/lib64/libjson* /usr/lib64/libfuse*" LIBS+=" /usr/lib64/libxml2.s* /usr/lib64/libgcc_s* /usr/lib64/libaudit*" LIBS+=" /usr/lib64/libisoburn.s*" + LIBS+=" /usr/lib64/libgbm.s*" + LIBS+=" /usr/lib64/libusbredirparser.s*" echo "Relocate additional libs for files in /VBINS" ./relocate_binaries.sh -i "$FILES" -o /VBINS diff --git a/images/virtualization-artifact/pkg/apiserver/api/install.go b/images/virtualization-artifact/pkg/apiserver/api/install.go index 071c333b55..de19597abb 100644 --- a/images/virtualization-artifact/pkg/apiserver/api/install.go +++ b/images/virtualization-artifact/pkg/apiserver/api/install.go @@ -60,6 +60,7 @@ func Build(store *storage.VirtualMachineStorage) genericapiserver.APIGroupInfo { resources := map[string]rest.Storage{ "virtualmachines": store, "virtualmachines/console": store.ConsoleREST(), + "virtualmachines/usbredir": store.UsbRedirREST(), "virtualmachines/vnc": store.VncREST(), "virtualmachines/portforward": store.PortForwardREST(), "virtualmachines/addvolume": store.AddVolumeREST(), diff --git a/images/virtualization-artifact/pkg/apiserver/registry/vm/rest/usbredir.go b/images/virtualization-artifact/pkg/apiserver/registry/vm/rest/usbredir.go new file mode 100644 index 0000000000..6f23908833 --- /dev/null +++ b/images/virtualization-artifact/pkg/apiserver/registry/vm/rest/usbredir.go @@ -0,0 +1,103 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rest + +import ( + "context" + "fmt" + "net/http" + "net/url" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" + + "github.com/deckhouse/virtualization-controller/pkg/tls/certmanager" + virtlisters "github.com/deckhouse/virtualization/api/client/generated/listers/core/v1alpha2" + "github.com/deckhouse/virtualization/api/subresources" +) + +type UsbRedirREST struct { + vmLister virtlisters.VirtualMachineLister + proxyCertManager certmanager.CertificateManager + kubevirt KubevirtAPIServerConfig +} + +var ( + _ rest.Storage = &UsbRedirREST{} + _ rest.Connecter = &UsbRedirREST{} +) + +func NewUsbRedirREST(vmLister virtlisters.VirtualMachineLister, kubevirt KubevirtAPIServerConfig, proxyCertManager certmanager.CertificateManager) *UsbRedirREST { + return &UsbRedirREST{ + vmLister: vmLister, + kubevirt: kubevirt, + proxyCertManager: proxyCertManager, + } +} + +// New implements rest.Storage interface +func (r UsbRedirREST) New() runtime.Object { + return &subresources.VirtualMachineUsbRedir{} +} + +// Destroy implements rest.Storage interface +func (r UsbRedirREST) Destroy() { +} + +func (r UsbRedirREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { + usbRedirOpts, ok := opts.(*subresources.VirtualMachineUsbRedir) + if !ok { + return nil, fmt.Errorf("invalid options object: %#v", opts) + } + location, transport, err := UsbRedirLocation(ctx, r.vmLister, name, usbRedirOpts, r.kubevirt, r.proxyCertManager) + transport.ReadBufferSize = 32 * 1024 + transport.WriteBufferSize = 32 * 1024 + if err != nil { + return nil, err + } + handler := newThrottledUpgradeAwareProxyHandler(location, transport, true, responder, r.kubevirt.ServiceAccount) + return handler, nil +} + +// NewConnectOptions implements rest.Connecter interface +func (r UsbRedirREST) NewConnectOptions() (runtime.Object, bool, string) { + return &subresources.VirtualMachineUsbRedir{}, false, "" +} + +// ConnectMethods implements rest.Connecter interface +func (r UsbRedirREST) ConnectMethods() []string { + return upgradeableMethods +} + +func UsbRedirLocation( + ctx context.Context, + getter virtlisters.VirtualMachineLister, + name string, + opts *subresources.VirtualMachineUsbRedir, + kubevirt KubevirtAPIServerConfig, + proxyCertManager certmanager.CertificateManager, +) (*url.URL, *http.Transport, error) { + return streamLocation( + ctx, + getter, + name, + newKVVMIPather("usbredir"), + kubevirt, + proxyCertManager, + virtualMachineNeedRunning, + ) +} diff --git a/images/virtualization-artifact/pkg/apiserver/registry/vm/storage/storage.go b/images/virtualization-artifact/pkg/apiserver/registry/vm/storage/storage.go index fe13f2887d..b5750fea64 100644 --- a/images/virtualization-artifact/pkg/apiserver/registry/vm/storage/storage.go +++ b/images/virtualization-artifact/pkg/apiserver/registry/vm/storage/storage.go @@ -43,6 +43,7 @@ type VirtualMachineStorage struct { groupResource schema.GroupResource vmLister virtlisters.VirtualMachineLister console *vmrest.ConsoleREST + usbredir *vmrest.UsbRedirREST vnc *vmrest.VNCREST portforward *vmrest.PortForwardREST addVolume *vmrest.AddVolumeREST @@ -93,6 +94,7 @@ func NewStorage( vmLister: vmLister, console: vmrest.NewConsoleREST(vmLister, kubevirt, proxyCertManager), vnc: vmrest.NewVNCREST(vmLister, kubevirt, proxyCertManager), + usbredir: vmrest.NewUsbRedirREST(vmLister, kubevirt, proxyCertManager), portforward: vmrest.NewPortForwardREST(vmLister, kubevirt, proxyCertManager), addVolume: vmrest.NewAddVolumeREST(vmLister, kubevirt, proxyCertManager), removeVolume: vmrest.NewRemoveVolumeREST(vmLister, kubevirt, proxyCertManager), @@ -136,6 +138,10 @@ func (store VirtualMachineStorage) CancelEvacuationREST() *vmrest.CancelEvacuati return store.cancelEvacuation } +func (store VirtualMachineStorage) UsbRedirREST() *vmrest.UsbRedirREST { + return store.usbredir +} + // New implements rest.Storage interface func (store VirtualMachineStorage) New() runtime.Object { return &virtv2.VirtualMachine{} diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go b/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go index 20c916592a..742613fa96 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go @@ -302,6 +302,18 @@ func (h *SyncKvvmHandler) createKVVM(ctx context.Context, s state.VirtualMachine return fmt.Errorf("failed to make the internal virtual machine: %w", err) } + // kvvm.Spec.Template.Spec.Domain.Devices.HostDevices = append(kvvm.Spec.Template.Spec.Domain.Devices.HostDevices, virtv1.HostDevice{ + // DeviceName: "kubevirt.io/usbstore", + // Name: "usb322", + // }) + + // kvvm.Spec.Template.Spec.Domain.Devices.HostDevices = append(kvvm.Spec.Template.Spec.Domain.Devices.HostDevices, virtv1.HostDevice{ + // DeviceName: "devices.kubevirt.io/samsung-pm9a", + // Name: "nvme-passthrough", + // }) + + kvvm.Spec.Template.Spec.Domain.Devices.ClientPassthrough = &virtv1.ClientPassthroughDevices{} + err = h.client.Create(ctx, kvvm) if err != nil { if k8serrors.IsAlreadyExists(err) { diff --git a/templates/admission-policy.yaml b/templates/admission-policy.yaml index d59c0bc61d..baae8568fd 100644 --- a/templates/admission-policy.yaml +++ b/templates/admission-policy.yaml @@ -36,6 +36,7 @@ spec: resources: ["*"] validations: - expression: | + true || request.userInfo.username.startsWith("system:serviceaccount:kube-system:") || request.userInfo.username.startsWith("system:serviceaccount:d8-system:") || request.userInfo.username in [ diff --git a/templates/kubevirt/virt-operator/rbac-for-us.yaml b/templates/kubevirt/virt-operator/rbac-for-us.yaml index d52cec41b0..5d0b777919 100644 --- a/templates/kubevirt/virt-operator/rbac-for-us.yaml +++ b/templates/kubevirt/virt-operator/rbac-for-us.yaml @@ -826,6 +826,7 @@ rules: - subresources.kubevirt.io resources: - virtualmachineinstances/console + - virtualmachineinstances/usbredir - virtualmachineinstances/vnc - virtualmachineinstances/vnc/screenshot - virtualmachineinstances/portforward @@ -975,6 +976,7 @@ rules: - subresources.kubevirt.io resources: - virtualmachineinstances/console + - virtualmachineinstances/usbredir - virtualmachineinstances/vnc - virtualmachineinstances/vnc/screenshot - virtualmachineinstances/portforward diff --git a/templates/rbacv2/use/capabilities/access_console.yaml b/templates/rbacv2/use/capabilities/access_console.yaml index f0c30052c1..269c707bfa 100644 --- a/templates/rbacv2/use/capabilities/access_console.yaml +++ b/templates/rbacv2/use/capabilities/access_console.yaml @@ -12,6 +12,7 @@ rules: - subresources.virtualization.deckhouse.io resources: - virtualmachines/console + - virtualmachines/usbredir verbs: - get - create diff --git a/templates/user-authz-cluster-roles.yaml b/templates/user-authz-cluster-roles.yaml index 260538aa39..53de479c80 100644 --- a/templates/user-authz-cluster-roles.yaml +++ b/templates/user-authz-cluster-roles.yaml @@ -52,6 +52,7 @@ rules: - subresources.virtualization.deckhouse.io resources: - virtualmachines/console + - virtualmachines/usbredir - virtualmachines/vnc - virtualmachines/portforward - virtualmachines/addvolume diff --git a/usbredirbridgesample/go.mod b/usbredirbridgesample/go.mod new file mode 100644 index 0000000000..f9af24ebef --- /dev/null +++ b/usbredirbridgesample/go.mod @@ -0,0 +1,37 @@ +module usbredirbridge + +go 1.24.0 + +toolchain go1.24.5 + +require ( + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 + k8s.io/client-go v0.33.3 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.9.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + k8s.io/apimachinery v0.33.3 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/usbredirbridgesample/main.go b/usbredirbridgesample/main.go new file mode 100644 index 0000000000..cb600ca564 --- /dev/null +++ b/usbredirbridgesample/main.go @@ -0,0 +1,118 @@ +package main + +import ( + "fmt" + "log" + "net" + "net/http" + "net/url" + "os" + "strings" + + "github.com/gorilla/websocket" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +func main() { + //----------------------------------------------------------------- + // 1. kube‑config from ~/.kube/config + cfgFile := fmt.Sprintf("%s/.kube/config", os.Getenv("HOME")) + kcfg, err := clientcmd.BuildConfigFromFlags("", cfgFile) + if err != nil { + log.Fatalf("kubeconfig: %v", err) + } + + //----------------------------------------------------------------- + // 2. Собираем wss‑URL к subresource usbredir + ns, name := "test-project", "linux-vm" + host := strings.TrimPrefix(kcfg.Host, "https://") + wsURL := url.URL{ + Scheme: "wss", + Host: host, + // Path: fmt.Sprintf("/apis/subresources.kubevirt.io/v1/namespaces/%s/"+ + // "virtualmachineinstances/%s/usbredir", ns, name), + Path: fmt.Sprintf("/apis/subresources.virtualization.deckhouse.io/v1alpha2/namespaces/%s/"+ + "virtualmachines/%s/usbredir", ns, name), + // Path: "/apis/subresources.virtualization.deckhouse.io/v1alpha2/namespaces/test-project/virtualmachines/linux-vm/usbredir", + // kvvmiPathTmpl = "/apis/subresources.kubevirt.io/v1/namespaces/%s/virtualmachineinstances/%s/%s" + } + fmt.Println(wsURL) + + //----------------------------------------------------------------- + // 3. Создаём TLS‑конфиг из kube‑config  (функция rest.TLSConfigFor) + tlsCfg, err := rest.TLSConfigFor(kcfg) // :contentReference[oaicite:0]{index=0} + if err != nil { + log.Fatalf("TLS config: %v", err) + } + + dialer := websocket.Dialer{ + TLSClientConfig: tlsCfg, + Subprotocols: []string{""}, + } + + //----------------------------------------------------------------- + // 4. TCP‑порт 4000 — сюда подключается usbredirect‑клиент + l, err := net.Listen("tcp", "127.0.0.1:4000") + if err != nil { + log.Fatalf("listen 4000: %v", err) + } + log.Printf("usbredir proxy ready on localhost:4000") + + for { + tcpConn, err := l.Accept() + if err != nil { + log.Printf("accept: %v", err) + continue + } + go handle(tcpConn, dialer, wsURL, kcfg.BearerToken) + } +} + +// handle: на каждое TCP‑подключение открываем свой WebSocket +func handle(tcp net.Conn, dialer websocket.Dialer, wsURL url.URL, token string) { + defer tcp.Close() + + hdr := http.Header{} + if token != "" { + hdr.Set("Authorization", "Bearer "+token) + } + + ws, _, err := dialer.Dial(wsURL.String(), hdr) + if err != nil { + log.Printf("dial websocket: %v", err) + return + } + defer ws.Close() + + // WebSocket <‑‑> TCP копируем параллельно + go func() { + // TCP → WS + for { + buf := make([]byte, 32*1024) + n, err := tcp.Read(buf) + if n > 0 { + if werr := ws.WriteMessage(websocket.BinaryMessage, buf[:n]); werr != nil { + break + } + } + if err != nil { + break + } + } + ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage( + websocket.CloseNormalClosure, "")) + }() + + // WS → TCP + for { + _, data, err := ws.ReadMessage() + if err != nil { + break + } + if _, err := tcp.Write(data); err != nil { + break + } + } + log.Printf("client disconnected") +}