github.com/intel/goresctrl@v0.5.0/pkg/rdt/rdt_test.go (about)

     1  /*
     2  Copyright 2019-2021 Intel Corporation
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package rdt
    18  
    19  import (
    20  	stdlog "log"
    21  	"os"
    22  	"os/exec"
    23  	"path/filepath"
    24  	"regexp"
    25  	"sort"
    26  	"strings"
    27  	"testing"
    28  
    29  	"sigs.k8s.io/yaml"
    30  
    31  	"github.com/google/go-cmp/cmp"
    32  
    33  	grclog "github.com/intel/goresctrl/pkg/log"
    34  	"github.com/intel/goresctrl/pkg/testutils"
    35  	"github.com/intel/goresctrl/pkg/utils"
    36  	testdata "github.com/intel/goresctrl/test/data"
    37  )
    38  
    39  const mockGroupPrefix string = "goresctrl."
    40  
    41  type mockResctrlFs struct {
    42  	t *testing.T
    43  
    44  	origDir string
    45  	baseDir string
    46  }
    47  
    48  func newMockResctrlFs(t *testing.T, name, mountOpts string) (*mockResctrlFs, error) {
    49  	var err error
    50  	m := &mockResctrlFs{t: t}
    51  
    52  	m.origDir = testdata.Path(name)
    53  	m.baseDir, err = os.MkdirTemp("", "goresctrl.test.")
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	// Create resctrl filesystem mock
    59  	m.copyFromOrig("", "")
    60  
    61  	// Create mountinfo mock
    62  	mountInfoPath = filepath.Join(m.baseDir, "mounts")
    63  	resctrlPath := filepath.Join(m.baseDir, "resctrl")
    64  	data := "resctrl " + resctrlPath + " resctrl " + mountOpts + " 0 0\n"
    65  	if err := os.WriteFile(mountInfoPath, []byte(data), 0644); err != nil {
    66  		m.delete()
    67  		return nil, err
    68  	}
    69  	return m, nil
    70  }
    71  
    72  func (m *mockResctrlFs) delete() {
    73  	if err := os.RemoveAll(m.baseDir); err != nil {
    74  		m.t.Fatalf("failed to delete mock resctrl fs: %v", err)
    75  	}
    76  }
    77  
    78  func (m *mockResctrlFs) initMockMonGroup(class, name string) {
    79  	m.copyFromOrig(filepath.Join("mon_groups", "example"), filepath.Join(mockGroupPrefix+class, "mon_groups", mockGroupPrefix+name))
    80  }
    81  
    82  func (m *mockResctrlFs) copyFromOrig(relSrc, relDst string) {
    83  	absSrc := filepath.Join(m.origDir, relSrc)
    84  	if s, err := os.Stat(absSrc); err != nil {
    85  		m.t.Fatalf("%v", err)
    86  	} else if s.IsDir() {
    87  		absSrc = filepath.Join(absSrc, ".")
    88  	}
    89  
    90  	absDst := filepath.Join(m.baseDir, "resctrl", relDst)
    91  	cmd := exec.Command("cp", "-r", absSrc, absDst)
    92  	if err := cmd.Run(); err != nil {
    93  		m.t.Fatalf("failed to copy mock data %q -> %q: %v", absSrc, absDst, err)
    94  	}
    95  }
    96  
    97  func (m *mockResctrlFs) verifyTextFile(relPath, content string) {
    98  	verifyTextFile(m.t, filepath.Join(m.baseDir, "resctrl", relPath), content)
    99  }
   100  
   101  func verifyTextFile(t *testing.T, path, content string) {
   102  	data, err := os.ReadFile(path)
   103  	if err != nil {
   104  		t.Fatalf("failed to read %q: %v", path, err)
   105  	}
   106  	if string(data) != content {
   107  		t.Fatalf("unexpected content in %q\nexpected:\n  %q\nfound:\n  %q", path, content, data)
   108  	}
   109  }
   110  
   111  func parseTestConfig(t *testing.T, data string) *Config {
   112  	c := &Config{}
   113  	if err := yaml.Unmarshal([]byte(data), c); err != nil {
   114  		t.Fatalf("failed to parse rdt config: %v", err)
   115  	}
   116  	return c
   117  }
   118  
   119  // TestRdt tests the rdt public API, i.e. exported functionality of the package
   120  func TestRdt(t *testing.T) {
   121  	const rdtTestConfig string = `
   122  partitions:
   123    priority:
   124      l3Allocation:
   125        all: 60%
   126      mbAllocation:
   127        all: [100%]
   128      classes:
   129        Guaranteed:
   130          l3Allocation:
   131            all: 100%
   132    default:
   133      l3Allocation:
   134        all: 40%
   135      mbAllocation:
   136        all: [100%]
   137      classes:
   138        Burstable:
   139          l3Allocation:
   140            all: 100%
   141          mbAllocation:
   142            all: [66%]
   143        BestEffort:
   144          l3Allocation:
   145            all: 66%
   146          mbAllocation:
   147            all: [33%]
   148          kubernetes:
   149            denyPodAnnotation: true
   150  kubernetes:
   151    allowedPodAnnotationClasses: [bar, foo]
   152  `
   153  
   154  	verifyGroupNames := func(a interface{}, b []string) {
   155  		var names []string
   156  
   157  		switch v := a.(type) {
   158  		case []CtrlGroup:
   159  			for _, g := range v {
   160  				names = append(names, g.Name())
   161  			}
   162  		case []MonGroup:
   163  			for _, g := range v {
   164  				names = append(names, g.Name())
   165  			}
   166  		default:
   167  			t.Errorf("Invalid type '%T' in verifyGroupNames()", a)
   168  			return
   169  		}
   170  		if len(b) == 0 && len(names) == 0 {
   171  			return
   172  		}
   173  		sort.Strings(names)
   174  		sort.Strings(b)
   175  		if !cmp.Equal(names, b) {
   176  			t.Errorf("unexpected class/group names: expected %s got %s", b, names)
   177  		}
   178  	}
   179  
   180  	// Set group remove function so that mock groups can be removed
   181  	groupRemoveFunc = os.RemoveAll
   182  
   183  	//
   184  	// 1. test uninitialized interface
   185  	//
   186  	rdt = nil
   187  	SetLogger(grclog.NewLoggerWrapper(stdlog.New(os.Stderr, "[ rdt-test-1 ] ", 0)))
   188  
   189  	if err := SetConfig(&Config{}, false); err == nil {
   190  		t.Errorf("setting config on uninitialized rdt succeeded unexpectedly")
   191  
   192  	}
   193  	if classes := GetClasses(); len(classes) != 0 {
   194  		t.Errorf("uninitialized rdt contains classes %s", classes)
   195  	}
   196  	if _, ok := GetClass(""); ok {
   197  		t.Errorf("expected to not get a class with empty name")
   198  	}
   199  	if MonSupported() {
   200  		t.Errorf("unitialized rdt claims monitoring to be supported")
   201  	}
   202  	if features := GetMonFeatures(); len(features) != 0 {
   203  		t.Errorf("uninitialized rdt returned monitoring features %s", features)
   204  	}
   205  
   206  	//
   207  	// 2. Test setting up RDT with L3 L3_MON and MB support
   208  	//
   209  	mockFs, err := newMockResctrlFs(t, "resctrl.full", "")
   210  	if err != nil {
   211  		t.Fatalf("failed to set up mock resctrl fs: %v", err)
   212  	}
   213  	defer mockFs.delete()
   214  
   215  	if err := Initialize(mockGroupPrefix); err != nil {
   216  		t.Fatalf("rdt initialization failed: %v", err)
   217  	}
   218  
   219  	// Check that existing groups were read correctly on init
   220  	classes := GetClasses()
   221  	verifyGroupNames(classes, []string{"Guaranteed", "Stale", RootClassName})
   222  
   223  	cls, _ := GetClass(RootClassName)
   224  	verifyGroupNames(cls.GetMonGroups(), []string{})
   225  	cls, _ = GetClass("Guaranteed")
   226  	verifyGroupNames(cls.GetMonGroups(), []string{"predefined_group_empty", "predefined_group_live"})
   227  	cls, _ = GetClass("Stale")
   228  	if err := cls.AddPids("99"); err != nil {
   229  		t.Fatalf("AddPids() failed: %v", err)
   230  	}
   231  
   232  	// Invalid test config content should cause an error
   233  	if err := SetConfigFromData([]byte("partitions: foo"), true); err == nil {
   234  		t.Fatalf("rdt configuration with invalid file succeeded unexpetedly")
   235  	}
   236  	// Non-existent configuration file should cause an error
   237  	if err := SetConfigFromFile("non-existent-config-file", true); err == nil {
   238  		t.Fatalf("rdt configuration with non-existent file succeeded unexpetedly")
   239  	}
   240  	// Configuration should fail as "Stale" class has pids assigned to it
   241  	testConfigFile := testutils.CreateTempFile(t, rdtTestConfig)
   242  	defer os.Remove(testConfigFile)
   243  	if err := SetConfigFromFile(testConfigFile, false); err == nil {
   244  		t.Fatalf("rdt configuration succeeded unexpetedly")
   245  	}
   246  	// Forced configuration should succeed
   247  	if err := SetConfigFromFile(testConfigFile, true); err != nil {
   248  		t.Fatalf("rdt forced configuration failed: %v", err)
   249  	}
   250  
   251  	// Check that KubernetesOptions of classes are parsed and propagated correctly
   252  	if !rdt.conf.Classes["BestEffort"].Kubernetes.DenyPodAnnotation {
   253  		t.Fatal("DenyPodAnnotation of class BestEffort should be 'true'")
   254  	}
   255  
   256  	// Empty mon group(s) should be pruned after configuration
   257  	cls, _ = GetClass("Guaranteed")
   258  	verifyGroupNames(cls.GetMonGroups(), []string{"predefined_group_live"})
   259  
   260  	// Check that SetLogger() takes effect in the control interface, too
   261  	l := grclog.NewLoggerWrapper(stdlog.New(os.Stderr, "[ rdt-test-2 ] ", 0))
   262  	SetLogger(l)
   263  	if l != rdt.Logger {
   264  		t.Errorf("unexpected logger implementation")
   265  	}
   266  
   267  	// Check that the path() and relPath() methods work correctly
   268  	if p := rdt.classes["Guaranteed"].path("foo"); p != filepath.Join(mockFs.baseDir, "resctrl", "goresctrl.Guaranteed", "foo") {
   269  		t.Errorf("path() returned wrong path %q", p)
   270  	}
   271  	if p := rdt.classes["Guaranteed"].relPath("foo"); p != filepath.Join("goresctrl.Guaranteed", "foo") {
   272  		t.Errorf("relPath() returned wrong path %q", p)
   273  	}
   274  
   275  	// Verify that ctrl groups are correctly configured
   276  	mockFs.verifyTextFile(rdt.classes["BestEffort"].relPath("schemata"),
   277  		"L3:0=3f;1=3f;2=3f;3=3f\nMB:0=33;1=33;2=33;3=33\n")
   278  	mockFs.verifyTextFile(rdt.classes["Burstable"].relPath("schemata"),
   279  		"L3:0=ff;1=ff;2=ff;3=ff\nMB:0=66;1=66;2=66;3=66\n")
   280  	mockFs.verifyTextFile(rdt.classes["Guaranteed"].relPath("schemata"),
   281  		"L3:0=fff00;1=fff00;2=fff00;3=fff00\nMB:0=100;1=100;2=100;3=100\n")
   282  
   283  	// Verify that existing goresctrl monitor groups were removed
   284  	for _, cls := range []string{RootClassName, "Guaranteed"} {
   285  		files, _ := os.ReadDir(rdt.classes[cls].path("mon_groups"))
   286  		for _, f := range files {
   287  			if strings.HasPrefix(mockGroupPrefix, f.Name()) {
   288  				t.Errorf("unexpected monitor group found %q", f.Name())
   289  			}
   290  		}
   291  	}
   292  
   293  	// Verify GetClasses
   294  	classes = GetClasses()
   295  	verifyGroupNames(classes, []string{"BestEffort", "Burstable", "Guaranteed", RootClassName})
   296  
   297  	// Verify assigning pids to classes (ctrl groups)
   298  	cls, _ = GetClass("Guaranteed")
   299  	if n := cls.Name(); n != "Guaranteed" {
   300  		t.Errorf("CtrlGroup.Name() returned %q, expected %q", n, "Guaranteed")
   301  	}
   302  
   303  	pids := []string{"10", "11", "12"}
   304  	if err := cls.AddPids(pids...); err != nil {
   305  		t.Errorf("AddPids() failed: %v", err)
   306  	}
   307  	if p, err := cls.GetPids(); err != nil {
   308  		t.Errorf("GetPids() failed: %v", err)
   309  	} else if !cmp.Equal(p, pids) {
   310  		t.Errorf("GetPids() returned %s, expected %s", p, pids)
   311  	}
   312  
   313  	mockFs.verifyTextFile(rdt.classes["Guaranteed"].relPath("tasks"), "10\n11\n12\n")
   314  
   315  	// Verify MonSupported and GetMonFeatures
   316  	if !MonSupported() {
   317  		t.Errorf("MonSupported() returned false, expected true")
   318  	}
   319  	expectedMonFeatures := map[MonResource][]string{MonResourceL3: []string{"llc_occupancy", "mbm_local_bytes", "mbm_total_bytes"}}
   320  	if features := GetMonFeatures(); !cmp.Equal(features, expectedMonFeatures) {
   321  		t.Fatalf("GetMonFeatures() returned %v, expected %v", features, expectedMonFeatures)
   322  	}
   323  
   324  	// Test creating monitoring groups
   325  	cls, _ = GetClass("Guaranteed")
   326  	mgName := "test_group"
   327  	mgAnnotations := map[string]string{"a_key": "a_value"}
   328  	mg, err := cls.CreateMonGroup(mgName, mgAnnotations)
   329  	if err != nil {
   330  		t.Fatalf("creating mon group failed: %v", err)
   331  	}
   332  	if n := mg.Name(); n != mgName {
   333  		t.Errorf("MonGroup.Name() returned %q, expected %q", n, mgName)
   334  	}
   335  	if a := mg.GetAnnotations(); !cmp.Equal(a, mgAnnotations) {
   336  		t.Errorf("MonGroup.GetAnnotations() returned %s, expected %s", a, mgAnnotations)
   337  	}
   338  	if n := mg.Parent().Name(); n != "Guaranteed" {
   339  		t.Errorf("MonGroup.Parent().Name() returned %q, expected %q", n, "Guaranteed")
   340  	}
   341  
   342  	if _, ok := cls.GetMonGroup("non-existing-group"); ok {
   343  		t.Errorf("unexpected success when querying non-existing group")
   344  	}
   345  	if _, ok := cls.GetMonGroup(mgName); !ok {
   346  		t.Errorf("unexpected error when querying mon group: %v", err)
   347  	}
   348  
   349  	verifyGroupNames(cls.GetMonGroups(), []string{"predefined_group_live", mgName})
   350  
   351  	mgPath := rdt.classes["Guaranteed"].path("mon_groups", "goresctrl."+mgName)
   352  	if _, err := os.Stat(mgPath); err != nil {
   353  		t.Errorf("mon group directory not found: %v", err)
   354  	}
   355  
   356  	// Check that the monGroup.path() and relPath() methods work correctly
   357  	mgi := rdt.classes["Guaranteed"].monGroups[mgName]
   358  	if p := mgi.path("foo"); p != filepath.Join(mockFs.baseDir, "resctrl", "goresctrl.Guaranteed", "mon_groups", "goresctrl."+mgName, "foo") {
   359  		t.Errorf("path() returned wrong path %q", p)
   360  	}
   361  	if p := mgi.relPath("foo"); p != filepath.Join("goresctrl.Guaranteed", "mon_groups", "goresctrl."+mgName, "foo") {
   362  		t.Errorf("relPath() returned wrong path %q", p)
   363  	}
   364  
   365  	// Test deleting monitoring groups
   366  	if err := cls.DeleteMonGroup(mgName); err != nil {
   367  		t.Errorf("unexpected error when deleting mon group: %v", err)
   368  	}
   369  	if _, ok := cls.GetMonGroup("non-existing-group"); ok {
   370  		t.Errorf("unexpected success when querying deleted group")
   371  	}
   372  	if _, err := os.Stat(mgPath); !os.IsNotExist(err) {
   373  		t.Errorf("unexpected error when checking directory of deleted mon group: %v", err)
   374  	}
   375  
   376  	for _, n := range []string{"foo", "bar", "baz"} {
   377  		if _, err := cls.CreateMonGroup(n, map[string]string{}); err != nil {
   378  			t.Errorf("creating mon group failed: %v", err)
   379  		}
   380  	}
   381  	if err := cls.DeleteMonGroups(); err != nil {
   382  		t.Errorf("unexpected error when deleting all mon groups: %v", err)
   383  	}
   384  	if mgs := cls.GetMonGroups(); len(mgs) != 0 {
   385  		t.Errorf("unexpected mon groups exist: %v", mgs)
   386  	}
   387  
   388  	// Verify assigning pids to monitor group
   389  	mgName = "test_group_2"
   390  	mockFs.initMockMonGroup("Guaranteed", mgName)
   391  	cls, _ = GetClass("Guaranteed")
   392  	mg, _ = cls.CreateMonGroup(mgName, nil)
   393  
   394  	pids = []string{"10"}
   395  	if err := mg.AddPids(pids...); err != nil {
   396  		t.Errorf("MonGroup.AddPids() failed: %v", err)
   397  	}
   398  	if p, err := mg.GetPids(); err != nil {
   399  		t.Errorf("MonGroup.GetPids() failed: %v", err)
   400  	} else if !cmp.Equal(p, pids) {
   401  		t.Errorf("MonGroup.GetPids() returned %s, expected %s", p, pids)
   402  	}
   403  	mockFs.verifyTextFile(rdt.classes["Guaranteed"].monGroups[mgName].relPath("tasks"), "10\n")
   404  
   405  	// Verify monitoring functionality
   406  	expected := MonData{
   407  		L3: MonL3Data{
   408  			0: MonLeafData{
   409  				"llc_occupancy":   1,
   410  				"mbm_local_bytes": 2,
   411  				"mbm_total_bytes": 3,
   412  			},
   413  			1: MonLeafData{
   414  				"llc_occupancy":   11,
   415  				"mbm_local_bytes": 12,
   416  				"mbm_total_bytes": 13,
   417  			},
   418  			2: MonLeafData{
   419  				"llc_occupancy":   21,
   420  				"mbm_local_bytes": 22,
   421  				"mbm_total_bytes": 23,
   422  			},
   423  			3: MonLeafData{
   424  				"llc_occupancy":   31,
   425  				"mbm_local_bytes": 32,
   426  				"mbm_total_bytes": 33,
   427  			},
   428  		},
   429  	}
   430  	md := mg.GetMonData()
   431  	if !cmp.Equal(md, expected) {
   432  		t.Errorf("unexcpected monitoring data\nexpected:\n%s\nreceived:\n%s", utils.DumpJSON(expected), utils.DumpJSON(md))
   433  	}
   434  
   435  	//
   436  	// 3. Test discovery
   437  	//
   438  	if err := DiscoverClasses(""); err != nil {
   439  		t.Fatalf("DiscoverClasses() failed unexpectedly")
   440  	}
   441  	classes = GetClasses()
   442  	verifyGroupNames(classes, []string{"Guaranteed", "non_goresctrl.Group", RootClassName})
   443  
   444  	if err := DiscoverClasses("non_goresctrl."); err != nil {
   445  		t.Fatalf("DiscoverClasses() failed unexpectedly")
   446  	}
   447  	classes = GetClasses()
   448  	verifyGroupNames(classes, []string{"Group", RootClassName})
   449  
   450  	if err := DiscoverClasses("non-existing-prefix"); err != nil {
   451  		t.Fatalf("DiscoverClasses() failed unexpectedly")
   452  	}
   453  	classes = GetClasses()
   454  	verifyGroupNames(classes, []string{RootClassName})
   455  }
   456  
   457  // TestConfig tests configuration parsing and resolving
   458  func TestConfig(t *testing.T) {
   459  	type Schemata struct {
   460  		l2     string
   461  		l2code string
   462  		l2data string
   463  		l3     string
   464  		l3code string
   465  		l3data string
   466  		mb     string
   467  	}
   468  
   469  	type TC struct {
   470  		name        string
   471  		fs          string
   472  		fsMountOpts string
   473  		config      string
   474  		configErrRe string
   475  		schemata    map[string]Schemata
   476  	}
   477  
   478  	tcs := []TC{
   479  		// Testcase
   480  		TC{
   481  			name:   "Empty config",
   482  			fs:     "resctrl.full",
   483  			config: "",
   484  			schemata: map[string]Schemata{
   485  				"system/default": Schemata{
   486  					l3: "0=fffff;1=fffff;2=fffff;3=fffff",
   487  					mb: "0=100;1=100;2=100;3=100",
   488  				},
   489  			},
   490  		},
   491  		// Testcase
   492  		TC{
   493  			name: "Complex config",
   494  			fs:   "resctrl.full",
   495  			config: `
   496  partitions:
   497    part-1:
   498      l3Allocation:
   499        all: 60%
   500        1: "0xff000"
   501        2: "9-15"
   502      mbAllocation:
   503        all: [100%]
   504      classes:
   505        class-1:
   506          l3Allocation: 100%
   507        class-2:
   508          l3Allocation:
   509            all: 100%
   510            0-1: 10%
   511            2: "0x70"
   512          mbAllocation:
   513            all: [40%]
   514            3: [10%]
   515    part-2:
   516      l3Allocation:
   517        all: 39%
   518        1: "0-10"
   519        2: "0-6"
   520      mbAllocation:
   521        all: [50%]
   522        1: [80%]
   523        2: [100%]
   524      classes:
   525        class-3:
   526          l3Allocation: 100%
   527          mbAllocation:
   528            all: [40%]
   529            0: [80%]
   530        class-4:
   531          l3Allocation: 50%
   532          mbAllocation: [100%]
   533        system/default:
   534          l3Allocation: 60%
   535          mbAllocation: [60%]
   536    part-3:
   537      l3Allocation:
   538        all: 1%
   539        1: "0x800"
   540        2: "7,8"
   541      mbAllocation: [20%]
   542      classes:
   543        class-5:
   544          l3Allocation: 100%
   545          mbAllocation:
   546            all: [100%]
   547            0: [1%]
   548  `,
   549  			schemata: map[string]Schemata{
   550  				"class-1": Schemata{
   551  					l3: "0=fff;1=ff000;2=fe00;3=fff",
   552  					mb: "0=100;1=100;2=100;3=100",
   553  				},
   554  				"class-2": Schemata{
   555  					l3: "0=3;1=1000;2=e000;3=fff",
   556  					mb: "0=40;1=40;2=40;3=10",
   557  				},
   558  				"class-3": Schemata{
   559  					l3: "0=7f000;1=7ff;2=7f;3=7f000",
   560  					mb: "0=40;1=32;2=40;3=20",
   561  				},
   562  				"class-4": Schemata{
   563  					l3: "0=f000;1=3f;2=f;3=f000",
   564  					mb: "0=50;1=80;2=100;3=50",
   565  				},
   566  				"system/default": Schemata{
   567  					l3: "0=1f000;1=7f;2=1f;3=1f000",
   568  					mb: "0=30;1=48;2=60;3=30",
   569  				},
   570  				"class-5": Schemata{
   571  					l3: "0=80000;1=800;2=180;3=80000",
   572  					mb: "0=10;1=20;2=20;3=20",
   573  				},
   574  			},
   575  		},
   576  		// Testcase
   577  		TC{
   578  			name: "L3 CDP disabled",
   579  			fs:   "resctrl.nomb",
   580  			config: `
   581  partitions:
   582    part-1:
   583      l3Allocation:
   584        0,1:
   585          unified: 60%
   586          code: 70%
   587          data: 50%
   588        2,3: 40%
   589      classes:
   590        class-1:
   591    part-2:
   592      l3Allocation:
   593        0,1:
   594          unified: 40%
   595          code: 30%
   596          data: 50%
   597        2,3: 60%
   598      classes:
   599        class-2:
   600        system/default:
   601          l3Allocation:
   602            all: 100%
   603            3:
   604              unified: 80%
   605              code: 60%
   606              data: 90%
   607  
   608  `,
   609  			schemata: map[string]Schemata{
   610  				"class-1": Schemata{
   611  					l3: "0=fff;1=fff;2=ff;3=ff",
   612  				},
   613  				"class-2": Schemata{
   614  					l3: "0=ff000;1=ff000;2=fff00;3=fff00",
   615  				},
   616  				"system/default": Schemata{
   617  					l3: "0=ff000;1=ff000;2=fff00;3=3ff00",
   618  				},
   619  			},
   620  		},
   621  		// Testcase
   622  		TC{
   623  			name: "L3 CDP enabled",
   624  			fs:   "resctrl.nomb.cdp",
   625  			config: `
   626  partitions:
   627    part-1:
   628      l3Allocation:
   629        0,1:
   630          unified: 60%
   631          code: 70%
   632          data: 50%
   633        2,3: 40%
   634      classes:
   635        class-1:
   636    part-2:
   637      l3Allocation:
   638        0,1:
   639          unified: 40%
   640          code: 30%
   641          data: 50%
   642        2,3: 60%
   643      classes:
   644        class-2:
   645        "":
   646          l3Allocation:
   647            all: 100%
   648            3:
   649              unified: 80%
   650              code: 60%
   651              data: 90%
   652  
   653  `,
   654  			schemata: map[string]Schemata{
   655  				"class-1": Schemata{
   656  					l3code: "0=3fff;1=3fff;2=ff;3=ff",
   657  					l3data: "0=3ff;1=3ff;2=ff;3=ff",
   658  				},
   659  				"class-2": Schemata{
   660  					l3code: "0=fc000;1=fc000;2=fff00;3=fff00",
   661  					l3data: "0=ffc00;1=ffc00;2=fff00;3=fff00",
   662  				},
   663  				"system/default": Schemata{
   664  					l3code: "0=fc000;1=fc000;2=fff00;3=ff00",
   665  					l3data: "0=ffc00;1=ffc00;2=fff00;3=7ff00",
   666  				},
   667  			},
   668  		},
   669  		// Testcase
   670  		TC{
   671  			name: "L3 optional",
   672  			fs:   "resctrl.nol3",
   673  			config: `
   674  options:
   675    l3:
   676      optional: true
   677  partitions:
   678    part-1:
   679      l3Allocation: 100%
   680      mbAllocation: [100%]
   681      classes:
   682        class-1:
   683          l3Allocation: 20%
   684          mbAllocation: [50%]
   685  `,
   686  			schemata: map[string]Schemata{
   687  				"class-1": Schemata{
   688  					mb: "0=50;1=50;2=50;3=50",
   689  				},
   690  				"system/default": Schemata{
   691  					mb: "0=100;1=100;2=100;3=100",
   692  				},
   693  			},
   694  		},
   695  		// Testcase
   696  		TC{
   697  			name: "Default L3 CAT",
   698  			fs:   "resctrl.full",
   699  			config: `
   700  options:
   701  partitions:
   702    part-1:
   703      mbAllocation: [100%]
   704      classes:
   705        class-1:
   706          mbAllocation: [50%]
   707  `,
   708  			schemata: map[string]Schemata{
   709  				"class-1": Schemata{
   710  					l3: "0=fffff;1=fffff;2=fffff;3=fffff",
   711  					mb: "0=50;1=50;2=50;3=50",
   712  				},
   713  				"system/default": Schemata{
   714  					l3: "0=fffff;1=fffff;2=fffff;3=fffff",
   715  					mb: "0=100;1=100;2=100;3=100",
   716  				},
   717  			},
   718  		},
   719  		// Testcase
   720  		TC{
   721  			name: "Default MBA",
   722  			fs:   "resctrl.full",
   723  			config: `
   724  options:
   725  partitions:
   726    part-1:
   727      l3Allocation: 100%
   728      classes:
   729        class-1:
   730          l3Allocation: 50%
   731  `,
   732  			schemata: map[string]Schemata{
   733  				"class-1": Schemata{
   734  					l3: "0=3ff;1=3ff;2=3ff;3=3ff",
   735  					mb: "0=100;1=100;2=100;3=100",
   736  				},
   737  				"system/default": Schemata{
   738  					l3: "0=fffff;1=fffff;2=fffff;3=fffff",
   739  					mb: "0=100;1=100;2=100;3=100",
   740  				},
   741  			},
   742  		},
   743  		// Testcase
   744  		TC{
   745  			name:        "duplicate class names (fail)",
   746  			fs:          "resctrl.nomb",
   747  			configErrRe: `"class-1" defined multiple times`,
   748  			config: `
   749  partitions:
   750    part-1:
   751      classes:
   752        class-1:
   753    part-2:
   754      classes:
   755        class-1:
   756  `,
   757  		},
   758  		// Testcase
   759  		TC{
   760  			name:        "duplicate root class (fail)",
   761  			fs:          "resctrl.nomb",
   762  			configErrRe: `"system/default" defined multiple times`,
   763  			config: `
   764  partitions:
   765    part-1:
   766      classes:
   767        "":
   768    part-2:
   769      classes:
   770        system/default:
   771  `,
   772  		},
   773  		// Testcase
   774  		TC{
   775  			name:        "invalid class name",
   776  			fs:          "resctrl.nomb",
   777  			configErrRe: `unqualified class name`,
   778  			config: `
   779  partitions:
   780    part-1:
   781      classes:
   782        "..":
   783  `,
   784  		},
   785  		// Testcase
   786  		TC{
   787  			name:        "Invalid cache ids (fail)",
   788  			fs:          "resctrl.nomb",
   789  			configErrRe: `failed to parse L3 allocation request for partition "part-1": invalid integer "a"`,
   790  			config: `
   791  partitions:
   792    part-1:
   793      l3Allocation:
   794        a: 100%
   795  `,
   796  		},
   797  		// Testcase
   798  		TC{
   799  			name:        "L3 invalid allocation schema #3, missing unified (fail)",
   800  			fs:          "resctrl.nomb",
   801  			configErrRe: `failed to parse L3 allocation request for partition "part-1": 'unified' not specified in cache schema`,
   802  			config: `
   803  partitions:
   804    part-1:
   805      l3Allocation:
   806        all:
   807          data: 100%
   808  `,
   809  		},
   810  		// Testcase
   811  		TC{
   812  			name:        "L3 invalid allocation schema #4, missing code (fail)",
   813  			fs:          "resctrl.nomb",
   814  			configErrRe: `failed to parse L3 allocation request for partition "part-1": 'data' specified but missing 'code' from cache schema`,
   815  			config: `
   816  partitions:
   817    part-1:
   818      l3Allocation:
   819        all:
   820          unified: 100%
   821          data: 100%
   822  `,
   823  		},
   824  		// Testcase
   825  		TC{
   826  			name:        "L3 invalid allocation schema #5, missing data (fail)",
   827  			fs:          "resctrl.nomb",
   828  			configErrRe: `failed to parse L3 allocation request for partition "part-1": 'code' specified but missing 'data' from cache schema`,
   829  			config: `
   830  partitions:
   831    part-1:
   832      l3Allocation:
   833        all:
   834          unified: 100%
   835          code: 100%
   836  `,
   837  		},
   838  		// Testcase
   839  		TC{
   840  			name:        "L3 required (fail)",
   841  			fs:          "resctrl.nol3",
   842  			configErrRe: `L3 cache allocation for "class-1" specified in configuration but not supported by system`,
   843  			config: `
   844  partitions:
   845    part-1:
   846      l3Allocation: 100%
   847      classes:
   848        class-1:
   849          l3Allocation: 20%
   850  `,
   851  		},
   852  		// Testcase
   853  		TC{
   854  			name: "MB optional",
   855  			fs:   "resctrl.nomb",
   856  			config: `
   857  options:
   858    mb:
   859      optional: true
   860  partitions:
   861    part-1:
   862      l3Allocation: 100%
   863      mbAllocation: [100%]
   864      classes:
   865        class-1:
   866          l3Allocation: 0-7
   867          mbAllocation: [50%]
   868  `,
   869  			schemata: map[string]Schemata{
   870  				"class-1": Schemata{
   871  					l3: "0=ff;1=ff;2=ff;3=ff",
   872  				},
   873  				"system/default": Schemata{
   874  					l3: "0=fffff;1=fffff;2=fffff;3=fffff",
   875  				},
   876  			},
   877  		},
   878  		// Testcase
   879  		TC{
   880  			name:        "MB required (fail)",
   881  			fs:          "resctrl.nomb",
   882  			configErrRe: `memory bandwidth allocation for "class-1" specified in configuration but not supported by system`,
   883  			config: `
   884  partitions:
   885    part-1:
   886      mbAllocation: [100%]
   887      classes:
   888        class-1:
   889          mbAllocation: [50%]
   890  `,
   891  		},
   892  		// Testcase
   893  		TC{
   894  			name:        "L3 mix rel and abs allocation in partition (fail)",
   895  			fs:          "resctrl.full",
   896  			configErrRe: "error resolving L3 allocation for cache id 0: mixing absolute and relative allocations between partitions not supported",
   897  			config: `
   898  partitions:
   899    part-1:
   900      l3Allocation: "0xff"
   901    part-2:
   902      l3Allocation: 50%
   903  `,
   904  		},
   905  		// Testcase
   906  		TC{
   907  			name:        "L3 mix rel and abs allocation in partition #2 (fail)",
   908  			fs:          "resctrl.full",
   909  			configErrRe: "error resolving L3 allocation for cache id 0: mixing relative and absolute allocations between partitions not supported",
   910  			config: `
   911  partitions:
   912    part-1:
   913      l3Allocation: 50%
   914    part-2:
   915      l3Allocation: "0xff"
   916  `,
   917  		},
   918  		// Testcase
   919  		TC{
   920  			name: "L3 mix rel and abs allocation in classes",
   921  			fs:   "resctrl.nomb",
   922  			config: `
   923  partitions:
   924    part-1:
   925      l3Allocation: 100%
   926      classes:
   927        class-1:
   928          l3Allocation:
   929              all: 100%
   930              1: 50%
   931        class-2:
   932          l3Allocation:
   933              all: 50%
   934              1: "0x7"
   935              2: "1-2"
   936  `,
   937  			schemata: map[string]Schemata{
   938  				"class-1": Schemata{
   939  					l3: "0=fffff;1=3ff;2=fffff;3=fffff",
   940  				},
   941  				"class-2": Schemata{
   942  					l3: "0=3ff;1=7;2=6;3=3ff",
   943  				},
   944  				"system/default": Schemata{
   945  					l3: "0=fffff;1=fffff;2=fffff;3=fffff",
   946  				},
   947  			},
   948  		},
   949  		// Testcase
   950  		TC{
   951  			name: "L3 partial allocation",
   952  			fs:   "resctrl.nomb",
   953  			config: `
   954  partitions:
   955    part-1:
   956      l3Allocation:
   957        all: "21%"
   958        1: "42%"
   959        2: "63%"
   960        3: "89%"
   961      classes:
   962        class-1:
   963    part-2:
   964      l3Allocation:
   965        all: "29%"
   966        1: "8%"
   967        2: "19%"
   968        3: "11%"
   969      classes:
   970        class-2:
   971  `,
   972  			schemata: map[string]Schemata{
   973  				"class-1": Schemata{
   974  					l3: "0=f;1=ff;2=1fff;3=3ffff",
   975  				},
   976  				"class-2": Schemata{
   977  					l3: "0=3f0;1=300;2=e000;3=c0000",
   978  				},
   979  				"system/default": Schemata{
   980  					l3: "0=fffff;1=fffff;2=fffff;3=fffff",
   981  				},
   982  			},
   983  		},
   984  		// Testcase
   985  		TC{
   986  			name:        "L3 partition non-contiguous bitmask (fail)",
   987  			fs:          "resctrl.nomb",
   988  			configErrRe: `failed to parse L3 allocation request for partition "part-1": invalid cache bitmask "0x2f": more than one continuous block of ones`,
   989  			config: `
   990  partitions:
   991    part-1:
   992      l3Allocation:
   993        all: "100%"
   994        1: "0x2f"
   995  `,
   996  		},
   997  		// Testcase
   998  		TC{
   999  			name:        "L3 overlapping partitions (fail)",
  1000  			fs:          "resctrl.nomb",
  1001  			configErrRe: `overlapping L3 partition allocation requests for cache id 2`,
  1002  			config: `
  1003  partitions:
  1004    part-1:
  1005      l3Allocation: "0xff"
  1006    part-2:
  1007      l3Allocation:
  1008        all: "0xff00"
  1009        2: "0xff80"
  1010  `,
  1011  		},
  1012  		// Testcase
  1013  		TC{
  1014  			name:        "L3 nan percentage in partition (fail)",
  1015  			fs:          "resctrl.nomb",
  1016  			configErrRe: `failed to parse L3 allocation request for partition "part-1": strconv.ParseUint: parsing "1f": invalid syntax`,
  1017  			config: `
  1018  partitions:
  1019    part-1:
  1020      l3Allocation: "1f%"
  1021  `,
  1022  		},
  1023  		// Testcase
  1024  		TC{
  1025  			name:        "L3 percentage range in partition (fail)",
  1026  			fs:          "resctrl.nomb",
  1027  			configErrRe: `invalid configuration: percentage ranges in partition allocation not supported`,
  1028  			config: `
  1029  partitions:
  1030    part-1:
  1031      l3Allocation: "50-100%"
  1032  `,
  1033  		},
  1034  		// Testcase
  1035  		TC{
  1036  			name:        "L3 missing for one partition (fail)",
  1037  			fs:          "resctrl.full",
  1038  			configErrRe: `invalid configuration: some partitions \(part-2\) missing L3 "unified" allocation request`,
  1039  			config: `
  1040  partitions:
  1041    part-1:
  1042      l3Allocation: "50%"
  1043      mbAllocation: ["100%"]
  1044    part-2:
  1045      mbAllocation: ["100%"]
  1046  `,
  1047  		},
  1048  		// Testcase
  1049  		TC{
  1050  			name:        "L3 percentage over 100 in partition (fail)",
  1051  			fs:          "resctrl.nomb",
  1052  			configErrRe: `failed to parse L3 allocation request for partition "part-1": invalid percentage value "101%"`,
  1053  			config: `
  1054  partitions:
  1055    part-1:
  1056      l3Allocation: "101%"
  1057  `,
  1058  		},
  1059  		// Testcase
  1060  		TC{
  1061  			name:        "L3 missing cdp (fail)",
  1062  			fs:          "resctrl.nomb",
  1063  			configErrRe: `some partitions \(part-2\) missing L3 "code" allocation request for cache id [0-3]`,
  1064  			config: `
  1065  partitions:
  1066    part-1:
  1067      l3Allocation:
  1068        all:
  1069          unified: "50%"
  1070          code: "40%"
  1071          data: "60%"
  1072    part-2:
  1073      l3Allocation: "50%"
  1074  `,
  1075  		},
  1076  		// Testcase
  1077  		TC{
  1078  			name:        "L3 total percentage over 100 (fail)",
  1079  			fs:          "resctrl.nomb",
  1080  			configErrRe: `accumulated L3 "data" partition allocation requests for cache id [0-3] exceeds 100%`,
  1081  			config: `
  1082  partitions:
  1083    part-1:
  1084      l3Allocation:
  1085        all:
  1086          unified: "50%"
  1087          code: "40%"
  1088          data: "60%"
  1089    part-2:
  1090      l3Allocation:
  1091        all:
  1092          unified: "50%"
  1093          code: "40%"
  1094          data: "60%"
  1095  `,
  1096  		},
  1097  		// Testcase
  1098  		TC{
  1099  			name:        "L3 class allocation does not fit partition (fail)",
  1100  			fs:          "resctrl.nomb",
  1101  			configErrRe: `bitmask 0x1ff00 \(0x1ff << 8\) does not fit basemask 0xff00`,
  1102  			config: `
  1103  partitions:
  1104    part-1:
  1105      l3Allocation: "0xff00"
  1106      classes:
  1107        class-1:
  1108          l3Allocation: "0x1ff"
  1109  `,
  1110  		},
  1111  		// Testcase
  1112  		TC{
  1113  			name: "L3 min cbm bits is respected",
  1114  			fs:   "resctrl.nomb",
  1115  			config: `
  1116  partitions:
  1117    part-1:
  1118      l3Allocation: "100%"
  1119      classes:
  1120        class-1:
  1121          l3Allocation:
  1122            all: "1%"
  1123            1-2: "99-100%"
  1124  `,
  1125  			schemata: map[string]Schemata{
  1126  				"class-1": Schemata{
  1127  					l3: "0=3;1=c0000;2=c0000;3=3",
  1128  				},
  1129  				"system/default": Schemata{
  1130  					l3: "0=fffff;1=fffff;2=fffff;3=fffff",
  1131  				},
  1132  			},
  1133  		},
  1134  		// Testcase
  1135  		TC{
  1136  			name:        "L3 too few bits (fail)",
  1137  			fs:          "resctrl.nomb",
  1138  			configErrRe: `bitmask 0x1ff00 \(0x1ff << 8\) does not fit basemask 0xff00`,
  1139  			config: `
  1140  partitions:
  1141    part-1:
  1142      l3Allocation: "0xff00"
  1143      classes:
  1144        class-1:
  1145          l3Allocation: "0x1ff"
  1146  `,
  1147  		},
  1148  		// Testcase
  1149  		TC{
  1150  			name:        "L3 invalid percentage range in class (fail)",
  1151  			fs:          "resctrl.nomb",
  1152  			configErrRe: `invalid configuration: failed to resolve L3 allocation for class "class-1": invalid percentage range`,
  1153  			config: `
  1154  partitions:
  1155    part-1:
  1156      l3Allocation: "100%"
  1157      classes:
  1158        class-1:
  1159          l3Allocation: "0-101%"
  1160  `,
  1161  		},
  1162  		// Testcase
  1163  		TC{
  1164  			name:        "L3 missing from partition (fail)",
  1165  			fs:          "resctrl.nomb",
  1166  			configErrRe: `L3 allocation missing from partition "part-1"`,
  1167  			config: `
  1168  partitions:
  1169    part-1:
  1170      classes:
  1171        class-1:
  1172          l3Allocation: "100%"
  1173  `,
  1174  		},
  1175  		// Testcase
  1176  		TC{
  1177  			name: "MB allocation under minimum",
  1178  			fs:   "resctrl.nol3",
  1179  			config: `
  1180  partitions:
  1181    part-1:
  1182      mbAllocation: ["1%"]
  1183      classes:
  1184        class-1:
  1185          mbAllocation: ["100%"]
  1186  `,
  1187  			schemata: map[string]Schemata{
  1188  				"class-1": Schemata{
  1189  					mb: "0=10;1=10;2=10;3=10",
  1190  				},
  1191  				"system/default": Schemata{
  1192  					mb: "0=100;1=100;2=100;3=100",
  1193  				},
  1194  			},
  1195  		},
  1196  		// Testcase
  1197  		TC{
  1198  			name: "L2, partial allocation",
  1199  			fs:   "resctrl.l2",
  1200  			config: `
  1201  partitions:
  1202    part-1:
  1203      l2Allocation:
  1204        all: 30%
  1205        1: 75%
  1206      classes:
  1207        class-1:
  1208    part-2:
  1209      l2Allocation:
  1210        0: 30%
  1211        1:
  1212          unified: 20%
  1213      classes:
  1214        class-2:
  1215    part-3:
  1216      l2Allocation:
  1217        0: 40%
  1218        1: 5%
  1219      classes:
  1220        system/default:
  1221  `,
  1222  			schemata: map[string]Schemata{
  1223  				"class-1": Schemata{
  1224  					l2: "0=3;1=3f",
  1225  				},
  1226  				"class-2": Schemata{
  1227  					l2: "0=c;1=40",
  1228  				},
  1229  				"system/default": Schemata{
  1230  					l2: "0=f0;1=80",
  1231  				},
  1232  			},
  1233  		},
  1234  		// Testcase
  1235  		TC{
  1236  			name: "L2 CDP",
  1237  			fs:   "resctrl.l2cdp",
  1238  			config: `
  1239  partitions:
  1240    part-1:
  1241      l2Allocation:
  1242        all: 42%
  1243        2:
  1244          unified: 30%
  1245          code: 20%
  1246          data: 50%
  1247        3:
  1248          unified: 30%
  1249          code: 40%
  1250          data: 50%
  1251      l3Allocation: 30%
  1252      classes:
  1253        class-1:
  1254    part-2:
  1255      l2Allocation:
  1256        all: 43%
  1257        2:
  1258          unified: 70%
  1259          code: 40%
  1260          data: 30%
  1261        3:
  1262          unified: 30%
  1263          code: 60%
  1264          data: 50%
  1265      l3Allocation: 50%
  1266      classes:
  1267        class-2:
  1268          l2Allocation:
  1269            all: 80%
  1270            2:
  1271              unified: 80%
  1272              code: 60%
  1273              data: 90%
  1274        system/default:
  1275          l3Allocation: 60%
  1276  
  1277  `,
  1278  			schemata: map[string]Schemata{
  1279  				"class-1": Schemata{
  1280  					l2code: "0=ff;1=ff;2=f;3=ff",
  1281  					l2data: "0=ff;1=ff;2=3ff;3=3ff",
  1282  					l3:     "0=7",
  1283  				},
  1284  				"class-2": Schemata{
  1285  					l2code: "0=ff00;1=ff00;2=1f0;3=3ff00",
  1286  					l2data: "0=ff00;1=ff00;2=fc00;3=3fc00",
  1287  					l3:     "0=1f8",
  1288  				},
  1289  				"system/default": Schemata{
  1290  					l2code: "0=1ff00;1=1ff00;2=ff0;3=fff00",
  1291  					l2data: "0=1ff00;1=1ff00;2=fc00;3=ffc00",
  1292  					l3:     "0=78",
  1293  				},
  1294  			},
  1295  		},
  1296  		// Testcase
  1297  		TC{
  1298  			name: "L2 optional",
  1299  			fs:   "resctrl.nomb",
  1300  			config: `
  1301  options:
  1302    l2:
  1303      optional: true
  1304  partitions:
  1305    part-1:
  1306      l2Allocation: 50%
  1307      l3Allocation: 50%
  1308      classes:
  1309        class-1:
  1310          l2Allocation: 20%
  1311  `,
  1312  			schemata: map[string]Schemata{
  1313  				"class-1": Schemata{
  1314  					l3: "0=3ff;1=3ff;2=3ff;3=3ff",
  1315  				},
  1316  				"system/default": Schemata{
  1317  					l3: "0=fffff;1=fffff;2=fffff;3=fffff",
  1318  				},
  1319  			},
  1320  		},
  1321  		// Testcase
  1322  		TC{
  1323  			name:        "MB nan percentage value in partition (fail)",
  1324  			fs:          "resctrl.nol3",
  1325  			configErrRe: `failed to resolve MB allocation for partition "part-1": strconv.ParseUint: parsing "xyz"`,
  1326  			config: `
  1327  partitions:
  1328    part-1:
  1329      mbAllocation: ["xyz%"]
  1330  `,
  1331  		},
  1332  		// Testcase
  1333  		TC{
  1334  			name:        "MB invalid percentage value in class (fail)",
  1335  			fs:          "resctrl.nol3",
  1336  			configErrRe: `failed to resolve MB allocation for class "class-1":.*invalid syntax`,
  1337  			config: `
  1338  partitions:
  1339    part-1:
  1340      mbAllocation: ["100%"]
  1341      classes:
  1342        class-1:
  1343          mbAllocation: ["1a%"]
  1344  `,
  1345  		},
  1346  		// Testcase
  1347  		TC{
  1348  			name:        "MB missing percentage value (fail)",
  1349  			fs:          "resctrl.nol3",
  1350  			configErrRe: `missing '%' value from mbSchema`,
  1351  			config: `
  1352  partitions:
  1353    part-1:
  1354      mbAllocation: ["100MBps"]
  1355  `,
  1356  		},
  1357  		// Testcase
  1358  		TC{
  1359  			name:        "MB missing from partition (fail)",
  1360  			fs:          "resctrl.nol3",
  1361  			configErrRe: `MB allocation missing from partition "part-1"`,
  1362  			config: `
  1363  partitions:
  1364    part-1:
  1365      classes:
  1366        class-1:
  1367          mbAllocation: ["100%"]
  1368  `,
  1369  		},
  1370  		// Testcase
  1371  		TC{
  1372  			name:        "MB MBps",
  1373  			fs:          "resctrl.nol3.mbps",
  1374  			fsMountOpts: "mba_MBps",
  1375  			config: `
  1376  partitions:
  1377    part-1:
  1378      mbAllocation: ["50%", "1000MBps"]
  1379      classes:
  1380        class-1:
  1381          mbAllocation: ["100%", "1500MBps"]
  1382    part-2:
  1383      mbAllocation:
  1384        all: ["1000MBps"]
  1385        # Unsupported values should just be ignored
  1386        0,1: [50, "1GBps", "500MBps"]
  1387      classes:
  1388        class-2:
  1389          mbAllocation: ["750MBps"]
  1390  `,
  1391  			schemata: map[string]Schemata{
  1392  				"class-1": Schemata{
  1393  					mb: "0=1000;1=1000;2=1000;3=1000",
  1394  				},
  1395  				"class-2": Schemata{
  1396  					mb: "0=500;1=500;2=750;3=750",
  1397  				},
  1398  				"system/default": Schemata{
  1399  					mb: "0=4294967295;1=4294967295;2=4294967295;3=4294967295",
  1400  				},
  1401  			},
  1402  		},
  1403  		// Testcase
  1404  		TC{
  1405  			name:        "MB nan MBps value (fail)",
  1406  			fs:          "resctrl.nol3.mbps",
  1407  			fsMountOpts: "mba_MBps",
  1408  			configErrRe: `failed to resolve MB allocation for partition "part-1":.* invalid syntax`,
  1409  			config: `
  1410  partitions:
  1411    part-1:
  1412      mbAllocation: ["0xffMBps"]
  1413  `,
  1414  		},
  1415  		// Testcase
  1416  		TC{
  1417  			name:        "MB missing MBps value (fail)",
  1418  			fs:          "resctrl.nol3.mbps",
  1419  			fsMountOpts: "mba_MBps",
  1420  			configErrRe: `missing 'MBps' value from mbSchema`,
  1421  			config: `
  1422  partitions:
  1423    part-1:
  1424      mbAllocation: ["100%"]
  1425  `,
  1426  		},
  1427  	}
  1428  
  1429  	verifySchemata := func(tc *TC) {
  1430  		for n, s := range tc.schemata {
  1431  			expected := ""
  1432  			if s.l2 != "" {
  1433  				expected += "L2:" + s.l2 + "\n"
  1434  			}
  1435  			if s.l2code != "" {
  1436  				expected += "L2CODE:" + s.l2code + "\n"
  1437  			}
  1438  			if s.l2data != "" {
  1439  				expected += "L2DATA:" + s.l2data + "\n"
  1440  			}
  1441  			if s.l3 != "" {
  1442  				expected += "L3:" + s.l3 + "\n"
  1443  			}
  1444  			if s.l3code != "" {
  1445  				expected += "L3CODE:" + s.l3code + "\n"
  1446  			}
  1447  			if s.l3data != "" {
  1448  				expected += "L3DATA:" + s.l3data + "\n"
  1449  			}
  1450  			if s.mb != "" {
  1451  				expected += "MB:" + s.mb + "\n"
  1452  			}
  1453  			if c, ok := rdt.classes[n]; !ok {
  1454  				t.Fatalf("verifySchemata: class %q does not exists in %v", n, rdt.classes)
  1455  			} else {
  1456  				verifyTextFile(t, c.path("schemata"), expected)
  1457  			}
  1458  		}
  1459  
  1460  		if len(tc.schemata) != len(rdt.classes) {
  1461  			var a, b []string
  1462  			for n := range tc.schemata {
  1463  				a = append(a, n)
  1464  			}
  1465  			for n := range rdt.classes {
  1466  				b = append(b, n)
  1467  			}
  1468  			t.Fatalf("unexpected set of classes: expected %v, got %v", a, b)
  1469  		}
  1470  	}
  1471  
  1472  	// Set group remove function so that mock groups can be removed
  1473  	groupRemoveFunc = os.RemoveAll
  1474  
  1475  	for _, tc := range tcs {
  1476  		t.Logf("Running test case %q", tc.name)
  1477  
  1478  		mockFs, err := newMockResctrlFs(t, tc.fs, tc.fsMountOpts)
  1479  		if err != nil {
  1480  			t.Fatalf("failed to set up mock resctrl fs: %v", err)
  1481  		}
  1482  		defer mockFs.delete()
  1483  
  1484  		conf := parseTestConfig(t, tc.config)
  1485  		confDataOld, err := yaml.Marshal(conf)
  1486  		if err != nil {
  1487  			t.Fatalf("marshalling config failed: %v", err)
  1488  		}
  1489  
  1490  		if err := Initialize(mockGroupPrefix); err != nil {
  1491  			t.Fatalf("resctrl initialization failed: %v", err)
  1492  		}
  1493  
  1494  		err = SetConfig(conf, false)
  1495  		if tc.configErrRe != "" {
  1496  			if err == nil {
  1497  				t.Fatalf("resctrl configuration succeeded unexpectedly")
  1498  			} else {
  1499  				m, e := regexp.MatchString(tc.configErrRe, err.Error())
  1500  				if e != nil {
  1501  					t.Fatalf("error in regexp matching: %v", e)
  1502  				}
  1503  				if !m {
  1504  					t.Fatalf("unexpected error message:\n  %q\n  does NOT match regexp\n  %q", err.Error(), tc.configErrRe)
  1505  				}
  1506  			}
  1507  		} else {
  1508  			if err != nil {
  1509  				t.Fatalf("resctrl configuration failed: %v", err)
  1510  			}
  1511  			verifySchemata(&tc)
  1512  		}
  1513  
  1514  		if confDataNew, err := yaml.Marshal(conf); err != nil {
  1515  			t.Fatalf("marshalling config failed: %v", err)
  1516  		} else if !cmp.Equal(confDataNew, confDataOld) {
  1517  			t.Fatalf("SetConfig altered config data:\n%s\nVS.\n%s", confDataOld, confDataNew)
  1518  		}
  1519  	}
  1520  }
  1521  
  1522  func TestBitMap(t *testing.T) {
  1523  	// Test ListStr()
  1524  	testSet := map[bitmask]string{
  1525  		0x0:                "",
  1526  		0x1:                "0",
  1527  		0x2:                "1",
  1528  		0xf:                "0-3",
  1529  		0x555:              "0,2,4,6,8,10",
  1530  		0xaaa:              "1,3,5,7,9,11",
  1531  		0x1d1a:             "1,3-4,8,10-12",
  1532  		0xffffffffffffffff: "0-63",
  1533  	}
  1534  	for i, s := range testSet {
  1535  		// Test conversion to string
  1536  		listStr := i.listStr()
  1537  		if listStr != s {
  1538  			t.Errorf("from %#x expected %q, got %q", i, s, listStr)
  1539  		}
  1540  
  1541  		// Test conversion from string
  1542  		b, err := listStrToBitmask(s)
  1543  		if err != nil {
  1544  			t.Errorf("unexpected err when converting %q: %v", s, err)
  1545  		}
  1546  		if b != i {
  1547  			t.Errorf("from %q expected %#x, got %#x", s, i, b)
  1548  		}
  1549  	}
  1550  
  1551  	// Negative tests for ListStrToBitmask
  1552  	negTestSet := []string{
  1553  		",",
  1554  		"-",
  1555  		"1,",
  1556  		",12",
  1557  		"-4",
  1558  		"0-",
  1559  		"13-13",
  1560  		"14-13",
  1561  		"a-2",
  1562  		"b",
  1563  		"3-c",
  1564  		"64",
  1565  		"1,2,,3",
  1566  		"1,2,3-",
  1567  	}
  1568  	for _, s := range negTestSet {
  1569  		b, err := listStrToBitmask(s)
  1570  		if err == nil {
  1571  			t.Errorf("expected err but got %#x when converting %q", b, s)
  1572  		}
  1573  	}
  1574  
  1575  	// Test MarshalJSON
  1576  	if s, err := bitmask(10).MarshalJSON(); err != nil {
  1577  	} else if string(s) != `"0xa"` {
  1578  		t.Errorf(`expected "0xa" but returned %s`, s)
  1579  	}
  1580  }
  1581  
  1582  func TestListStrToArray(t *testing.T) {
  1583  	testSet := map[string][]int{
  1584  		"":              {},
  1585  		"0":             {0},
  1586  		"1":             {1},
  1587  		"0-3":           {0, 1, 2, 3},
  1588  		"4,2,0,6,10,8":  {0, 2, 4, 6, 8, 10},
  1589  		"1,3,5,7,9,11":  {1, 3, 5, 7, 9, 11},
  1590  		"1,3-4,10-12,8": {1, 3, 4, 8, 10, 11, 12},
  1591  	}
  1592  	for s, expected := range testSet {
  1593  		// Test conversion from string to list of integers
  1594  		a, err := listStrToArray(s)
  1595  		if err != nil {
  1596  			t.Errorf("unexpected error when converting %q: %v", s, err)
  1597  		}
  1598  		if !cmp.Equal(a, expected) {
  1599  			t.Errorf("from %q expected %v, got %v", s, expected, a)
  1600  		}
  1601  	}
  1602  
  1603  	// Negative test cases
  1604  	negTestSet := []string{
  1605  		",",
  1606  		"-",
  1607  		"1,",
  1608  		"256",
  1609  		"256-257",
  1610  		"0-256",
  1611  		",12",
  1612  		"-4",
  1613  		"0-",
  1614  		"13-13",
  1615  		"14-13",
  1616  		"a-2",
  1617  		"b",
  1618  		"3-c",
  1619  		"1,2,,3",
  1620  		"1,2,3-",
  1621  	}
  1622  	for _, s := range negTestSet {
  1623  		a, err := listStrToArray(s)
  1624  		if err == nil {
  1625  			t.Errorf("expected err but got %v when converting %q", a, s)
  1626  		}
  1627  	}
  1628  }
  1629  
  1630  // TestCacheAllocation tests the types implementing cacheAllocation interface
  1631  func TestCacheAllocation(t *testing.T) {
  1632  	// Need to setup resctrl and initialize because pct allocations need
  1633  	// the "info" structure
  1634  	mockFs, err := newMockResctrlFs(t, "resctrl.nomb", "")
  1635  	if err != nil {
  1636  		t.Fatalf("failed to set up mock resctrl fs: %v", err)
  1637  	}
  1638  	defer mockFs.delete()
  1639  
  1640  	if err := Initialize(mockGroupPrefix); err != nil {
  1641  		t.Fatalf("resctrl initialization failed: %v", err)
  1642  	}
  1643  
  1644  	// Test absolute allocation
  1645  	abs := catAbsoluteAllocation(0x7)
  1646  	if res, err := abs.Overlay(0xf00, 1); err != nil {
  1647  		t.Errorf("unexpected error when overlaying catAbsoluteAllocation: %v", err)
  1648  	} else if res != 0x700 {
  1649  		t.Errorf("expected 0x700 but got %#x when overlaying catAbsoluteAllocation", res)
  1650  	}
  1651  
  1652  	if _, err := abs.Overlay(0, 1); err == nil {
  1653  		t.Errorf("unexpected success when overlaying catAbsoluteAllocation with empty basemask")
  1654  	}
  1655  
  1656  	if _, err := abs.Overlay(0x30, 1); err == nil {
  1657  		t.Errorf("unexpected success when overlaying too wide catAbsoluteAllocation")
  1658  	}
  1659  
  1660  	if _, err := abs.Overlay(0xf0f, 1); err == nil {
  1661  		t.Errorf("unexpected success when overlaying catAbsoluteAllocation with non-contiguous basemask")
  1662  	}
  1663  
  1664  	if _, err := catAbsoluteAllocation(0x1).Overlay(0x10, 2); err == nil {
  1665  		t.Errorf("unexpected success when overlaying catAbsoluteAllocation with too small basemask")
  1666  	}
  1667  
  1668  	// Test percentage allocation
  1669  	if res, err := (catPctRangeAllocation{lowPct: 0, highPct: 100}).Overlay(0xff00, 4); err != nil {
  1670  		t.Errorf("unexpected error when overlaying catPctAllocation: %v", err)
  1671  	} else if res != 0xff00 {
  1672  		t.Errorf("expected 0xff00 but got %#x when overlaying catPctAllocation", res)
  1673  	}
  1674  	if res, err := (catPctRangeAllocation{lowPct: 99, highPct: 100}).Overlay(0xff00, 4); err != nil {
  1675  		t.Errorf("unexpected error when overlaying catPctAllocation: %v", err)
  1676  	} else if res != 0xf000 {
  1677  		t.Errorf("expected 0xf000 but got %#x when overlaying catPctAllocation", res)
  1678  	}
  1679  	if res, err := (catPctRangeAllocation{lowPct: 0, highPct: 1}).Overlay(0xff00, 4); err != nil {
  1680  		t.Errorf("unexpected error when overlaying catPctAllocation: %v", err)
  1681  	} else if res != 0xf00 {
  1682  		t.Errorf("expected 0xf00 but got %#x when overlaying catPctAllocation", res)
  1683  	}
  1684  	if res, err := (catPctRangeAllocation{lowPct: 20, highPct: 30}).Overlay(0x3ff00, 4); err != nil {
  1685  		t.Errorf("unexpected error when overlaying catPctAllocation: %v", err)
  1686  	} else if res != 0xf00 {
  1687  		t.Errorf("expected 0xf00 but got %#x when overlaying catPctAllocation", res)
  1688  	}
  1689  	if res, err := (catPctRangeAllocation{lowPct: 30, highPct: 60}).Overlay(0xf00, 4); err != nil {
  1690  		t.Errorf("unexpected error when overlaying catPctAllocation: %v", err)
  1691  	} else if res != 0xf00 {
  1692  		t.Errorf("expected 0xf00 but got %#x when overlaying catPctAllocation", res)
  1693  	}
  1694  	if _, err := (catPctRangeAllocation{lowPct: 20, highPct: 10}).Overlay(0xff00, 4); err == nil {
  1695  		t.Errorf("unexpected success when overlaying catPctAllocation of invalid percentage range")
  1696  	}
  1697  	if _, err := (catPctRangeAllocation{lowPct: 0, highPct: 100}).Overlay(0, 4); err == nil {
  1698  		t.Errorf("unexpected success when overlaying catPctAllocation of invalid percentage range")
  1699  	}
  1700  }
  1701  
  1702  func TestCacheProportion(t *testing.T) {
  1703  	// Test percentage
  1704  	if a, err := CacheProportion("10%").parse(2); err != nil {
  1705  		t.Errorf("unexpected error when parsing cache allocation: %v", err)
  1706  	} else if a != catPctAllocation(10) {
  1707  		t.Errorf("expected 10%% but got %d%%", a)
  1708  	}
  1709  	if _, err := CacheProportion("1a%").parse(2); err == nil {
  1710  		t.Errorf("unexpected success when parsing percentage cache allocation")
  1711  	}
  1712  	if _, err := CacheProportion("101%").parse(2); err == nil {
  1713  		t.Errorf("unexpected success when parsing percentage cache allocation")
  1714  	}
  1715  
  1716  	// Test percentage ranges
  1717  	if a, err := CacheProportion("10-20%").parse(2); err != nil {
  1718  		t.Errorf("unexpected error when parsing cache allocation: %v", err)
  1719  	} else if a != (catPctRangeAllocation{lowPct: 10, highPct: 20}) {
  1720  		t.Errorf("expected {10 20} but got %v", a)
  1721  	}
  1722  	if _, err := CacheProportion("a-100%").parse(2); err == nil {
  1723  		t.Errorf("unexpected success when parsing percentage range cache allocation")
  1724  	}
  1725  	if _, err := CacheProportion("0-1f%").parse(2); err == nil {
  1726  		t.Errorf("unexpected success when parsing percentage range cache allocation")
  1727  	}
  1728  	if _, err := CacheProportion("20-10%").parse(2); err == nil {
  1729  		t.Errorf("unexpected success when parsing percentage range cache allocation")
  1730  	}
  1731  	if _, err := CacheProportion("20-101%").parse(2); err == nil {
  1732  		t.Errorf("unexpected success when parsing percentage range cache allocation")
  1733  	}
  1734  
  1735  	// Test bitmask
  1736  	if a, err := CacheProportion("0xf0").parse(2); err != nil {
  1737  		t.Errorf("unexpected error when parsing cache allocation: %v", err)
  1738  	} else if a != catAbsoluteAllocation(0xf0) {
  1739  		t.Errorf("expected 0xf0 but got %#x", a)
  1740  	}
  1741  	if _, err := CacheProportion("0x40").parse(2); err == nil {
  1742  		t.Errorf("unexpected success when parsing bitmask cache allocation")
  1743  	}
  1744  	if _, err := CacheProportion("0x11").parse(2); err == nil {
  1745  		t.Errorf("unexpected success when parsing bitmask cache allocation")
  1746  	}
  1747  	if _, err := CacheProportion("0xg").parse(2); err == nil {
  1748  		t.Errorf("unexpected success when parsing bitmask cache allocation")
  1749  	}
  1750  
  1751  	// Test bit numbers
  1752  	if a, err := CacheProportion("3,4,5-7,8").parse(2); err != nil {
  1753  		t.Errorf("unexpected error when parsing cache allocation: %v", err)
  1754  	} else if a != catAbsoluteAllocation(0x1f8) {
  1755  		t.Errorf("expected 0x1f8 but got %#x", a)
  1756  	}
  1757  	if _, err := CacheProportion("3,5").parse(2); err == nil {
  1758  		t.Errorf("unexpected success when parsing bitmask cache allocation")
  1759  	}
  1760  	if _, err := CacheProportion("1").parse(2); err == nil {
  1761  		t.Errorf("unexpected success when parsing bitmask cache allocation")
  1762  	}
  1763  	if _, err := CacheProportion("3-x").parse(2); err == nil {
  1764  		t.Errorf("unexpected success when parsing bitmask cache allocation")
  1765  	}
  1766  }
  1767  
  1768  func TestIsQualifiedClassName(t *testing.T) {
  1769  	tcs := map[string]bool{
  1770  		"foo":          true,
  1771  		RootClassName:  true,
  1772  		RootClassAlias: true,
  1773  		".":            false,
  1774  		"..":           false,
  1775  		"foo/bar":      false,
  1776  		"foo\n":        false,
  1777  	}
  1778  
  1779  	for name, expected := range tcs {
  1780  		if r := IsQualifiedClassName(name); r != expected {
  1781  			t.Errorf("IsQualifiedClassName(%q) returned %v (expected %v)", name, r, expected)
  1782  		}
  1783  	}
  1784  }