package handler import ( "context" "testing" apimodel "github.com/goodrain/rainbond/api/model" "github.com/goodrain/rainbond/db" dbdao "github.com/goodrain/rainbond/db/dao" dbmodel "github.com/goodrain/rainbond/db/model" mqpb "github.com/goodrain/rainbond/mq/api/grpc/pb" gclient "github.com/goodrain/rainbond/mq/client" workermodel "github.com/goodrain/rainbond/worker/discover/model" "google.golang.org/grpc" ) type resourceSyncTestManager struct { db.Manager serviceDao dbdao.TenantServiceDao eventDao dbdao.EventDao serviceProbeDao dbdao.ServiceProbeDao relationDao dbdao.TenantServiceRelationDao attributeDao dbdao.ComponentK8sAttributeDao } func (m resourceSyncTestManager) TenantServiceDao() dbdao.TenantServiceDao { return m.serviceDao } func (m resourceSyncTestManager) ServiceEventDao() dbdao.EventDao { return m.eventDao } func (m resourceSyncTestManager) ServiceProbeDao() dbdao.ServiceProbeDao { return m.serviceProbeDao } func (m resourceSyncTestManager) TenantServiceRelationDao() dbdao.TenantServiceRelationDao { return m.relationDao } func (m resourceSyncTestManager) ComponentK8sAttributeDao() dbdao.ComponentK8sAttributeDao { return m.attributeDao } type resourceSyncTenantServiceDao struct { dbdao.TenantServiceDao service *dbmodel.TenantServices updatedService *dbmodel.TenantServices } func (d *resourceSyncTenantServiceDao) GetServiceByID(serviceID string) (*dbmodel.TenantServices, error) { return d.service, nil } func (d *resourceSyncTenantServiceDao) IsK8sComponentNameDuplicate(appID, serviceID, k8sComponentName string) bool { return false } func (d *resourceSyncTenantServiceDao) UpdateModel(arg dbmodel.Interface) error { service, ok := arg.(*dbmodel.TenantServices) if !ok { return nil } copied := *service d.updatedService = &copied d.service = &copied return nil } type resourceSyncEventDao struct { dbdao.EventDao statuses []dbmodel.EventStatus } func (d *resourceSyncEventDao) SetEventStatus(_ context.Context, status dbmodel.EventStatus) error { d.statuses = append(d.statuses, status) return nil } type resourceSyncServiceProbeDao struct { dbdao.ServiceProbeDao added int updated int deleted int } func (d *resourceSyncServiceProbeDao) AddModel(dbmodel.Interface) error { d.added++ return nil } func (d *resourceSyncServiceProbeDao) UpdateModel(dbmodel.Interface) error { d.updated++ return nil } func (d *resourceSyncServiceProbeDao) DeleteModel(string, ...interface{}) error { d.deleted++ return nil } type resourceSyncRelationDao struct { dbdao.TenantServiceRelationDao added int deleted int } func (d *resourceSyncRelationDao) AddModel(dbmodel.Interface) error { d.added++ return nil } func (d *resourceSyncRelationDao) DeleteRelationByDepID(serviceID, depID string) error { d.deleted++ return nil } type resourceSyncComponentK8sAttributeDao struct { dbdao.ComponentK8sAttributeDao attributes map[string]*dbmodel.ComponentK8sAttributes deleted []string } func (d *resourceSyncComponentK8sAttributeDao) GetByComponentIDAndName(componentID, name string) (*dbmodel.ComponentK8sAttributes, error) { if d.attributes == nil { return nil, nil } attr := d.attributes[name] if attr == nil { return nil, nil } copied := *attr return &copied, nil } func (d *resourceSyncComponentK8sAttributeDao) UpdateModel(mo dbmodel.Interface) error { attr, ok := mo.(*dbmodel.ComponentK8sAttributes) if !ok { return nil } if d.attributes == nil { d.attributes = map[string]*dbmodel.ComponentK8sAttributes{} } copied := *attr d.attributes[attr.Name] = &copied return nil } func (d *resourceSyncComponentK8sAttributeDao) CreateOrUpdateAttributesInBatch(attributes []*dbmodel.ComponentK8sAttributes) error { if d.attributes == nil { d.attributes = map[string]*dbmodel.ComponentK8sAttributes{} } for _, attr := range attributes { copied := *attr d.attributes[attr.Name] = &copied } return nil } func (d *resourceSyncComponentK8sAttributeDao) DeleteByComponentIDAndName(componentID, name string) error { d.deleted = append(d.deleted, componentID+"/"+name) if d.attributes != nil { delete(d.attributes, name) } return nil } type noopMQClient struct{} func (m *noopMQClient) Enqueue(context.Context, *mqpb.EnqueueRequest, ...grpc.CallOption) (*mqpb.TaskReply, error) { return nil, nil } func (m *noopMQClient) Topics(context.Context, *mqpb.TopicRequest, ...grpc.CallOption) (*mqpb.TaskReply, error) { return nil, nil } func (m *noopMQClient) Dequeue(context.Context, *mqpb.DequeueRequest, ...grpc.CallOption) (*mqpb.TaskMessage, error) { return nil, nil } func (m *noopMQClient) Close() {} func (m *noopMQClient) SendBuilderTopic(gclient.TaskStruct) error { return nil } // capability_id: rainbond.vm-probe-change-syncs-spec func TestServiceProbeSyncsVirtualMachineSpecWhenVMProbeChanges(t *testing.T) { for _, tc := range []struct { name string action string wantAdd int wantUpdate int wantDelete int }{ {name: "add", action: "add", wantAdd: 1}, {name: "update", action: "update", wantUpdate: 1}, {name: "delete", action: "delete", wantDelete: 1}, } { t.Run(tc.name, func(t *testing.T) { serviceDao := &resourceSyncTenantServiceDao{ service: &dbmodel.TenantServices{ ServiceID: "service-vm", ServiceAlias: "service-vm", ExtendMethod: "vm", }, } probeDao := &resourceSyncServiceProbeDao{} db.SetTestManager(resourceSyncTestManager{ serviceDao: serviceDao, eventDao: &resourceSyncEventDao{}, serviceProbeDao: probeDao, }) defer db.SetTestManager(nil) syncedServiceID := "" action := &ServiceAction{ syncVirtualMachineSpecHook: func(serviceID string) error { syncedServiceID = serviceID return nil }, } err := action.ServiceProbe(&dbmodel.TenantServiceProbe{ ServiceID: "service-vm", ProbeID: "probe-vm", Mode: "readiness", }, tc.action) if err != nil { t.Fatalf("expected no error, got %v", err) } if syncedServiceID != "service-vm" { t.Fatalf("expected vm spec sync for service-vm, got %q", syncedServiceID) } if probeDao.added != tc.wantAdd || probeDao.updated != tc.wantUpdate || probeDao.deleted != tc.wantDelete { t.Fatalf("unexpected probe dao calls: add=%d update=%d delete=%d", probeDao.added, probeDao.updated, probeDao.deleted) } }) } } func TestServiceUpdateSyncsVirtualMachineSpecWhenVMResourcesChange(t *testing.T) { serviceDao := &resourceSyncTenantServiceDao{ service: &dbmodel.TenantServices{ ServiceID: "service-vm", ServiceAlias: "service-vm", ExtendMethod: "vm", ContainerCPU: 500, ContainerMemory: 512, ContainerGPU: 0, K8sComponentName: "demo-vm", }, } db.SetTestManager(resourceSyncTestManager{ serviceDao: serviceDao, eventDao: &resourceSyncEventDao{}, }) defer db.SetTestManager(nil) syncedServiceID := "" action := &ServiceAction{ syncVirtualMachineSpecHook: func(serviceID string) error { syncedServiceID = serviceID return nil }, } err := action.ServiceUpdate(map[string]interface{}{ "service_id": "service-vm", "container_cpu": 1000, "container_memory": 1024, "k8s_component_name": "demo-vm", }) if err != nil { t.Fatalf("expected no error, got %v", err) } if syncedServiceID != "service-vm" { t.Fatalf("expected vm spec sync for service-vm, got %q", syncedServiceID) } if serviceDao.updatedService == nil { t.Fatal("expected updated service to be persisted") } if serviceDao.updatedService.ContainerCPU != 1000 || serviceDao.updatedService.ContainerMemory != 1024 { t.Fatalf("expected updated resources to be persisted, got cpu=%d mem=%d", serviceDao.updatedService.ContainerCPU, serviceDao.updatedService.ContainerMemory) } } func TestServiceVerticalSyncsVirtualMachineSpecWhenVMResourcesChange(t *testing.T) { serviceDao := &resourceSyncTenantServiceDao{ service: &dbmodel.TenantServices{ ServiceID: "service-vm", ServiceAlias: "service-vm", ExtendMethod: "vm", ContainerCPU: 500, ContainerMemory: 512, ContainerGPU: 0, }, } db.SetTestManager(resourceSyncTestManager{ serviceDao: serviceDao, eventDao: &resourceSyncEventDao{}, }) defer db.SetTestManager(nil) syncedServiceID := "" cpu := 1000 memory := 1024 action := &ServiceAction{ MQClient: &noopMQClient{}, syncVirtualMachineSpecHook: func(serviceID string) error { syncedServiceID = serviceID return nil }, } err := action.ServiceVertical(context.Background(), &workermodel.VerticalScalingTaskBody{ ServiceID: "service-vm", ContainerCPU: &cpu, ContainerMemory: &memory, }) if err != nil { t.Fatalf("expected no error, got %v", err) } if syncedServiceID != "service-vm" { t.Fatalf("expected vm spec sync for service-vm, got %q", syncedServiceID) } if serviceDao.updatedService == nil { t.Fatal("expected updated service to be persisted") } if serviceDao.updatedService.ContainerCPU != 1000 || serviceDao.updatedService.ContainerMemory != 1024 { t.Fatalf("expected updated resources to be persisted, got cpu=%d mem=%d", serviceDao.updatedService.ContainerCPU, serviceDao.updatedService.ContainerMemory) } } func TestServiceDependSyncsVirtualMachineSpecWhenVMDependencyChanges(t *testing.T) { for _, tc := range []struct { name string action string }{ {name: "add", action: "add"}, {name: "delete", action: "delete"}, } { t.Run(tc.name, func(t *testing.T) { serviceDao := &resourceSyncTenantServiceDao{ service: &dbmodel.TenantServices{ ServiceID: "service-vm", ServiceAlias: "service-vm", ExtendMethod: "vm", }, } db.SetTestManager(resourceSyncTestManager{ serviceDao: serviceDao, eventDao: &resourceSyncEventDao{}, relationDao: &resourceSyncRelationDao{}, }) defer db.SetTestManager(nil) syncedServiceID := "" action := &ServiceAction{ syncVirtualMachineSpecHook: func(serviceID string) error { syncedServiceID = serviceID return nil }, } err := action.ServiceDepend(tc.action, &apimodel.DependService{ TenantID: "tenant-1", ServiceID: "service-vm", DepServiceID: "dep-service", }) if err != nil { t.Fatalf("expected no error, got %v", err) } if syncedServiceID != "service-vm" { t.Fatalf("expected vm spec sync for service-vm, got %q", syncedServiceID) } }) } }