asteris-llc/converge

View on GitHub
resource/lvm/vg/vg_test.go

Summary

Maintainability
C
1 day
Test Coverage
// 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
}