diff --git a/core/steps/network/interface_create.go b/core/steps/network/interface_create.go index 4fd3cceb..96b55a16 100644 --- a/core/steps/network/interface_create.go +++ b/core/steps/network/interface_create.go @@ -78,6 +78,10 @@ func (s *createInterface) Do(ctx context.Context) ([]planner.Procedure, error) { return nil, fmt.Errorf("checking if networking interface exists: %w", err) } if exists { + // This whole block is unreachable right now, because + // the Do function is called only if ShouldDo returns + // true. It retruns false if IfaceExists returns true. + // Line 76 will never return exists=true details, err := s.svc.IfaceDetails(ctx, deviceName) if err != nil { return nil, fmt.Errorf("getting interface details: %w", err) diff --git a/core/steps/network/interface_create_test.go b/core/steps/network/interface_create_test.go new file mode 100644 index 00000000..c2d5db86 --- /dev/null +++ b/core/steps/network/interface_create_test.go @@ -0,0 +1,285 @@ +package network_test + +import ( + "context" + "testing" + + "github.com/golang/mock/gomock" + g "github.com/onsi/gomega" + "github.com/weaveworks/flintlock/core/errors" + "github.com/weaveworks/flintlock/core/models" + "github.com/weaveworks/flintlock/core/ports" + "github.com/weaveworks/flintlock/core/steps/network" + "github.com/weaveworks/flintlock/infrastructure/mock" +) + +func TestNewNetworkInterface_everythingIsEmpty(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + g.RegisterTestingT(t) + + var status *models.NetworkInterfaceStatus + vmid, _ := models.NewVMID(vmName, nsName) + iface := &models.NetworkInterface{} + svc := mock.NewMockNetworkService(mockCtrl) + ctx := context.Background() + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Times(0) + + step := network.NewNetworkInterface(vmid, iface, status, svc) + shouldDo, err := step.ShouldDo(ctx) + + g.Expect(err).To(g.BeNil()) + g.Expect(shouldDo).To(g.BeTrue()) + + _, err = step.Do(ctx) + + g.Expect(err).To(g.MatchError(errors.ErrGuestDeviceNameRequired)) +} + +func TestNewNetworkInterface_doesNotExist(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + g.RegisterTestingT(t) + + var status *models.NetworkInterfaceStatus + vmid, _ := models.NewVMID(vmName, nsName) + iface := &models.NetworkInterface{GuestDeviceName: defaultEthDevice} + svc := mock.NewMockNetworkService(mockCtrl) + ctx := context.Background() + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Times(0) + + step := network.NewNetworkInterface(vmid, iface, status, svc) + shouldDo, err := step.ShouldDo(ctx) + + g.Expect(err).To(g.BeNil()) + g.Expect(shouldDo).To(g.BeTrue()) + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(false, nil). + Times(1) + + svc.EXPECT(). + IfaceCreate(gomock.Eq(ctx), gomock.Eq(ports.IfaceCreateInput{ + DeviceName: expectedTapDeviceName, + })). + Return(&ports.IfaceDetails{ + DeviceName: expectedTapDeviceName, + Type: models.IfaceTypeTap, + MAC: defaultMACAddress, + Index: 0, + }, nil). + Times(1) + + _, err = step.Do(ctx) + + g.Expect(err).To(g.BeNil()) +} + +func TestNewNetworkInterface_existingInterface(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + g.RegisterTestingT(t) + + vmid, _ := models.NewVMID(vmName, nsName) + iface := &models.NetworkInterface{ + GuestDeviceName: defaultEthDevice, + AllowMetadataRequests: false, + GuestMAC: defaultMACAddress, + Type: models.IfaceTypeTap, + } + status := &models.NetworkInterfaceStatus{ + HostDeviceName: expectedTapDeviceName, + Index: 0, + MACAddress: defaultMACAddress, + } + svc := mock.NewMockNetworkService(mockCtrl) + ctx := context.Background() + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(true, nil). + Times(1) + + step := network.NewNetworkInterface(vmid, iface, status, svc) + shouldDo, err := step.ShouldDo(ctx) + + g.Expect(err).To(g.BeNil()) + g.Expect(shouldDo).To(g.BeFalse()) + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(true, nil). + Times(1) + + svc.EXPECT(). + IfaceDetails(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(&ports.IfaceDetails{ + DeviceName: expectedTapDeviceName, + Type: models.IfaceTypeTap, + MAC: defaultMACAddress, + Index: 0, + }, nil). + Times(1) + + _, err = step.Do(ctx) + + g.Expect(err).To(g.BeNil()) +} + +func TestNewNetworkInterface_missingInterface(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + g.RegisterTestingT(t) + + vmid, _ := models.NewVMID(vmName, nsName) + iface, status := fullNetworkInterface() + svc := mock.NewMockNetworkService(mockCtrl) + ctx := context.Background() + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(false, nil). + Times(1) + + step := network.NewNetworkInterface(vmid, iface, status, svc) + shouldDo, err := step.ShouldDo(ctx) + + g.Expect(err).To(g.BeNil()) + g.Expect(shouldDo).To(g.BeTrue()) + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(false, nil). + Times(1) + + svc.EXPECT(). + IfaceCreate(gomock.Eq(ctx), gomock.Eq(ports.IfaceCreateInput{ + DeviceName: expectedTapDeviceName, + MAC: defaultMACAddress, + })). + Return(&ports.IfaceDetails{ + DeviceName: expectedTapDeviceName, + Type: models.IfaceTypeTap, + MAC: reverseMACAddress, + Index: 0, + }, nil). + Times(1) + + _, err = step.Do(ctx) + + g.Expect(err).To(g.BeNil()) + g.Expect(status.MACAddress).To(g.Equal(reverseMACAddress)) +} + +func TestNewNetworkInterface_svcError(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + g.RegisterTestingT(t) + + vmid, _ := models.NewVMID(vmName, nsName) + iface, status := fullNetworkInterface() + svc := mock.NewMockNetworkService(mockCtrl) + ctx := context.Background() + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(false, errors.ErrParentIfaceRequired). + Times(2) + + step := network.NewNetworkInterface(vmid, iface, status, svc) + shouldDo, err := step.ShouldDo(ctx) + + g.Expect(err).ToNot(g.BeNil()) + g.Expect(shouldDo).To(g.BeFalse()) + + _, err = step.Do(ctx) + + g.Expect(err).To(g.MatchError(errors.ErrParentIfaceRequired)) +} + +func TestNewNetworkInterface_fillChangedStatus(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + g.RegisterTestingT(t) + + vmid, _ := models.NewVMID(vmName, nsName) + iface, status := fullNetworkInterface() + iface.Type = models.IfaceTypeMacvtap + svc := mock.NewMockNetworkService(mockCtrl) + ctx := context.Background() + + step := network.NewNetworkInterface(vmid, iface, status, svc) + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedMacvtapDeviceName)). + Return(true, nil). + Times(1) + + svc.EXPECT(). + IfaceDetails(gomock.Eq(ctx), gomock.Eq(expectedMacvtapDeviceName)). + Return(&ports.IfaceDetails{ + DeviceName: expectedMacvtapDeviceName, + Type: models.IfaceTypeMacvtap, + MAC: reverseMACAddress, + Index: 0, + }, nil). + Times(1) + + _, err := step.Do(ctx) + + g.Expect(err).To(g.BeNil()) + g.Expect(status.MACAddress).To(g.Equal(reverseMACAddress)) + g.Expect(status.HostDeviceName).To(g.Equal(expectedMacvtapDeviceName)) +} + +func TestNewNetworkInterface_createError(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + g.RegisterTestingT(t) + + vmid, _ := models.NewVMID(vmName, nsName) + status := &models.NetworkInterfaceStatus{} + iface := &models.NetworkInterface{GuestDeviceName: defaultEthDevice} + svc := mock.NewMockNetworkService(mockCtrl) + ctx := context.Background() + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Times(0) + + step := network.NewNetworkInterface(vmid, iface, status, svc) + shouldDo, err := step.ShouldDo(ctx) + + g.Expect(err).To(g.BeNil()) + g.Expect(shouldDo).To(g.BeTrue()) + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(false, nil). + Times(1) + + svc.EXPECT(). + IfaceCreate(gomock.Eq(ctx), gomock.Eq(ports.IfaceCreateInput{ + DeviceName: expectedTapDeviceName, + })). + Return(nil, errors.ErrParentIfaceRequired). + Times(1) + + _, err = step.Do(ctx) + + g.Expect(err).To(g.MatchError(errors.ErrParentIfaceRequired)) +} diff --git a/core/steps/network/interface_delete_test.go b/core/steps/network/interface_delete_test.go new file mode 100644 index 00000000..9fcba8fe --- /dev/null +++ b/core/steps/network/interface_delete_test.go @@ -0,0 +1,154 @@ +package network_test + +import ( + "context" + "testing" + + "github.com/golang/mock/gomock" + g "github.com/onsi/gomega" + "github.com/vishvananda/netlink" + "github.com/weaveworks/flintlock/core/errors" + "github.com/weaveworks/flintlock/core/models" + "github.com/weaveworks/flintlock/core/ports" + "github.com/weaveworks/flintlock/core/steps/network" + "github.com/weaveworks/flintlock/infrastructure/mock" +) + +func TestDeleteNetworkInterface_doesNotExist(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + g.RegisterTestingT(t) + + vmid, _ := models.NewVMID(vmName, nsName) + iface := &models.NetworkInterface{} + svc := mock.NewMockNetworkService(mockCtrl) + ctx := context.Background() + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(false, nil). + Times(1) + + step := network.DeleteNetworkInterface(vmid, iface, svc) + shouldDo, err := step.ShouldDo(ctx) + + g.Expect(err).To(g.BeNil()) + g.Expect(shouldDo).To(g.BeFalse()) + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(false, nil). + Times(1) + + _, err = step.Do(ctx) + + g.Expect(err).To(g.BeNil()) +} + +func TestDeleteNetworkInterface_exists(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + g.RegisterTestingT(t) + + vmid, _ := models.NewVMID(vmName, nsName) + iface := &models.NetworkInterface{} + svc := mock.NewMockNetworkService(mockCtrl) + ctx := context.Background() + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(true, nil). + Times(1) + + step := network.DeleteNetworkInterface(vmid, iface, svc) + shouldDo, err := step.ShouldDo(ctx) + + g.Expect(err).To(g.BeNil()) + g.Expect(shouldDo).To(g.BeTrue()) + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(true, nil). + Times(1) + + svc.EXPECT(). + IfaceDelete( + gomock.Eq(ctx), + gomock.Eq(ports.DeleteIfaceInput{DeviceName: expectedTapDeviceName}), + ). + Return(nil). + Times(1) + + _, err = step.Do(ctx) + + g.Expect(err).To(g.BeNil()) +} + +func TestDeleteNetworkInterface_exists_errorDeleting(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + g.RegisterTestingT(t) + + vmid, _ := models.NewVMID(vmName, nsName) + iface := &models.NetworkInterface{} + svc := mock.NewMockNetworkService(mockCtrl) + ctx := context.Background() + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(true, nil). + Times(1) + + step := network.DeleteNetworkInterface(vmid, iface, svc) + shouldDo, err := step.ShouldDo(ctx) + + g.Expect(err).To(g.BeNil()) + g.Expect(shouldDo).To(g.BeTrue()) + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(true, nil). + Times(1) + + svc.EXPECT(). + IfaceDelete( + gomock.Eq(ctx), + gomock.Eq(ports.DeleteIfaceInput{DeviceName: expectedTapDeviceName}), + ). + Return(netlink.LinkNotFoundError{}). + Times(1) + + _, err = step.Do(ctx) + + g.Expect(err).ToNot(g.BeNil()) +} + +func TestDeleteNetworkInterface_IfaceExistsError(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + g.RegisterTestingT(t) + + vmid, _ := models.NewVMID(vmName, nsName) + iface := &models.NetworkInterface{} + svc := mock.NewMockNetworkService(mockCtrl) + ctx := context.Background() + + svc.EXPECT(). + IfaceExists(gomock.Eq(ctx), gomock.Eq(expectedTapDeviceName)). + Return(false, errors.ErrParentIfaceRequired). + Times(2) + + step := network.DeleteNetworkInterface(vmid, iface, svc) + shouldDo, err := step.ShouldDo(ctx) + + g.Expect(err).ToNot(g.BeNil()) + g.Expect(shouldDo).To(g.BeFalse()) + + _, err = step.Do(ctx) + + g.Expect(err).To(g.MatchError(errors.ErrParentIfaceRequired)) +} diff --git a/core/steps/network/testconst_test.go b/core/steps/network/testconst_test.go new file mode 100644 index 00000000..03490f35 --- /dev/null +++ b/core/steps/network/testconst_test.go @@ -0,0 +1,28 @@ +package network_test + +import "github.com/weaveworks/flintlock/core/models" + +const ( + vmName = "testvm" + nsName = "testns" + defaultMACAddress = "AA:BB:CC:DD:EE:FF" + reverseMACAddress = "FF:EE:DD:CC:BB:AA" + expectedTapDeviceName = "testns_testvm_tap" + expectedMacvtapDeviceName = "testns_testvm_vtap" + defaultEthDevice = "eth0" +) + +func fullNetworkInterface() (*models.NetworkInterface, *models.NetworkInterfaceStatus) { + iface := &models.NetworkInterface{ + GuestDeviceName: defaultEthDevice, + AllowMetadataRequests: true, + GuestMAC: defaultMACAddress, + } + status := &models.NetworkInterfaceStatus{ + HostDeviceName: expectedTapDeviceName, + Index: 0, + MACAddress: defaultMACAddress, + } + + return iface, status +}