Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions pkg/cdi/container-edits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,24 @@ func TestApplyContainerEdits(t *testing.T) {
},
},
},
{
name: "empty spec, legacy rdt monitoring",
spec: &oci.Spec{},
edits: &cdi.ContainerEdits{
IntelRdt: &cdi.IntelRdt{
ClosID: "clos-1",
EnableCMT: true,
},
},
result: &oci.Spec{
Linux: &oci.Linux{
IntelRdt: &oci.LinuxIntelRdt{
ClosID: "clos-1",
EnableMonitoring: true,
},
},
},
},
{
name: "non-empty spec, overriding rdt",
spec: &oci.Spec{
Expand Down
2 changes: 1 addition & 1 deletion pkg/cdi/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (i *IntelRdt) toOCI() *spec.LinuxIntelRdt {
L3CacheSchema: i.L3CacheSchema,
MemBwSchema: i.MemBwSchema,
Schemata: i.Schemata,
EnableMonitoring: i.EnableMonitoring,
EnableMonitoring: i.EnableMonitoring || i.EnableCMT || i.EnableMBM,
}
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/cdi/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,14 @@ func (s *Spec) validate() (map[string]*Device, error) {

// ParseSpec parses CDI Spec data into a raw CDI Spec.
func ParseSpec(data []byte) (*cdi.Spec, error) {
var contents map[string]interface{}
if err := yaml.UnmarshalStrict(data, &contents); err != nil {
return nil, fmt.Errorf("failed to unmarshal CDI Spec: %w", err)
}
if err := cdi.ValidateVersionContents(contents); err != nil {
return nil, fmt.Errorf("failed to validate CDI Spec version: %w", err)
}

var raw *cdi.Spec
err := yaml.UnmarshalStrict(data, &raw)
if err != nil {
Expand Down
73 changes: 73 additions & 0 deletions pkg/cdi/spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,79 @@ devices:
}
}

func TestParseSpecIntelRdtVersionCompatibility(t *testing.T) {
baseSpec := `
cdiVersion: "%s"
kind: vendor.com/device
containerEdits:
intelRdt:
%s
devices:
- name: "dev1"
containerEdits:
deviceNodes:
- path: "/dev/null"
`

for _, tc := range []struct {
name string
version string
intelRdt string
shouldParse bool
shouldValidate bool
enableCMT bool
}{
{
name: "legacy CMT is accepted before v1.1.0",
version: "1.0.0",
intelRdt: " enableCMT: true",
shouldParse: true,
shouldValidate: true,
enableCMT: true,
},
{
name: "legacy CMT is rejected for v1.1.0",
version: "1.1.0",
intelRdt: " enableCMT: true",
shouldParse: false,
shouldValidate: false,
},
{
name: "new monitoring field is rejected before v1.1.0",
version: "1.0.0",
intelRdt: " enableMonitoring: false",
shouldParse: false,
shouldValidate: false,
},
{
name: "unknown RDT fields are still rejected",
version: "1.0.0",
intelRdt: " unknown: true",
shouldParse: false,
shouldValidate: false,
},
} {
t.Run(tc.name, func(t *testing.T) {
raw, err := ParseSpec([]byte(fmt.Sprintf(baseSpec, tc.version, tc.intelRdt)))
if !tc.shouldParse {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.enableCMT, raw.ContainerEdits.IntelRdt.EnableCMT)

spec, err := newSpec(raw, tc.name, 0)
if !tc.shouldValidate {
require.Error(t, err)
require.Nil(t, spec)
return
}
require.NoError(t, err)
require.NotNil(t, spec)
})
}
}

func TestWriteSpec(t *testing.T) {
type testCase struct {
name string
Expand Down
20 changes: 12 additions & 8 deletions schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,11 @@ func Load(source string) (*Schema, error) {

// ReadAndValidate all data from the given reader, using the schema for validation.
func (s *Schema) ReadAndValidate(r io.Reader) ([]byte, error) {
loader, reader := schema.NewReaderLoader(r)
data, err := io.ReadAll(reader)
data, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("failed to read data for validation: %w", err)
}
return data, s.validate(loader)
return data, s.ValidateData(data)
}

// ValidateReader validates the data read from an io.Reader against the schema.
Expand All @@ -197,6 +196,11 @@ func (s *Schema) ValidateData(data []byte) error {
if err != nil {
return fmt.Errorf("failed to JSON remarshal data for validation: %w", err)
}
} else {
err = json.Unmarshal(data, &any)
if err != nil {
return fmt.Errorf("failed to JSON unmarshal data for validation: %w", err)
}
}

if err := s.validate(schema.NewBytesLoader(data)); err != nil {
Expand All @@ -208,10 +212,6 @@ func (s *Schema) ValidateData(data []byte) error {

// ValidateFile validates the given JSON file against the schema.
func (s *Schema) ValidateFile(path string) error {
if filepath.Ext(path) == ".json" {
return s.validate(schema.NewReferenceLoader("file://" + path))
}

data, err := os.ReadFile(path)
if err != nil {
return err
Expand Down Expand Up @@ -308,12 +308,16 @@ func (c schemaContents) getDevices() ([]schemaContents, error) {

// validateContents performs additional validation against the schema contents.
func (s *Schema) validateContents(any map[string]interface{}) error {
if any == nil || s == nil {
if any == nil || s == nil || s.schema == nil {
return nil
}

contents := schemaContents(any)

if err := cdi.ValidateVersionContents(any); err != nil {
return err
}

if specAnnotations, ok := contents.getAnnotations(); ok {
if err := validation.ValidateSpecAnnotations("", specAnnotations); err != nil {
return err
Expand Down
82 changes: 82 additions & 0 deletions schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,88 @@ func TestValidateData(t *testing.T) {
}
}

func TestValidateDataIntelRdtVersionCompatibility(t *testing.T) {
scm := loadSchema(t, schema.BuiltinSchemaName)

for _, tc := range []struct {
name string
data string
wantErr bool
}{
{
name: "legacy field is valid before v1.1.0",
data: `
cdiVersion: "1.0.0"
kind: "vendor.com/device"
containerEdits:
intelRdt:
enableCMT: true
devices:
- name: "gpu0"
containerEdits:
deviceNodes:
- path: "/dev/null"
`,
},
{
name: "legacy field is invalid for v1.1.0 even when false",
data: `
cdiVersion: "1.1.0"
kind: "vendor.com/device"
containerEdits:
intelRdt:
enableMBM: false
devices:
- name: "gpu0"
containerEdits:
deviceNodes:
- path: "/dev/null"
`,
wantErr: true,
},
{
name: "new monitoring field is invalid before v1.1.0 even when false",
data: `
cdiVersion: "1.0.0"
kind: "vendor.com/device"
containerEdits:
intelRdt:
enableMonitoring: false
devices:
- name: "gpu0"
containerEdits:
deviceNodes:
- path: "/dev/null"
`,
wantErr: true,
},
{
name: "device scoped new RDT field is invalid before v1.1.0",
data: `
cdiVersion: "1.0.0"
kind: "vendor.com/device"
devices:
- name: "gpu0"
containerEdits:
intelRdt:
schemata: []
deviceNodes:
- path: "/dev/null"
`,
wantErr: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
err := scm.ValidateData([]byte(tc.data))
if tc.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
})
}
}

func TestValidateReader(t *testing.T) {
type testCase struct {
testName string
Expand Down
2 changes: 2 additions & 0 deletions specs-go/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ type IntelRdt struct {
ClosID string `json:"closID,omitempty" yaml:"closID,omitempty"`
L3CacheSchema string `json:"l3CacheSchema,omitempty" yaml:"l3CacheSchema,omitempty"`
MemBwSchema string `json:"memBwSchema,omitempty" yaml:"memBwSchema,omitempty"`
EnableCMT bool `json:"enableCMT,omitempty" yaml:"enableCMT,omitempty"` // Added in v0.7.0. Removed in v1.1.0.
EnableMBM bool `json:"enableMBM,omitempty" yaml:"enableMBM,omitempty"` // Added in v0.7.0. Removed in v1.1.0.
Schemata []string `json:"schemata,omitempty" yaml:"schemata,omitempty"` // Added in v1.1.0.
EnableMonitoring bool `json:"enableMonitoring,omitempty" yaml:"enableMonitoring,omitempty"` // Added in v1.1.0.
}
Expand Down
Loading