github.com/k8snetworkplumbingwg/sriov-network-operator@v1.2.1-0.20240408194816-2d2e5a45d453/api/v1/helper_test.go (about)

     1  package v1_test
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/json"
     7  	"flag"
     8  	"os"
     9  	"path/filepath"
    10  	"testing"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	intstrutil "k8s.io/apimachinery/pkg/util/intstr"
    15  
    16  	v1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1"
    17  	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts"
    18  )
    19  
    20  var update = flag.Bool("updategolden", false, "update .golden files")
    21  
    22  func init() {
    23  	// when running go tests path is local to the file, overriding it.
    24  	v1.ManifestsPath = "../../bindata/manifests/cni-config"
    25  }
    26  
    27  func newNodeState() *v1.SriovNetworkNodeState {
    28  	return &v1.SriovNetworkNodeState{
    29  		Spec: v1.SriovNetworkNodeStateSpec{},
    30  		Status: v1.SriovNetworkNodeStateStatus{
    31  			Interfaces: []v1.InterfaceExt{
    32  				{
    33  					VFs: []v1.VirtualFunction{
    34  						{},
    35  					},
    36  					DeviceID:   "158b",
    37  					Driver:     "i40e",
    38  					Mtu:        1500,
    39  					Name:       "ens803f0",
    40  					PciAddress: "0000:86:00.0",
    41  					Vendor:     "8086",
    42  					NumVfs:     4,
    43  					TotalVfs:   64,
    44  				},
    45  				{
    46  					VFs: []v1.VirtualFunction{
    47  						{},
    48  					},
    49  					DeviceID:   "158b",
    50  					Driver:     "i40e",
    51  					Mtu:        1500,
    52  					Name:       "ens803f1",
    53  					PciAddress: "0000:86:00.1",
    54  					Vendor:     "8086",
    55  					NumVfs:     4,
    56  					TotalVfs:   64,
    57  				},
    58  				{
    59  					VFs: []v1.VirtualFunction{
    60  						{},
    61  					},
    62  					DeviceID:   "1015",
    63  					Driver:     "i40e",
    64  					Mtu:        1500,
    65  					Name:       "ens803f2",
    66  					PciAddress: "0000:86:00.2",
    67  					Vendor:     "8086",
    68  					NumVfs:     4,
    69  					TotalVfs:   64,
    70  				},
    71  			},
    72  		},
    73  	}
    74  }
    75  
    76  func newNodePolicy() *v1.SriovNetworkNodePolicy {
    77  	return &v1.SriovNetworkNodePolicy{
    78  		ObjectMeta: metav1.ObjectMeta{
    79  			Name: "p1",
    80  		},
    81  		Spec: v1.SriovNetworkNodePolicySpec{
    82  			DeviceType: consts.DeviceTypeNetDevice,
    83  			NicSelector: v1.SriovNetworkNicSelector{
    84  				PfNames:     []string{"ens803f1"},
    85  				RootDevices: []string{"0000:86:00.1"},
    86  				Vendor:      "8086",
    87  			},
    88  			NodeSelector: map[string]string{
    89  				"feature.node.kubernetes.io/network-sriov.capable": "true",
    90  			},
    91  			NumVfs:       2,
    92  			Priority:     99,
    93  			ResourceName: "p1res",
    94  		},
    95  	}
    96  }
    97  
    98  func newVirtioVdpaNodePolicy() *v1.SriovNetworkNodePolicy {
    99  	return &v1.SriovNetworkNodePolicy{
   100  		ObjectMeta: metav1.ObjectMeta{
   101  			Name: "p1",
   102  		},
   103  		Spec: v1.SriovNetworkNodePolicySpec{
   104  			DeviceType: consts.DeviceTypeNetDevice,
   105  			VdpaType:   consts.VdpaTypeVirtio,
   106  			NicSelector: v1.SriovNetworkNicSelector{
   107  				PfNames:     []string{"ens803f1#2-3"},
   108  				RootDevices: []string{"0000:86:00.1"},
   109  				Vendor:      "8086",
   110  			},
   111  			NodeSelector: map[string]string{
   112  				"feature.node.kubernetes.io/network-sriov.capable": "true",
   113  			},
   114  			NumVfs:       4,
   115  			Priority:     99,
   116  			ResourceName: "virtiovdpa",
   117  		},
   118  	}
   119  }
   120  
   121  func newVhostVdpaNodePolicy() *v1.SriovNetworkNodePolicy {
   122  	return &v1.SriovNetworkNodePolicy{
   123  		ObjectMeta: metav1.ObjectMeta{
   124  			Name: "p1",
   125  		},
   126  		Spec: v1.SriovNetworkNodePolicySpec{
   127  			DeviceType: consts.DeviceTypeNetDevice,
   128  			VdpaType:   consts.VdpaTypeVhost,
   129  			NicSelector: v1.SriovNetworkNicSelector{
   130  				PfNames:     []string{"ens803f1"},
   131  				RootDevices: []string{"0000:86:00.1"},
   132  				Vendor:      "8086",
   133  			},
   134  			NodeSelector: map[string]string{
   135  				"feature.node.kubernetes.io/network-sriov.capable": "true",
   136  			},
   137  			NumVfs:       2,
   138  			Priority:     99,
   139  			ResourceName: "vhostvdpa",
   140  		},
   141  	}
   142  }
   143  
   144  func TestRendering(t *testing.T) {
   145  	testtable := []struct {
   146  		tname   string
   147  		network v1.SriovNetwork
   148  	}{
   149  		{
   150  			tname: "simple",
   151  			network: v1.SriovNetwork{
   152  				Spec: v1.SriovNetworkSpec{
   153  					NetworkNamespace: "testnamespace",
   154  					ResourceName:     "testresource",
   155  				},
   156  			},
   157  		},
   158  		{
   159  			tname: "chained",
   160  			network: v1.SriovNetwork{
   161  				Spec: v1.SriovNetworkSpec{
   162  					NetworkNamespace: "testnamespace",
   163  					ResourceName:     "testresource",
   164  					MetaPluginsConfig: `
   165  					{
   166  						"type": "vrf",
   167  						"vrfname": "blue"
   168  					}
   169  					`,
   170  				},
   171  			},
   172  		},
   173  	}
   174  	for _, tc := range testtable {
   175  		t.Run(tc.tname, func(t *testing.T) {
   176  			var b bytes.Buffer
   177  			w := bufio.NewWriter(&b)
   178  			rendered, err := tc.network.RenderNetAttDef()
   179  			if err != nil {
   180  				t.Fatal("failed rendering network attachment definition", err)
   181  			}
   182  			encoder := json.NewEncoder(w)
   183  			encoder.SetIndent("", "  ")
   184  			encoder.Encode(rendered)
   185  			w.Flush()
   186  			gp := filepath.Join("testdata", filepath.FromSlash(t.Name())+".golden")
   187  			if *update {
   188  				t.Log("update golden file")
   189  				if err := os.WriteFile(gp, b.Bytes(), 0644); err != nil {
   190  					t.Fatalf("failed to update golden file: %s", err)
   191  				}
   192  			}
   193  			g, err := os.ReadFile(gp)
   194  			if err != nil {
   195  				t.Fatalf("failed reading .golden: %s", err)
   196  			}
   197  			t.Log(b.String())
   198  			if !bytes.Equal(b.Bytes(), g) {
   199  				t.Errorf("bytes do not match .golden file")
   200  			}
   201  		})
   202  	}
   203  }
   204  
   205  func TestIBRendering(t *testing.T) {
   206  	testtable := []struct {
   207  		tname   string
   208  		network v1.SriovIBNetwork
   209  	}{
   210  		{
   211  			tname: "simpleib",
   212  			network: v1.SriovIBNetwork{
   213  				Spec: v1.SriovIBNetworkSpec{
   214  					NetworkNamespace: "testnamespace",
   215  					ResourceName:     "testresource",
   216  					Capabilities:     "foo",
   217  				},
   218  			},
   219  		},
   220  	}
   221  	for _, tc := range testtable {
   222  		t.Run(tc.tname, func(t *testing.T) {
   223  			var b bytes.Buffer
   224  			w := bufio.NewWriter(&b)
   225  			rendered, err := tc.network.RenderNetAttDef()
   226  			if err != nil {
   227  				t.Fatal("failed rendering network attachment definition", err)
   228  			}
   229  			encoder := json.NewEncoder(w)
   230  			encoder.SetIndent("", "  ")
   231  			encoder.Encode(rendered)
   232  			w.Flush()
   233  			gp := filepath.Join("testdata", filepath.FromSlash(t.Name())+".golden")
   234  			if *update {
   235  				t.Log("update golden file")
   236  				if err := os.WriteFile(gp, b.Bytes(), 0644); err != nil {
   237  					t.Fatalf("failed to update golden file: %s", err)
   238  				}
   239  			}
   240  			g, err := os.ReadFile(gp)
   241  			if err != nil {
   242  				t.Fatalf("failed reading .golden: %s", err)
   243  			}
   244  			t.Log(b.String())
   245  			if !bytes.Equal(b.Bytes(), g) {
   246  				t.Errorf("bytes do not match .golden file")
   247  			}
   248  		})
   249  	}
   250  }
   251  
   252  func TestSriovNetworkNodePolicyApply(t *testing.T) {
   253  	testtable := []struct {
   254  		tname              string
   255  		currentState       *v1.SriovNetworkNodeState
   256  		policy             *v1.SriovNetworkNodePolicy
   257  		expectedInterfaces v1.Interfaces
   258  		equalP             bool
   259  		expectedErr        bool
   260  	}{
   261  		{
   262  			tname:        "starting config",
   263  			currentState: newNodeState(),
   264  			policy:       newNodePolicy(),
   265  			equalP:       false,
   266  			expectedInterfaces: []v1.Interface{
   267  				{
   268  					Name:       "ens803f1",
   269  					NumVfs:     2,
   270  					PciAddress: "0000:86:00.1",
   271  					VfGroups: []v1.VfGroup{
   272  						{
   273  							DeviceType:   consts.DeviceTypeNetDevice,
   274  							ResourceName: "p1res",
   275  							VfRange:      "0-1",
   276  							PolicyName:   "p1",
   277  						},
   278  					},
   279  				},
   280  			},
   281  		},
   282  		{
   283  			tname: "one policy present different pf",
   284  			currentState: func() *v1.SriovNetworkNodeState {
   285  				st := newNodeState()
   286  				st.Spec.Interfaces = []v1.Interface{
   287  					{
   288  						Name:       "ens803f0",
   289  						NumVfs:     2,
   290  						PciAddress: "0000:86:00.0",
   291  						VfGroups: []v1.VfGroup{
   292  							{
   293  								DeviceType:   consts.DeviceTypeNetDevice,
   294  								ResourceName: "prevres",
   295  								VfRange:      "0-1",
   296  								PolicyName:   "p2",
   297  							},
   298  						},
   299  					},
   300  				}
   301  				return st
   302  			}(),
   303  			policy: newNodePolicy(),
   304  			equalP: false,
   305  			expectedInterfaces: []v1.Interface{
   306  				{
   307  					Name:       "ens803f0",
   308  					NumVfs:     2,
   309  					PciAddress: "0000:86:00.0",
   310  					VfGroups: []v1.VfGroup{
   311  						{
   312  							DeviceType:   consts.DeviceTypeNetDevice,
   313  							ResourceName: "prevres",
   314  							VfRange:      "0-1",
   315  							PolicyName:   "p2",
   316  						},
   317  					},
   318  				},
   319  				{
   320  					Name:       "ens803f1",
   321  					NumVfs:     2,
   322  					PciAddress: "0000:86:00.1",
   323  					VfGroups: []v1.VfGroup{
   324  						{
   325  							DeviceType:   consts.DeviceTypeNetDevice,
   326  							ResourceName: "p1res",
   327  							VfRange:      "0-1",
   328  							PolicyName:   "p1",
   329  						},
   330  					},
   331  				},
   332  			},
   333  		},
   334  		{
   335  			// policy overwrites (applied last has higher priority) what is inside the
   336  			// SriovNetworkNodeState
   337  			tname: "one policy present same pf different priority",
   338  			currentState: func() *v1.SriovNetworkNodeState {
   339  				st := newNodeState()
   340  				st.Spec.Interfaces = []v1.Interface{
   341  					{
   342  						Name:       "ens803f1",
   343  						NumVfs:     3,
   344  						PciAddress: "0000:86:00.1",
   345  						VfGroups: []v1.VfGroup{
   346  							{
   347  								DeviceType:   consts.DeviceTypeVfioPci,
   348  								ResourceName: "vfiores",
   349  								VfRange:      "0-1",
   350  								PolicyName:   "p2",
   351  							},
   352  						},
   353  					},
   354  				}
   355  				return st
   356  			}(),
   357  			policy: newNodePolicy(),
   358  			equalP: false,
   359  			expectedInterfaces: []v1.Interface{
   360  				{
   361  					Name:       "ens803f1",
   362  					NumVfs:     2,
   363  					PciAddress: "0000:86:00.1",
   364  					VfGroups: []v1.VfGroup{
   365  						{
   366  							DeviceType:   consts.DeviceTypeNetDevice,
   367  							ResourceName: "p1res",
   368  							VfRange:      "0-1",
   369  							PolicyName:   "p1",
   370  						},
   371  					},
   372  				},
   373  			},
   374  		},
   375  		{
   376  			// policy with same priority, but there is VfRange overlap so
   377  			// only the last applied stays, NumVfs and MTU is still merged
   378  			tname: "one policy present same pf same priority",
   379  			currentState: func() *v1.SriovNetworkNodeState {
   380  				st := newNodeState()
   381  				st.Spec.Interfaces = []v1.Interface{
   382  					{
   383  						Name:       "ens803f1",
   384  						NumVfs:     3,
   385  						PciAddress: "0000:86:00.1",
   386  						Mtu:        2000,
   387  						VfGroups: []v1.VfGroup{
   388  							{
   389  								DeviceType:   consts.DeviceTypeVfioPci,
   390  								ResourceName: "vfiores",
   391  								VfRange:      "0-1",
   392  								PolicyName:   "p2",
   393  							},
   394  						},
   395  					},
   396  				}
   397  				return st
   398  			}(),
   399  			policy: newNodePolicy(),
   400  			equalP: true,
   401  			expectedInterfaces: []v1.Interface{
   402  				{
   403  					Mtu:        2000,
   404  					Name:       "ens803f1",
   405  					NumVfs:     3,
   406  					PciAddress: "0000:86:00.1",
   407  					VfGroups: []v1.VfGroup{
   408  						{
   409  							DeviceType:   consts.DeviceTypeNetDevice,
   410  							ResourceName: "p1res",
   411  							VfRange:      "0-1",
   412  							PolicyName:   "p1",
   413  						},
   414  					},
   415  				},
   416  			},
   417  		},
   418  		{
   419  			// policy with same priority, VfRange's do not overlap so all is merged
   420  			tname: "one policy present same pf same priority partitioning",
   421  			currentState: func() *v1.SriovNetworkNodeState {
   422  				st := newNodeState()
   423  				st.Spec.Interfaces = []v1.Interface{
   424  					{
   425  						Name:       "ens803f1",
   426  						NumVfs:     5,
   427  						PciAddress: "0000:86:00.1",
   428  						VfGroups: []v1.VfGroup{
   429  							{
   430  								DeviceType:   consts.DeviceTypeVfioPci,
   431  								ResourceName: "vfiores",
   432  								VfRange:      "2-4",
   433  								PolicyName:   "p2",
   434  							},
   435  						},
   436  					},
   437  				}
   438  				return st
   439  			}(),
   440  			policy: newNodePolicy(),
   441  			equalP: true,
   442  			expectedInterfaces: []v1.Interface{
   443  				{
   444  					Name:       "ens803f1",
   445  					NumVfs:     5,
   446  					PciAddress: "0000:86:00.1",
   447  					VfGroups: []v1.VfGroup{
   448  						{
   449  							DeviceType:   consts.DeviceTypeNetDevice,
   450  							ResourceName: "p1res",
   451  							VfRange:      "0-1",
   452  							PolicyName:   "p1",
   453  						},
   454  						{
   455  							DeviceType:   consts.DeviceTypeVfioPci,
   456  							ResourceName: "vfiores",
   457  							VfRange:      "2-4",
   458  							PolicyName:   "p2",
   459  						},
   460  					},
   461  				},
   462  			},
   463  		},
   464  		{
   465  			// vdpa policy with same priority (both virtio and vhost), VfRange's do not overlap so all is merged
   466  			tname: "one vdpa policy present same pf same priority partitioning",
   467  			currentState: func() *v1.SriovNetworkNodeState {
   468  				st := newNodeState()
   469  				st.Spec.Interfaces = []v1.Interface{
   470  					{
   471  						Name:       "ens803f1",
   472  						NumVfs:     4,
   473  						PciAddress: "0000:86:00.1",
   474  						VfGroups: []v1.VfGroup{
   475  							{
   476  								DeviceType:   consts.DeviceTypeNetDevice,
   477  								VdpaType:     consts.VdpaTypeVhost,
   478  								ResourceName: "vhostvdpa",
   479  								VfRange:      "0-1",
   480  								PolicyName:   "p2",
   481  							},
   482  						},
   483  					},
   484  				}
   485  				return st
   486  			}(),
   487  			policy: newVirtioVdpaNodePolicy(),
   488  			equalP: true,
   489  			expectedInterfaces: []v1.Interface{
   490  				{
   491  					Name:       "ens803f1",
   492  					NumVfs:     4,
   493  					PciAddress: "0000:86:00.1",
   494  					VfGroups: []v1.VfGroup{
   495  						{
   496  							DeviceType:   consts.DeviceTypeNetDevice,
   497  							VdpaType:     consts.VdpaTypeVirtio,
   498  							ResourceName: "virtiovdpa",
   499  							VfRange:      "2-3",
   500  							PolicyName:   "p1",
   501  						},
   502  						{
   503  							DeviceType:   consts.DeviceTypeNetDevice,
   504  							VdpaType:     consts.VdpaTypeVhost,
   505  							ResourceName: "vhostvdpa",
   506  							VfRange:      "0-1",
   507  							PolicyName:   "p2",
   508  						},
   509  					},
   510  				},
   511  			},
   512  		},
   513  		{
   514  			// policy with same priority that overwrites the 2 present groups in
   515  			// SriovNetworkNodeState because they overlap VfRange
   516  			tname: "two policy present same pf same priority overlap",
   517  			currentState: func() *v1.SriovNetworkNodeState {
   518  				st := newNodeState()
   519  				st.Spec.Interfaces = []v1.Interface{
   520  					{
   521  						Name:       "ens803f1",
   522  						NumVfs:     2,
   523  						PciAddress: "0000:86:00.1",
   524  						VfGroups: []v1.VfGroup{
   525  							{
   526  								DeviceType:   consts.DeviceTypeVfioPci,
   527  								ResourceName: "vfiores1",
   528  								VfRange:      "0-0",
   529  								PolicyName:   "p2",
   530  							},
   531  							{
   532  								DeviceType:   consts.DeviceTypeVfioPci,
   533  								ResourceName: "vfiores2",
   534  								VfRange:      "1-1",
   535  								PolicyName:   "p3",
   536  							},
   537  						},
   538  					},
   539  				}
   540  				return st
   541  			}(),
   542  			policy: newNodePolicy(),
   543  			equalP: true,
   544  			expectedInterfaces: []v1.Interface{
   545  				{
   546  					Name:       "ens803f1",
   547  					NumVfs:     2,
   548  					PciAddress: "0000:86:00.1",
   549  					VfGroups: []v1.VfGroup{
   550  						{
   551  							DeviceType:   consts.DeviceTypeNetDevice,
   552  							ResourceName: "p1res",
   553  							VfRange:      "0-1",
   554  							PolicyName:   "p1",
   555  						},
   556  					},
   557  				},
   558  			},
   559  		},
   560  		{
   561  			// policy with same priority that overwrites the present group in
   562  			// SriovNetworkNodeState because of same ResourceName
   563  			tname: "one policy present same pf same priority same ResourceName",
   564  			currentState: func() *v1.SriovNetworkNodeState {
   565  				st := newNodeState()
   566  				st.Spec.Interfaces = []v1.Interface{
   567  					{
   568  						Name:       "ens803f1",
   569  						NumVfs:     4,
   570  						PciAddress: "0000:86:00.1",
   571  						VfGroups: []v1.VfGroup{
   572  							{
   573  								DeviceType:   consts.DeviceTypeVfioPci,
   574  								ResourceName: "p1res",
   575  								VfRange:      "2-3",
   576  								PolicyName:   "p2",
   577  							},
   578  						},
   579  					},
   580  				}
   581  				return st
   582  			}(),
   583  			policy: newNodePolicy(),
   584  			equalP: true,
   585  			expectedInterfaces: []v1.Interface{
   586  				{
   587  					Name:       "ens803f1",
   588  					NumVfs:     4,
   589  					PciAddress: "0000:86:00.1",
   590  					VfGroups: []v1.VfGroup{
   591  						{
   592  							DeviceType:   consts.DeviceTypeNetDevice,
   593  							ResourceName: "p1res",
   594  							VfRange:      "0-1",
   595  							PolicyName:   "p1",
   596  						},
   597  					},
   598  				},
   599  			},
   600  		},
   601  		{
   602  			// policy with diff priority that have non-overlapping VF groups will be
   603  			// merged
   604  			tname: "one policy present same pf diff priority no overlap VFs",
   605  			currentState: func() *v1.SriovNetworkNodeState {
   606  				st := newNodeState()
   607  				st.Spec.Interfaces = []v1.Interface{
   608  					{
   609  						Name:       "ens803f1",
   610  						NumVfs:     4,
   611  						PciAddress: "0000:86:00.1",
   612  						VfGroups: []v1.VfGroup{
   613  							{
   614  								DeviceType:   consts.DeviceTypeVfioPci,
   615  								ResourceName: "p2res",
   616  								VfRange:      "2-3",
   617  								PolicyName:   "p2",
   618  							},
   619  						},
   620  					},
   621  				}
   622  				return st
   623  			}(),
   624  			policy: newNodePolicy(),
   625  			equalP: false,
   626  			expectedInterfaces: []v1.Interface{
   627  				{
   628  					Name:       "ens803f1",
   629  					NumVfs:     4,
   630  					PciAddress: "0000:86:00.1",
   631  					VfGroups: []v1.VfGroup{
   632  						{
   633  							DeviceType:   consts.DeviceTypeNetDevice,
   634  							ResourceName: "p1res",
   635  							VfRange:      "0-1",
   636  							PolicyName:   "p1",
   637  						},
   638  						{
   639  							DeviceType:   consts.DeviceTypeVfioPci,
   640  							ResourceName: "p2res",
   641  							VfRange:      "2-3",
   642  							PolicyName:   "p2",
   643  						},
   644  					},
   645  				},
   646  			},
   647  		},
   648  		{
   649  			tname:        "no selectors",
   650  			currentState: newNodeState(),
   651  			policy: &v1.SriovNetworkNodePolicy{
   652  				ObjectMeta: metav1.ObjectMeta{
   653  					Name: "p1",
   654  				},
   655  				Spec: v1.SriovNetworkNodePolicySpec{
   656  					DeviceType: consts.DeviceTypeNetDevice,
   657  					NicSelector: v1.SriovNetworkNicSelector{
   658  						PfNames:     []string{},
   659  						RootDevices: []string{},
   660  					},
   661  					NodeSelector: map[string]string{
   662  						"feature.node.kubernetes.io/network-sriov.capable": "true",
   663  					},
   664  					NumVfs:       2,
   665  					Priority:     99,
   666  					ResourceName: "p1res",
   667  				},
   668  			},
   669  			equalP:             false,
   670  			expectedInterfaces: nil,
   671  		},
   672  		{
   673  			tname:        "bad pf partition",
   674  			currentState: newNodeState(),
   675  			policy: &v1.SriovNetworkNodePolicy{
   676  				ObjectMeta: metav1.ObjectMeta{
   677  					Name: "p1",
   678  				},
   679  				Spec: v1.SriovNetworkNodePolicySpec{
   680  					DeviceType: consts.DeviceTypeNetDevice,
   681  					NicSelector: v1.SriovNetworkNicSelector{
   682  						PfNames:     []string{"ens803f0#a-c"},
   683  						RootDevices: []string{},
   684  					},
   685  					NodeSelector: map[string]string{
   686  						"feature.node.kubernetes.io/network-sriov.capable": "true",
   687  					},
   688  					NumVfs:       2,
   689  					Priority:     99,
   690  					ResourceName: "p1res",
   691  				},
   692  			},
   693  			equalP:             false,
   694  			expectedInterfaces: nil,
   695  			expectedErr:        true,
   696  		},
   697  	}
   698  	for _, tc := range testtable {
   699  		t.Run(tc.tname, func(t *testing.T) {
   700  			err := tc.policy.Apply(tc.currentState, tc.equalP)
   701  			if tc.expectedErr && err == nil {
   702  				t.Errorf("Apply expecting error.")
   703  			} else if !tc.expectedErr && err != nil {
   704  				t.Errorf("Apply error:\n%s", err)
   705  			}
   706  			if diff := cmp.Diff(tc.expectedInterfaces, tc.currentState.Spec.Interfaces); diff != "" {
   707  				t.Errorf("SriovNetworkNodeState spec diff (-want +got):\n%s", diff)
   708  			}
   709  		})
   710  	}
   711  }
   712  
   713  func TestVirtioVdpaNodePolicyApply(t *testing.T) {
   714  	testtable := []struct {
   715  		tname              string
   716  		currentState       *v1.SriovNetworkNodeState
   717  		policy             *v1.SriovNetworkNodePolicy
   718  		expectedInterfaces v1.Interfaces
   719  		equalP             bool
   720  		expectedErr        bool
   721  	}{
   722  		{
   723  			tname:        "virtio/vdpa configuration",
   724  			currentState: newNodeState(),
   725  			policy:       newVirtioVdpaNodePolicy(),
   726  			equalP:       false,
   727  			expectedInterfaces: []v1.Interface{
   728  				{
   729  					Name:       "ens803f1",
   730  					NumVfs:     4,
   731  					PciAddress: "0000:86:00.1",
   732  					VfGroups: []v1.VfGroup{
   733  						{
   734  							DeviceType:   consts.DeviceTypeNetDevice,
   735  							VdpaType:     consts.VdpaTypeVirtio,
   736  							ResourceName: "virtiovdpa",
   737  							VfRange:      "2-3",
   738  							PolicyName:   "p1",
   739  						},
   740  					},
   741  				},
   742  			},
   743  		},
   744  	}
   745  	for _, tc := range testtable {
   746  		t.Run(tc.tname, func(t *testing.T) {
   747  			err := tc.policy.Apply(tc.currentState, tc.equalP)
   748  			if tc.expectedErr && err == nil {
   749  				t.Errorf("Apply expecting error.")
   750  			} else if !tc.expectedErr && err != nil {
   751  				t.Errorf("Apply error:\n%s", err)
   752  			}
   753  			if diff := cmp.Diff(tc.expectedInterfaces, tc.currentState.Spec.Interfaces); diff != "" {
   754  				t.Errorf("SriovNetworkNodeState spec diff (-want +got):\n%s", diff)
   755  			}
   756  		})
   757  	}
   758  }
   759  
   760  func TestVhostVdpaNodePolicyApply(t *testing.T) {
   761  	testtable := []struct {
   762  		tname              string
   763  		currentState       *v1.SriovNetworkNodeState
   764  		policy             *v1.SriovNetworkNodePolicy
   765  		expectedInterfaces v1.Interfaces
   766  		equalP             bool
   767  		expectedErr        bool
   768  	}{
   769  		{
   770  			tname:        "vhost/vdpa configuration",
   771  			currentState: newNodeState(),
   772  			policy:       newVhostVdpaNodePolicy(),
   773  			equalP:       false,
   774  			expectedInterfaces: []v1.Interface{
   775  				{
   776  					Name:       "ens803f1",
   777  					NumVfs:     2,
   778  					PciAddress: "0000:86:00.1",
   779  					VfGroups: []v1.VfGroup{
   780  						{
   781  							DeviceType:   consts.DeviceTypeNetDevice,
   782  							VdpaType:     consts.VdpaTypeVhost,
   783  							ResourceName: "vhostvdpa",
   784  							VfRange:      "0-1",
   785  							PolicyName:   "p1",
   786  						},
   787  					},
   788  				},
   789  			},
   790  		},
   791  	}
   792  	for _, tc := range testtable {
   793  		t.Run(tc.tname, func(t *testing.T) {
   794  			err := tc.policy.Apply(tc.currentState, tc.equalP)
   795  			if tc.expectedErr && err == nil {
   796  				t.Errorf("Apply expecting error.")
   797  			} else if !tc.expectedErr && err != nil {
   798  				t.Errorf("Apply error:\n%s", err)
   799  			}
   800  			if diff := cmp.Diff(tc.expectedInterfaces, tc.currentState.Spec.Interfaces); diff != "" {
   801  				t.Errorf("SriovNetworkNodeState spec diff (-want +got):\n%s", diff)
   802  			}
   803  		})
   804  	}
   805  }
   806  
   807  func TestGetEswitchModeFromSpec(t *testing.T) {
   808  	testtable := []struct {
   809  		tname          string
   810  		spec           *v1.Interface
   811  		expectedResult string
   812  	}{
   813  		{
   814  			tname:          "set to legacy",
   815  			spec:           &v1.Interface{EswitchMode: v1.ESwithModeLegacy},
   816  			expectedResult: v1.ESwithModeLegacy,
   817  		},
   818  		{
   819  			tname:          "set to switchdev",
   820  			spec:           &v1.Interface{EswitchMode: v1.ESwithModeSwitchDev},
   821  			expectedResult: v1.ESwithModeSwitchDev,
   822  		},
   823  		{
   824  			tname:          "not set",
   825  			spec:           &v1.Interface{},
   826  			expectedResult: v1.ESwithModeLegacy,
   827  		},
   828  	}
   829  	for _, tc := range testtable {
   830  		t.Run(tc.tname, func(t *testing.T) {
   831  			result := v1.GetEswitchModeFromSpec(tc.spec)
   832  			if diff := cmp.Diff(tc.expectedResult, result); diff != "" {
   833  				t.Errorf("unexpected result (-want +got):\n%s", diff)
   834  			}
   835  		})
   836  	}
   837  }
   838  
   839  func TestGetEswitchModeFromStatus(t *testing.T) {
   840  	testtable := []struct {
   841  		tname          string
   842  		spec           *v1.InterfaceExt
   843  		expectedResult string
   844  	}{
   845  		{
   846  			tname:          "set to legacy",
   847  			spec:           &v1.InterfaceExt{EswitchMode: v1.ESwithModeLegacy},
   848  			expectedResult: v1.ESwithModeLegacy,
   849  		},
   850  		{
   851  			tname:          "set to switchdev",
   852  			spec:           &v1.InterfaceExt{EswitchMode: v1.ESwithModeSwitchDev},
   853  			expectedResult: v1.ESwithModeSwitchDev,
   854  		},
   855  		{
   856  			tname:          "not set",
   857  			spec:           &v1.InterfaceExt{},
   858  			expectedResult: v1.ESwithModeLegacy,
   859  		},
   860  	}
   861  	for _, tc := range testtable {
   862  		t.Run(tc.tname, func(t *testing.T) {
   863  			result := v1.GetEswitchModeFromStatus(tc.spec)
   864  			if diff := cmp.Diff(tc.expectedResult, result); diff != "" {
   865  				t.Errorf("unexpected result (-want +got):\n%s", diff)
   866  			}
   867  		})
   868  	}
   869  }
   870  
   871  func TestSriovNetworkPoolConfig_MaxUnavailable(t *testing.T) {
   872  	testtable := []struct {
   873  		tname       string
   874  		maxUn       intstrutil.IntOrString
   875  		maxUnNil    bool
   876  		numOfNodes  int
   877  		expectedNum int
   878  		expectedErr bool
   879  	}{
   880  		{
   881  			tname:       "valid int MaxUnavailable",
   882  			maxUn:       intstrutil.FromInt32(1),
   883  			numOfNodes:  1,
   884  			expectedNum: 1,
   885  			expectedErr: false,
   886  		},
   887  		{
   888  			tname:       "invalid string MaxUnavailable",
   889  			maxUn:       intstrutil.FromString("bla"),
   890  			numOfNodes:  1,
   891  			expectedNum: 0,
   892  			expectedErr: true,
   893  		},
   894  		{
   895  			tname:       "valid string percentage MaxUnavailable",
   896  			maxUn:       intstrutil.FromString("33%"),
   897  			numOfNodes:  10,
   898  			expectedNum: 3,
   899  			expectedErr: false,
   900  		},
   901  		{
   902  			tname:       "negative int MaxUnavailable",
   903  			maxUn:       intstrutil.FromInt32(-1),
   904  			numOfNodes:  10,
   905  			expectedNum: 0,
   906  			expectedErr: true,
   907  		},
   908  		{
   909  			tname:       "out of range int MaxUnavailable",
   910  			maxUn:       intstrutil.FromString("99999999999999999."),
   911  			numOfNodes:  10,
   912  			expectedNum: 0,
   913  			expectedErr: true,
   914  		},
   915  		{
   916  			tname:       "over 100%",
   917  			maxUn:       intstrutil.FromString("10000%"),
   918  			numOfNodes:  10,
   919  			expectedNum: 0,
   920  			expectedErr: true,
   921  		},
   922  		{
   923  			tname:       "parallel",
   924  			maxUn:       intstrutil.FromInt32(-1),
   925  			maxUnNil:    true,
   926  			numOfNodes:  10,
   927  			expectedNum: -1,
   928  			expectedErr: false,
   929  		},
   930  		{
   931  			tname:       "zero",
   932  			maxUn:       intstrutil.FromString("30%"),
   933  			maxUnNil:    false,
   934  			numOfNodes:  1,
   935  			expectedNum: 0,
   936  			expectedErr: false,
   937  		},
   938  	}
   939  	for _, tc := range testtable {
   940  		t.Run(tc.tname, func(t *testing.T) {
   941  			pool := v1.SriovNetworkPoolConfig{
   942  				Spec: v1.SriovNetworkPoolConfigSpec{
   943  					MaxUnavailable: &tc.maxUn,
   944  				},
   945  			}
   946  
   947  			if tc.maxUnNil {
   948  				pool.Spec.MaxUnavailable = nil
   949  			}
   950  
   951  			num, err := pool.MaxUnavailable(tc.numOfNodes)
   952  			if tc.expectedErr && err == nil {
   953  				t.Errorf("MaxUnavailable expecting error.")
   954  			} else if !tc.expectedErr && err != nil {
   955  				t.Errorf("MaxUnavailable error:\n%s", err)
   956  			}
   957  
   958  			if tc.expectedNum != num {
   959  				t.Errorf("unexpected number of MaxUnavailable.")
   960  			}
   961  		})
   962  	}
   963  }