New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use NodeWrapper to directly initialize nodes with labels #92514
Use NodeWrapper to directly initialize nodes with labels #92514
Conversation
Hi @nodo. Thanks for your PR. I'm waiting for a kubernetes member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
@alculquicondor @Huang-Wei I have opened this PR as WIP to get your feedback about the cleanup. In particular:
|
There are just 2 callers using
I think it makes more sense to eliminate it by migrating to NodeWrapper.
My 2 cents is to use NodeWrapper inside createNode() & createNodes() to craft the Node(s) object first, and then call API server to create them. |
pkg/scheduler/testing/wrappers.go
Outdated
return n | ||
} | ||
|
||
func (n *NodeWrapper) Capacity(capacity v1.ResourceList) *NodeWrapper { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest using paired key-value string (or just values) instead of v1.ResourceList here. v1.ResourceList is too low-level API, which is friendly for new-users to build, and if you look at the usage of createNode(), there is only one caller specifying a non-nil resourceList.
Option 1:
func (n *NodeWrapper) Capacity(pairs ...string) *NodeWrapper {
res := v1.ResourceList{}
for i := 0; i < len(pairs); i += 2 {
k, v := pairs[i], pairs[i+1]
res[v1.ResourceName(k)] = resource.MustParse(v)
}
n.Status.Capacity, n.Status.Allocatable = res, res
return n
}
Option 2:
func (n *NodeWrapper) Capacity(cpu, mem string, pods int64) *NodeWrapper {
res := v1.ResourceList{
v1.ResourcePods: *resource.NewQuantity(pods, resource.DecimalSI),
}
if len(cpu) != 0 {
res[v1.ResourceCPU] = resource.MustParse(cpu)
}
if len(mem) != 0 {
res[v1.ResourceMemory] = resource.MustParse(mem)
}
n.Status.Capacity, n.Status.Allocatable = res, res
return n
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer Option 1, but accepting map[string]string, instead of the pairs of lists
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup, option 1 is also my preference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: map[v1.ResourceName]string
should be less error-prone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, I will make the change!
pkg/scheduler/testing/wrappers.go
Outdated
return n | ||
} | ||
|
||
func (n *NodeWrapper) Images(images []v1.ContainerImage) *NodeWrapper { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar as above, this function should take the burden of building []v1.ContainerImage inside, so users just pass in image names, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
/ok-to-test |
pkg/scheduler/testing/wrappers.go
Outdated
return n | ||
} | ||
|
||
func (n *NodeWrapper) Images(images []v1.ContainerImage) *NodeWrapper { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
pkg/scheduler/testing/wrappers.go
Outdated
return n | ||
} | ||
|
||
func (n *NodeWrapper) Capacity(capacity v1.ResourceList) *NodeWrapper { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer Option 1, but accepting map[string]string, instead of the pairs of lists
@alculquicondor @Huang-Wei Thanks a lot for the feedback. I have done some changes and cleaned up the rest of the integration tests. Feel free to review it when you have time. |
pkg/scheduler/testing/wrappers.go
Outdated
// Capacity sets the capacity and the allocatable resources of the inner node. | ||
// Each entry in `resources` corresponds to a resource name and its quantity. | ||
func (n *NodeWrapper) Capacity(resources map[v1.ResourceName]string) *NodeWrapper { | ||
res := v1.ResourceList{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: res := make(v1.ResourceList)
test/integration/scheduler/util.go
Outdated
@@ -194,6 +167,10 @@ func createNodes(cs clientset.Interface, prefix string, res *v1.ResourceList, nu | |||
return nodes[:], nil | |||
} | |||
|
|||
func defaultNodeWrapper() *st.NodeWrapper { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer we don't hide the details in this function.
Each caller should define the resources they need in their tests.
I know this goes against having repeated code, but this is actually a good practice when writing tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO v1.ResourcePods
is a bit special, unlike zero cpu/mem, a zero Pods number would simply fail a NodeFit plugin in any case. Which is usually not what we want. So having a default v1.ResourcePods
looks good to me. However, I'd prefer to set the default value on NodesWrapper side - which would also benefit UT (I did that way in another PR: https://github.com/kubernetes/kubernetes/pull/92571/files?file-filters%5B%5D=.go#diff-1cea28cd0be3cdbab57f5dc287dc98c0R427-R438)
If you do so, this function is not needed anymore - you can just replace callers of defaultNodeWrapper()
directly to st.MakeNode()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sg, we can just do res[pods] = 32
at the beginning of the function, and the loop would override it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, that would save one if
block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, I have made the change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some nits.
for name, value := range resources { | ||
res[name] = resource.MustParse(value) | ||
} | ||
n.Status.Capacity, n.Status.Allocatable = res, res |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would be good to ensure v1.ResourcePods
is always present, otherwise some pod scheduling may fail due to "no Pods quota".
FYI: I put it in https://github.com/kubernetes/kubernetes/pull/92571/files?file-filters%5B%5D=.go#diff-1cea28cd0be3cdbab57f5dc287dc98c0R427-R438
pkg/scheduler/testing/wrappers.go
Outdated
// Images sets the images of the inner node. Each entry in `images` corresponds | ||
// to an image name and its size in bytes. | ||
func (n *NodeWrapper) Images(images map[string]int64) *NodeWrapper { | ||
containerImages := []v1.ContainerImage{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: var containerImages []v1.ContainerImage
.
test/integration/scheduler/util.go
Outdated
@@ -194,6 +167,10 @@ func createNodes(cs clientset.Interface, prefix string, res *v1.ResourceList, nu | |||
return nodes[:], nil | |||
} | |||
|
|||
func defaultNodeWrapper() *st.NodeWrapper { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO v1.ResourcePods
is a bit special, unlike zero cpu/mem, a zero Pods number would simply fail a NodeFit plugin in any case. Which is usually not what we want. So having a default v1.ResourcePods
looks good to me. However, I'd prefer to set the default value on NodesWrapper side - which would also benefit UT (I did that way in another PR: https://github.com/kubernetes/kubernetes/pull/92571/files?file-filters%5B%5D=.go#diff-1cea28cd0be3cdbab57f5dc287dc98c0R427-R438)
If you do so, this function is not needed anymore - you can just replace callers of defaultNodeWrapper()
directly to st.MakeNode()
.
df00fed
to
b9a36c3
Compare
@@ -390,3 +391,27 @@ func (n *NodeWrapper) Label(k, v string) *NodeWrapper { | |||
n.Labels[k] = v | |||
return n | |||
} | |||
|
|||
// Capacity sets the capacity and the allocatable resources of the inner node. | |||
// Each entry in `resources` corresponds to a resource name and its quantity. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indicate that sets 32 pods limit by default
@@ -1841,7 +1842,7 @@ func initTestSchedulerForFrameworkTest(t *testing.T, testCtx *testutils.TestCont | |||
go testCtx.Scheduler.Run(testCtx.Ctx) | |||
|
|||
if nodeCount > 0 { | |||
_, err := createNodes(testCtx.ClientSet, "test-node", nil, nodeCount) | |||
_, err := createNodes(testCtx.ClientSet, "test-node", st.MakeNode(), nodeCount) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't MakeNode call Capacity(nil)
or something like that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally! My bad, let me fix it.
Using NodeWrapper in the integration tests gives more flexibility when creating nodes. For instance, tests can create nodes with labels or with a specific sets of resources. Also, NodeWrapper initialises a node with a capacity of 32 pods, which can be overridden by the caller. This makes sure that a node is usable as soon as it is created.
b9a36c3
to
2e1042f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/lgtm
Thanks for the cleanup!
@nodo: The following test failed, say
Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
/approve Thanks @nodo . |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: Huang-Wei, nodo The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
What type of PR is this?
/kind cleanup
What this PR does / why we need it:
In #92074 (comment), we discussed the possibility to use
NodeWrapper
in the scheduler integration tests so that we have more flexibility when creating the nodes. For instance we can create nodes directly with a label.Does this PR introduce a user-facing change?: