k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/framework/plugins/dynamicresources/structured/namedresources/namedresourcesmodel_test.go (about) 1 /* 2 Copyright 2024 The Kubernetes Authors. 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 namedresources 18 19 import ( 20 "testing" 21 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 25 resourceapi "k8s.io/api/resource/v1alpha2" 26 "k8s.io/kubernetes/test/utils/ktesting" 27 "k8s.io/utils/ptr" 28 ) 29 30 func instance(allocated bool, name string, attributes ...resourceapi.NamedResourcesAttribute) InstanceAllocation { 31 return InstanceAllocation{ 32 Allocated: allocated, 33 Instance: &resourceapi.NamedResourcesInstance{ 34 Name: name, 35 Attributes: attributes, 36 }, 37 } 38 } 39 40 func TestModel(t *testing.T) { 41 testcases := map[string]struct { 42 resources []*resourceapi.NamedResourcesResources 43 allocations []*resourceapi.NamedResourcesAllocationResult 44 45 expectModel Model 46 }{ 47 "empty": {}, 48 49 "nil": { 50 resources: []*resourceapi.NamedResourcesResources{nil}, 51 allocations: []*resourceapi.NamedResourcesAllocationResult{nil}, 52 }, 53 54 "available": { 55 resources: []*resourceapi.NamedResourcesResources{ 56 { 57 Instances: []resourceapi.NamedResourcesInstance{ 58 {Name: "a"}, 59 {Name: "b"}, 60 }, 61 }, 62 { 63 Instances: []resourceapi.NamedResourcesInstance{ 64 {Name: "x"}, 65 {Name: "y"}, 66 }, 67 }, 68 }, 69 70 expectModel: Model{Instances: []InstanceAllocation{instance(false, "a"), instance(false, "b"), instance(false, "x"), instance(false, "y")}}, 71 }, 72 73 "allocated": { 74 resources: []*resourceapi.NamedResourcesResources{ 75 { 76 Instances: []resourceapi.NamedResourcesInstance{ 77 {Name: "a"}, 78 {Name: "b"}, 79 }, 80 }, 81 { 82 Instances: []resourceapi.NamedResourcesInstance{ 83 {Name: "x"}, 84 {Name: "y"}, 85 }, 86 }, 87 }, 88 allocations: []*resourceapi.NamedResourcesAllocationResult{ 89 { 90 Name: "something-else", 91 }, 92 { 93 Name: "a", 94 }, 95 }, 96 97 expectModel: Model{Instances: []InstanceAllocation{instance(true, "a"), instance(false, "b"), instance(false, "x"), instance(false, "y")}}, 98 }, 99 } 100 101 for name, tc := range testcases { 102 t.Run(name, func(t *testing.T) { 103 var actualModel Model 104 for _, resources := range tc.resources { 105 AddResources(&actualModel, resources) 106 } 107 for _, allocation := range tc.allocations { 108 AddAllocation(&actualModel, allocation) 109 } 110 111 require.Equal(t, tc.expectModel, actualModel) 112 }) 113 } 114 115 } 116 117 func TestController(t *testing.T) { 118 filterAny := &resourceapi.NamedResourcesFilter{ 119 Selector: "true", 120 } 121 filterNone := &resourceapi.NamedResourcesFilter{ 122 Selector: "false", 123 } 124 filterBrokenType := &resourceapi.NamedResourcesFilter{ 125 Selector: "1", 126 } 127 filterBrokenEvaluation := &resourceapi.NamedResourcesFilter{ 128 Selector: `attributes.bool["no-such-attribute"]`, 129 } 130 filterAttribute := &resourceapi.NamedResourcesFilter{ 131 Selector: `attributes.bool["usable"]`, 132 } 133 134 requestAny := &resourceapi.NamedResourcesRequest{ 135 Selector: "true", 136 } 137 requestNone := &resourceapi.NamedResourcesRequest{ 138 Selector: "false", 139 } 140 requestBrokenType := &resourceapi.NamedResourcesRequest{ 141 Selector: "1", 142 } 143 requestBrokenEvaluation := &resourceapi.NamedResourcesRequest{ 144 Selector: `attributes.bool["no-such-attribute"]`, 145 } 146 requestAttribute := &resourceapi.NamedResourcesRequest{ 147 Selector: `attributes.bool["usable"]`, 148 } 149 150 instance1 := "instance-1" 151 oneInstance := Model{ 152 Instances: []InstanceAllocation{{ 153 Instance: &resourceapi.NamedResourcesInstance{ 154 Name: instance1, 155 }, 156 }}, 157 } 158 159 instance2 := "instance-2" 160 twoInstances := Model{ 161 Instances: []InstanceAllocation{ 162 { 163 Instance: &resourceapi.NamedResourcesInstance{ 164 Name: instance1, 165 Attributes: []resourceapi.NamedResourcesAttribute{{ 166 Name: "usable", 167 NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{ 168 BoolValue: ptr.To(false), 169 }, 170 }}, 171 }, 172 }, 173 { 174 Instance: &resourceapi.NamedResourcesInstance{ 175 Name: instance2, 176 Attributes: []resourceapi.NamedResourcesAttribute{{ 177 Name: "usable", 178 NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{ 179 BoolValue: ptr.To(true), 180 }, 181 }}, 182 }, 183 }, 184 }, 185 } 186 187 testcases := map[string]struct { 188 model Model 189 filter *resourceapi.NamedResourcesFilter 190 requests []*resourceapi.NamedResourcesRequest 191 192 expectCreateErr bool 193 expectAllocation []string 194 expectAllocateErr bool 195 }{ 196 "empty": {}, 197 198 "broken-filter": { 199 filter: filterBrokenType, 200 201 expectCreateErr: true, 202 }, 203 204 "broken-request": { 205 requests: []*resourceapi.NamedResourcesRequest{requestBrokenType}, 206 207 expectCreateErr: true, 208 }, 209 210 "no-resources": { 211 filter: filterAny, 212 requests: []*resourceapi.NamedResourcesRequest{requestAny}, 213 214 expectAllocateErr: true, 215 }, 216 217 "okay": { 218 model: oneInstance, 219 filter: filterAny, 220 requests: []*resourceapi.NamedResourcesRequest{requestAny}, 221 222 expectAllocation: []string{instance1}, 223 }, 224 225 "filter-mismatch": { 226 model: oneInstance, 227 filter: filterNone, 228 requests: []*resourceapi.NamedResourcesRequest{requestAny}, 229 230 expectAllocateErr: true, 231 }, 232 233 "request-mismatch": { 234 model: oneInstance, 235 filter: filterAny, 236 requests: []*resourceapi.NamedResourcesRequest{requestNone}, 237 238 expectAllocateErr: true, 239 }, 240 241 "many": { 242 model: twoInstances, 243 filter: filterAny, 244 requests: []*resourceapi.NamedResourcesRequest{requestAny, requestAny}, 245 246 expectAllocation: []string{instance1, instance2}, 247 }, 248 249 "too-many": { 250 model: oneInstance, 251 filter: filterAny, 252 requests: []*resourceapi.NamedResourcesRequest{requestAny, requestAny}, 253 254 expectAllocateErr: true, 255 }, 256 257 "filter-evaluation-error": { 258 model: oneInstance, 259 filter: filterBrokenEvaluation, 260 requests: []*resourceapi.NamedResourcesRequest{requestAny}, 261 262 expectAllocateErr: true, 263 }, 264 265 "request-evaluation-error": { 266 model: oneInstance, 267 filter: filterAny, 268 requests: []*resourceapi.NamedResourcesRequest{requestBrokenEvaluation}, 269 270 expectAllocateErr: true, 271 }, 272 273 "filter-attribute": { 274 model: twoInstances, 275 filter: filterAttribute, 276 requests: []*resourceapi.NamedResourcesRequest{requestAny}, 277 278 expectAllocation: []string{instance2}, 279 }, 280 281 "request-attribute": { 282 model: twoInstances, 283 filter: filterAny, 284 requests: []*resourceapi.NamedResourcesRequest{requestAttribute}, 285 286 expectAllocation: []string{instance2}, 287 }, 288 } 289 290 for name, tc := range testcases { 291 t.Run(name, func(t *testing.T) { 292 tCtx := ktesting.Init(t) 293 294 controller, createErr := NewClaimController(tc.filter, tc.requests) 295 if createErr != nil { 296 if !tc.expectCreateErr { 297 tCtx.Fatalf("unexpected create error: %v", createErr) 298 } 299 return 300 } 301 if tc.expectCreateErr { 302 tCtx.Fatalf("did not get expected create error") 303 } 304 305 allocation, createErr := controller.Allocate(tCtx, tc.model) 306 if createErr != nil { 307 if !tc.expectAllocateErr { 308 tCtx.Fatalf("unexpected allocate error: %v", createErr) 309 } 310 return 311 } 312 if tc.expectAllocateErr { 313 tCtx.Fatalf("did not get expected allocate error") 314 } 315 316 expectAllocation := []*resourceapi.NamedResourcesAllocationResult{} 317 for _, name := range tc.expectAllocation { 318 expectAllocation = append(expectAllocation, &resourceapi.NamedResourcesAllocationResult{Name: name}) 319 } 320 require.Equal(tCtx, expectAllocation, allocation) 321 322 isSuitable, isSuitableErr := controller.NodeIsSuitable(tCtx, tc.model) 323 assert.Equal(tCtx, len(expectAllocation) == len(tc.requests), isSuitable, "is suitable") 324 assert.Equal(tCtx, createErr, isSuitableErr) 325 }) 326 } 327 }