sigs.k8s.io/kueue@v0.6.2/pkg/visibility/api/rest/pending_workloads_lq_test.go (about)

     1  // Copyright 2023 The Kubernetes Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package rest
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"github.com/google/go-cmp/cmp/cmpopts"
    24  	"k8s.io/apimachinery/pkg/api/errors"
    25  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apiserver/pkg/endpoints/request"
    28  
    29  	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
    30  	visibility "sigs.k8s.io/kueue/apis/visibility/v1alpha1"
    31  	"sigs.k8s.io/kueue/pkg/constants"
    32  	"sigs.k8s.io/kueue/pkg/queue"
    33  	utiltesting "sigs.k8s.io/kueue/pkg/util/testing"
    34  )
    35  
    36  func TestPendingWorkloadsInLQ(t *testing.T) {
    37  	const (
    38  		nsNameA  = "nsA"
    39  		nsNameB  = "nsB"
    40  		cqNameA  = "cqA"
    41  		cqNameB  = "cqB"
    42  		lqNameA  = "lqA"
    43  		lqNameB  = "lqB"
    44  		lowPrio  = 50
    45  		highPrio = 100
    46  	)
    47  
    48  	defaultQueryParams := &visibility.PendingWorkloadOptions{
    49  		Offset: 0,
    50  		Limit:  constants.DefaultPendingWorkloadsLimit,
    51  	}
    52  
    53  	scheme := runtime.NewScheme()
    54  	if err := kueue.AddToScheme(scheme); err != nil {
    55  		t.Fatalf("Failed adding kueue scheme: %s", err)
    56  	}
    57  	if err := visibility.AddToScheme(scheme); err != nil {
    58  		t.Fatalf("Failed adding kueue scheme: %s", err)
    59  	}
    60  
    61  	now := time.Now()
    62  	cases := map[string]struct {
    63  		clusterQueues []*kueue.ClusterQueue
    64  		queues        []*kueue.LocalQueue
    65  		workloads     []*kueue.Workload
    66  		req           *req
    67  		wantResp      *resp
    68  		wantErrMatch  func(error) bool
    69  	}{
    70  		"single ClusterQueue and single LocalQueue setup with two workloads and default query parameters": {
    71  			clusterQueues: []*kueue.ClusterQueue{
    72  				utiltesting.MakeClusterQueue(cqNameA).Obj(),
    73  			},
    74  			queues: []*kueue.LocalQueue{
    75  				utiltesting.MakeLocalQueue(lqNameA, nsNameA).ClusterQueue(cqNameA).Obj(),
    76  			},
    77  			workloads: []*kueue.Workload{
    78  				utiltesting.MakeWorkload("a", nsNameA).Queue(lqNameA).Priority(highPrio).Creation(now).Obj(),
    79  				utiltesting.MakeWorkload("b", nsNameA).Queue(lqNameA).Priority(lowPrio).Creation(now).Obj(),
    80  			},
    81  			req: &req{
    82  				nsName:      nsNameA,
    83  				queueName:   lqNameA,
    84  				queryParams: defaultQueryParams,
    85  			},
    86  			wantResp: &resp{
    87  				wantPendingWorkloads: []visibility.PendingWorkload{
    88  					{
    89  						ObjectMeta: v1.ObjectMeta{
    90  							Name:              "a",
    91  							Namespace:         nsNameA,
    92  							CreationTimestamp: v1.NewTime(now),
    93  						},
    94  						LocalQueueName:         lqNameA,
    95  						Priority:               highPrio,
    96  						PositionInClusterQueue: 0,
    97  						PositionInLocalQueue:   0,
    98  					},
    99  					{
   100  						ObjectMeta: v1.ObjectMeta{
   101  							Name:              "b",
   102  							Namespace:         nsNameA,
   103  							CreationTimestamp: v1.NewTime(now),
   104  						},
   105  						LocalQueueName:         lqNameA,
   106  						Priority:               lowPrio,
   107  						PositionInClusterQueue: 1,
   108  						PositionInLocalQueue:   1,
   109  					}},
   110  			},
   111  		},
   112  		"single ClusterQueue and two LocalQueue setup with four workloads and default query parameters; LocalQueue A request": {
   113  			clusterQueues: []*kueue.ClusterQueue{
   114  				utiltesting.MakeClusterQueue(cqNameA).Obj(),
   115  			},
   116  			queues: []*kueue.LocalQueue{
   117  				utiltesting.MakeLocalQueue(lqNameA, nsNameA).ClusterQueue(cqNameA).Obj(),
   118  				utiltesting.MakeLocalQueue(lqNameB, nsNameA).ClusterQueue(cqNameA).Obj(),
   119  			},
   120  			workloads: []*kueue.Workload{
   121  				utiltesting.MakeWorkload("lqA-high-prio", nsNameA).Queue(lqNameA).Priority(highPrio).Creation(now).Obj(),
   122  				utiltesting.MakeWorkload("lqA-low-prio", nsNameA).Queue(lqNameA).Priority(lowPrio).Creation(now).Obj(),
   123  				utiltesting.MakeWorkload("lqB-high-prio", nsNameA).Queue(lqNameB).Priority(highPrio).Creation(now.Add(time.Second)).Obj(),
   124  				utiltesting.MakeWorkload("lqB-low-prio", nsNameA).Queue(lqNameB).Priority(lowPrio).Creation(now.Add(time.Second)).Obj(),
   125  			},
   126  			req: &req{
   127  				nsName:      nsNameA,
   128  				queueName:   lqNameA,
   129  				queryParams: defaultQueryParams,
   130  			},
   131  			wantResp: &resp{
   132  				wantPendingWorkloads: []visibility.PendingWorkload{
   133  					{
   134  						ObjectMeta: v1.ObjectMeta{
   135  							Name:              "lqA-high-prio",
   136  							Namespace:         nsNameA,
   137  							CreationTimestamp: v1.NewTime(now),
   138  						},
   139  						LocalQueueName:         lqNameA,
   140  						Priority:               highPrio,
   141  						PositionInClusterQueue: 0,
   142  						PositionInLocalQueue:   0,
   143  					},
   144  					{
   145  						ObjectMeta: v1.ObjectMeta{
   146  							Name:              "lqA-low-prio",
   147  							Namespace:         nsNameA,
   148  							CreationTimestamp: v1.NewTime(now),
   149  						},
   150  						LocalQueueName:         lqNameA,
   151  						Priority:               lowPrio,
   152  						PositionInClusterQueue: 2,
   153  						PositionInLocalQueue:   1,
   154  					}},
   155  			},
   156  		},
   157  		"single ClusterQueue and two LocalQueue setup with four workloads and default query parameters; LocalQueue B request": {
   158  			clusterQueues: []*kueue.ClusterQueue{
   159  				utiltesting.MakeClusterQueue(cqNameA).Obj(),
   160  			},
   161  			queues: []*kueue.LocalQueue{
   162  				utiltesting.MakeLocalQueue(lqNameA, nsNameA).ClusterQueue(cqNameA).Obj(),
   163  				utiltesting.MakeLocalQueue(lqNameB, nsNameA).ClusterQueue(cqNameA).Obj(),
   164  			},
   165  			workloads: []*kueue.Workload{
   166  				utiltesting.MakeWorkload("lqA-high-prio", nsNameA).Queue(lqNameA).Priority(highPrio).Creation(now).Obj(),
   167  				utiltesting.MakeWorkload("lqA-low-prio", nsNameA).Queue(lqNameA).Priority(lowPrio).Creation(now).Obj(),
   168  				utiltesting.MakeWorkload("lqB-high-prio", nsNameA).Queue(lqNameB).Priority(highPrio).Creation(now.Add(time.Second)).Obj(),
   169  				utiltesting.MakeWorkload("lqB-low-prio", nsNameA).Queue(lqNameB).Priority(lowPrio).Creation(now.Add(time.Second)).Obj(),
   170  			},
   171  			req: &req{
   172  				nsName:      nsNameA,
   173  				queueName:   lqNameB,
   174  				queryParams: defaultQueryParams,
   175  			},
   176  			wantResp: &resp{
   177  				wantPendingWorkloads: []visibility.PendingWorkload{
   178  					{
   179  						ObjectMeta: v1.ObjectMeta{
   180  							Name:              "lqB-high-prio",
   181  							Namespace:         nsNameA,
   182  							CreationTimestamp: v1.NewTime(now.Add(time.Second)),
   183  						},
   184  						LocalQueueName:         lqNameB,
   185  						Priority:               highPrio,
   186  						PositionInClusterQueue: 1,
   187  						PositionInLocalQueue:   0,
   188  					},
   189  					{
   190  						ObjectMeta: v1.ObjectMeta{
   191  							Name:              "lqB-low-prio",
   192  							Namespace:         nsNameA,
   193  							CreationTimestamp: v1.NewTime(now.Add(time.Second)),
   194  						},
   195  						LocalQueueName:         lqNameB,
   196  						Priority:               lowPrio,
   197  						PositionInClusterQueue: 3,
   198  						PositionInLocalQueue:   1,
   199  					},
   200  				},
   201  			},
   202  		},
   203  		"two Namespaces, two ClusterQueue and two LocalQueue setup with four workloads and default query parameters, LocalQueue A request": {
   204  			clusterQueues: []*kueue.ClusterQueue{
   205  				utiltesting.MakeClusterQueue(cqNameA).Obj(),
   206  				utiltesting.MakeClusterQueue(cqNameB).Obj(),
   207  			},
   208  			queues: []*kueue.LocalQueue{
   209  				utiltesting.MakeLocalQueue(lqNameA, nsNameA).ClusterQueue(cqNameA).Obj(),
   210  				utiltesting.MakeLocalQueue(lqNameB, nsNameB).ClusterQueue(cqNameB).Obj(),
   211  			},
   212  			workloads: []*kueue.Workload{
   213  				utiltesting.MakeWorkload("lqA-high-prio", nsNameA).Queue(lqNameA).Priority(highPrio).Creation(now).Obj(),
   214  				utiltesting.MakeWorkload("lqA-low-prio", nsNameA).Queue(lqNameA).Priority(lowPrio).Creation(now).Obj(),
   215  				utiltesting.MakeWorkload("lqB-high-prio", nsNameB).Queue(lqNameB).Priority(highPrio).Creation(now.Add(time.Second)).Obj(),
   216  				utiltesting.MakeWorkload("lqB-low-prio", nsNameB).Queue(lqNameB).Priority(lowPrio).Creation(now.Add(time.Second)).Obj(),
   217  			},
   218  			req: &req{
   219  				nsName:      nsNameA,
   220  				queueName:   lqNameA,
   221  				queryParams: defaultQueryParams,
   222  			},
   223  			wantResp: &resp{
   224  				wantPendingWorkloads: []visibility.PendingWorkload{
   225  					{
   226  						ObjectMeta: v1.ObjectMeta{
   227  							Name:              "lqA-high-prio",
   228  							Namespace:         nsNameA,
   229  							CreationTimestamp: v1.NewTime(now),
   230  						},
   231  						LocalQueueName:         lqNameA,
   232  						Priority:               highPrio,
   233  						PositionInClusterQueue: 0,
   234  						PositionInLocalQueue:   0,
   235  					},
   236  					{
   237  						ObjectMeta: v1.ObjectMeta{
   238  							Name:              "lqA-low-prio",
   239  							Namespace:         nsNameA,
   240  							CreationTimestamp: v1.NewTime(now),
   241  						},
   242  						LocalQueueName:         lqNameA,
   243  						Priority:               lowPrio,
   244  						PositionInClusterQueue: 1,
   245  						PositionInLocalQueue:   1,
   246  					},
   247  				},
   248  			},
   249  		},
   250  		"two Namespaces, two ClusterQueue and two LocalQueue setup with four workloads and default query parameters, LocalQueue B request": {
   251  			clusterQueues: []*kueue.ClusterQueue{
   252  				utiltesting.MakeClusterQueue(cqNameA).Obj(),
   253  				utiltesting.MakeClusterQueue(cqNameB).Obj(),
   254  			},
   255  			queues: []*kueue.LocalQueue{
   256  				utiltesting.MakeLocalQueue(lqNameA, nsNameA).ClusterQueue(cqNameA).Obj(),
   257  				utiltesting.MakeLocalQueue(lqNameB, nsNameB).ClusterQueue(cqNameB).Obj(),
   258  			},
   259  			workloads: []*kueue.Workload{
   260  				utiltesting.MakeWorkload("lqA-high-prio", nsNameA).Queue(lqNameA).Priority(highPrio).Creation(now).Obj(),
   261  				utiltesting.MakeWorkload("lqA-low-prio", nsNameA).Queue(lqNameA).Priority(lowPrio).Creation(now).Obj(),
   262  				utiltesting.MakeWorkload("lqB-high-prio", nsNameB).Queue(lqNameB).Priority(highPrio).Creation(now.Add(time.Second)).Obj(),
   263  				utiltesting.MakeWorkload("lqB-low-prio", nsNameB).Queue(lqNameB).Priority(lowPrio).Creation(now.Add(time.Second)).Obj(),
   264  			},
   265  			req: &req{
   266  				nsName:      nsNameB,
   267  				queueName:   lqNameB,
   268  				queryParams: defaultQueryParams,
   269  			},
   270  			wantResp: &resp{
   271  				wantPendingWorkloads: []visibility.PendingWorkload{
   272  					{
   273  						ObjectMeta: v1.ObjectMeta{
   274  							Name:              "lqB-high-prio",
   275  							Namespace:         nsNameB,
   276  							CreationTimestamp: v1.NewTime(now.Add(time.Second)),
   277  						},
   278  						LocalQueueName:         lqNameB,
   279  						Priority:               highPrio,
   280  						PositionInClusterQueue: 0,
   281  						PositionInLocalQueue:   0,
   282  					},
   283  					{
   284  						ObjectMeta: v1.ObjectMeta{
   285  							Name:              "lqB-low-prio",
   286  							Namespace:         nsNameB,
   287  							CreationTimestamp: v1.NewTime(now.Add(time.Second)),
   288  						},
   289  						LocalQueueName:         lqNameB,
   290  						Priority:               lowPrio,
   291  						PositionInClusterQueue: 1,
   292  						PositionInLocalQueue:   1,
   293  					},
   294  				},
   295  			},
   296  		},
   297  		"limit query parameter set": {
   298  			clusterQueues: []*kueue.ClusterQueue{
   299  				utiltesting.MakeClusterQueue(cqNameA).Obj(),
   300  			},
   301  			queues: []*kueue.LocalQueue{
   302  				utiltesting.MakeLocalQueue(lqNameA, nsNameA).ClusterQueue(cqNameA).Obj(),
   303  			},
   304  			workloads: []*kueue.Workload{
   305  				utiltesting.MakeWorkload("a", nsNameA).Queue(lqNameA).Priority(highPrio).Creation(now).Obj(),
   306  				utiltesting.MakeWorkload("b", nsNameA).Queue(lqNameA).Priority(highPrio).Creation(now.Add(time.Second)).Obj(),
   307  				utiltesting.MakeWorkload("c", nsNameA).Queue(lqNameA).Priority(highPrio).Creation(now.Add(time.Second * 2)).Obj(),
   308  			},
   309  			req: &req{
   310  				nsName:    nsNameA,
   311  				queueName: lqNameA,
   312  				queryParams: &visibility.PendingWorkloadOptions{
   313  					Limit: 2,
   314  				},
   315  			},
   316  			wantResp: &resp{
   317  				wantPendingWorkloads: []visibility.PendingWorkload{
   318  					{
   319  						ObjectMeta: v1.ObjectMeta{
   320  							Name:              "a",
   321  							Namespace:         nsNameA,
   322  							CreationTimestamp: v1.NewTime(now),
   323  						},
   324  						LocalQueueName:         lqNameA,
   325  						Priority:               highPrio,
   326  						PositionInClusterQueue: 0,
   327  						PositionInLocalQueue:   0,
   328  					},
   329  					{
   330  						ObjectMeta: v1.ObjectMeta{
   331  							Name:              "b",
   332  							Namespace:         nsNameA,
   333  							CreationTimestamp: v1.NewTime(now.Add(time.Second)),
   334  						},
   335  						LocalQueueName:         lqNameA,
   336  						Priority:               highPrio,
   337  						PositionInClusterQueue: 1,
   338  						PositionInLocalQueue:   1,
   339  					},
   340  				},
   341  			},
   342  		},
   343  		"offset query parameter set": {
   344  			clusterQueues: []*kueue.ClusterQueue{
   345  				utiltesting.MakeClusterQueue(cqNameA).Obj(),
   346  			},
   347  			queues: []*kueue.LocalQueue{
   348  				utiltesting.MakeLocalQueue(lqNameA, nsNameA).ClusterQueue(cqNameA).Obj(),
   349  			},
   350  			workloads: []*kueue.Workload{
   351  				utiltesting.MakeWorkload("a", nsNameA).Queue(lqNameA).Priority(highPrio).Creation(now).Obj(),
   352  				utiltesting.MakeWorkload("b", nsNameA).Queue(lqNameA).Priority(highPrio).Creation(now.Add(time.Second)).Obj(),
   353  				utiltesting.MakeWorkload("c", nsNameA).Queue(lqNameA).Priority(highPrio).Creation(now.Add(time.Second * 2)).Obj(),
   354  			},
   355  			req: &req{
   356  				nsName:    nsNameA,
   357  				queueName: lqNameA,
   358  				queryParams: &visibility.PendingWorkloadOptions{
   359  					Offset: 1,
   360  					Limit:  constants.DefaultPendingWorkloadsLimit,
   361  				},
   362  			},
   363  			wantResp: &resp{
   364  				wantPendingWorkloads: []visibility.PendingWorkload{
   365  					{
   366  						ObjectMeta: v1.ObjectMeta{
   367  							Name:              "b",
   368  							Namespace:         nsNameA,
   369  							CreationTimestamp: v1.NewTime(now.Add(time.Second)),
   370  						},
   371  						LocalQueueName:         lqNameA,
   372  						Priority:               highPrio,
   373  						PositionInClusterQueue: 1,
   374  						PositionInLocalQueue:   1,
   375  					},
   376  					{
   377  						ObjectMeta: v1.ObjectMeta{
   378  							Name:              "c",
   379  							Namespace:         nsNameA,
   380  							CreationTimestamp: v1.NewTime(now.Add(time.Second * 2)),
   381  						},
   382  						LocalQueueName:         lqNameA,
   383  						Priority:               highPrio,
   384  						PositionInClusterQueue: 2,
   385  						PositionInLocalQueue:   2,
   386  					},
   387  				},
   388  			},
   389  		},
   390  		"limit and offset query parameters set": {
   391  			clusterQueues: []*kueue.ClusterQueue{
   392  				utiltesting.MakeClusterQueue(cqNameA).Obj(),
   393  			},
   394  			queues: []*kueue.LocalQueue{
   395  				utiltesting.MakeLocalQueue(lqNameA, nsNameA).ClusterQueue(cqNameA).Obj(),
   396  			},
   397  			workloads: []*kueue.Workload{
   398  				utiltesting.MakeWorkload("a", nsNameA).Queue(lqNameA).Priority(highPrio).Creation(now).Obj(),
   399  				utiltesting.MakeWorkload("b", nsNameA).Queue(lqNameA).Priority(highPrio).Creation(now.Add(time.Second)).Obj(),
   400  				utiltesting.MakeWorkload("c", nsNameA).Queue(lqNameA).Priority(highPrio).Creation(now.Add(time.Second * 2)).Obj(),
   401  			},
   402  			req: &req{
   403  				nsName:    nsNameA,
   404  				queueName: lqNameA,
   405  				queryParams: &visibility.PendingWorkloadOptions{
   406  					Offset: 1,
   407  					Limit:  1,
   408  				},
   409  			},
   410  			wantResp: &resp{
   411  				wantPendingWorkloads: []visibility.PendingWorkload{
   412  					{
   413  						ObjectMeta: v1.ObjectMeta{
   414  							Name:              "b",
   415  							Namespace:         nsNameA,
   416  							CreationTimestamp: v1.NewTime(now.Add(time.Second)),
   417  						},
   418  						LocalQueueName:         lqNameA,
   419  						Priority:               highPrio,
   420  						PositionInClusterQueue: 1,
   421  						PositionInLocalQueue:   1,
   422  					},
   423  				},
   424  			},
   425  		},
   426  		"nonexistent queue name": {
   427  			req: &req{
   428  				queueName:   "nonexistent-queue",
   429  				queryParams: defaultQueryParams,
   430  			},
   431  			wantResp: &resp{
   432  				wantErr: errors.NewNotFound(visibility.Resource("localqueue"), "invalid-name"),
   433  			},
   434  			wantErrMatch: errors.IsNotFound,
   435  		},
   436  	}
   437  	for name, tc := range cases {
   438  		t.Run(name, func(t *testing.T) {
   439  			manager := queue.NewManager(utiltesting.NewFakeClient(), nil)
   440  			ctx, cancel := context.WithCancel(context.Background())
   441  			defer cancel()
   442  			go manager.CleanUpOnContext(ctx)
   443  			pendingWorkloadsInLqRest := NewPendingWorkloadsInLqREST(manager)
   444  			for _, cq := range tc.clusterQueues {
   445  				if err := manager.AddClusterQueue(ctx, cq); err != nil {
   446  					t.Fatalf("Adding cluster queue %s: %v", cq.Name, err)
   447  				}
   448  			}
   449  			for _, q := range tc.queues {
   450  				if err := manager.AddLocalQueue(ctx, q); err != nil {
   451  					t.Fatalf("Adding queue %q: %v", q.Name, err)
   452  				}
   453  			}
   454  			for _, w := range tc.workloads {
   455  				manager.AddOrUpdateWorkload(w)
   456  			}
   457  
   458  			ctx = request.WithNamespace(ctx, tc.req.nsName)
   459  			info, err := pendingWorkloadsInLqRest.Get(ctx, tc.req.queueName, tc.req.queryParams)
   460  			if tc.wantErrMatch != nil {
   461  				if !tc.wantErrMatch(err) {
   462  					t.Errorf("Error differs: (-want,+got):\n%s", cmp.Diff(tc.wantResp.wantErr.Error(), err.Error()))
   463  				}
   464  			} else if err != nil {
   465  				t.Error(err)
   466  			} else {
   467  				pendingWorkloadsInfo := info.(*visibility.PendingWorkloadsSummary)
   468  				if diff := cmp.Diff(tc.wantResp.wantPendingWorkloads, pendingWorkloadsInfo.Items, cmpopts.EquateEmpty()); diff != "" {
   469  					t.Errorf("Pending workloads differ: (-want,+got):\n%s", diff)
   470  				}
   471  			}
   472  		})
   473  	}
   474  }