...

Source file src/edge-infra.dev/pkg/sds/interlock/topic/host/host_test.go

Documentation: edge-infra.dev/pkg/sds/interlock/topic/host

     1  package host
     2  
     3  import (
     4  	"context"
     5  	"net/url"
     6  	"testing"
     7  
     8  	"github.com/spf13/afero"
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  	v1 "k8s.io/api/core/v1"
    12  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    13  	kruntime "k8s.io/apimachinery/pkg/runtime"
    14  	"k8s.io/apimachinery/pkg/types"
    15  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    16  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    17  	"sigs.k8s.io/controller-runtime/pkg/cache/informertest"
    18  	"sigs.k8s.io/controller-runtime/pkg/client"
    19  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    20  	"sigs.k8s.io/controller-runtime/pkg/controller/controllertest"
    21  
    22  	"edge-infra.dev/pkg/sds/interlock/internal/config"
    23  	"edge-infra.dev/pkg/sds/interlock/internal/constants"
    24  	"edge-infra.dev/pkg/sds/interlock/topic"
    25  	"edge-infra.dev/pkg/sds/interlock/websocket"
    26  	"edge-infra.dev/pkg/sds/lib/k8s/retryclient"
    27  )
    28  
    29  // CreateScheme creates a new scheme, adds all types of the automatically generated
    30  // clientset, and returns it.
    31  func createScheme() *kruntime.Scheme {
    32  	scheme := kruntime.NewScheme()
    33  	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
    34  	return scheme
    35  }
    36  
    37  // GetFakeKubeClient returns a fake client initialised with a slice of
    38  // Kubernets objects. To be used for testing purposes.
    39  func GetFakeKubeClient(initObjs ...client.Object) client.Client {
    40  	return fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(initObjs...).Build()
    41  }
    42  
    43  // getTestHostNode returns a Node object initialised with name and uid fields
    44  // To be used for testing purposes.
    45  func getTestHostNode(name, uid string) *v1.Node {
    46  	return &v1.Node{
    47  		ObjectMeta: metav1.ObjectMeta{
    48  			Name: name,
    49  			UID:  types.UID(uid),
    50  		},
    51  	}
    52  }
    53  
    54  func setLOMFlag(t *testing.T, fs *afero.Fs) {
    55  	err := (*fs).MkdirAll(constants.ZynstraConfigDir, 0755)
    56  	require.NoError(t, err)
    57  
    58  	path, _ := url.JoinPath(constants.ZynstraConfigDir, constants.LANOutageModeFlagFileName)
    59  	err = afero.WriteFile(*fs, path, []byte{}, 0644)
    60  	require.NoError(t, err)
    61  }
    62  
    63  func TestIsHostNode(t *testing.T) {
    64  	tests := map[string]struct {
    65  		input       interface{}
    66  		nodeNameEnv string
    67  		want        bool
    68  	}{
    69  		"Invalid": {
    70  			input:       struct{}{},
    71  			nodeNameEnv: "",
    72  			want:        false,
    73  		},
    74  		"Incorrect_HostName": {
    75  			input:       getTestHostNode("node1", ""),
    76  			nodeNameEnv: "node2",
    77  			want:        false,
    78  		},
    79  		"Correct_HostName": {
    80  			input:       getTestHostNode("node1", ""),
    81  			nodeNameEnv: "node1",
    82  			want:        true,
    83  		},
    84  	}
    85  
    86  	for name, tc := range tests {
    87  		t.Run(name, func(t *testing.T) {
    88  			t.Setenv("NODE_NAME", tc.nodeNameEnv)
    89  			got := IsHostNode(tc.input)
    90  			assert.Equal(t, tc.want, got)
    91  		})
    92  	}
    93  }
    94  
    95  func TestDiscoverLANOutageMode(t *testing.T) {
    96  	trueMemFs := afero.NewMemMapFs()
    97  	setLOMFlag(t, &trueMemFs)
    98  
    99  	falseMemFs := afero.NewMemMapFs()
   100  	err := falseMemFs.MkdirAll(constants.ZynstraConfigDir, 0755)
   101  	require.NoError(t, err)
   102  
   103  	emptyMemFs := afero.NewMemMapFs()
   104  
   105  	testCases := map[string]struct {
   106  		fs   afero.Fs
   107  		want bool
   108  	}{
   109  		"FileExists": {
   110  			trueMemFs,
   111  			true,
   112  		},
   113  		"FolderExistsFileDoesNotExist": {
   114  			falseMemFs,
   115  			false,
   116  		},
   117  		"FolderDoesNotExist": {
   118  			emptyMemFs,
   119  			false,
   120  		},
   121  	}
   122  
   123  	for name, tc := range testCases {
   124  		t.Run(name, func(t *testing.T) {
   125  			got, err := discoverLANOutageMode(tc.fs)
   126  			require.NoError(t, err)
   127  			assert.Equal(t, tc.want, got)
   128  		})
   129  	}
   130  }
   131  
   132  func TestNewState(t *testing.T) {
   133  	t.Setenv("NODE_NAME", "this-node")
   134  
   135  	node := getTestHostNode("this-node", "uid")
   136  	cli := GetFakeKubeClient(node)
   137  	fs := afero.NewMemMapFs()
   138  	setLOMFlag(t, &fs)
   139  
   140  	cfg := &config.Config{
   141  		Fs:              fs,
   142  		KubeRetryClient: retryclient.New(cli, cli, retryclient.Config{}),
   143  		Cache:           &informertest.FakeInformers{},
   144  	}
   145  
   146  	state, err := newState(context.Background(), cfg)
   147  	require.NoError(t, err)
   148  
   149  	assert.Equal(t, "this-node", state.Hostname)
   150  	assert.Equal(t, "uid", state.NodeUID)
   151  	assert.True(t, state.Network.LANOutageMode)
   152  }
   153  
   154  type mockCache struct {
   155  	*informertest.FakeInformers
   156  }
   157  
   158  func newTestHostWithFakeInformer(t *testing.T, initState *State) (*Host, *controllertest.FakeInformer) {
   159  	testCfg := config.Config{
   160  		Cache: mockCache{&informertest.FakeInformers{}},
   161  	}
   162  	testHost := Host{
   163  		topic: topic.NewTopic(
   164  			TopicName,
   165  			initState,
   166  			nil,
   167  			websocket.NewManager(),
   168  		),
   169  	}
   170  
   171  	err := testHost.SetupAPIInformers(context.Background(), &testCfg)
   172  	require.NoError(t, err)
   173  
   174  	informer, err := testCfg.Cache.GetInformer(context.Background(), &v1.Node{})
   175  	require.NoError(t, err)
   176  
   177  	fakeInformer, ok := informer.(*controllertest.FakeInformer)
   178  	require.True(t, ok)
   179  
   180  	return &testHost, fakeInformer
   181  }
   182  
   183  func TestOnAdd(t *testing.T) {
   184  	t.Setenv("NODE_NAME", "new-node")
   185  	want := "uid1"
   186  	testHost, fakeInformer := newTestHostWithFakeInformer(t, &State{})
   187  
   188  	newNode := getTestHostNode("new-node", want)
   189  	fakeInformer.Add(newNode)
   190  
   191  	hostState := testHost.topic.State()
   192  	state, ok := hostState.(*State)
   193  	require.True(t, ok)
   194  
   195  	assert.Equal(t, want, state.NodeUID)
   196  }
   197  
   198  func TestOnUpdate(t *testing.T) {
   199  	initState := &State{
   200  		NodeUID: "uid1",
   201  	}
   202  	testHost, fakeInformer := newTestHostWithFakeInformer(t, initState)
   203  
   204  	oldNode := getTestHostNode("new-node", "uid1")
   205  	newNode := getTestHostNode("new-node", "uid2")
   206  	fakeInformer.Update(oldNode, newNode)
   207  
   208  	hostState := testHost.topic.State()
   209  	state, ok := hostState.(*State)
   210  	require.True(t, ok)
   211  
   212  	assert.Equal(t, "uid1", state.NodeUID)
   213  }
   214  
   215  func TestOnDelete(t *testing.T) {
   216  	testHost, fakeInformer := newTestHostWithFakeInformer(t, &State{
   217  		NodeUID: "uid1",
   218  	})
   219  
   220  	node := getTestHostNode("new-node", "uid1")
   221  	fakeInformer.Delete(node)
   222  
   223  	hostState := testHost.topic.State()
   224  	state, ok := hostState.(*State)
   225  	require.True(t, ok)
   226  
   227  	assert.Equal(t, "uid1", state.NodeUID)
   228  }
   229  
   230  type testStruct struct {
   231  	state *State
   232  }
   233  
   234  func (t *testStruct) updateState(fn topic.UpdateFunc) error {
   235  	return fn(t.state)
   236  }
   237  
   238  func TestUpdateNodeUID(t *testing.T) {
   239  	tests := map[string]struct {
   240  		previousNodeUID string
   241  		want            string
   242  	}{
   243  		"NotPreviouslySet": {
   244  			previousNodeUID: "",
   245  			want:            "uid1",
   246  		},
   247  		"PreviouslySet": {
   248  			previousNodeUID: "uid",
   249  			want:            "uid1",
   250  		},
   251  	}
   252  
   253  	for name, tc := range tests {
   254  		t.Run(name, func(t *testing.T) {
   255  			ts := testStruct{&State{NodeUID: tc.previousNodeUID}}
   256  			node := getTestHostNode("test", tc.want)
   257  
   258  			updateNodeUID(ts.updateState, node)
   259  
   260  			assert.Equal(t, tc.want, ts.state.NodeUID)
   261  		})
   262  	}
   263  }
   264  

View as plain text