api/handler/service_vm_hotplug_test.go
Rama main · réplica de lectura del repositorio original.
package handler
import (
"context"
"errors"
"reflect"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/golang/mock/gomock"
"github.com/goodrain/rainbond/db"
dbdao "github.com/goodrain/rainbond/db/dao"
dbmodel "github.com/goodrain/rainbond/db/model"
"github.com/jinzhu/gorm"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
dynamicfake "k8s.io/client-go/dynamic/fake"
k8sfake "k8s.io/client-go/kubernetes/fake"
v1 "kubevirt.io/api/core/v1"
kubecli "kubevirt.io/client-go/kubecli"
)
func TestBuildVMHotplugAddVolumeOptionsUsesSCSIBusForIndexedDiskPath(t *testing.T) {
opts := buildVMHotplugAddVolumeOptions("manual99", "/disk-1")
if opts == nil || opts.Disk == nil || opts.Disk.DiskDevice.Disk == nil {
t.Fatalf("expected hotplug add volume options to create a disk target, got %#v", opts)
}
if opts.Disk.DiskDevice.Disk.Bus != v1.DiskBusSCSI {
t.Fatalf("expected indexed vm hotplug disk to use scsi bus, got %q", opts.Disk.DiskDevice.Disk.Bus)
}
}
func TestResolveVMHotplugAccessModesConvertsShorthand(t *testing.T) {
modes := resolveVMHotplugAccessModes(&dbmodel.TenantServiceVolume{AccessMode: "RWX"})
if !reflect.DeepEqual(modes, []corev1.PersistentVolumeAccessMode{corev1.ReadWriteMany}) {
t.Fatalf("expected RWX to convert to ReadWriteMany, got %#v", modes)
}
}
func TestResolveVMHotplugAccessModesKeepsExpandedValues(t *testing.T) {
modes := resolveVMHotplugAccessModes(&dbmodel.TenantServiceVolume{AccessMode: "ReadWriteOnce,ReadOnlyMany"})
expected := []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce, corev1.ReadOnlyMany}
if !reflect.DeepEqual(modes, expected) {
t.Fatalf("expected expanded access modes to be preserved, got %#v", modes)
}
}
func TestResolveVMHotplugAccessModesDefaultsToReadWriteMany(t *testing.T) {
modes := resolveVMHotplugAccessModes(&dbmodel.TenantServiceVolume{})
if !reflect.DeepEqual(modes, []corev1.PersistentVolumeAccessMode{corev1.ReadWriteMany}) {
t.Fatalf("expected empty access mode to default to ReadWriteMany, got %#v", modes)
}
}
// capability_id: rainbond.vm-hotplug.data-volume-capacity-gi
func TestBuildVMHotplugDataVolumeObjectUsesGiCapacity(t *testing.T) {
obj := buildVMHotplugDataVolumeObject("demo-ns", "tenant-1", &dbmodel.TenantServiceVolume{
ServiceID: "service-vm",
VolumeName: "data-1",
VolumePath: "/disk-1",
VolumeType: "nfs-storage",
VolumeCapacity: 10,
AccessMode: "RWX",
}, "manual99")
storage, found, err := unstructured.NestedString(obj.Object, "spec", "storage", "resources", "requests", "storage")
if err != nil || !found {
t.Fatalf("expected DataVolume storage request, found=%t err=%v obj=%#v", found, err, obj.Object)
}
if storage != "10Gi" {
t.Fatalf("expected 10GB vm hotplug disk to request 10Gi, got %q", storage)
}
}
type hotplugVolumeTestManager struct {
db.Manager
tx *gorm.DB
volumeDao dbdao.TenantServiceVolumeDao
serviceDao dbdao.TenantServiceDao
}
func (m hotplugVolumeTestManager) Begin() *gorm.DB {
return m.tx
}
func (m hotplugVolumeTestManager) TenantServiceVolumeDaoTransactions(*gorm.DB) dbdao.TenantServiceVolumeDao {
return m.volumeDao
}
func (m hotplugVolumeTestManager) TenantServiceDao() dbdao.TenantServiceDao {
return m.serviceDao
}
type hotplugTenantServiceVolumeDao struct {
dbdao.TenantServiceVolumeDao
added *dbmodel.TenantServiceVolume
}
func (d *hotplugTenantServiceVolumeDao) AddModel(arg dbmodel.Interface) error {
volume, ok := arg.(*dbmodel.TenantServiceVolume)
if !ok {
return nil
}
copied := *volume
copied.ID = 99
volume.ID = 99
d.added = &copied
return nil
}
type hotplugTenantServiceDao struct {
dbdao.TenantServiceDao
service *dbmodel.TenantServices
}
func (d *hotplugTenantServiceDao) GetServiceByID(serviceID string) (*dbmodel.TenantServices, error) {
return d.service, nil
}
type hotunplugVolumeDeleteTestManager struct {
db.Manager
tx *gorm.DB
volumeDao dbdao.TenantServiceVolumeDao
mountRelationDao dbdao.TenantServiceMountRelationDao
configFileDao dbdao.TenantServiceConfigFileDao
}
func (m hotunplugVolumeDeleteTestManager) Begin() *gorm.DB {
return m.tx
}
func (m hotunplugVolumeDeleteTestManager) TenantServiceVolumeDaoTransactions(*gorm.DB) dbdao.TenantServiceVolumeDao {
return m.volumeDao
}
func (m hotunplugVolumeDeleteTestManager) TenantServiceMountRelationDaoTransactions(*gorm.DB) dbdao.TenantServiceMountRelationDao {
return m.mountRelationDao
}
func (m hotunplugVolumeDeleteTestManager) TenantServiceConfigFileDaoTransactions(*gorm.DB) dbdao.TenantServiceConfigFileDao {
return m.configFileDao
}
type hotunplugTenantServiceVolumeDao struct {
dbdao.TenantServiceVolumeDao
volume *dbmodel.TenantServiceVolume
deletedVolume string
}
func (d *hotunplugTenantServiceVolumeDao) GetVolumeByServiceIDAndName(serviceID, name string) (*dbmodel.TenantServiceVolume, error) {
return d.volume, nil
}
func (d *hotunplugTenantServiceVolumeDao) DeleteModel(serviceID string, args ...interface{}) error {
if len(args) > 0 {
if volumeName, ok := args[0].(string); ok {
d.deletedVolume = volumeName
}
}
return nil
}
type hotunplugTenantServiceMountRelationDao struct {
dbdao.TenantServiceMountRelationDao
}
func (d *hotunplugTenantServiceMountRelationDao) GetTenantServiceMountRelationsByDepServiceAndVolumeName(serviceID, volumeName string) ([]*dbmodel.TenantServiceMountRelation, error) {
return nil, nil
}
type hotunplugTenantServiceConfigFileDao struct {
dbdao.TenantServiceConfigFileDao
serviceID string
volumeName string
created *dbmodel.TenantServiceConfigFile
}
func (d *hotunplugTenantServiceConfigFileDao) AddModel(arg dbmodel.Interface) error {
cfg, ok := arg.(*dbmodel.TenantServiceConfigFile)
if !ok {
return nil
}
copied := *cfg
d.created = &copied
return nil
}
func (d *hotunplugTenantServiceConfigFileDao) DelByVolumeID(serviceID, volumeName string) error {
d.serviceID = serviceID
d.volumeName = volumeName
return nil
}
type hotplugConfigFileTestManager struct {
db.Manager
tx *gorm.DB
volumeDao dbdao.TenantServiceVolumeDao
configFileDao dbdao.TenantServiceConfigFileDao
serviceDao dbdao.TenantServiceDao
}
func (m hotplugConfigFileTestManager) Begin() *gorm.DB {
return m.tx
}
func (m hotplugConfigFileTestManager) TenantServiceVolumeDaoTransactions(*gorm.DB) dbdao.TenantServiceVolumeDao {
return m.volumeDao
}
func (m hotplugConfigFileTestManager) TenantServiceConfigFileDaoTransactions(*gorm.DB) dbdao.TenantServiceConfigFileDao {
return m.configFileDao
}
func (m hotplugConfigFileTestManager) TenantServiceDao() dbdao.TenantServiceDao {
return m.serviceDao
}
type envAttrTestManager struct {
db.Manager
envDao dbdao.TenantServiceEnvVarDao
serviceDao dbdao.TenantServiceDao
}
func (m envAttrTestManager) TenantServiceEnvVarDao() dbdao.TenantServiceEnvVarDao {
return m.envDao
}
func (m envAttrTestManager) TenantServiceDao() dbdao.TenantServiceDao {
return m.serviceDao
}
type envAttrDaoStub struct {
dbdao.TenantServiceEnvVarDao
updated bool
}
func (d *envAttrDaoStub) AddModel(arg dbmodel.Interface) error {
return nil
}
func (d *envAttrDaoStub) DeleteModel(serviceID string, args ...interface{}) error {
return nil
}
func (d *envAttrDaoStub) UpdateModelByAttrName(env *dbmodel.TenantServiceEnvVar, oldAttrName string) error {
d.updated = true
return nil
}
func TestVolumnVarHotplugsRunningVMDataDisk(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("create sqlmock: %v", err)
}
defer sqlDB.Close()
gdb, err := gorm.Open("mysql", sqlDB)
if err != nil {
t.Fatalf("open gorm db: %v", err)
}
defer gdb.Close()
mock.ExpectBegin()
tx := gdb.Begin()
if err := tx.Error; err != nil {
t.Fatalf("begin tx: %v", err)
}
mock.ExpectCommit()
volumeDao := &hotplugTenantServiceVolumeDao{}
serviceDao := &hotplugTenantServiceDao{
service: &dbmodel.TenantServices{
ServiceID: "service-vm",
ExtendMethod: "vm",
},
}
db.SetTestManager(hotplugVolumeTestManager{tx: tx, volumeDao: volumeDao, serviceDao: serviceDao})
defer db.SetTestManager(nil)
hotplugCalled := false
syncCalled := false
action := &ServiceAction{
hotplugVMDataDiskHook: func(tenantID string, volume *dbmodel.TenantServiceVolume) error {
hotplugCalled = true
if volume.ID != 99 {
t.Fatalf("expected persisted volume id 99, got %d", volume.ID)
}
return nil
},
syncVirtualMachineSpecHook: func(serviceID string) error {
syncCalled = true
return nil
},
}
apiErr := action.VolumnVar(&dbmodel.TenantServiceVolume{
ServiceID: "service-vm",
VolumeName: "data-1",
VolumePath: "/disk",
VolumeType: dbmodel.VMVolumeType.String(),
VolumeCapacity: 10240,
}, "tenant-1", "", "add")
if apiErr != nil {
t.Fatalf("expected no error, got %v", apiErr)
}
if !hotplugCalled {
t.Fatal("expected running vm volume add to hotplug data disk")
}
if syncCalled {
t.Fatal("did not expect running vm volume add to sync vm spec directly")
}
if volumeDao.added == nil || volumeDao.added.VolumeName != "data-1" {
t.Fatalf("expected volume to be persisted, got %#v", volumeDao.added)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("unmet sql expectations: %v", err)
}
}
func TestVolumnVarAddSyncsVMConfigFileImmediately(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("create sqlmock: %v", err)
}
defer sqlDB.Close()
gdb, err := gorm.Open("mysql", sqlDB)
if err != nil {
t.Fatalf("open gorm db: %v", err)
}
defer gdb.Close()
mock.ExpectBegin()
tx := gdb.Begin()
if err := tx.Error; err != nil {
t.Fatalf("begin tx: %v", err)
}
mock.ExpectCommit()
volumeDao := &hotplugTenantServiceVolumeDao{}
configFileDao := &hotunplugTenantServiceConfigFileDao{}
serviceDao := &hotplugTenantServiceDao{
service: &dbmodel.TenantServices{
ServiceID: "service-vm",
ExtendMethod: "vm",
},
}
db.SetTestManager(hotplugConfigFileTestManager{
tx: tx,
volumeDao: volumeDao,
configFileDao: configFileDao,
serviceDao: serviceDao,
})
defer db.SetTestManager(nil)
hotplugCalled := false
syncCalled := false
action := &ServiceAction{
hotplugVMDataDiskHook: func(tenantID string, volume *dbmodel.TenantServiceVolume) error {
hotplugCalled = true
return nil
},
syncVirtualMachineSpecHook: func(serviceID string) error {
syncCalled = true
if serviceID != "service-vm" {
t.Fatalf("expected vm spec sync for service-vm, got %s", serviceID)
}
return nil
},
}
apiErr := action.VolumnVar(&dbmodel.TenantServiceVolume{
ServiceID: "service-vm",
VolumeName: "test",
VolumePath: "test.txt",
VolumeType: dbmodel.ConfigFileVolumeType.String(),
}, "tenant-1", "demo=1", "add")
if apiErr != nil {
t.Fatalf("expected no error, got %v", apiErr)
}
if hotplugCalled {
t.Fatal("did not expect config-file add to go through vm hotplug path")
}
if !syncCalled {
t.Fatal("expected config-file add to sync vm spec immediately")
}
if volumeDao.added == nil || volumeDao.added.VolumeType != dbmodel.ConfigFileVolumeType.String() {
t.Fatalf("expected config-file volume to be persisted, got %#v", volumeDao.added)
}
if configFileDao.created == nil || configFileDao.created.VolumeName != "test" {
t.Fatalf("expected config-file content to be persisted, got %#v", configFileDao.created)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("unmet sql expectations: %v", err)
}
}
func TestEnvAttrSyncsVMSpecAfterUpdate(t *testing.T) {
serviceDao := &hotplugTenantServiceDao{
service: &dbmodel.TenantServices{
ServiceID: "service-vm",
ExtendMethod: "vm",
},
}
envDao := &envAttrDaoStub{}
db.SetTestManager(envAttrTestManager{
envDao: envDao,
serviceDao: serviceDao,
})
defer db.SetTestManager(nil)
syncCalled := false
action := &ServiceAction{
syncVirtualMachineSpecHook: func(serviceID string) error {
syncCalled = true
if serviceID != "service-vm" {
t.Fatalf("expected service-vm sync target, got %s", serviceID)
}
return nil
},
}
err := action.EnvAttr("update", &dbmodel.TenantServiceEnvVar{
ServiceID: "service-vm",
AttrName: "DEMO_HOST",
AttrValue: "demo-service",
}, "DEMO_HOST")
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if !syncCalled {
t.Fatal("expected vm env update to sync vm spec")
}
if !envDao.updated {
t.Fatal("expected env dao update to be called")
}
}
func TestHotplugVMDataDiskSyncsStoppedVMForSelectedStorageClassVolume(t *testing.T) {
serviceDao := &hotplugTenantServiceDao{
service: &dbmodel.TenantServices{
ServiceID: "service-vm",
ExtendMethod: "vm",
},
}
db.SetTestManager(hotplugVolumeTestManager{serviceDao: serviceDao})
defer db.SetTestManager(nil)
syncCalled := false
action := &ServiceAction{
syncVirtualMachineSpecHook: func(serviceID string) error {
syncCalled = true
if serviceID != "service-vm" {
t.Fatalf("expected service-vm sync target, got %s", serviceID)
}
return nil
},
}
action.getVirtualMachineByServiceIDHook = func(serviceID string) (*v1.VirtualMachine, error) {
return &v1.VirtualMachine{
Status: v1.VirtualMachineStatus{
PrintableStatus: v1.VirtualMachineStatusStopped,
},
}, nil
}
err := action.hotplugVMDataDisk("tenant-1", &dbmodel.TenantServiceVolume{
ServiceID: "service-vm",
VolumeName: "data-1",
VolumePath: "/disk-1",
VolumeType: "nfs-storage",
VolumeCapacity: 20,
})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if !syncCalled {
t.Fatal("expected selected storage class vm disk to sync stopped vm spec")
}
}
// capability_id: rainbond.vm-hotplug.add-volume-conflict-retry
func TestPerformVMHotplugAddVolumeRetriesConflicts(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockClient := kubecli.NewMockKubevirtClient(ctrl)
mockVM := kubecli.NewMockVirtualMachineInterface(ctrl)
mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVM).Times(2)
attempts := 0
mockVM.EXPECT().AddVolume(gomock.Any(), "demo-vm", gomock.Any()).Times(2).DoAndReturn(
func(_ context.Context, _ string, opts *v1.AddVolumeOptions) error {
if opts == nil || opts.Name != "manual99" {
t.Fatalf("expected add volume options for manual99, got %#v", opts)
}
attempts++
if attempts == 1 {
return k8serrors.NewConflict(schema.GroupResource{Group: "kubevirt.io", Resource: "virtualmachines"}, "demo-vm", errors.New("the object has been modified"))
}
return nil
},
)
action := &ServiceAction{
kubevirtClient: mockClient,
}
action.getVirtualMachineByServiceIDHook = func(serviceID string) (*v1.VirtualMachine, error) {
return &v1.VirtualMachine{
ObjectMeta: metav1.ObjectMeta{
Name: "demo-vm",
Namespace: "demo-ns",
},
Status: v1.VirtualMachineStatus{
PrintableStatus: v1.VirtualMachineStatusRunning,
},
}, nil
}
if err := action.performVMHotplugAddVolume("service-vm", buildVMHotplugAddVolumeOptions("manual99", "/disk-1")); err != nil {
t.Fatalf("expected conflict retry to succeed, got %v", err)
}
if attempts != 2 {
t.Fatalf("expected add volume to retry once after conflict, got %d attempts", attempts)
}
}
// capability_id: rainbond.vm-hotplug.remove-volume-running-vm
func TestHotunplugVMDataDiskRemovesVolumeFromRunningVM(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
serviceDao := &hotplugTenantServiceDao{
service: &dbmodel.TenantServices{
ServiceID: "service-vm",
ExtendMethod: "vm",
},
}
db.SetTestManager(hotplugVolumeTestManager{serviceDao: serviceDao})
defer db.SetTestManager(nil)
mockClient := kubecli.NewMockKubevirtClient(ctrl)
mockVM := kubecli.NewMockVirtualMachineInterface(ctrl)
mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVM)
mockVM.EXPECT().RemoveVolume(gomock.Any(), "demo-vm", gomock.Any()).DoAndReturn(
func(_ context.Context, _ string, opts *v1.RemoveVolumeOptions) error {
if opts == nil || opts.Name != "manual42" {
t.Fatalf("expected remove volume options for manual42, got %#v", opts)
}
return nil
},
)
action := &ServiceAction{
kubevirtClient: mockClient,
}
action.getVirtualMachineByServiceIDHook = func(serviceID string) (*v1.VirtualMachine, error) {
return &v1.VirtualMachine{
ObjectMeta: metav1.ObjectMeta{
Name: "demo-vm",
Namespace: "demo-ns",
},
Status: v1.VirtualMachineStatus{
PrintableStatus: v1.VirtualMachineStatusRunning,
},
}, nil
}
err := action.hotunplugVMDataDisk(&dbmodel.TenantServiceVolume{
Model: dbmodel.Model{ID: 42},
ServiceID: "service-vm",
VolumeName: "data-1",
VolumePath: "/disk-1",
VolumeType: "nfs-storage",
VolumeCapacity: 20,
})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
}
// capability_id: rainbond.vm-hotplug.remove-volume-running-vm
func TestHotunplugVMDataDiskDeletesBackingDataVolumeAndPVC(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
serviceDao := &hotplugTenantServiceDao{
service: &dbmodel.TenantServices{
ServiceID: "service-vm",
ExtendMethod: "vm",
},
}
db.SetTestManager(hotplugVolumeTestManager{serviceDao: serviceDao})
defer db.SetTestManager(nil)
mockClient := kubecli.NewMockKubevirtClient(ctrl)
mockVM := kubecli.NewMockVirtualMachineInterface(ctrl)
mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVM)
mockVM.EXPECT().RemoveVolume(gomock.Any(), "demo-vm", gomock.Any()).Return(nil)
dv := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "cdi.kubevirt.io/v1beta1",
"kind": "DataVolume",
"metadata": map[string]interface{}{
"name": "manual42",
"namespace": "demo-ns",
},
},
}
dynamicClient := dynamicfake.NewSimpleDynamicClient(runtime.NewScheme(), dv)
originalDynamicClient := vmHotplugDynamicClient
vmHotplugDynamicClient = func() dynamic.Interface {
return dynamicClient
}
defer func() {
vmHotplugDynamicClient = originalDynamicClient
}()
kubeClient := k8sfake.NewSimpleClientset(&corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "manual42",
Namespace: "demo-ns",
},
})
action := &ServiceAction{
kubevirtClient: mockClient,
kubeClient: kubeClient,
}
action.getVirtualMachineByServiceIDHook = func(serviceID string) (*v1.VirtualMachine, error) {
return &v1.VirtualMachine{
ObjectMeta: metav1.ObjectMeta{
Name: "demo-vm",
Namespace: "demo-ns",
},
Status: v1.VirtualMachineStatus{
PrintableStatus: v1.VirtualMachineStatusRunning,
},
}, nil
}
err := action.hotunplugVMDataDisk(&dbmodel.TenantServiceVolume{
Model: dbmodel.Model{ID: 42},
ServiceID: "service-vm",
VolumeName: "data-1",
VolumePath: "/disk-1",
VolumeType: "nfs-storage",
VolumeCapacity: 20,
})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
_, err = dynamicClient.Resource(dataVolumeGVR).Namespace("demo-ns").Get(context.Background(), "manual42", metav1.GetOptions{})
if !k8serrors.IsNotFound(err) {
t.Fatalf("expected backing DataVolume to be deleted, got err=%v", err)
}
_, err = kubeClient.CoreV1().PersistentVolumeClaims("demo-ns").Get(context.Background(), "manual42", metav1.GetOptions{})
if !k8serrors.IsNotFound(err) {
t.Fatalf("expected backing PVC to be deleted, got err=%v", err)
}
}
// capability_id: rainbond.vm-hotplug.remove-volume-running-vm
func TestVolumnVarDeleteHotunplugsRunningVMDataDisk(t *testing.T) {
sqlDB, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("create sqlmock: %v", err)
}
defer sqlDB.Close()
gdb, err := gorm.Open("mysql", sqlDB)
if err != nil {
t.Fatalf("open gorm db: %v", err)
}
defer gdb.Close()
mock.ExpectBegin()
tx := gdb.Begin()
if err := tx.Error; err != nil {
t.Fatalf("begin tx: %v", err)
}
mock.ExpectCommit()
volumeDao := &hotunplugTenantServiceVolumeDao{
volume: &dbmodel.TenantServiceVolume{
Model: dbmodel.Model{ID: 42},
ServiceID: "service-vm",
VolumeName: "data-1",
VolumePath: "/disk-1",
VolumeType: "nfs-storage",
},
}
configFileDao := &hotunplugTenantServiceConfigFileDao{}
db.SetTestManager(hotunplugVolumeDeleteTestManager{
tx: tx,
volumeDao: volumeDao,
mountRelationDao: &hotunplugTenantServiceMountRelationDao{},
configFileDao: configFileDao,
})
defer db.SetTestManager(nil)
hotunplugCalled := false
syncCalled := false
action := &ServiceAction{
MQClient: &noopMQClient{},
hotunplugVMDataDiskHook: func(volume *dbmodel.TenantServiceVolume) error {
hotunplugCalled = true
if volume == nil || volume.ID != 42 {
t.Fatalf("expected deleted vm volume with id 42, got %#v", volume)
}
return nil
},
syncVirtualMachineSpecHook: func(serviceID string) error {
syncCalled = true
return nil
},
}
apiErr := action.VolumnVar(&dbmodel.TenantServiceVolume{
ServiceID: "service-vm",
VolumeName: "data-1",
}, "tenant-1", "", "delete")
if apiErr != nil {
t.Fatalf("expected no error, got %v", apiErr)
}
if !hotunplugCalled {
t.Fatal("expected delete to hotunplug running vm data disk")
}
if syncCalled {
t.Fatal("did not expect delete to fall back to vm spec sync when hotunplug hook handled it")
}
if volumeDao.deletedVolume != "data-1" {
t.Fatalf("expected volume data-1 to be deleted, got %q", volumeDao.deletedVolume)
}
if configFileDao.serviceID != "service-vm" || configFileDao.volumeName != "data-1" {
t.Fatalf("expected config file cleanup for deleted volume, got service=%q volume=%q", configFileDao.serviceID, configFileDao.volumeName)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("unmet sql expectations: %v", err)
}
}