From ea312a3b9a50c5dccba75ae4794509e635e144c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Wed, 21 Jan 2026 15:32:39 +0800 Subject: [PATCH] fix(region): cloudpods container sync (#24112) --- go.mod | 2 +- go.sum | 4 +- pkg/compute/models/cloudsync.go | 189 ++++++++++++++++++ pkg/compute/models/containers.go | 1 + pkg/mcclient/cloudpods/containers.go | 159 +++++++++++++++ pkg/mcclient/cloudpods/instance.go | 13 ++ vendor/modules.txt | 2 +- .../x/cloudmux/pkg/cloudprovider/resources.go | 38 +++- .../cloudmux/pkg/multicloud/instance_base.go | 4 + .../pkg/multicloud/remotefile/instance.go | 4 + 10 files changed, 407 insertions(+), 9 deletions(-) create mode 100644 pkg/mcclient/cloudpods/containers.go diff --git a/go.mod b/go.mod index c0e0846e95..e3d7caf35f 100644 --- a/go.mod +++ b/go.mod @@ -99,7 +99,7 @@ require ( k8s.io/cri-api v0.28.15 k8s.io/klog/v2 v2.20.0 moul.io/http2curl/v2 v2.3.0 - yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260115031205-22d194eacc4c + yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260121071824-81b6a939374a yunion.io/x/executor v0.0.0-20250518005516-5402e9e0bed0 yunion.io/x/jsonutils v1.0.1-0.20250507052344-1abcf4f443b1 yunion.io/x/log v1.0.1-0.20240305175729-7cf2d6cd5a91 diff --git a/go.sum b/go.sum index f4a852d5b7..522f3be82f 100644 --- a/go.sum +++ b/go.sum @@ -1661,8 +1661,8 @@ sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260115031205-22d194eacc4c h1:hQ0NR7Zjwedj/46E1MstfF9TZYRRJikAgbDtzpvTBl4= -yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260115031205-22d194eacc4c/go.mod h1:aWRX5Phwz3nbHUNnIAm1oVogjguXPYDDgCOy/9Hnnvk= +yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260121071824-81b6a939374a h1:xd+aDmE0tlNKqRvcTWOnQ+LBqtX/JHCQIL9deWrMdaE= +yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260121071824-81b6a939374a/go.mod h1:aWRX5Phwz3nbHUNnIAm1oVogjguXPYDDgCOy/9Hnnvk= yunion.io/x/executor v0.0.0-20250518005516-5402e9e0bed0 h1:msG4SiDSVU7CrXH06WuHlNEZXIooTcmNbfrIGHuIHBU= yunion.io/x/executor v0.0.0-20250518005516-5402e9e0bed0/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws= yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634= diff --git a/pkg/compute/models/cloudsync.go b/pkg/compute/models/cloudsync.go index 4574d011ef..1bd6fe1df3 100644 --- a/pkg/compute/models/cloudsync.go +++ b/pkg/compute/models/cloudsync.go @@ -1192,6 +1192,26 @@ func SyncVMPeripherals( if err != nil && errors.Cause(err) != cloudprovider.ErrNotSupported && errors.Cause(err) != cloudprovider.ErrNotImplemented { logclient.AddSimpleActionLog(local, logclient.ACT_CLOUD_SYNC, errors.Wrapf(err, "syncVMIsolateDevice"), userCred, false) } + err = syncVMContainers(ctx, userCred, provider, local, remote) + if err != nil && errors.Cause(err) != cloudprovider.ErrNotSupported && errors.Cause(err) != cloudprovider.ErrNotImplemented { + logclient.AddSimpleActionLog(local, logclient.ACT_CLOUD_SYNC, errors.Wrapf(err, "syncVMContainers"), userCred, false) + } +} + +func syncVMContainers(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, localVM *SGuest, remoteVM cloudprovider.ICloudVM) error { + if localVM.Hypervisor != api.HYPERVISOR_POD { + return nil + } + containers, err := remoteVM.GetContainers() + if err != nil { + return errors.Wrap(err, "remoteVM.GetContainers") + } + result := localVM.SyncVMContainers(ctx, userCred, containers) + log.Infof("syncVMContainers for VM %s provider %s result: %s", localVM.Name, provider.Name, result.Result()) + if result.IsError() { + return result.AllError() + } + return nil } func syncVMNics( @@ -1314,6 +1334,175 @@ func (self *SGuest) SyncVMIsolateDevices(ctx context.Context, userCred mcclient. return nil } +func (guest *SGuest) GetContainers() ([]SContainer, error) { + q := GetContainerManager().Query().Equals("guest_id", guest.Id) + ret := []SContainer{} + err := db.FetchModelObjects(GetContainerManager(), q, &ret) + if err != nil { + return nil, errors.Wrapf(err, "GetContainers") + } + return ret, nil +} + +func (guest *SGuest) SyncVMContainers( + ctx context.Context, + userCred mcclient.TokenCredential, + containers []cloudprovider.ICloudContainer, +) compare.SyncResult { + lockman.LockRawObject(ctx, guest.Id, GetContainerManager().Keyword()) + defer lockman.ReleaseRawObject(ctx, guest.Id, GetContainerManager().Keyword()) + + result := compare.SyncResult{} + + dbContainers, err := guest.GetContainers() + if err != nil { + result.Error(errors.Wrapf(err, "GetContainers")) + return result + } + + removed := make([]SContainer, 0) + commondb := make([]SContainer, 0) + commonext := make([]cloudprovider.ICloudContainer, 0) + added := make([]cloudprovider.ICloudContainer, 0) + err = compare.CompareSets(dbContainers, containers, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(errors.Wrapf(err, "compare.CompareSets")) + return result + } + + for i := 0; i < len(removed); i += 1 { + err = removed[i].Delete(ctx, userCred) + if err != nil { + result.AddError(errors.Wrapf(err, "Delete(%s)", removed[i].Id)) + continue + } + result.Delete() + } + + for i := 0; i < len(commondb); i += 1 { + if commondb[i].PendingDeleted != guest.PendingDeleted { //避免主机正常,磁盘在回收站的情况 + db.Update(&commondb[i], func() error { + commondb[i].PendingDeleted = guest.PendingDeleted + return nil + }) + } + err := commondb[i].SyncWithCloudContainer(ctx, userCred, commonext[i]) + if err != nil { + result.AddError(errors.Wrapf(err, "SyncWithCloudContainer(%s)", commondb[i].Id)) + continue + } + commondb[i].SyncCloudProjectId(userCred, guest.GetOwnerId()) + result.Update() + } + + for i := 0; i < len(added); i += 1 { + err := guest.syncNewContainer(ctx, userCred, added[i]) + if err != nil { + result.AddError(errors.Wrapf(err, "syncNewContainer(%s)", added[i].GetGlobalId())) + continue + } + result.Add() + } + return result +} + +func (self *SContainer) SyncWithCloudContainer(ctx context.Context, userCred mcclient.TokenCredential, container cloudprovider.ICloudContainer) error { + _, err := db.Update(self, func() error { + self.Status = container.GetStatus() + self.Spec.Image = container.GetImage() + self.Spec.Command = container.GetCommand() + self.Spec.Envs = make([]*apis.ContainerKeyValue, 0) + for _, env := range container.GetEnvs() { + self.Spec.Envs = append(self.Spec.Envs, &apis.ContainerKeyValue{ + Key: env.Key, + Value: env.Value, + }) + } + self.Spec.VolumeMounts = make([]*apis.ContainerVolumeMount, 0) + volumeMounts, err := container.GetVolumentMounts() + if err != nil && err != cloudprovider.ErrNotImplemented { + return errors.Wrapf(err, "GetVolumentMounts") + } + for _, volumeMount := range volumeMounts { + self.Spec.VolumeMounts = append(self.Spec.VolumeMounts, &apis.ContainerVolumeMount{ + UniqueName: volumeMount.GetName(), + ReadOnly: volumeMount.IsReadOnly(), + Type: apis.ContainerVolumeMountType(volumeMount.GetType()), + }) + } + self.Spec.Devices = make([]*api.ContainerDevice, 0) + devices, err := container.GetDevices() + if err != nil && err != cloudprovider.ErrNotImplemented { + return errors.Wrapf(err, "GetDevices") + } + for _, device := range devices { + self.Spec.Devices = append(self.Spec.Devices, &api.ContainerDevice{ + IsolatedDevice: &api.ContainerIsolatedDevice{ + Id: device.GetId(), + }, + Type: apis.ContainerDeviceType(device.GetType()), + }) + } + return nil + }) + return err +} + +func (guest *SGuest) syncNewContainer(ctx context.Context, userCred mcclient.TokenCredential, container cloudprovider.ICloudContainer) error { + res := &SContainer{ + GuestId: guest.Id, + Spec: &api.ContainerSpec{}, + } + res.SetModelManager(GetContainerManager(), res) + res.DomainId = guest.DomainId + res.ProjectId = guest.ProjectId + res.StartedAt = container.GetStartedAt() + res.LastFinishedAt = container.GetLastFinishedAt() + res.RestartCount = container.GetRestartCount() + res.ExternalId = container.GetGlobalId() + res.Name = container.GetName() + res.Status = container.GetStatus() + res.Spec.Image = container.GetImage() + res.Spec.Command = container.GetCommand() + res.Spec.Envs = make([]*apis.ContainerKeyValue, 0) + for _, env := range container.GetEnvs() { + res.Spec.Envs = append(res.Spec.Envs, &apis.ContainerKeyValue{ + Key: env.Key, + Value: env.Value, + }) + } + res.Spec.VolumeMounts = make([]*apis.ContainerVolumeMount, 0) + volumeMounts, err := container.GetVolumentMounts() + if err != nil && err != cloudprovider.ErrNotImplemented { + log.Errorf("GetVolumentMounts error: %v", err) + } + for _, volumeMount := range volumeMounts { + res.Spec.VolumeMounts = append(res.Spec.VolumeMounts, &apis.ContainerVolumeMount{ + UniqueName: volumeMount.GetName(), + ReadOnly: volumeMount.IsReadOnly(), + Type: apis.ContainerVolumeMountType(volumeMount.GetType()), + }) + } + res.Spec.Devices = make([]*api.ContainerDevice, 0) + devices, err := container.GetDevices() + if err != nil && err != cloudprovider.ErrNotImplemented { + log.Errorf("GetDevices error: %v", err) + } + for _, device := range devices { + res.Spec.Devices = append(res.Spec.Devices, &api.ContainerDevice{ + IsolatedDevice: &api.ContainerIsolatedDevice{ + Id: device.GetId(), + }, + Type: apis.ContainerDeviceType(device.GetType()), + }) + } + err = GetContainerManager().TableSpec().Insert(ctx, res) + if err != nil { + return errors.Wrapf(err, "Insert(%s)", res.Id) + } + return nil +} + func syncSkusFromPrivateCloud( ctx context.Context, userCred mcclient.TokenCredential, diff --git a/pkg/compute/models/containers.go b/pkg/compute/models/containers.go index 4f0cf14338..59d10f7a02 100644 --- a/pkg/compute/models/containers.go +++ b/pkg/compute/models/containers.go @@ -72,6 +72,7 @@ type SContainerManager struct { type SContainer struct { db.SVirtualResourceBase + db.SExternalizedResourceBase // GuestId is also the pod id GuestId string `width:"36" charset:"ascii" create:"required" list:"user" index:"true"` diff --git a/pkg/mcclient/cloudpods/containers.go b/pkg/mcclient/cloudpods/containers.go new file mode 100644 index 0000000000..4643747ae6 --- /dev/null +++ b/pkg/mcclient/cloudpods/containers.go @@ -0,0 +1,159 @@ +// Copyright 2019 Yunion +// +// 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 cloudpods + +import ( + "time" + + "yunion.io/x/cloudmux/pkg/cloudprovider" + "yunion.io/x/cloudmux/pkg/multicloud" + + "yunion.io/x/onecloud/pkg/apis" + api "yunion.io/x/onecloud/pkg/apis/compute" + modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute" +) + +type SContainer struct { + multicloud.SResourceBase + CloudpodsTags + region *SRegion + + api.SContainer +} + +func (region *SRegion) GetContainers(guestId string) ([]SContainer, error) { + ret := []SContainer{} + params := map[string]interface{}{ + "guest_id": guestId, + } + err := region.list(&modules.Containers, params, &ret) + if err != nil { + return nil, err + } + return ret, nil +} + +func (container *SContainer) GetId() string { + return container.Id +} + +func (container *SContainer) GetGlobalId() string { + return container.Id +} + +func (container *SContainer) GetName() string { + return container.Name +} + +func (container *SContainer) GetStatus() string { + return container.Status +} + +func (container *SContainer) GetStartedAt() time.Time { + return container.StartedAt +} + +func (container *SContainer) GetLastFinishedAt() time.Time { + return container.LastFinishedAt +} + +func (container *SContainer) GetRestartCount() int { + return container.RestartCount +} + +func (container *SContainer) GetVolumentMounts() ([]cloudprovider.ICloudVolumeMount, error) { + if container.Spec == nil { + return []cloudprovider.ICloudVolumeMount{}, nil + } + ret := []cloudprovider.ICloudVolumeMount{} + for id := range container.Spec.VolumeMounts { + ret = append(ret, &SContainerVolumeMount{ + container.Spec.VolumeMounts[id], + }) + } + return ret, nil +} + +func (container *SContainer) GetDevices() ([]cloudprovider.IContainerDevice, error) { + if container.Spec == nil { + return []cloudprovider.IContainerDevice{}, nil + } + ret := []cloudprovider.IContainerDevice{} + for id := range container.Spec.Devices { + ret = append(ret, &SContainerDevice{ + container.Spec.Devices[id], + }) + } + return ret, nil +} + +type SContainerVolumeMount struct { + *apis.ContainerVolumeMount +} + +func (volumeMount *SContainerVolumeMount) GetName() string { + return volumeMount.UniqueName +} + +func (volumeMount *SContainerVolumeMount) IsReadOnly() bool { + return volumeMount.ReadOnly +} + +func (volumeMount *SContainerVolumeMount) GetType() string { + return string(volumeMount.Type) +} + +type SContainerDevice struct { + *api.ContainerDevice +} + +func (device *SContainerDevice) GetId() string { + if device.IsolatedDevice != nil { + return device.IsolatedDevice.Id + } + return "" +} + +func (device *SContainerDevice) GetType() string { + return string(device.Type) +} + +func (container *SContainer) GetImage() string { + if container.Spec == nil { + return "" + } + return container.Spec.Image +} + +func (container *SContainer) GetCommand() []string { + if container.Spec == nil { + return []string{} + } + return container.Spec.Command +} + +func (container *SContainer) GetEnvs() []cloudprovider.SContainerEnv { + if container.Spec == nil { + return []cloudprovider.SContainerEnv{} + } + envs := make([]cloudprovider.SContainerEnv, 0) + for _, env := range container.Spec.Envs { + envs = append(envs, cloudprovider.SContainerEnv{ + Key: env.Key, + Value: env.Value, + }) + } + return envs +} diff --git a/pkg/mcclient/cloudpods/instance.go b/pkg/mcclient/cloudpods/instance.go index 4a32c39c73..3a287844c1 100644 --- a/pkg/mcclient/cloudpods/instance.go +++ b/pkg/mcclient/cloudpods/instance.go @@ -614,3 +614,16 @@ func (vm *SInstance) GetIsolateDeviceIds() ([]string, error) { } return ret, nil } + +func (vm *SInstance) GetContainers() ([]cloudprovider.ICloudContainer, error) { + containers, err := vm.host.zone.region.GetContainers(vm.Id) + if err != nil { + return nil, err + } + ret := []cloudprovider.ICloudContainer{} + for i := range containers { + containers[i].region = vm.host.zone.region + ret = append(ret, &containers[i]) + } + return ret, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 208b3426b0..3734265522 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2243,7 +2243,7 @@ sigs.k8s.io/structured-merge-diff/v4/value # sigs.k8s.io/yaml v1.2.0 ## explicit; go 1.12 sigs.k8s.io/yaml -# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260115031205-22d194eacc4c +# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260121071824-81b6a939374a ## explicit; go 1.24 yunion.io/x/cloudmux/pkg/apis yunion.io/x/cloudmux/pkg/apis/billing diff --git a/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/resources.go b/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/resources.go index 876a93d340..24fe85ac4d 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/resources.go +++ b/vendor/yunion.io/x/cloudmux/pkg/cloudprovider/resources.go @@ -367,11 +367,6 @@ type ICloudVM interface { GetVga() string GetVdi() string - // GetOSArch() string - // GetOsType() TOsType - // GetOSName() string - // GetBios() string - GetMachine() string GetInstanceType() string @@ -418,6 +413,39 @@ type ICloudVM interface { GetPowerStates() string GetHealthStatus() string GetIsolateDeviceIds() ([]string, error) + + GetContainers() ([]ICloudContainer, error) +} + +type SContainerEnv struct { + Key string + Value string +} + +type ICloudContainer interface { + ICloudResource + + GetImage() string + GetCommand() []string + GetEnvs() []SContainerEnv + + GetStartedAt() time.Time + GetLastFinishedAt() time.Time + GetRestartCount() int + + GetVolumentMounts() ([]ICloudVolumeMount, error) + GetDevices() ([]IContainerDevice, error) +} + +type ICloudVolumeMount interface { + GetName() string + IsReadOnly() bool + GetType() string +} + +type IContainerDevice interface { + GetId() string + GetType() string } type ICloudNic interface { diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/instance_base.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/instance_base.go index 0f43b261d3..a106b5b0fa 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/instance_base.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/instance_base.go @@ -102,3 +102,7 @@ func (instance *SInstanceBase) GetError() error { func (instance *SInstanceBase) GetIsolateDeviceIds() ([]string, error) { return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "GetIsolateDeviceIds") } + +func (instance *SInstanceBase) GetContainers() ([]cloudprovider.ICloudContainer, error) { + return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "GetContainers") +} diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/remotefile/instance.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/remotefile/instance.go index 36275bbfda..64fa6a9070 100644 --- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/remotefile/instance.go +++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/remotefile/instance.go @@ -292,3 +292,7 @@ func (self *SInstance) GetHealthStatus() string { func (self *SInstance) GetIsolateDeviceIds() ([]string, error) { return nil, cloudprovider.ErrNotSupported } + +func (self *SInstance) GetContainers() ([]cloudprovider.ICloudContainer, error) { + return nil, cloudprovider.ErrNotSupported +}