api/handler/service_sync_volume_relations_test.go
Rama main · réplica de lectura del repositorio original.
// Copyright (C) 2014-2024 Goodrain Co., Ltd.
// RAINBOND, Application Management Platform
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. For any non-GPL usage of Rainbond,
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
// must be obtained first.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package handler
import (
"errors"
"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"
"github.com/jinzhu/gorm"
)
const (
syncVolumeAppID = "consumer-app-id"
syncVolumeTenantID = "tenant-id"
syncVolumeConsumerComponentID = "consumer-component-id"
syncVolumeProviderComponentID = "provider-component-id"
syncVolumeName = "shared-config"
syncVolumeMountPath = "/etc/example/config.yaml"
)
type syncVolumeRelationTestManager struct {
db.Manager
serviceDao dbdao.TenantServiceDao
volumeDao dbdao.TenantServiceVolumeDao
mountRelationDao dbdao.TenantServiceMountRelationDao
}
func (m syncVolumeRelationTestManager) TenantServiceDao() dbdao.TenantServiceDao {
return m.serviceDao
}
func (m syncVolumeRelationTestManager) TenantServiceVolumeDao() dbdao.TenantServiceVolumeDao {
return m.volumeDao
}
func (m syncVolumeRelationTestManager) TenantServiceMountRelationDaoTransactions(*gorm.DB) dbdao.TenantServiceMountRelationDao {
return m.mountRelationDao
}
type syncVolumeRelationTenantServiceDao struct {
dbdao.TenantServiceDao
services []*dbmodel.TenantServices
externalServices []*dbmodel.TenantServices
externalServicesErr error
requestedAppID string
requestedServiceIDs []string
}
func (d *syncVolumeRelationTenantServiceDao) ListByAppID(appID string) ([]*dbmodel.TenantServices, error) {
d.requestedAppID = appID
return d.services, nil
}
func (d *syncVolumeRelationTenantServiceDao) GetServiceByIDs(serviceIDs []string) ([]*dbmodel.TenantServices, error) {
d.requestedServiceIDs = append([]string(nil), serviceIDs...)
return d.externalServices, d.externalServicesErr
}
type syncVolumeRelationVolumeDao struct {
dbdao.TenantServiceVolumeDao
providerVolume *dbmodel.TenantServiceVolume
requestedIDs []string
}
func (d *syncVolumeRelationVolumeDao) ListVolumesByComponentIDs(componentIDs []string) ([]*dbmodel.TenantServiceVolume, error) {
d.requestedIDs = append([]string(nil), componentIDs...)
if d.providerVolume == nil {
return nil, nil
}
for _, componentID := range componentIDs {
if componentID == d.providerVolume.ServiceID {
return []*dbmodel.TenantServiceVolume{d.providerVolume}, nil
}
}
return nil, nil
}
type syncVolumeRelationMountDao struct {
dbdao.TenantServiceMountRelationDao
deletedComponentIDs []string
createdRelations []*dbmodel.TenantServiceMountRelation
}
func (d *syncVolumeRelationMountDao) DeleteByComponentIDs(componentIDs []string) error {
d.deletedComponentIDs = append([]string(nil), componentIDs...)
return nil
}
func (d *syncVolumeRelationMountDao) CreateOrUpdateVolumeRelsInBatch(relations []*dbmodel.TenantServiceMountRelation) error {
d.createdRelations = append([]*dbmodel.TenantServiceMountRelation(nil), relations...)
return nil
}
func setSyncVolumeRelationTestManager(t *testing.T, manager syncVolumeRelationTestManager) {
t.Helper()
originalManager := db.GetManager()
db.SetTestManager(manager)
t.Cleanup(func() {
db.SetTestManager(originalManager)
})
}
func syncVolumeRelationTestComponent() *apimodel.Component {
return &apimodel.Component{
ComponentBase: apimodel.ComponentBase{ComponentID: syncVolumeConsumerComponentID},
VolumeRelations: []apimodel.VolumeRelation{
{
MountPath: syncVolumeMountPath,
DependServiceID: syncVolumeProviderComponentID,
DependVolumeName: syncVolumeName,
},
},
}
}
// capability_id: rainbond.app-upgrade.cross-app-config-mount
func TestSyncComponentVolumeRelsPreservesCrossApplicationConfigFileMount(t *testing.T) {
const hostPath = "/grdata/services/provider/shared-config"
serviceDao := &syncVolumeRelationTenantServiceDao{
services: []*dbmodel.TenantServices{{ServiceID: syncVolumeConsumerComponentID}},
externalServices: []*dbmodel.TenantServices{
{ServiceID: syncVolumeProviderComponentID, TenantID: syncVolumeTenantID},
},
}
volumeDao := &syncVolumeRelationVolumeDao{
providerVolume: &dbmodel.TenantServiceVolume{
ServiceID: syncVolumeProviderComponentID,
VolumeName: syncVolumeName,
HostPath: hostPath,
VolumeType: dbmodel.ConfigFileVolumeType.String(),
},
}
mountDao := &syncVolumeRelationMountDao{}
setSyncVolumeRelationTestManager(t, syncVolumeRelationTestManager{
serviceDao: serviceDao,
volumeDao: volumeDao,
mountRelationDao: mountDao,
})
err := (&ServiceAction{}).SyncComponentVolumeRels(nil, &dbmodel.Application{
AppID: syncVolumeAppID,
TenantID: syncVolumeTenantID,
}, []*apimodel.Component{syncVolumeRelationTestComponent()})
if err != nil {
t.Fatalf("sync component volume relations: %v", err)
}
if serviceDao.requestedAppID != syncVolumeAppID {
t.Fatalf("expected application lookup for %q, got %q", syncVolumeAppID, serviceDao.requestedAppID)
}
if !containsSyncVolumeString(serviceDao.requestedServiceIDs, syncVolumeProviderComponentID) {
t.Fatalf("expected external provider lookup to include %q, got %v", syncVolumeProviderComponentID, serviceDao.requestedServiceIDs)
}
if !containsSyncVolumeString(volumeDao.requestedIDs, syncVolumeProviderComponentID) {
t.Fatalf("expected volume lookup to include cross-application provider %q, got %v", syncVolumeProviderComponentID, volumeDao.requestedIDs)
}
if len(mountDao.deletedComponentIDs) != 1 || mountDao.deletedComponentIDs[0] != syncVolumeConsumerComponentID {
t.Fatalf("expected existing relation deletion for %q, got %v", syncVolumeConsumerComponentID, mountDao.deletedComponentIDs)
}
if len(mountDao.createdRelations) != 1 {
t.Fatalf("expected one recreated mount relation, got %d", len(mountDao.createdRelations))
}
relation := mountDao.createdRelations[0]
if relation.TenantID != syncVolumeTenantID ||
relation.ServiceID != syncVolumeConsumerComponentID ||
relation.DependServiceID != syncVolumeProviderComponentID ||
relation.VolumeName != syncVolumeName ||
relation.VolumePath != syncVolumeMountPath ||
relation.HostPath != hostPath ||
relation.VolumeType != dbmodel.ConfigFileVolumeType.String() {
t.Fatalf("recreated mount relation does not preserve the shared config-file mount: %#v", relation)
}
}
// capability_id: rainbond.app-upgrade.cross-app-config-mount
func TestSyncComponentVolumeRelsRejectsInvalidExternalProviders(t *testing.T) {
testCases := []struct {
name string
externalServices []*dbmodel.TenantServices
}{
{
name: "provider belongs to another tenant",
externalServices: []*dbmodel.TenantServices{
{ServiceID: syncVolumeProviderComponentID, TenantID: "other-tenant-id"},
},
},
{
name: "provider does not exist",
externalServices: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
serviceDao := &syncVolumeRelationTenantServiceDao{
services: []*dbmodel.TenantServices{{ServiceID: syncVolumeConsumerComponentID}},
externalServices: tc.externalServices,
}
volumeDao := &syncVolumeRelationVolumeDao{
providerVolume: &dbmodel.TenantServiceVolume{
ServiceID: syncVolumeProviderComponentID,
VolumeName: syncVolumeName,
VolumeType: dbmodel.ConfigFileVolumeType.String(),
},
}
mountDao := &syncVolumeRelationMountDao{}
setSyncVolumeRelationTestManager(t, syncVolumeRelationTestManager{
serviceDao: serviceDao,
volumeDao: volumeDao,
mountRelationDao: mountDao,
})
err := (&ServiceAction{}).SyncComponentVolumeRels(nil, &dbmodel.Application{
AppID: syncVolumeAppID,
TenantID: syncVolumeTenantID,
}, []*apimodel.Component{syncVolumeRelationTestComponent()})
if err != nil {
t.Fatalf("sync component volume relations: %v", err)
}
if !containsSyncVolumeString(serviceDao.requestedServiceIDs, syncVolumeProviderComponentID) {
t.Fatalf("expected external provider lookup to include %q, got %v", syncVolumeProviderComponentID, serviceDao.requestedServiceIDs)
}
if containsSyncVolumeString(volumeDao.requestedIDs, syncVolumeProviderComponentID) {
t.Fatalf("expected invalid external provider %q to be excluded from volume lookup, got %v", syncVolumeProviderComponentID, volumeDao.requestedIDs)
}
if len(mountDao.createdRelations) != 0 {
t.Fatalf("expected no relation for invalid external provider, got %#v", mountDao.createdRelations)
}
})
}
}
// capability_id: rainbond.app-upgrade.cross-app-config-mount
func TestSyncComponentVolumeRelsReturnsExternalProviderLookupError(t *testing.T) {
expectedErr := errors.New("external provider lookup failed")
serviceDao := &syncVolumeRelationTenantServiceDao{
services: []*dbmodel.TenantServices{{ServiceID: syncVolumeConsumerComponentID}},
externalServicesErr: expectedErr,
}
setSyncVolumeRelationTestManager(t, syncVolumeRelationTestManager{serviceDao: serviceDao})
err := (&ServiceAction{}).SyncComponentVolumeRels(nil, &dbmodel.Application{
AppID: syncVolumeAppID,
TenantID: syncVolumeTenantID,
}, []*apimodel.Component{syncVolumeRelationTestComponent()})
if !errors.Is(err, expectedErr) {
t.Fatalf("expected external provider lookup error %v, got %v", expectedErr, err)
}
}
func containsSyncVolumeString(values []string, target string) bool {
for _, value := range values {
if value == target {
return true
}
}
return false
}