resource/lvm/vg/vg_test.go
// Copyright © 2016 Asteris, LLC
//
// 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 vg_test
import (
"github.com/asteris-llc/converge/helpers/comparison"
"github.com/asteris-llc/converge/helpers/fakerenderer"
"github.com/asteris-llc/converge/resource"
"github.com/asteris-llc/converge/resource/lvm/lowlevel"
"github.com/asteris-llc/converge/resource/lvm/sampledata"
"github.com/asteris-llc/converge/resource/lvm/testhelpers"
"github.com/asteris-llc/converge/resource/lvm/vg"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
"fmt"
"testing"
)
// TestVGCheck is test for VG.Check
func TestVGCheck(t *testing.T) {
t.Run("check prerequisites failure", func(t *testing.T) {
lvm, m := testhelpers.MakeFakeLvm()
m.On("Check").Return(fmt.Errorf("failed"))
_ = simpleCheckFailure(t, lvm, "vg0", []string{"/dev/sda1"}, false, false)
})
t.Run("QueryVolumeGroups failure", func(t *testing.T) {
lvm, m := testhelpers.MakeFakeLvm()
m.On("Check").Return(nil)
m.On("QueryVolumeGroups").Return(make(map[string]*lowlevel.VolumeGroup), nil)
m.On("QueryPhysicalVolumes").Return(make(map[string]*lowlevel.PhysicalVolume), fmt.Errorf("failed"))
_ = simpleCheckFailure(t, lvm, "vg0", []string{"/dev/sda1"}, false, false)
})
t.Run("QueryPhysicalVolumes failure", func(t *testing.T) {
lvm, m := testhelpers.MakeFakeLvm()
m.On("Check").Return(nil)
m.On("QueryVolumeGroups").Return(make(map[string]*lowlevel.VolumeGroup), fmt.Errorf("failed"))
m.On("QueryPhysicalVolumes").Return(make(map[string]*lowlevel.PhysicalVolume), nil)
_ = simpleCheckFailure(t, lvm, "vg0", []string{"/dev/sda1"}, false, false)
})
t.Run("single device", func(t *testing.T) {
lvm, _ := testhelpers.MakeFakeLvmEmpty()
status, _ := simpleCheckSuccess(t, lvm, "vg0", []string{"/dev/sda1"}, false, false)
assert.True(t, status.HasChanges())
comparison.AssertDiff(t, status.Diffs(), "vg0", "<not exists>", "/dev/sda1")
})
t.Run("multiple devices", func(t *testing.T) {
lvm, _ := testhelpers.MakeFakeLvmEmpty()
status, _ := simpleCheckSuccess(t, lvm, "vg0", []string{"/dev/sda1", "/dev/sdb1", "/dev/sdc1"}, false, false)
assert.True(t, status.HasChanges())
comparison.AssertDiff(t, status.Diffs(), "vg0", "<not exists>", "/dev/sda1, /dev/sdb1, /dev/sdc1")
})
t.Run("device from another VG", func(t *testing.T) {
lvm, _ := testhelpers.MakeFakeLvmNonEmpty()
_ = simpleCheckFailure(t, lvm, "vg0", []string{"/dev/sda1", "/dev/sdb1", "/dev/sdc1"}, false, false)
})
t.Run("device remove", func(t *testing.T) {
lvm, _ := testhelpers.MakeFakeLvmNonEmpty()
status, _ := simpleCheckSuccess(t, lvm, "vg1", []string{"/dev/sdc1"}, true, false)
assert.True(t, status.HasChanges())
comparison.AssertDiff(t, status.Diffs(), "/dev/sdd1", "<group vg1>", "<removed>")
// `/dev/sdc1` should remain intact
assert.NotContains(t, "/dev/sdc1", status.Diffs())
})
t.Run("device force remove", func(t *testing.T) {
lvm, _ := testhelpers.MakeFakeLvmNonEmpty()
status, _ := simpleCheckSuccess(t, lvm, "vg1", []string{"/dev/sdc1"}, true, true)
assert.True(t, status.HasChanges())
comparison.AssertDiff(t, status.Diffs(), "/dev/sdd1", "<group vg1>", "<destructed>")
// `/dev/sdc1` should remain intact
assert.NotContains(t, "/dev/sdc1", status.Diffs())
})
t.Run("one device add", func(t *testing.T) {
lvm, _ := testhelpers.MakeFakeLvmNonEmpty()
status, _ := simpleCheckSuccess(t, lvm, "vg1", []string{"/dev/sdc1", "/dev/sdd1", "/dev/sde1"}, false, false)
assert.True(t, status.HasChanges())
comparison.AssertDiff(t, status.Diffs(), "/dev/sde1", "<no group>", "<group vg1>")
// `/dev/sdc1` and `/dev/sdd1` should remain intact
assert.NotContains(t, "/dev/sdc1", status.Diffs())
assert.NotContains(t, "/dev/sdd1", status.Diffs())
})
t.Run("once device add, one remove", func(t *testing.T) {
lvm, _ := testhelpers.MakeFakeLvmNonEmpty()
status, _ := simpleCheckSuccess(t, lvm, "vg1", []string{"/dev/sdc1", "/dev/sde1"}, true, false)
assert.True(t, status.HasChanges())
comparison.AssertDiff(t, status.Diffs(), "/dev/sde1", "<no group>", "<group vg1>")
comparison.AssertDiff(t, status.Diffs(), "/dev/sdd1", "<group vg1>", "<removed>")
// `/dev/sdc1` should remain intact
assert.NotContains(t, "/dev/sdc1", status.Diffs())
})
}
// TestVGApply is test for VG.Apply()
func TestVGApply(t *testing.T) {
t.Run("single device", func(t *testing.T) {
lvm, m := testhelpers.MakeFakeLvmEmpty()
m.On("CreateVolumeGroup", mock.Anything, mock.Anything).Return(nil)
_ = simpleApplySuccess(t, lvm, "vg0", []string{"/dev/sda1"}, false, false)
m.AssertCalled(t, "CreateVolumeGroup", "vg0", []string{"/dev/sda1"})
})
t.Run("multiple devices", func(t *testing.T) {
lvm, m := testhelpers.MakeFakeLvmEmpty()
m.On("CreateVolumeGroup", "vg0", []string{"/dev/sda1", "/dev/sdb1", "/dev/sdc1"}).Return(nil)
_ = simpleApplySuccess(t, lvm, "vg0", []string{"/dev/sda1", "/dev/sdb1", "/dev/sdc1"}, false, false)
m.AssertCalled(t, "CreateVolumeGroup", "vg0", []string{"/dev/sda1", "/dev/sdb1", "/dev/sdc1"})
})
t.Run("one device add", func(t *testing.T) {
lvm, m := testhelpers.MakeFakeLvmNonEmpty()
m.On("ExtendVolumeGroup", "vg1", mock.Anything).Return(nil)
_ = simpleApplySuccess(t, lvm, "vg1", []string{"/dev/sdc1", "/dev/sdd1", "/dev/sde1", "/dev/sda"}, false, false)
m.AssertCalled(t, "ExtendVolumeGroup", "vg1", "/dev/sde1")
m.AssertCalled(t, "ExtendVolumeGroup", "vg1", "/dev/sda")
})
t.Run("device remove", func(t *testing.T) {
lvm, m := testhelpers.MakeFakeLvmNonEmpty()
m.On("ReduceVolumeGroup", "vg1", mock.Anything).Return(nil)
m.On("RemovePhysicalVolume", mock.Anything, mock.Anything).Return(nil)
_ = simpleApplySuccess(t, lvm, "vg1", []string{"/dev/sdc1"}, true, false)
m.AssertCalled(t, "ReduceVolumeGroup", "vg1", "/dev/sdd1")
m.AssertNotCalled(t, "RemovePhysicalVolume", mock.Anything)
})
t.Run("device force remove", func(t *testing.T) {
lvm, m := testhelpers.MakeFakeLvmNonEmpty()
m.On("ReduceVolumeGroup", "vg1", mock.Anything).Return(nil)
m.On("RemovePhysicalVolume", mock.Anything, mock.Anything).Return(nil)
_ = simpleApplySuccess(t, lvm, "vg1", []string{"/dev/sdc1"}, true, true)
m.AssertCalled(t, "ReduceVolumeGroup", "vg1", "/dev/sdd1")
m.AssertCalled(t, "RemovePhysicalVolume", "/dev/sdd1", mock.Anything)
})
t.Run("CreatePhysicalVolume failure", func(t *testing.T) {
lvm, m := testhelpers.MakeFakeLvmEmpty()
m.On("CreateVolumeGroup", mock.Anything, mock.Anything).Return(fmt.Errorf("failed"))
_ = simpleApplyFailure(t, lvm, "vg0", []string{"/dev/sda1"}, false, false)
m.AssertCalled(t, "CreateVolumeGroup", "vg0", []string{"/dev/sda1"})
})
t.Run("ExtendVolumeGroup failure", func(t *testing.T) {
lvm, m := testhelpers.MakeFakeLvmNonEmpty()
m.On("ExtendVolumeGroup", "vg1", mock.Anything).Return(fmt.Errorf("failed"))
_ = simpleApplyFailure(t, lvm, "vg1", []string{"/dev/sdc1", "/dev/sdd1", "/dev/sde1", "/dev/sda"}, false, false)
m.AssertCalled(t, "ExtendVolumeGroup", "vg1", mock.Anything)
})
t.Run("ReduceVolumeGroup failure", func(t *testing.T) {
lvm, m := testhelpers.MakeFakeLvmNonEmpty()
m.On("ReduceVolumeGroup", "vg1", mock.Anything).Return(fmt.Errorf("failure"))
m.On("RemovePhysicalVolume", mock.Anything, mock.Anything).Return(nil)
_ = simpleApplyFailure(t, lvm, "vg1", []string{"/dev/sdc1"}, true, true)
// also check that "RemovePhysicalVolume" not called, if ReduceVolumeGroup failed
m.AssertNotCalled(t, "RemovePhysicalVolume", mock.Anything)
})
t.Run("RemovePhysicalVolume failure", func(t *testing.T) {
lvm, m := testhelpers.MakeFakeLvmNonEmpty()
m.On("ReduceVolumeGroup", "vg1", mock.Anything).Return(nil)
m.On("RemovePhysicalVolume", mock.Anything, mock.Anything).Return(fmt.Errorf("failure"))
_ = simpleApplyFailure(t, lvm, "vg1", []string{"/dev/sdc1"}, true, true)
m.AssertCalled(t, "RemovePhysicalVolume", "/dev/sdd1", mock.Anything)
})
}
// TestCreateVolume is a full-blown test, using fake engine to trace from high-level
// graph node vg.resourceVG, to commands passed to LVM tools. It cover only straighforward
// cases. Use mock-LVM for real tests of highlevel stuff.
func TestCreateVolume(t *testing.T) {
t.Run("single device", func(t *testing.T) {
lvm, me := testhelpers.MakeLvmWithMockExec()
me.On("Getuid").Return(0) // assume, that we have root
me.On("Lookup", mock.Anything).Return(nil) // assume, that all tools are here
me.On("Read", "pvs", mock.Anything).Return("", nil)
me.On("Read", "vgs", mock.Anything).Return("", nil)
me.On("Run", "vgcreate", []string{"vg0", "/dev/sda1"}).Return(nil)
fr := fakerenderer.New()
r := vg.NewResourceVG(lvm, "vg0", []string{"/dev/sda1"}, false, false)
status, err := r.Check(context.Background(), fr)
require.NoError(t, err)
assert.True(t, status.HasChanges())
comparison.AssertDiff(t, status.Diffs(), "vg0", "<not exists>", "/dev/sda1")
status, err = r.Apply(context.Background())
assert.NoError(t, err)
me.AssertCalled(t, "Run", "vgcreate", []string{"vg0", "/dev/sda1"})
})
t.Run("multiple devices", func(t *testing.T) {
lvm, me := testhelpers.MakeLvmWithMockExec()
me.On("Getuid").Return(0) // assume, that we have root
me.On("Lookup", mock.Anything).Return(nil) // assume, that all tools are here
me.On("Read", "pvs", mock.Anything).Return(sampledata.Pvs, nil)
me.On("Read", "vgs", mock.Anything).Return(sampledata.Vgs, nil)
fr := fakerenderer.New()
r := vg.NewResourceVG(lvm, "vg1", []string{"/dev/md127"}, false, false)
_, err := r.Check(context.Background(), fr)
assert.Error(t, err)
})
t.Run("volume which already exists", func(t *testing.T) {
lvm, me := testhelpers.MakeLvmWithMockExec()
me.On("Getuid").Return(0) // assume, that we have root
me.On("Lookup", mock.Anything).Return(nil) // assume, that all tools are here
me.On("Read", "pvs", mock.Anything).Return(sampledata.Pvs, nil)
me.On("Read", "vgs", mock.Anything).Return(sampledata.Vgs, nil)
fr := fakerenderer.New()
r := vg.NewResourceVG(lvm, "vg0", []string{"/dev/md127"}, false, false)
status, err := r.Check(context.Background(), fr)
assert.NoError(t, err)
assert.False(t, status.HasChanges())
})
}
func simpleCheckFailure(t *testing.T, lvm lowlevel.LVM, group string, devs []string, remove bool, forceRemove bool) resource.TaskStatus {
r := vg.NewResourceVG(lvm, group, devs, remove, forceRemove)
status, err := r.Check(context.Background(), fakerenderer.New())
assert.Error(t, err)
return status
}
func simpleCheckSuccess(t *testing.T, lvm lowlevel.LVM, group string, devs []string, remove bool, forceRemove bool) (resource.TaskStatus, resource.Task) {
r := vg.NewResourceVG(lvm, group, devs, remove, forceRemove)
status, err := r.Check(context.Background(), fakerenderer.New())
require.NoError(t, err)
require.NotNil(t, status)
return status, r
}
func simpleApplySuccess(t *testing.T, lvm lowlevel.LVM, group string, devs []string, remove bool, forceRemove bool) resource.TaskStatus {
checkStatus, vg := simpleCheckSuccess(t, lvm, group, devs, remove, forceRemove)
require.True(t, checkStatus.HasChanges())
status, err := vg.Apply(context.Background())
require.NoError(t, err)
return status
}
func simpleApplyFailure(t *testing.T, lvm lowlevel.LVM, group string, devs []string, remove bool, forceRemove bool) resource.TaskStatus {
checkStatus, vg := simpleCheckSuccess(t, lvm, group, devs, remove, forceRemove)
require.True(t, checkStatus.HasChanges())
status, err := vg.Apply(context.Background())
require.Error(t, err)
return status
}