...

Source file src/edge-infra.dev/pkg/sds/k8s/controllers/terminalctl/terminalctl_test.go

Documentation: edge-infra.dev/pkg/sds/k8s/controllers/terminalctl

     1  package terminalctl
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/fluxcd/pkg/ssa"
    11  	"github.com/google/uuid"
    12  	"gotest.tools/v3/assert"
    13  	"gotest.tools/v3/assert/cmp"
    14  	corev1 "k8s.io/api/core/v1"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	ctrl "sigs.k8s.io/controller-runtime"
    17  	"sigs.k8s.io/controller-runtime/pkg/client"
    18  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    19  
    20  	"edge-infra.dev/pkg/k8s/unstructured"
    21  	"edge-infra.dev/pkg/lib/fog"
    22  	v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1"
    23  	"edge-infra.dev/pkg/sds/k8s/controllers/terminalctl/pkg/plugins"
    24  	"edge-infra.dev/test/f2"
    25  	"edge-infra.dev/test/f2/x/ktest"
    26  )
    27  
    28  var plugin *fakePlugin
    29  
    30  var f f2.Framework
    31  
    32  func TestMain(m *testing.M) {
    33  	ctrl.SetLogger(fog.New())
    34  
    35  	f = f2.New(context.Background(),
    36  		f2.WithExtensions(
    37  			ktest.New(
    38  				ktest.WithCtrlManager(createMgr),
    39  			),
    40  		),
    41  	).
    42  		Setup(func(ctx f2.Context) (f2.Context, error) {
    43  			k, err := ktest.FromContext(ctx)
    44  			if err != nil {
    45  				return ctx, err
    46  			}
    47  
    48  			// Override timeouts if we aren't using a live cluster
    49  			if !*k.Env.UseExistingCluster {
    50  				k.Timeout = 5 * time.Second
    51  				k.Tick = 10 * time.Millisecond
    52  			}
    53  
    54  			plugin = &fakePlugin{}
    55  			plugins.Register(plugin)
    56  
    57  			if err := registerController(k.Manager, &Config{}); err != nil {
    58  				return ctx, err
    59  			}
    60  
    61  			return ctx, nil
    62  		})
    63  
    64  	os.Exit(f.Run(m))
    65  }
    66  
    67  type fakePlugin struct {
    68  	plugins.TerminalRegistrationPlugin
    69  
    70  	lock        sync.Mutex
    71  	invDisabled bool
    72  }
    73  
    74  func (fp *fakePlugin) Finalize(_ context.Context, _ client.Client, _ *v1ien.IENode) error {
    75  	return nil
    76  }
    77  
    78  func (fp *fakePlugin) Reconcile(ctx context.Context, mgr *ssa.ResourceManager, terminal *v1ien.IENode) ([]*unstructured.Unstructured, error) {
    79  	if terminal.Name == "a-terminal-inventory-creation" {
    80  		return fp.inventoryCreationReconcile(ctx, mgr, terminal)
    81  	}
    82  
    83  	return nil, nil
    84  }
    85  
    86  func (fp *fakePlugin) inventoryCreationReconcile(_ context.Context, _ *ssa.ResourceManager, _ *v1ien.IENode) ([]*unstructured.Unstructured, error) {
    87  	fp.lock.Lock()
    88  	defer fp.lock.Unlock()
    89  	if fp.invDisabled {
    90  		return nil, nil
    91  	}
    92  
    93  	return unstructured.ToUnstructuredArray(createInventoryCM())
    94  }
    95  
    96  func TestTerminalctl_Finalizers(t *testing.T) {
    97  	var (
    98  		terminal *v1ien.IENode
    99  	)
   100  
   101  	feature := f2.NewFeature("Finalizers").
   102  		Setup("Create terminal Resource", func(ctx f2.Context, t *testing.T) f2.Context {
   103  			k := ktest.FromContextT(ctx, t)
   104  
   105  			terminal = createTerminal("a-terminal-finalizers")
   106  			assert.NilError(t, k.Client.Create(ctx, terminal))
   107  
   108  			return ctx
   109  		}).
   110  		Test("Finalizer added", func(ctx f2.Context, t *testing.T) f2.Context {
   111  			k := ktest.FromContextT(ctx, t)
   112  
   113  			k.WaitOn(t, k.Check(terminal, terminalFinalizer))
   114  
   115  			return ctx
   116  		}).
   117  		Test("Terminal deletion", func(ctx f2.Context, t *testing.T) f2.Context {
   118  			k := ktest.FromContextT(ctx, t)
   119  
   120  			assert.NilError(t, k.Client.Delete(ctx, terminal))
   121  			te := v1ien.IENode{ObjectMeta: metav1.ObjectMeta{Name: terminal.Name}}
   122  			k.WaitOn(t, k.ObjDeleted(&te))
   123  
   124  			return ctx
   125  		}).
   126  		Feature()
   127  	f.Test(t, feature)
   128  }
   129  
   130  func TestTerminalctlInventory(t *testing.T) {
   131  	var (
   132  		t1 *v1ien.IENode
   133  	)
   134  
   135  	createInvFeature := f2.NewFeature("Terminalctl Inventory Creation").
   136  		Setup("Create terminal Resource", func(ctx f2.Context, t *testing.T) f2.Context {
   137  			k := ktest.FromContextT(ctx, t)
   138  
   139  			t1 = createTerminal("a-terminal-inventory-creation")
   140  			assert.NilError(t, k.Client.Create(ctx, t1))
   141  
   142  			return ctx
   143  		}).
   144  		Test("Plugin Executed", func(ctx f2.Context, t *testing.T) f2.Context {
   145  			k := ktest.FromContextT(ctx, t)
   146  
   147  			k.WaitOn(t, k.ObjExists(createInventoryCM()))
   148  
   149  			return ctx
   150  		}).
   151  		Test("Inventory added to terminal", func(ctx f2.Context, t *testing.T) f2.Context {
   152  			k := ktest.FromContextT(ctx, t)
   153  
   154  			te := &v1ien.IENode{ObjectMeta: metav1.ObjectMeta{Name: t1.Name}}
   155  			k.WaitOn(t, k.Check(te, func(o client.Object) cmp.Result {
   156  				te := o.(*v1ien.IENode)
   157  				if te.Status.Inventory == nil {
   158  					return cmp.ResultFailure("nil inventory")
   159  				}
   160  
   161  				entriesLength := len(te.Status.Inventory.Entries) == 1
   162  				// "<namespace>_<name>_<group>_<kind>"
   163  				entryID := te.Status.Inventory.Entries[0].ID == "default_inventory-cm__ConfigMap"
   164  
   165  				if entriesLength && entryID {
   166  					return cmp.ResultSuccess
   167  				}
   168  				return cmp.ResultFailure("inventory didnt match expected")
   169  			}))
   170  
   171  			return ctx
   172  		}).
   173  		Test("Old inventory removed", func(ctx f2.Context, t *testing.T) f2.Context {
   174  			k := ktest.FromContextT(ctx, t)
   175  
   176  			// Stop returning inventory to check resources are cleaned up
   177  			toggleInvPlugin(true)
   178  
   179  			// Update terminal to force reconcilliation
   180  			updateTerminalDefinition(ctx, t, k.Client, "a-terminal-inventory-creation")
   181  
   182  			k.WaitOn(t, k.Check(t1, func(o client.Object) cmp.Result {
   183  				te := o.(*v1ien.IENode)
   184  				if len(te.Status.Inventory.Entries) == 0 {
   185  					return cmp.ResultSuccess
   186  				}
   187  				return cmp.ResultFailure("inventory wasn't cleaned up")
   188  			}))
   189  
   190  			k.WaitOn(t, k.ObjDeleted(createInventoryCM()))
   191  
   192  			return ctx
   193  		}).
   194  		Test("Inventory Recreated", func(ctx f2.Context, t *testing.T) f2.Context {
   195  			k := ktest.FromContextT(ctx, t)
   196  
   197  			toggleInvPlugin(false)
   198  
   199  			// Update terminal to force reconcilliation
   200  			updateTerminalDefinition(ctx, t, k.Client, "a-terminal-inventory-creation")
   201  
   202  			k.WaitOn(t, k.Check(t1, func(o client.Object) cmp.Result {
   203  				te := o.(*v1ien.IENode)
   204  				if te.Status.Inventory == nil {
   205  					return cmp.ResultFailure("nil inventory")
   206  				}
   207  
   208  				if len(te.Status.Inventory.Entries) == 1 {
   209  					// "<namespace>_<name>_<group>_<kind>"
   210  					entryID := te.Status.Inventory.Entries[0].ID == "default_inventory-cm__ConfigMap"
   211  					if entryID {
   212  						return cmp.ResultSuccess
   213  					}
   214  				}
   215  				return cmp.ResultFailure("inventory didnt match expected")
   216  			}))
   217  
   218  			k.WaitOn(t, k.ObjExists(createInventoryCM()))
   219  
   220  			return ctx
   221  		}).
   222  		Test("Inventory removed on deletion", func(ctx f2.Context, t *testing.T) f2.Context {
   223  			k := ktest.FromContextT(ctx, t)
   224  
   225  			assert.NilError(t, k.Client.Delete(ctx, t1))
   226  			k.WaitOn(t, k.ObjsDeleted([]client.Object{t1, createInventoryCM()}))
   227  
   228  			return ctx
   229  		}).
   230  		Feature()
   231  
   232  	f.Test(t, createInvFeature)
   233  }
   234  
   235  func createTerminal(name string) *v1ien.IENode {
   236  	return &v1ien.IENode{
   237  		ObjectMeta: metav1.ObjectMeta{
   238  			Name: name,
   239  		},
   240  		TypeMeta: metav1.TypeMeta{
   241  			Kind:       v1ien.IENodeGVK.Kind,
   242  			APIVersion: v1ien.IENodeGVK.GroupVersion().String(),
   243  		},
   244  		Spec: v1ien.IENodeSpec{
   245  			ClusterEdgeID: "clusterEdgeID-1",
   246  			Role:          "worker",
   247  			Network: []v1ien.Network{
   248  				{
   249  					DHCP4:      true,
   250  					DHCP6:      false,
   251  					MacAddress: "ee:c5:39:b6:47:c3",
   252  				},
   253  			},
   254  			NetworkServices: v1ien.NetworkServices{
   255  				DNSServers: []string{"8.8.8.8", "8.8.4.4"},
   256  				KubeVip:    "10.10.10.10",
   257  			},
   258  			PrimaryInterface: &v1ien.PrimaryInterface{
   259  				InterfaceID:  "b212995a-1af4-4a2a-8479-6b095d788741",
   260  				MacAddresses: []string{"ee:c5:39:b6:47:c3"},
   261  			},
   262  		},
   263  	}
   264  }
   265  
   266  func createInventoryCM() *corev1.ConfigMap {
   267  	return &corev1.ConfigMap{
   268  		TypeMeta: metav1.TypeMeta{
   269  			Kind:       "ConfigMap",
   270  			APIVersion: "v1",
   271  		},
   272  		ObjectMeta: metav1.ObjectMeta{
   273  			Name:      "inventory-cm",
   274  			Namespace: "default",
   275  		},
   276  		Data: map[string]string{},
   277  	}
   278  }
   279  
   280  func terminalFinalizer(o client.Object) cmp.Result {
   281  	if controllerutil.ContainsFinalizer(o, v1ien.Finalizer) {
   282  		return cmp.ResultSuccess
   283  	}
   284  	return cmp.ResultFailure("finalizer not present")
   285  }
   286  
   287  func toggleInvPlugin(disabled bool) {
   288  	plugin.lock.Lock()
   289  	plugin.invDisabled = disabled
   290  	plugin.lock.Unlock()
   291  }
   292  
   293  // helper function to update a terminal to force reconciliation
   294  func updateTerminalDefinition(ctx context.Context, t *testing.T, c client.Client, name string) {
   295  	t.Helper()
   296  	te := createTerminal(name)
   297  	assert.NilError(t, c.Get(ctx, client.ObjectKeyFromObject(te), te))
   298  	te.Spec.ClusterEdgeID = uuid.NewString()
   299  	assert.NilError(t, c.Update(ctx, te))
   300  }
   301  

View as plain text