k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/cm/dra/plugin/client_test.go (about) 1 /* 2 Copyright 2023 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 plugin 18 19 import ( 20 "context" 21 "fmt" 22 "net" 23 "os" 24 "path/filepath" 25 "sync" 26 "testing" 27 28 "github.com/stretchr/testify/assert" 29 "google.golang.org/grpc" 30 drapbv1alpha3 "k8s.io/kubelet/pkg/apis/dra/v1alpha3" 31 ) 32 33 type fakeV1alpha3GRPCServer struct { 34 drapbv1alpha3.UnimplementedNodeServer 35 } 36 37 var _ drapbv1alpha3.NodeServer = &fakeV1alpha3GRPCServer{} 38 39 func (f *fakeV1alpha3GRPCServer) NodePrepareResources(ctx context.Context, in *drapbv1alpha3.NodePrepareResourcesRequest) (*drapbv1alpha3.NodePrepareResourcesResponse, error) { 40 return &drapbv1alpha3.NodePrepareResourcesResponse{Claims: map[string]*drapbv1alpha3.NodePrepareResourceResponse{"dummy": {CDIDevices: []string{"dummy"}}}}, nil 41 } 42 43 func (f *fakeV1alpha3GRPCServer) NodeUnprepareResources(ctx context.Context, in *drapbv1alpha3.NodeUnprepareResourcesRequest) (*drapbv1alpha3.NodeUnprepareResourcesResponse, error) { 44 45 return &drapbv1alpha3.NodeUnprepareResourcesResponse{}, nil 46 } 47 48 func (f *fakeV1alpha3GRPCServer) NodeListAndWatchResources(req *drapbv1alpha3.NodeListAndWatchResourcesRequest, srv drapbv1alpha3.Node_NodeListAndWatchResourcesServer) error { 49 if err := srv.Send(&drapbv1alpha3.NodeListAndWatchResourcesResponse{}); err != nil { 50 return err 51 } 52 if err := srv.Send(&drapbv1alpha3.NodeListAndWatchResourcesResponse{}); err != nil { 53 return err 54 } 55 return nil 56 } 57 58 type tearDown func() 59 60 func setupFakeGRPCServer(version string) (string, tearDown, error) { 61 p, err := os.MkdirTemp("", "dra_plugin") 62 if err != nil { 63 return "", nil, err 64 } 65 66 closeCh := make(chan struct{}) 67 addr := filepath.Join(p, "server.sock") 68 teardown := func() { 69 close(closeCh) 70 os.RemoveAll(addr) 71 } 72 73 listener, err := net.Listen("unix", addr) 74 if err != nil { 75 teardown() 76 return "", nil, err 77 } 78 79 s := grpc.NewServer() 80 switch version { 81 case v1alpha3Version: 82 fakeGRPCServer := &fakeV1alpha3GRPCServer{} 83 drapbv1alpha3.RegisterNodeServer(s, fakeGRPCServer) 84 default: 85 return "", nil, fmt.Errorf("unsupported version: %s", version) 86 } 87 88 go func() { 89 go s.Serve(listener) 90 <-closeCh 91 s.GracefulStop() 92 }() 93 94 return addr, teardown, nil 95 } 96 97 func TestGRPCConnIsReused(t *testing.T) { 98 addr, teardown, err := setupFakeGRPCServer(v1alpha3Version) 99 if err != nil { 100 t.Fatal(err) 101 } 102 defer teardown() 103 104 reusedConns := make(map[*grpc.ClientConn]int) 105 wg := sync.WaitGroup{} 106 m := sync.Mutex{} 107 108 p := &plugin{ 109 endpoint: addr, 110 } 111 112 conn, err := p.getOrCreateGRPCConn() 113 defer func() { 114 err := conn.Close() 115 if err != nil { 116 t.Error(err) 117 } 118 }() 119 if err != nil { 120 t.Fatal(err) 121 } 122 123 // ensure the plugin we are using is registered 124 draPlugins.add("dummy-plugin", p) 125 defer draPlugins.delete("dummy-plugin") 126 127 // we call `NodePrepareResource` 2 times and check whether a new connection is created or the same is reused 128 for i := 0; i < 2; i++ { 129 wg.Add(1) 130 go func() { 131 defer wg.Done() 132 client, err := NewDRAPluginClient("dummy-plugin") 133 if err != nil { 134 t.Error(err) 135 return 136 } 137 138 req := &drapbv1alpha3.NodePrepareResourcesRequest{ 139 Claims: []*drapbv1alpha3.Claim{ 140 { 141 Namespace: "dummy-namespace", 142 Uid: "dummy-uid", 143 Name: "dummy-claim", 144 ResourceHandle: "dummy-resource", 145 }, 146 }, 147 } 148 client.NodePrepareResources(context.TODO(), req) 149 150 client.(*plugin).Lock() 151 conn := client.(*plugin).conn 152 client.(*plugin).Unlock() 153 154 m.Lock() 155 defer m.Unlock() 156 reusedConns[conn]++ 157 }() 158 } 159 160 wg.Wait() 161 // We should have only one entry otherwise it means another gRPC connection has been created 162 if len(reusedConns) != 1 { 163 t.Errorf("expected length to be 1 but got %d", len(reusedConns)) 164 } 165 if counter, ok := reusedConns[conn]; ok && counter != 2 { 166 t.Errorf("expected counter to be 2 but got %d", counter) 167 } 168 } 169 170 func TestNewDRAPluginClient(t *testing.T) { 171 for _, test := range []struct { 172 description string 173 setup func(string) tearDown 174 pluginName string 175 shouldError bool 176 }{ 177 { 178 description: "plugin name is empty", 179 setup: func(_ string) tearDown { 180 return func() {} 181 }, 182 pluginName: "", 183 shouldError: true, 184 }, 185 { 186 description: "plugin name not found in the list", 187 setup: func(_ string) tearDown { 188 return func() {} 189 }, 190 pluginName: "plugin-name-not-found-in-the-list", 191 shouldError: true, 192 }, 193 { 194 description: "plugin exists", 195 setup: func(name string) tearDown { 196 draPlugins.add(name, &plugin{}) 197 return func() { 198 draPlugins.delete(name) 199 } 200 }, 201 pluginName: "dummy-plugin", 202 }, 203 } { 204 t.Run(test.description, func(t *testing.T) { 205 teardown := test.setup(test.pluginName) 206 defer teardown() 207 208 client, err := NewDRAPluginClient(test.pluginName) 209 if test.shouldError { 210 assert.Nil(t, client) 211 assert.Error(t, err) 212 } else { 213 assert.NotNil(t, client) 214 assert.Nil(t, err) 215 } 216 }) 217 } 218 } 219 220 func TestNodeUnprepareResources(t *testing.T) { 221 for _, test := range []struct { 222 description string 223 serverSetup func(string) (string, tearDown, error) 224 serverVersion string 225 request *drapbv1alpha3.NodeUnprepareResourcesRequest 226 }{ 227 { 228 description: "server supports v1alpha3", 229 serverSetup: setupFakeGRPCServer, 230 serverVersion: v1alpha3Version, 231 request: &drapbv1alpha3.NodeUnprepareResourcesRequest{}, 232 }, 233 } { 234 t.Run(test.description, func(t *testing.T) { 235 addr, teardown, err := setupFakeGRPCServer(test.serverVersion) 236 if err != nil { 237 t.Fatal(err) 238 } 239 defer teardown() 240 241 p := &plugin{ 242 endpoint: addr, 243 clientTimeout: PluginClientTimeout, 244 } 245 246 conn, err := p.getOrCreateGRPCConn() 247 defer func() { 248 err := conn.Close() 249 if err != nil { 250 t.Error(err) 251 } 252 }() 253 if err != nil { 254 t.Fatal(err) 255 } 256 257 draPlugins.add("dummy-plugin", p) 258 defer draPlugins.delete("dummy-plugin") 259 260 client, err := NewDRAPluginClient("dummy-plugin") 261 if err != nil { 262 t.Fatal(err) 263 } 264 265 _, err = client.NodeUnprepareResources(context.TODO(), test.request) 266 if err != nil { 267 t.Fatal(err) 268 } 269 }) 270 } 271 } 272 273 func TestListAndWatchResources(t *testing.T) { 274 for _, test := range []struct { 275 description string 276 serverSetup func(string) (string, tearDown, error) 277 serverVersion string 278 request *drapbv1alpha3.NodeListAndWatchResourcesRequest 279 responses []*drapbv1alpha3.NodeListAndWatchResourcesResponse 280 expectError string 281 }{ 282 { 283 description: "server supports NodeResources API", 284 serverSetup: setupFakeGRPCServer, 285 serverVersion: v1alpha3Version, 286 request: &drapbv1alpha3.NodeListAndWatchResourcesRequest{}, 287 responses: []*drapbv1alpha3.NodeListAndWatchResourcesResponse{ 288 {}, 289 {}, 290 }, 291 expectError: "EOF", 292 }, 293 } { 294 t.Run(test.description, func(t *testing.T) { 295 addr, teardown, err := setupFakeGRPCServer(test.serverVersion) 296 if err != nil { 297 t.Fatal(err) 298 } 299 defer teardown() 300 301 p := &plugin{ 302 endpoint: addr, 303 } 304 305 conn, err := p.getOrCreateGRPCConn() 306 defer func() { 307 err := conn.Close() 308 if err != nil { 309 t.Error(err) 310 } 311 }() 312 if err != nil { 313 t.Fatal(err) 314 } 315 316 draPlugins.add("dummy-plugin", p) 317 defer draPlugins.delete("dummy-plugin") 318 319 client, err := NewDRAPluginClient("dummy-plugin") 320 if err != nil { 321 t.Fatal(err) 322 } 323 324 stream, err := client.NodeListAndWatchResources(context.Background(), test.request) 325 if err != nil { 326 t.Fatal(err) 327 } 328 var actualResponses []*drapbv1alpha3.NodeListAndWatchResourcesResponse 329 var actualErr error 330 for { 331 resp, err := stream.Recv() 332 if err != nil { 333 actualErr = err 334 break 335 } 336 actualResponses = append(actualResponses, resp) 337 } 338 assert.Equal(t, test.responses, actualResponses) 339 assert.Contains(t, actualErr.Error(), test.expectError) 340 }) 341 } 342 }