k8s.io/kubernetes@v1.29.3/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 drapbv1alpha2 "k8s.io/kubelet/pkg/apis/dra/v1alpha2" 31 drapbv1alpha3 "k8s.io/kubelet/pkg/apis/dra/v1alpha3" 32 ) 33 34 type fakeV1alpha3GRPCServer struct { 35 drapbv1alpha3.UnimplementedNodeServer 36 } 37 38 func (f *fakeV1alpha3GRPCServer) NodePrepareResource(ctx context.Context, in *drapbv1alpha3.NodePrepareResourcesRequest) (*drapbv1alpha3.NodePrepareResourcesResponse, error) { 39 return &drapbv1alpha3.NodePrepareResourcesResponse{Claims: map[string]*drapbv1alpha3.NodePrepareResourceResponse{"dummy": {CDIDevices: []string{"dummy"}}}}, nil 40 } 41 42 func (f *fakeV1alpha3GRPCServer) NodeUnprepareResource(ctx context.Context, in *drapbv1alpha3.NodeUnprepareResourcesRequest) (*drapbv1alpha3.NodeUnprepareResourcesResponse, error) { 43 return &drapbv1alpha3.NodeUnprepareResourcesResponse{}, nil 44 } 45 46 type fakeV1alpha2GRPCServer struct { 47 drapbv1alpha2.UnimplementedNodeServer 48 } 49 50 func (f *fakeV1alpha2GRPCServer) NodePrepareResource(ctx context.Context, in *drapbv1alpha2.NodePrepareResourceRequest) (*drapbv1alpha2.NodePrepareResourceResponse, error) { 51 return &drapbv1alpha2.NodePrepareResourceResponse{CdiDevices: []string{"dummy"}}, nil 52 } 53 54 func (f *fakeV1alpha2GRPCServer) NodeUnprepareResource(ctx context.Context, in *drapbv1alpha2.NodeUnprepareResourceRequest) (*drapbv1alpha2.NodeUnprepareResourceResponse, error) { 55 return &drapbv1alpha2.NodeUnprepareResourceResponse{}, 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 v1alpha2Version: 82 fakeGRPCServer := &fakeV1alpha2GRPCServer{} 83 drapbv1alpha2.RegisterNodeServer(s, fakeGRPCServer) 84 case v1alpha3Version: 85 fakeGRPCServer := &fakeV1alpha3GRPCServer{} 86 drapbv1alpha3.RegisterNodeServer(s, fakeGRPCServer) 87 default: 88 return "", nil, fmt.Errorf("unsupported version: %s", version) 89 } 90 91 go func() { 92 go s.Serve(listener) 93 <-closeCh 94 s.GracefulStop() 95 }() 96 97 return addr, teardown, nil 98 } 99 100 func TestGRPCConnIsReused(t *testing.T) { 101 addr, teardown, err := setupFakeGRPCServer(v1alpha3Version) 102 if err != nil { 103 t.Fatal(err) 104 } 105 defer teardown() 106 107 reusedConns := make(map[*grpc.ClientConn]int) 108 wg := sync.WaitGroup{} 109 m := sync.Mutex{} 110 111 p := &plugin{ 112 endpoint: addr, 113 version: v1alpha3Version, 114 } 115 116 conn, err := p.getOrCreateGRPCConn() 117 defer func() { 118 err := conn.Close() 119 if err != nil { 120 t.Error(err) 121 } 122 }() 123 if err != nil { 124 t.Fatal(err) 125 } 126 127 // ensure the plugin we are using is registered 128 draPlugins.add("dummy-plugin", p) 129 defer draPlugins.delete("dummy-plugin") 130 131 // we call `NodePrepareResource` 2 times and check whether a new connection is created or the same is reused 132 for i := 0; i < 2; i++ { 133 wg.Add(1) 134 go func() { 135 defer wg.Done() 136 client, err := NewDRAPluginClient("dummy-plugin") 137 if err != nil { 138 t.Error(err) 139 return 140 } 141 142 req := &drapbv1alpha3.NodePrepareResourcesRequest{ 143 Claims: []*drapbv1alpha3.Claim{ 144 { 145 Namespace: "dummy-namespace", 146 Uid: "dummy-uid", 147 Name: "dummy-claim", 148 ResourceHandle: "dummy-resource", 149 }, 150 }, 151 } 152 client.NodePrepareResources(context.TODO(), req) 153 154 client.(*plugin).Lock() 155 conn := client.(*plugin).conn 156 client.(*plugin).Unlock() 157 158 m.Lock() 159 defer m.Unlock() 160 reusedConns[conn]++ 161 }() 162 } 163 164 wg.Wait() 165 // We should have only one entry otherwise it means another gRPC connection has been created 166 if len(reusedConns) != 1 { 167 t.Errorf("expected length to be 1 but got %d", len(reusedConns)) 168 } 169 if counter, ok := reusedConns[conn]; ok && counter != 2 { 170 t.Errorf("expected counter to be 2 but got %d", counter) 171 } 172 } 173 174 func TestNewDRAPluginClient(t *testing.T) { 175 for _, test := range []struct { 176 description string 177 setup func(string) tearDown 178 pluginName string 179 shouldError bool 180 }{ 181 { 182 description: "plugin name is empty", 183 setup: func(_ string) tearDown { 184 return func() {} 185 }, 186 pluginName: "", 187 shouldError: true, 188 }, 189 { 190 description: "plugin name not found in the list", 191 setup: func(_ string) tearDown { 192 return func() {} 193 }, 194 pluginName: "plugin-name-not-found-in-the-list", 195 shouldError: true, 196 }, 197 { 198 description: "plugin exists", 199 setup: func(name string) tearDown { 200 draPlugins.add(name, &plugin{}) 201 return func() { 202 draPlugins.delete(name) 203 } 204 }, 205 pluginName: "dummy-plugin", 206 }, 207 } { 208 t.Run(test.description, func(t *testing.T) { 209 teardown := test.setup(test.pluginName) 210 defer teardown() 211 212 client, err := NewDRAPluginClient(test.pluginName) 213 if test.shouldError { 214 assert.Nil(t, client) 215 assert.Error(t, err) 216 } else { 217 assert.NotNil(t, client) 218 assert.Nil(t, err) 219 } 220 }) 221 } 222 } 223 224 func TestNodeUnprepareResource(t *testing.T) { 225 for _, test := range []struct { 226 description string 227 serverSetup func(string) (string, tearDown, error) 228 serverVersion string 229 request *drapbv1alpha3.NodeUnprepareResourcesRequest 230 }{ 231 { 232 description: "server supports v1alpha3", 233 serverSetup: setupFakeGRPCServer, 234 serverVersion: v1alpha3Version, 235 request: &drapbv1alpha3.NodeUnprepareResourcesRequest{}, 236 }, 237 { 238 description: "server supports v1alpha2, plugin client should fallback", 239 serverSetup: setupFakeGRPCServer, 240 serverVersion: v1alpha2Version, 241 request: &drapbv1alpha3.NodeUnprepareResourcesRequest{ 242 Claims: []*drapbv1alpha3.Claim{ 243 { 244 Namespace: "dummy-namespace", 245 Uid: "dummy-uid", 246 Name: "dummy-claim", 247 ResourceHandle: "dummy-resource", 248 }, 249 }, 250 }, 251 }, 252 } { 253 t.Run(test.description, func(t *testing.T) { 254 addr, teardown, err := setupFakeGRPCServer(test.serverVersion) 255 if err != nil { 256 t.Fatal(err) 257 } 258 defer teardown() 259 260 p := &plugin{ 261 endpoint: addr, 262 version: v1alpha3Version, 263 } 264 265 conn, err := p.getOrCreateGRPCConn() 266 defer func() { 267 err := conn.Close() 268 if err != nil { 269 t.Error(err) 270 } 271 }() 272 if err != nil { 273 t.Fatal(err) 274 } 275 276 draPlugins.add("dummy-plugin", p) 277 defer draPlugins.delete("dummy-plugin") 278 279 client, err := NewDRAPluginClient("dummy-plugin") 280 if err != nil { 281 t.Fatal(err) 282 } 283 284 _, err = client.NodeUnprepareResources(context.TODO(), test.request) 285 if err != nil { 286 t.Fatal(err) 287 } 288 }) 289 } 290 }