diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 3056830f512be..5f86d326ee6eb 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -14959,7 +14959,7 @@ "description": "Opaque provides driver-specific configuration parameters." }, "requests": { - "description": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.", + "description": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", "items": { "type": "string" }, @@ -15059,7 +15059,7 @@ "description": "Opaque provides driver-specific configuration parameters." }, "requests": { - "description": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.", + "description": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", "items": { "type": "string" }, @@ -15176,7 +15176,7 @@ "type": "string" }, "requests": { - "description": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.", + "description": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the constraint applies to all subrequests.", "items": { "type": "string" }, @@ -15187,31 +15187,39 @@ "type": "object" }, "io.k8s.api.resource.v1alpha3.DeviceRequest": { - "description": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nA DeviceClassName is currently required. Clients must check that it is indeed set. It's absence indicates that something changed in a way that is not supported by the client yet, in which case it must refuse to handle the request.", + "description": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.", "properties": { "adminAccess": { - "description": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "description": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", "type": "boolean" }, "allocationMode": { - "description": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "description": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", "type": "string" }, "count": { - "description": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", + "description": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", "format": "int64", "type": "integer" }, "deviceClassName": { - "description": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "description": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required if no subrequests are specified in the firstAvailable list and no class can be set if subrequests are specified in the firstAvailable list. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", "type": "string" }, + "firstAvailable": { + "description": "FirstAvailable contains subrequests, of which exactly one will be satisfied by the scheduler to satisfy this request. It tries to satisfy them in the order in which they are listed here. So if there are two entries in the list, the scheduler will only check the second one if it determines that the first one cannot be used.\n\nThis field may only be set in the entries of DeviceClaim.Requests.\n\nDRA does not yet implement scoring, so the scheduler will select the first set of devices that satisfies all the requests in the claim. And if the requirements can be satisfied on more than one node, other scheduling features will determine which node is chosen. This means that the set of devices allocated to a claim might not be the optimal set available to the cluster. Scoring will be implemented later.", + "items": { + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceSubRequest" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "name": { "description": "Name can be used to reference this request in a pod.spec.containers[].resources.claims entry and in a constraint of the claim.\n\nMust be a DNS label.", "type": "string" }, "selectors": { - "description": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", + "description": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", "items": { "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceSelector" }, @@ -15220,8 +15228,7 @@ } }, "required": [ - "name", - "deviceClassName" + "name" ], "type": "object" }, @@ -15245,7 +15252,7 @@ "type": "string" }, "request": { - "description": "Request is the name of the request in the claim which caused this device to be allocated. Multiple devices may have been allocated per request.", + "description": "Request is the name of the request in the claim which caused this device to be allocated. If it references a subrequest in the firstAvailable list on a DeviceRequest, this field must include both the name of the main request and the subrequest using the format
/.\n\nMultiple devices may have been allocated per request.", "type": "string" } }, @@ -15267,6 +15274,41 @@ }, "type": "object" }, + "io.k8s.api.resource.v1alpha3.DeviceSubRequest": { + "description": "DeviceSubRequest describes a request for device provided in the claim.spec.devices.requests[].firstAvailable array. Each is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nDeviceSubRequest is similar to Request, but doesn't expose the AdminAccess or FirstAvailable fields, as those can only be set on the top-level request. AdminAccess is not supported for requests with a prioritized list, and recursive FirstAvailable fields are not supported.", + "properties": { + "allocationMode": { + "description": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AlloctionMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "type": "string" + }, + "count": { + "description": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", + "format": "int64", + "type": "integer" + }, + "deviceClassName": { + "description": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this subrequest.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "type": "string" + }, + "name": { + "description": "Name can be used to reference this subrequest in the list of constraints or the list of configurations for the claim. References must use the format
/.\n\nMust be a DNS label.", + "type": "string" + }, + "selectors": { + "description": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", + "items": { + "$ref": "#/definitions/io.k8s.api.resource.v1alpha3.DeviceSelector" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "name", + "deviceClassName" + ], + "type": "object" + }, "io.k8s.api.resource.v1alpha3.NetworkDeviceData": { "description": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", "properties": { @@ -15779,7 +15821,7 @@ "description": "Opaque provides driver-specific configuration parameters." }, "requests": { - "description": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.", + "description": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", "items": { "type": "string" }, @@ -15892,7 +15934,7 @@ "description": "Opaque provides driver-specific configuration parameters." }, "requests": { - "description": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.", + "description": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", "items": { "type": "string" }, @@ -16009,7 +16051,7 @@ "type": "string" }, "requests": { - "description": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.", + "description": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the constraint applies to all subrequests.", "items": { "type": "string" }, @@ -16020,31 +16062,39 @@ "type": "object" }, "io.k8s.api.resource.v1beta1.DeviceRequest": { - "description": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nA DeviceClassName is currently required. Clients must check that it is indeed set. It's absence indicates that something changed in a way that is not supported by the client yet, in which case it must refuse to handle the request.", + "description": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.", "properties": { "adminAccess": { - "description": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "description": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", "type": "boolean" }, "allocationMode": { - "description": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "description": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", "type": "string" }, "count": { - "description": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", + "description": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", "format": "int64", "type": "integer" }, "deviceClassName": { - "description": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "description": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required if no subrequests are specified in the firstAvailable list and no class can be set if subrequests are specified in the firstAvailable list. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", "type": "string" }, + "firstAvailable": { + "description": "FirstAvailable contains subrequests, of which exactly one will be satisfied by the scheduler to satisfy this request. It tries to satisfy them in the order in which they are listed here. So if there are two entries in the list, the scheduler will only check the second one if it determines that the first one cannot be used.\n\nThis field may only be set in the entries of DeviceClaim.Requests.\n\nDRA does not yet implement scoring, so the scheduler will select the first set of devices that satisfies all the requests in the claim. And if the requirements can be satisfied on more than one node, other scheduling features will determine which node is chosen. This means that the set of devices allocated to a claim might not be the optimal set available to the cluster. Scoring will be implemented later.", + "items": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceSubRequest" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "name": { - "description": "Name can be used to reference this request in a pod.spec.containers[].resources.claims entry and in a constraint of the claim.\n\nMust be a DNS label.", + "description": "Name can be used to reference this request in a pod.spec.containers[].resources.claims entry and in a constraint of the claim.\n\nMust be a DNS label and unique among all DeviceRequests in a ResourceClaim.", "type": "string" }, "selectors": { - "description": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", + "description": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", "items": { "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceSelector" }, @@ -16053,8 +16103,7 @@ } }, "required": [ - "name", - "deviceClassName" + "name" ], "type": "object" }, @@ -16078,7 +16127,7 @@ "type": "string" }, "request": { - "description": "Request is the name of the request in the claim which caused this device to be allocated. Multiple devices may have been allocated per request.", + "description": "Request is the name of the request in the claim which caused this device to be allocated. If it references a subrequest in the firstAvailable list on a DeviceRequest, this field must include both the name of the main request and the subrequest using the format
/.\n\nMultiple devices may have been allocated per request.", "type": "string" } }, @@ -16100,6 +16149,41 @@ }, "type": "object" }, + "io.k8s.api.resource.v1beta1.DeviceSubRequest": { + "description": "DeviceSubRequest describes a request for device provided in the claim.spec.devices.requests[].firstAvailable array. Each is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nDeviceSubRequest is similar to Request, but doesn't expose the AdminAccess or FirstAvailable fields, as those can only be set on the top-level request. AdminAccess is not supported for requests with a prioritized list, and recursive FirstAvailable fields are not supported.", + "properties": { + "allocationMode": { + "description": "AllocationMode and its related fields define how devices are allocated to satisfy this subrequest. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This subrequest is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AlloctionMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other subrequests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "type": "string" + }, + "count": { + "description": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", + "format": "int64", + "type": "integer" + }, + "deviceClassName": { + "description": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this subrequest.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "type": "string" + }, + "name": { + "description": "Name can be used to reference this subrequest in the list of constraints or the list of configurations for the claim. References must use the format
/.\n\nMust be a DNS label.", + "type": "string" + }, + "selectors": { + "description": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this subrequest. All selectors must be satisfied for a device to be considered.", + "items": { + "$ref": "#/definitions/io.k8s.api.resource.v1beta1.DeviceSelector" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "name", + "deviceClassName" + ], + "type": "object" + }, "io.k8s.api.resource.v1beta1.NetworkDeviceData": { "description": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", "properties": { diff --git a/api/openapi-spec/v3/apis__resource.k8s.io__v1alpha3_openapi.json b/api/openapi-spec/v3/apis__resource.k8s.io__v1alpha3_openapi.json index 4082f020f9763..b3c40a8bd9276 100644 --- a/api/openapi-spec/v3/apis__resource.k8s.io__v1alpha3_openapi.json +++ b/api/openapi-spec/v3/apis__resource.k8s.io__v1alpha3_openapi.json @@ -240,7 +240,7 @@ "description": "Opaque provides driver-specific configuration parameters." }, "requests": { - "description": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.", + "description": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", "items": { "default": "", "type": "string" @@ -371,7 +371,7 @@ "description": "Opaque provides driver-specific configuration parameters." }, "requests": { - "description": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.", + "description": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", "items": { "default": "", "type": "string" @@ -523,7 +523,7 @@ "type": "string" }, "requests": { - "description": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.", + "description": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the constraint applies to all subrequests.", "items": { "default": "", "type": "string" @@ -535,33 +535,46 @@ "type": "object" }, "io.k8s.api.resource.v1alpha3.DeviceRequest": { - "description": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nA DeviceClassName is currently required. Clients must check that it is indeed set. It's absence indicates that something changed in a way that is not supported by the client yet, in which case it must refuse to handle the request.", + "description": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.", "properties": { "adminAccess": { - "description": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "description": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", "type": "boolean" }, "allocationMode": { - "description": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "description": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", "type": "string" }, "count": { - "description": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", + "description": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", "format": "int64", "type": "integer" }, "deviceClassName": { "default": "", - "description": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "description": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required if no subrequests are specified in the firstAvailable list and no class can be set if subrequests are specified in the firstAvailable list. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", "type": "string" }, + "firstAvailable": { + "description": "FirstAvailable contains subrequests, of which exactly one will be satisfied by the scheduler to satisfy this request. It tries to satisfy them in the order in which they are listed here. So if there are two entries in the list, the scheduler will only check the second one if it determines that the first one cannot be used.\n\nThis field may only be set in the entries of DeviceClaim.Requests.\n\nDRA does not yet implement scoring, so the scheduler will select the first set of devices that satisfies all the requests in the claim. And if the requirements can be satisfied on more than one node, other scheduling features will determine which node is chosen. This means that the set of devices allocated to a claim might not be the optimal set available to the cluster. Scoring will be implemented later.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceSubRequest" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "name": { "default": "", "description": "Name can be used to reference this request in a pod.spec.containers[].resources.claims entry and in a constraint of the claim.\n\nMust be a DNS label.", "type": "string" }, "selectors": { - "description": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", + "description": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", "items": { "allOf": [ { @@ -575,8 +588,7 @@ } }, "required": [ - "name", - "deviceClassName" + "name" ], "type": "object" }, @@ -604,7 +616,7 @@ }, "request": { "default": "", - "description": "Request is the name of the request in the claim which caused this device to be allocated. Multiple devices may have been allocated per request.", + "description": "Request is the name of the request in the claim which caused this device to be allocated. If it references a subrequest in the firstAvailable list on a DeviceRequest, this field must include both the name of the main request and the subrequest using the format
/.\n\nMultiple devices may have been allocated per request.", "type": "string" } }, @@ -630,6 +642,48 @@ }, "type": "object" }, + "io.k8s.api.resource.v1alpha3.DeviceSubRequest": { + "description": "DeviceSubRequest describes a request for device provided in the claim.spec.devices.requests[].firstAvailable array. Each is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nDeviceSubRequest is similar to Request, but doesn't expose the AdminAccess or FirstAvailable fields, as those can only be set on the top-level request. AdminAccess is not supported for requests with a prioritized list, and recursive FirstAvailable fields are not supported.", + "properties": { + "allocationMode": { + "description": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AlloctionMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "type": "string" + }, + "count": { + "description": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", + "format": "int64", + "type": "integer" + }, + "deviceClassName": { + "default": "", + "description": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this subrequest.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name can be used to reference this subrequest in the list of constraints or the list of configurations for the claim. References must use the format
/.\n\nMust be a DNS label.", + "type": "string" + }, + "selectors": { + "description": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1alpha3.DeviceSelector" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "name", + "deviceClassName" + ], + "type": "object" + }, "io.k8s.api.resource.v1alpha3.NetworkDeviceData": { "description": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", "properties": { diff --git a/api/openapi-spec/v3/apis__resource.k8s.io__v1beta1_openapi.json b/api/openapi-spec/v3/apis__resource.k8s.io__v1beta1_openapi.json index c2c971a888a9e..49d9d960dc3f3 100644 --- a/api/openapi-spec/v3/apis__resource.k8s.io__v1beta1_openapi.json +++ b/api/openapi-spec/v3/apis__resource.k8s.io__v1beta1_openapi.json @@ -245,7 +245,7 @@ "description": "Opaque provides driver-specific configuration parameters." }, "requests": { - "description": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.", + "description": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", "items": { "default": "", "type": "string" @@ -393,7 +393,7 @@ "description": "Opaque provides driver-specific configuration parameters." }, "requests": { - "description": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.", + "description": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", "items": { "default": "", "type": "string" @@ -545,7 +545,7 @@ "type": "string" }, "requests": { - "description": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.", + "description": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the constraint applies to all subrequests.", "items": { "default": "", "type": "string" @@ -557,33 +557,46 @@ "type": "object" }, "io.k8s.api.resource.v1beta1.DeviceRequest": { - "description": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nA DeviceClassName is currently required. Clients must check that it is indeed set. It's absence indicates that something changed in a way that is not supported by the client yet, in which case it must refuse to handle the request.", + "description": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.", "properties": { "adminAccess": { - "description": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "description": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", "type": "boolean" }, "allocationMode": { - "description": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "description": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", "type": "string" }, "count": { - "description": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", + "description": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", "format": "int64", "type": "integer" }, "deviceClassName": { "default": "", - "description": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "description": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required if no subrequests are specified in the firstAvailable list and no class can be set if subrequests are specified in the firstAvailable list. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", "type": "string" }, + "firstAvailable": { + "description": "FirstAvailable contains subrequests, of which exactly one will be satisfied by the scheduler to satisfy this request. It tries to satisfy them in the order in which they are listed here. So if there are two entries in the list, the scheduler will only check the second one if it determines that the first one cannot be used.\n\nThis field may only be set in the entries of DeviceClaim.Requests.\n\nDRA does not yet implement scoring, so the scheduler will select the first set of devices that satisfies all the requests in the claim. And if the requirements can be satisfied on more than one node, other scheduling features will determine which node is chosen. This means that the set of devices allocated to a claim might not be the optimal set available to the cluster. Scoring will be implemented later.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta1.DeviceSubRequest" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, "name": { "default": "", - "description": "Name can be used to reference this request in a pod.spec.containers[].resources.claims entry and in a constraint of the claim.\n\nMust be a DNS label.", + "description": "Name can be used to reference this request in a pod.spec.containers[].resources.claims entry and in a constraint of the claim.\n\nMust be a DNS label and unique among all DeviceRequests in a ResourceClaim.", "type": "string" }, "selectors": { - "description": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", + "description": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", "items": { "allOf": [ { @@ -597,8 +610,7 @@ } }, "required": [ - "name", - "deviceClassName" + "name" ], "type": "object" }, @@ -626,7 +638,7 @@ }, "request": { "default": "", - "description": "Request is the name of the request in the claim which caused this device to be allocated. Multiple devices may have been allocated per request.", + "description": "Request is the name of the request in the claim which caused this device to be allocated. If it references a subrequest in the firstAvailable list on a DeviceRequest, this field must include both the name of the main request and the subrequest using the format
/.\n\nMultiple devices may have been allocated per request.", "type": "string" } }, @@ -652,6 +664,48 @@ }, "type": "object" }, + "io.k8s.api.resource.v1beta1.DeviceSubRequest": { + "description": "DeviceSubRequest describes a request for device provided in the claim.spec.devices.requests[].firstAvailable array. Each is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nDeviceSubRequest is similar to Request, but doesn't expose the AdminAccess or FirstAvailable fields, as those can only be set on the top-level request. AdminAccess is not supported for requests with a prioritized list, and recursive FirstAvailable fields are not supported.", + "properties": { + "allocationMode": { + "description": "AllocationMode and its related fields define how devices are allocated to satisfy this subrequest. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This subrequest is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AlloctionMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other subrequests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "type": "string" + }, + "count": { + "description": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", + "format": "int64", + "type": "integer" + }, + "deviceClassName": { + "default": "", + "description": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this subrequest.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "type": "string" + }, + "name": { + "default": "", + "description": "Name can be used to reference this subrequest in the list of constraints or the list of configurations for the claim. References must use the format
/.\n\nMust be a DNS label.", + "type": "string" + }, + "selectors": { + "description": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this subrequest. All selectors must be satisfied for a device to be considered.", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.api.resource.v1beta1.DeviceSelector" + } + ], + "default": {} + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "name", + "deviceClassName" + ], + "type": "object" + }, "io.k8s.api.resource.v1beta1.NetworkDeviceData": { "description": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", "properties": { diff --git a/cmd/kube-controller-manager/app/core.go b/cmd/kube-controller-manager/app/core.go index 4bb284c0604b9..a6b9f5bb6e34f 100644 --- a/cmd/kube-controller-manager/app/core.go +++ b/cmd/kube-controller-manager/app/core.go @@ -408,7 +408,10 @@ func newResourceClaimControllerDescriptor() *ControllerDescriptor { func startResourceClaimController(ctx context.Context, controllerContext ControllerContext, controllerName string) (controller.Interface, bool, error) { ephemeralController, err := resourceclaim.NewController( klog.FromContext(ctx), - utilfeature.DefaultFeatureGate.Enabled(features.DRAAdminAccess), + resourceclaim.Features{ + AdminAccess: utilfeature.DefaultFeatureGate.Enabled(features.DRAAdminAccess), + PrioritizedList: utilfeature.DefaultFeatureGate.Enabled(features.DRAPrioritizedList), + }, controllerContext.ClientBuilder.ClientOrDie("resource-claim-controller"), controllerContext.InformerFactory.Core().V1().Pods(), controllerContext.InformerFactory.Resource().V1beta1().ResourceClaims(), diff --git a/pkg/apis/resource/types.go b/pkg/apis/resource/types.go index d802944f3d2c0..c2cb135a36db5 100644 --- a/pkg/apis/resource/types.go +++ b/pkg/apis/resource/types.go @@ -388,16 +388,12 @@ const ( // DeviceRequest is a request for devices required for a claim. // This is typically a request for a single resource like a device, but can // also ask for several identical devices. -// -// A DeviceClassName is currently required. Clients must check that it is -// indeed set. It's absence indicates that something changed in a way that -// is not supported by the client yet, in which case it must refuse to -// handle the request. type DeviceRequest struct { // Name can be used to reference this request in a pod.spec.containers[].resources.claims // entry and in a constraint of the claim. // - // Must be a DNS label. + // Must be a DNS label and unique among all DeviceRequests in a + // ResourceClaim. // // +required Name string @@ -406,7 +402,10 @@ type DeviceRequest struct { // additional configuration and selectors to be inherited by this // request. // - // A class is required. Which classes are available depends on the cluster. + // A class is required if no subrequests are specified in the + // firstAvailable list and no class can be set if subrequests + // are specified in the firstAvailable list. + // Which classes are available depends on the cluster. // // Administrators may use this to restrict which devices may get // requested by only installing classes with selectors for permitted @@ -414,7 +413,8 @@ type DeviceRequest struct { // then administrators can create an empty DeviceClass for users // to reference. // - // +required + // +optional + // +oneOf=deviceRequestType DeviceClassName string // Selectors define criteria which must be satisfied by a specific @@ -422,6 +422,9 @@ type DeviceRequest struct { // request. All selectors must be satisfied for a device to be // considered. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // +optional // +listType=atomic Selectors []DeviceSelector @@ -442,6 +445,9 @@ type DeviceRequest struct { // the mode is ExactCount and count is not specified, the default count is // one. Any other requests must specify this field. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // More modes may get added in the future. Clients must refuse to handle // requests with unknown modes. // @@ -451,6 +457,9 @@ type DeviceRequest struct { // Count is used only when the count mode is "ExactCount". Must be greater than zero. // If AllocationMode is ExactCount and this field is not specified, the default is one. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // +optional // +oneOf=AllocationMode Count int64 @@ -461,6 +470,9 @@ type DeviceRequest struct { // all ordinary claims to the device with respect to access modes and // any resource allocations. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // This is an alpha field and requires enabling the DRAAdminAccess // feature gate. Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. @@ -468,10 +480,105 @@ type DeviceRequest struct { // +optional // +featureGate=DRAAdminAccess AdminAccess *bool + + // FirstAvailable contains subrequests, of which exactly one will be + // satisfied by the scheduler to satisfy this request. It tries to + // satisfy them in the order in which they are listed here. So if + // there are two entries in the list, the scheduler will only check + // the second one if it determines that the first one cannot be used. + // + // This field may only be set in the entries of DeviceClaim.Requests. + // + // DRA does not yet implement scoring, so the scheduler will + // select the first set of devices that satisfies all the + // requests in the claim. And if the requirements can + // be satisfied on more than one node, other scheduling features + // will determine which node is chosen. This means that the set of + // devices allocated to a claim might not be the optimal set + // available to the cluster. Scoring will be implemented later. + // + // +optional + // +oneOf=deviceRequestType + // +listType=atomic + // +featureGate=DRAPrioritizedList + FirstAvailable []DeviceSubRequest +} + +// DeviceSubRequest describes a request for device provided in the +// claim.spec.devices.requests[].firstAvailable array. Each +// is typically a request for a single resource like a device, but can +// also ask for several identical devices. +// +// DeviceSubRequest is similar to Request, but doesn't expose the AdminAccess +// or FirstAvailable fields, as those can only be set on the top-level request. +// AdminAccess is not supported for requests with a prioritized list, and +// recursive FirstAvailable fields are not supported. +type DeviceSubRequest struct { + // Name can be used to reference this subrequest in the list of constraints + // or the list of configurations for the claim. References must use the + // format
/. + // + // Must be a DNS label. + // + // +required + Name string + + // DeviceClassName references a specific DeviceClass, which can define + // additional configuration and selectors to be inherited by this + // subrequest. + // + // A class is required. Which classes are available depends on the cluster. + // + // Administrators may use this to restrict which devices may get + // requested by only installing classes with selectors for permitted + // devices. If users are free to request anything without restrictions, + // then administrators can create an empty DeviceClass for users + // to reference. + // + // +required + DeviceClassName string + + // Selectors define criteria which must be satisfied by a specific + // device in order for that device to be considered for this + // subrequest. All selectors must be satisfied for a device to be + // considered. + // + // +optional + // +listType=atomic + Selectors []DeviceSelector + + // AllocationMode and its related fields define how devices are allocated + // to satisfy this subrequest. Supported values are: + // + // - ExactCount: This request is for a specific number of devices. + // This is the default. The exact number is provided in the + // count field. + // + // - All: This subrequest is for all of the matching devices in a pool. + // Allocation will fail if some devices are already allocated, + // unless adminAccess is requested. + // + // If AlloctionMode is not specified, the default mode is ExactCount. If + // the mode is ExactCount and count is not specified, the default count is + // one. Any other subrequests must specify this field. + // + // More modes may get added in the future. Clients must refuse to handle + // requests with unknown modes. + // + // +optional + AllocationMode DeviceAllocationMode + + // Count is used only when the count mode is "ExactCount". Must be greater than zero. + // If AllocationMode is ExactCount and this field is not specified, the default is one. + // + // +optional + // +oneOf=AllocationMode + Count int64 } const ( - DeviceSelectorsMaxSize = 32 + DeviceSelectorsMaxSize = 32 + FirstAvailableDeviceRequestMaxSize = 8 ) type DeviceAllocationMode string @@ -584,6 +691,10 @@ type DeviceConstraint struct { // constraint. If this is not specified, this constraint applies to all // requests in this claim. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the constraint applies to all subrequests. + // // +optional // +listType=atomic Requests []string @@ -621,6 +732,10 @@ type DeviceClaimConfiguration struct { // Requests lists the names of requests where the configuration applies. // If empty, it applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic Requests []string @@ -793,8 +908,12 @@ const AllocationResultsMaxSize = 32 // DeviceRequestAllocationResult contains the allocation result for one request. type DeviceRequestAllocationResult struct { // Request is the name of the request in the claim which caused this - // device to be allocated. Multiple devices may have been allocated - // per request. + // device to be allocated. If it references a subrequest in the + // firstAvailable list on a DeviceRequest, this field must + // include both the name of the main request and the subrequest + // using the format
/. + // + // Multiple devices may have been allocated per request. // // +required Request string @@ -849,6 +968,10 @@ type DeviceAllocationConfiguration struct { // Requests lists the names of requests where the configuration applies. // If empty, its applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic Requests []string diff --git a/pkg/apis/resource/v1alpha3/defaults.go b/pkg/apis/resource/v1alpha3/defaults.go index 848cc065ba60a..2d93bc2835414 100644 --- a/pkg/apis/resource/v1alpha3/defaults.go +++ b/pkg/apis/resource/v1alpha3/defaults.go @@ -26,6 +26,22 @@ func addDefaultingFuncs(scheme *runtime.Scheme) error { } func SetDefaults_DeviceRequest(obj *resourceapi.DeviceRequest) { + // If the deviceClassName is not set, then the request will have + // subrequests and the allocationMode and count fields should not + // be set. + if obj.DeviceClassName == "" { + return + } + if obj.AllocationMode == "" { + obj.AllocationMode = resourceapi.DeviceAllocationModeExactCount + } + + if obj.AllocationMode == resourceapi.DeviceAllocationModeExactCount && obj.Count == 0 { + obj.Count = 1 + } +} + +func SetDefaults_DeviceSubRequest(obj *resourceapi.DeviceSubRequest) { if obj.AllocationMode == "" { obj.AllocationMode = resourceapi.DeviceAllocationModeExactCount } diff --git a/pkg/apis/resource/v1alpha3/defaults_test.go b/pkg/apis/resource/v1alpha3/defaults_test.go index 5af5760e1479c..4d19747a2b0cc 100644 --- a/pkg/apis/resource/v1alpha3/defaults_test.go +++ b/pkg/apis/resource/v1alpha3/defaults_test.go @@ -34,7 +34,11 @@ func TestSetDefaultAllocationMode(t *testing.T) { claim := &v1alpha3.ResourceClaim{ Spec: v1alpha3.ResourceClaimSpec{ Devices: v1alpha3.DeviceClaim{ - Requests: []v1alpha3.DeviceRequest{{}}, + Requests: []v1alpha3.DeviceRequest{ + { + DeviceClassName: "device-class", + }, + }, }, }, } @@ -64,6 +68,72 @@ func TestSetDefaultAllocationMode(t *testing.T) { assert.Equal(t, nonDefaultCount, output.Spec.Devices.Requests[0].Count) } +func TestSetDefaultAllocationModeWithSubRequests(t *testing.T) { + claim := &v1alpha3.ResourceClaim{ + Spec: v1alpha3.ResourceClaimSpec{ + Devices: v1alpha3.DeviceClaim{ + Requests: []v1alpha3.DeviceRequest{ + { + Name: "req-1", + FirstAvailable: []v1alpha3.DeviceSubRequest{ + { + Name: "subReq-1", + }, + { + Name: "subReq-2", + }, + }, + }, + }, + }, + }, + } + + nilValueMode := v1alpha3.DeviceAllocationMode("") + nilValueCount := int64(0) + defaultMode := v1alpha3.DeviceAllocationModeExactCount + defaultCount := int64(1) + output := roundTrip(t, runtime.Object(claim)).(*v1alpha3.ResourceClaim) + // fields on the top-level DeviceRequest should not change + assert.Equal(t, nilValueMode, output.Spec.Devices.Requests[0].AllocationMode) + assert.Equal(t, nilValueCount, output.Spec.Devices.Requests[0].Count) + // fields on the subRequests should be defaulted. + assert.Equal(t, defaultMode, output.Spec.Devices.Requests[0].FirstAvailable[0].AllocationMode) + assert.Equal(t, defaultCount, output.Spec.Devices.Requests[0].FirstAvailable[0].Count) + assert.Equal(t, defaultMode, output.Spec.Devices.Requests[0].FirstAvailable[1].AllocationMode) + assert.Equal(t, defaultCount, output.Spec.Devices.Requests[0].FirstAvailable[1].Count) + + // field should not change + nonDefaultMode := v1alpha3.DeviceAllocationModeExactCount + nonDefaultCount := int64(10) + claim = &v1alpha3.ResourceClaim{ + Spec: v1alpha3.ResourceClaimSpec{ + Devices: v1alpha3.DeviceClaim{ + Requests: []v1alpha3.DeviceRequest{{ + Name: "req-1", + FirstAvailable: []v1alpha3.DeviceSubRequest{ + { + Name: "subReq-1", + AllocationMode: nonDefaultMode, + Count: nonDefaultCount, + }, + { + Name: "subReq-2", + AllocationMode: nonDefaultMode, + Count: nonDefaultCount, + }, + }, + }}, + }, + }, + } + output = roundTrip(t, runtime.Object(claim)).(*v1alpha3.ResourceClaim) + assert.Equal(t, nonDefaultMode, output.Spec.Devices.Requests[0].FirstAvailable[0].AllocationMode) + assert.Equal(t, nonDefaultCount, output.Spec.Devices.Requests[0].FirstAvailable[0].Count) + assert.Equal(t, nonDefaultMode, output.Spec.Devices.Requests[0].FirstAvailable[1].AllocationMode) + assert.Equal(t, nonDefaultCount, output.Spec.Devices.Requests[0].FirstAvailable[1].Count) +} + func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { codec := legacyscheme.Codecs.LegacyCodec(v1alpha3.SchemeGroupVersion) data, err := runtime.Encode(codec, obj) diff --git a/pkg/apis/resource/v1alpha3/zz_generated.conversion.go b/pkg/apis/resource/v1alpha3/zz_generated.conversion.go index 9b4d7d1fb684b..21d069e4f75aa 100644 --- a/pkg/apis/resource/v1alpha3/zz_generated.conversion.go +++ b/pkg/apis/resource/v1alpha3/zz_generated.conversion.go @@ -232,6 +232,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*resourcev1alpha3.DeviceSubRequest)(nil), (*resource.DeviceSubRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha3_DeviceSubRequest_To_resource_DeviceSubRequest(a.(*resourcev1alpha3.DeviceSubRequest), b.(*resource.DeviceSubRequest), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.DeviceSubRequest)(nil), (*resourcev1alpha3.DeviceSubRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_DeviceSubRequest_To_v1alpha3_DeviceSubRequest(a.(*resource.DeviceSubRequest), b.(*resourcev1alpha3.DeviceSubRequest), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*resourcev1alpha3.NetworkDeviceData)(nil), (*resource.NetworkDeviceData)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha3_NetworkDeviceData_To_resource_NetworkDeviceData(a.(*resourcev1alpha3.NetworkDeviceData), b.(*resource.NetworkDeviceData), scope) }); err != nil { @@ -814,6 +824,7 @@ func autoConvert_v1alpha3_DeviceRequest_To_resource_DeviceRequest(in *resourcev1 out.AllocationMode = resource.DeviceAllocationMode(in.AllocationMode) out.Count = in.Count out.AdminAccess = (*bool)(unsafe.Pointer(in.AdminAccess)) + out.FirstAvailable = *(*[]resource.DeviceSubRequest)(unsafe.Pointer(&in.FirstAvailable)) return nil } @@ -829,6 +840,7 @@ func autoConvert_resource_DeviceRequest_To_v1alpha3_DeviceRequest(in *resource.D out.AllocationMode = resourcev1alpha3.DeviceAllocationMode(in.AllocationMode) out.Count = in.Count out.AdminAccess = (*bool)(unsafe.Pointer(in.AdminAccess)) + out.FirstAvailable = *(*[]resourcev1alpha3.DeviceSubRequest)(unsafe.Pointer(&in.FirstAvailable)) return nil } @@ -885,6 +897,34 @@ func Convert_resource_DeviceSelector_To_v1alpha3_DeviceSelector(in *resource.Dev return autoConvert_resource_DeviceSelector_To_v1alpha3_DeviceSelector(in, out, s) } +func autoConvert_v1alpha3_DeviceSubRequest_To_resource_DeviceSubRequest(in *resourcev1alpha3.DeviceSubRequest, out *resource.DeviceSubRequest, s conversion.Scope) error { + out.Name = in.Name + out.DeviceClassName = in.DeviceClassName + out.Selectors = *(*[]resource.DeviceSelector)(unsafe.Pointer(&in.Selectors)) + out.AllocationMode = resource.DeviceAllocationMode(in.AllocationMode) + out.Count = in.Count + return nil +} + +// Convert_v1alpha3_DeviceSubRequest_To_resource_DeviceSubRequest is an autogenerated conversion function. +func Convert_v1alpha3_DeviceSubRequest_To_resource_DeviceSubRequest(in *resourcev1alpha3.DeviceSubRequest, out *resource.DeviceSubRequest, s conversion.Scope) error { + return autoConvert_v1alpha3_DeviceSubRequest_To_resource_DeviceSubRequest(in, out, s) +} + +func autoConvert_resource_DeviceSubRequest_To_v1alpha3_DeviceSubRequest(in *resource.DeviceSubRequest, out *resourcev1alpha3.DeviceSubRequest, s conversion.Scope) error { + out.Name = in.Name + out.DeviceClassName = in.DeviceClassName + out.Selectors = *(*[]resourcev1alpha3.DeviceSelector)(unsafe.Pointer(&in.Selectors)) + out.AllocationMode = resourcev1alpha3.DeviceAllocationMode(in.AllocationMode) + out.Count = in.Count + return nil +} + +// Convert_resource_DeviceSubRequest_To_v1alpha3_DeviceSubRequest is an autogenerated conversion function. +func Convert_resource_DeviceSubRequest_To_v1alpha3_DeviceSubRequest(in *resource.DeviceSubRequest, out *resourcev1alpha3.DeviceSubRequest, s conversion.Scope) error { + return autoConvert_resource_DeviceSubRequest_To_v1alpha3_DeviceSubRequest(in, out, s) +} + func autoConvert_v1alpha3_NetworkDeviceData_To_resource_NetworkDeviceData(in *resourcev1alpha3.NetworkDeviceData, out *resource.NetworkDeviceData, s conversion.Scope) error { out.InterfaceName = in.InterfaceName out.IPs = *(*[]string)(unsafe.Pointer(&in.IPs)) diff --git a/pkg/apis/resource/v1alpha3/zz_generated.defaults.go b/pkg/apis/resource/v1alpha3/zz_generated.defaults.go index 874f5eba20dd1..67ba496b84fdd 100644 --- a/pkg/apis/resource/v1alpha3/zz_generated.defaults.go +++ b/pkg/apis/resource/v1alpha3/zz_generated.defaults.go @@ -45,6 +45,10 @@ func SetObjectDefaults_ResourceClaim(in *resourcev1alpha3.ResourceClaim) { for i := range in.Spec.Devices.Requests { a := &in.Spec.Devices.Requests[i] SetDefaults_DeviceRequest(a) + for j := range a.FirstAvailable { + b := &a.FirstAvailable[j] + SetDefaults_DeviceSubRequest(b) + } } } @@ -59,6 +63,10 @@ func SetObjectDefaults_ResourceClaimTemplate(in *resourcev1alpha3.ResourceClaimT for i := range in.Spec.Spec.Devices.Requests { a := &in.Spec.Spec.Devices.Requests[i] SetDefaults_DeviceRequest(a) + for j := range a.FirstAvailable { + b := &a.FirstAvailable[j] + SetDefaults_DeviceSubRequest(b) + } } } diff --git a/pkg/apis/resource/v1beta1/defaults.go b/pkg/apis/resource/v1beta1/defaults.go index 59f402867cb65..6ce9b1de26a60 100644 --- a/pkg/apis/resource/v1beta1/defaults.go +++ b/pkg/apis/resource/v1beta1/defaults.go @@ -26,6 +26,22 @@ func addDefaultingFuncs(scheme *runtime.Scheme) error { } func SetDefaults_DeviceRequest(obj *resourceapi.DeviceRequest) { + // If the deviceClassName is not set, then the request will have + // subrequests and the allocationMode and count fields should not + // be set. + if obj.DeviceClassName == "" { + return + } + if obj.AllocationMode == "" { + obj.AllocationMode = resourceapi.DeviceAllocationModeExactCount + } + + if obj.AllocationMode == resourceapi.DeviceAllocationModeExactCount && obj.Count == 0 { + obj.Count = 1 + } +} + +func SetDefaults_DeviceSubRequest(obj *resourceapi.DeviceSubRequest) { if obj.AllocationMode == "" { obj.AllocationMode = resourceapi.DeviceAllocationModeExactCount } diff --git a/pkg/apis/resource/v1beta1/defaults_test.go b/pkg/apis/resource/v1beta1/defaults_test.go index 3c7d59e422fad..9b00b0732a115 100644 --- a/pkg/apis/resource/v1beta1/defaults_test.go +++ b/pkg/apis/resource/v1beta1/defaults_test.go @@ -34,7 +34,11 @@ func TestSetDefaultAllocationMode(t *testing.T) { claim := &v1beta1.ResourceClaim{ Spec: v1beta1.ResourceClaimSpec{ Devices: v1beta1.DeviceClaim{ - Requests: []v1beta1.DeviceRequest{{}}, + Requests: []v1beta1.DeviceRequest{ + { + DeviceClassName: "device-class", + }, + }, }, }, } @@ -64,6 +68,72 @@ func TestSetDefaultAllocationMode(t *testing.T) { assert.Equal(t, nonDefaultCount, output.Spec.Devices.Requests[0].Count) } +func TestSetDefaultAllocationModeWithSubRequests(t *testing.T) { + claim := &v1beta1.ResourceClaim{ + Spec: v1beta1.ResourceClaimSpec{ + Devices: v1beta1.DeviceClaim{ + Requests: []v1beta1.DeviceRequest{ + { + Name: "req-1", + FirstAvailable: []v1beta1.DeviceSubRequest{ + { + Name: "subReq-1", + }, + { + Name: "subReq-2", + }, + }, + }, + }, + }, + }, + } + + nilValueMode := v1beta1.DeviceAllocationMode("") + nilValueCount := int64(0) + defaultMode := v1beta1.DeviceAllocationModeExactCount + defaultCount := int64(1) + output := roundTrip(t, runtime.Object(claim)).(*v1beta1.ResourceClaim) + // fields on the top-level DeviceRequest should not change + assert.Equal(t, nilValueMode, output.Spec.Devices.Requests[0].AllocationMode) + assert.Equal(t, nilValueCount, output.Spec.Devices.Requests[0].Count) + // fields on the subRequests should be defaulted. + assert.Equal(t, defaultMode, output.Spec.Devices.Requests[0].FirstAvailable[0].AllocationMode) + assert.Equal(t, defaultCount, output.Spec.Devices.Requests[0].FirstAvailable[0].Count) + assert.Equal(t, defaultMode, output.Spec.Devices.Requests[0].FirstAvailable[1].AllocationMode) + assert.Equal(t, defaultCount, output.Spec.Devices.Requests[0].FirstAvailable[1].Count) + + // field should not change + nonDefaultMode := v1beta1.DeviceAllocationModeExactCount + nonDefaultCount := int64(10) + claim = &v1beta1.ResourceClaim{ + Spec: v1beta1.ResourceClaimSpec{ + Devices: v1beta1.DeviceClaim{ + Requests: []v1beta1.DeviceRequest{{ + Name: "req-1", + FirstAvailable: []v1beta1.DeviceSubRequest{ + { + Name: "subReq-1", + AllocationMode: nonDefaultMode, + Count: nonDefaultCount, + }, + { + Name: "subReq-2", + AllocationMode: nonDefaultMode, + Count: nonDefaultCount, + }, + }, + }}, + }, + }, + } + output = roundTrip(t, runtime.Object(claim)).(*v1beta1.ResourceClaim) + assert.Equal(t, nonDefaultMode, output.Spec.Devices.Requests[0].FirstAvailable[0].AllocationMode) + assert.Equal(t, nonDefaultCount, output.Spec.Devices.Requests[0].FirstAvailable[0].Count) + assert.Equal(t, nonDefaultMode, output.Spec.Devices.Requests[0].FirstAvailable[1].AllocationMode) + assert.Equal(t, nonDefaultCount, output.Spec.Devices.Requests[0].FirstAvailable[1].Count) +} + func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { codec := legacyscheme.Codecs.LegacyCodec(v1beta1.SchemeGroupVersion) data, err := runtime.Encode(codec, obj) diff --git a/pkg/apis/resource/v1beta1/zz_generated.conversion.go b/pkg/apis/resource/v1beta1/zz_generated.conversion.go index 74b674ee42b1a..e74a848827f4f 100644 --- a/pkg/apis/resource/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/resource/v1beta1/zz_generated.conversion.go @@ -241,6 +241,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*resourcev1beta1.DeviceSubRequest)(nil), (*resource.DeviceSubRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_DeviceSubRequest_To_resource_DeviceSubRequest(a.(*resourcev1beta1.DeviceSubRequest), b.(*resource.DeviceSubRequest), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*resource.DeviceSubRequest)(nil), (*resourcev1beta1.DeviceSubRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_resource_DeviceSubRequest_To_v1beta1_DeviceSubRequest(a.(*resource.DeviceSubRequest), b.(*resourcev1beta1.DeviceSubRequest), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*resourcev1beta1.NetworkDeviceData)(nil), (*resource.NetworkDeviceData)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_NetworkDeviceData_To_resource_NetworkDeviceData(a.(*resourcev1beta1.NetworkDeviceData), b.(*resource.NetworkDeviceData), scope) }); err != nil { @@ -793,6 +803,7 @@ func autoConvert_v1beta1_DeviceRequest_To_resource_DeviceRequest(in *resourcev1b out.AllocationMode = resource.DeviceAllocationMode(in.AllocationMode) out.Count = in.Count out.AdminAccess = (*bool)(unsafe.Pointer(in.AdminAccess)) + out.FirstAvailable = *(*[]resource.DeviceSubRequest)(unsafe.Pointer(&in.FirstAvailable)) return nil } @@ -808,6 +819,7 @@ func autoConvert_resource_DeviceRequest_To_v1beta1_DeviceRequest(in *resource.De out.AllocationMode = resourcev1beta1.DeviceAllocationMode(in.AllocationMode) out.Count = in.Count out.AdminAccess = (*bool)(unsafe.Pointer(in.AdminAccess)) + out.FirstAvailable = *(*[]resourcev1beta1.DeviceSubRequest)(unsafe.Pointer(&in.FirstAvailable)) return nil } @@ -864,6 +876,34 @@ func Convert_resource_DeviceSelector_To_v1beta1_DeviceSelector(in *resource.Devi return autoConvert_resource_DeviceSelector_To_v1beta1_DeviceSelector(in, out, s) } +func autoConvert_v1beta1_DeviceSubRequest_To_resource_DeviceSubRequest(in *resourcev1beta1.DeviceSubRequest, out *resource.DeviceSubRequest, s conversion.Scope) error { + out.Name = in.Name + out.DeviceClassName = in.DeviceClassName + out.Selectors = *(*[]resource.DeviceSelector)(unsafe.Pointer(&in.Selectors)) + out.AllocationMode = resource.DeviceAllocationMode(in.AllocationMode) + out.Count = in.Count + return nil +} + +// Convert_v1beta1_DeviceSubRequest_To_resource_DeviceSubRequest is an autogenerated conversion function. +func Convert_v1beta1_DeviceSubRequest_To_resource_DeviceSubRequest(in *resourcev1beta1.DeviceSubRequest, out *resource.DeviceSubRequest, s conversion.Scope) error { + return autoConvert_v1beta1_DeviceSubRequest_To_resource_DeviceSubRequest(in, out, s) +} + +func autoConvert_resource_DeviceSubRequest_To_v1beta1_DeviceSubRequest(in *resource.DeviceSubRequest, out *resourcev1beta1.DeviceSubRequest, s conversion.Scope) error { + out.Name = in.Name + out.DeviceClassName = in.DeviceClassName + out.Selectors = *(*[]resourcev1beta1.DeviceSelector)(unsafe.Pointer(&in.Selectors)) + out.AllocationMode = resourcev1beta1.DeviceAllocationMode(in.AllocationMode) + out.Count = in.Count + return nil +} + +// Convert_resource_DeviceSubRequest_To_v1beta1_DeviceSubRequest is an autogenerated conversion function. +func Convert_resource_DeviceSubRequest_To_v1beta1_DeviceSubRequest(in *resource.DeviceSubRequest, out *resourcev1beta1.DeviceSubRequest, s conversion.Scope) error { + return autoConvert_resource_DeviceSubRequest_To_v1beta1_DeviceSubRequest(in, out, s) +} + func autoConvert_v1beta1_NetworkDeviceData_To_resource_NetworkDeviceData(in *resourcev1beta1.NetworkDeviceData, out *resource.NetworkDeviceData, s conversion.Scope) error { out.InterfaceName = in.InterfaceName out.IPs = *(*[]string)(unsafe.Pointer(&in.IPs)) diff --git a/pkg/apis/resource/v1beta1/zz_generated.defaults.go b/pkg/apis/resource/v1beta1/zz_generated.defaults.go index a9e274fe6ab79..2335929c40476 100644 --- a/pkg/apis/resource/v1beta1/zz_generated.defaults.go +++ b/pkg/apis/resource/v1beta1/zz_generated.defaults.go @@ -45,6 +45,10 @@ func SetObjectDefaults_ResourceClaim(in *resourcev1beta1.ResourceClaim) { for i := range in.Spec.Devices.Requests { a := &in.Spec.Devices.Requests[i] SetDefaults_DeviceRequest(a) + for j := range a.FirstAvailable { + b := &a.FirstAvailable[j] + SetDefaults_DeviceSubRequest(b) + } } } @@ -59,6 +63,10 @@ func SetObjectDefaults_ResourceClaimTemplate(in *resourcev1beta1.ResourceClaimTe for i := range in.Spec.Spec.Devices.Requests { a := &in.Spec.Spec.Devices.Requests[i] SetDefaults_DeviceRequest(a) + for j := range a.FirstAvailable { + b := &a.FirstAvailable[j] + SetDefaults_DeviceSubRequest(b) + } } } diff --git a/pkg/apis/resource/validation/validation.go b/pkg/apis/resource/validation/validation.go index 753bfd2e22923..248ddcfcfc983 100644 --- a/pkg/apis/resource/validation/validation.go +++ b/pkg/apis/resource/validation/validation.go @@ -119,10 +119,41 @@ func validateDeviceClaim(deviceClaim *resource.DeviceClaim, fldPath *field.Path, return allErrs } -func gatherRequestNames(deviceClaim *resource.DeviceClaim) sets.Set[string] { - requestNames := sets.New[string]() +type requestNames map[string]sets.Set[string] + +func (r requestNames) Has(s string) bool { + segments := strings.Split(s, "/") + // If there are more than one / in the string, we + // know there can't be any match. + if len(segments) > 2 { + return false + } + // If the first segment doesn't have a match, we + // don't need to check the other one. + subRequestNames, found := r[segments[0]] + if !found { + return false + } + if len(segments) == 1 { + return true + } + // If the first segment matched and we have another one, + // check for a match for that too. + return subRequestNames.Has(segments[1]) +} + +func gatherRequestNames(deviceClaim *resource.DeviceClaim) requestNames { + requestNames := make(requestNames) for _, request := range deviceClaim.Requests { - requestNames.Insert(request.Name) + if len(request.FirstAvailable) == 0 { + requestNames[request.Name] = nil + continue + } + subRequestNames := sets.New[string]() + for _, subRequest := range request.FirstAvailable { + subRequestNames.Insert(subRequest.Name) + } + requestNames[request.Name] = subRequestNames } return requestNames } @@ -138,31 +169,83 @@ func gatherAllocatedDevices(allocationResult *resource.DeviceAllocationResult) s func validateDeviceRequest(request resource.DeviceRequest, fldPath *field.Path, stored bool) field.ErrorList { allErrs := validateRequestName(request.Name, fldPath.Child("name")) - if request.DeviceClassName == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("deviceClassName"), "")) - } else { - allErrs = append(allErrs, validateDeviceClassName(request.DeviceClassName, fldPath.Child("deviceClassName"))...) + + if request.DeviceClassName == "" && len(request.FirstAvailable) == 0 { + allErrs = append(allErrs, field.Required(fldPath, "exactly one of `deviceClassName` or `firstAvailable` must be specified")) + } else if request.DeviceClassName != "" && len(request.FirstAvailable) > 0 { + allErrs = append(allErrs, field.Invalid(fldPath, nil, "exactly one of `deviceClassName` or `firstAvailable` must be specified")) + } else if request.DeviceClassName != "" { + allErrs = append(allErrs, validateDeviceClass(request.DeviceClassName, fldPath.Child("deviceClassName"))...) + allErrs = append(allErrs, validateSelectorSlice(request.Selectors, fldPath.Child("selectors"), stored)...) + allErrs = append(allErrs, validateDeviceAllocationMode(request.AllocationMode, request.Count, fldPath.Child("allocationMode"), fldPath.Child("count"))...) + } else if len(request.FirstAvailable) > 0 { + if request.Selectors != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("selectors"), request.Selectors, "must not be specified when firstAvailable is set")) + } + if request.AllocationMode != "" { + allErrs = append(allErrs, field.Invalid(fldPath.Child("allocationMode"), request.AllocationMode, "must not be specified when firstAvailable is set")) + } + if request.Count != 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("count"), request.Count, "must not be specified when firstAvailable is set")) + } + if request.AdminAccess != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("adminAccess"), request.AdminAccess, "must not be specified when firstAvailable is set")) + } + allErrs = append(allErrs, validateSet(request.FirstAvailable, resource.FirstAvailableDeviceRequestMaxSize, + func(subRequest resource.DeviceSubRequest, fldPath *field.Path) field.ErrorList { + return validateDeviceSubRequest(subRequest, fldPath, stored) + }, + func(subRequest resource.DeviceSubRequest) (string, string) { + return subRequest.Name, "name" + }, + fldPath.Child("firstAvailable"))...) } - allErrs = append(allErrs, validateSlice(request.Selectors, resource.DeviceSelectorsMaxSize, - func(selector resource.DeviceSelector, fldPath *field.Path) field.ErrorList { - return validateSelector(selector, fldPath, stored) - }, - fldPath.Child("selectors"))...) - switch request.AllocationMode { + return allErrs +} + +func validateDeviceSubRequest(subRequest resource.DeviceSubRequest, fldPath *field.Path, stored bool) field.ErrorList { + allErrs := validateRequestName(subRequest.Name, fldPath.Child("name")) + allErrs = append(allErrs, validateDeviceClass(subRequest.DeviceClassName, fldPath.Child("deviceClassName"))...) + allErrs = append(allErrs, validateSelectorSlice(subRequest.Selectors, fldPath.Child("selectors"), stored)...) + allErrs = append(allErrs, validateDeviceAllocationMode(subRequest.AllocationMode, subRequest.Count, fldPath.Child("allocationMode"), fldPath.Child("count"))...) + return allErrs +} + +func validateDeviceAllocationMode(deviceAllocationMode resource.DeviceAllocationMode, count int64, allocModeFldPath, countFldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + switch deviceAllocationMode { case resource.DeviceAllocationModeAll: - if request.Count != 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("count"), request.Count, fmt.Sprintf("must not be specified when allocationMode is '%s'", request.AllocationMode))) + if count != 0 { + allErrs = append(allErrs, field.Invalid(countFldPath, count, fmt.Sprintf("must not be specified when allocationMode is '%s'", deviceAllocationMode))) } case resource.DeviceAllocationModeExactCount: - if request.Count <= 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("count"), request.Count, "must be greater than zero")) + if count <= 0 { + allErrs = append(allErrs, field.Invalid(countFldPath, count, "must be greater than zero")) } default: - allErrs = append(allErrs, field.NotSupported(fldPath.Child("allocationMode"), request.AllocationMode, []resource.DeviceAllocationMode{resource.DeviceAllocationModeAll, resource.DeviceAllocationModeExactCount})) + allErrs = append(allErrs, field.NotSupported(allocModeFldPath, deviceAllocationMode, []resource.DeviceAllocationMode{resource.DeviceAllocationModeAll, resource.DeviceAllocationModeExactCount})) + } + return allErrs +} + +func validateDeviceClass(deviceClass string, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + if deviceClass == "" { + allErrs = append(allErrs, field.Required(fldPath, "")) + } else { + allErrs = append(allErrs, validateDeviceClassName(deviceClass, fldPath)...) } return allErrs } +func validateSelectorSlice(selectors []resource.DeviceSelector, fldPath *field.Path, stored bool) field.ErrorList { + return validateSlice(selectors, resource.DeviceSelectorsMaxSize, + func(selector resource.DeviceSelector, fldPath *field.Path) field.ErrorList { + return validateSelector(selector, fldPath, stored) + }, + fldPath) +} + func validateSelector(selector resource.DeviceSelector, fldPath *field.Path, stored bool) field.ErrorList { var allErrs field.ErrorList if selector.CEL == nil { @@ -210,7 +293,7 @@ func convertCELErrorToValidationError(fldPath *field.Path, expression string, er return field.InternalError(fldPath, fmt.Errorf("unsupported error type: %w", err)) } -func validateDeviceConstraint(constraint resource.DeviceConstraint, fldPath *field.Path, requestNames sets.Set[string]) field.ErrorList { +func validateDeviceConstraint(constraint resource.DeviceConstraint, fldPath *field.Path, requestNames requestNames) field.ErrorList { var allErrs field.ErrorList allErrs = append(allErrs, validateSet(constraint.Requests, resource.DeviceRequestsMaxSize, func(name string, fldPath *field.Path) field.ErrorList { @@ -225,7 +308,7 @@ func validateDeviceConstraint(constraint resource.DeviceConstraint, fldPath *fie return allErrs } -func validateDeviceClaimConfiguration(config resource.DeviceClaimConfiguration, fldPath *field.Path, requestNames sets.Set[string], stored bool) field.ErrorList { +func validateDeviceClaimConfiguration(config resource.DeviceClaimConfiguration, fldPath *field.Path, requestNames requestNames, stored bool) field.ErrorList { var allErrs field.ErrorList allErrs = append(allErrs, validateSet(config.Requests, resource.DeviceRequestsMaxSize, func(name string, fldPath *field.Path) field.ErrorList { @@ -235,10 +318,20 @@ func validateDeviceClaimConfiguration(config resource.DeviceClaimConfiguration, return allErrs } -func validateRequestNameRef(name string, fldPath *field.Path, requestNames sets.Set[string]) field.ErrorList { - allErrs := validateRequestName(name, fldPath) +func validateRequestNameRef(name string, fldPath *field.Path, requestNames requestNames) field.ErrorList { + var allErrs field.ErrorList + segments := strings.Split(name, "/") + if len(segments) > 2 { + allErrs = append(allErrs, field.Invalid(fldPath, name, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'")) + return allErrs + } + + for i := range segments { + allErrs = append(allErrs, validateRequestName(segments[i], fldPath)...) + } + if !requestNames.Has(name) { - allErrs = append(allErrs, field.Invalid(fldPath, name, "must be the name of a request in the claim")) + allErrs = append(allErrs, field.Invalid(fldPath, name, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'")) } return allErrs } @@ -260,7 +353,7 @@ func validateOpaqueConfiguration(config resource.OpaqueDeviceConfiguration, fldP return allErrs } -func validateResourceClaimStatusUpdate(status, oldStatus *resource.ResourceClaimStatus, claimDeleted bool, requestNames sets.Set[string], fldPath *field.Path) field.ErrorList { +func validateResourceClaimStatusUpdate(status, oldStatus *resource.ResourceClaimStatus, claimDeleted bool, requestNames requestNames, fldPath *field.Path) field.ErrorList { var allErrs field.ErrorList allErrs = append(allErrs, validateSet(status.ReservedFor, resource.ResourceClaimReservedForMaxSize, validateResourceClaimUserReference, @@ -328,7 +421,7 @@ func validateResourceClaimUserReference(ref resource.ResourceClaimConsumerRefere // validateAllocationResult enforces constraints for *new* results, which in at // least one case (admin access) are more strict than before. Therefore it // may not be called to re-validate results which were stored earlier. -func validateAllocationResult(allocation *resource.AllocationResult, fldPath *field.Path, requestNames sets.Set[string], stored bool) field.ErrorList { +func validateAllocationResult(allocation *resource.AllocationResult, fldPath *field.Path, requestNames requestNames, stored bool) field.ErrorList { var allErrs field.ErrorList allErrs = append(allErrs, validateDeviceAllocationResult(allocation.Devices, fldPath.Child("devices"), requestNames, stored)...) if allocation.NodeSelector != nil { @@ -337,7 +430,7 @@ func validateAllocationResult(allocation *resource.AllocationResult, fldPath *fi return allErrs } -func validateDeviceAllocationResult(allocation resource.DeviceAllocationResult, fldPath *field.Path, requestNames sets.Set[string], stored bool) field.ErrorList { +func validateDeviceAllocationResult(allocation resource.DeviceAllocationResult, fldPath *field.Path, requestNames requestNames, stored bool) field.ErrorList { var allErrs field.ErrorList allErrs = append(allErrs, validateSlice(allocation.Results, resource.AllocationResultsMaxSize, func(result resource.DeviceRequestAllocationResult, fldPath *field.Path) field.ErrorList { @@ -351,7 +444,7 @@ func validateDeviceAllocationResult(allocation resource.DeviceAllocationResult, return allErrs } -func validateDeviceRequestAllocationResult(result resource.DeviceRequestAllocationResult, fldPath *field.Path, requestNames sets.Set[string]) field.ErrorList { +func validateDeviceRequestAllocationResult(result resource.DeviceRequestAllocationResult, fldPath *field.Path, requestNames requestNames) field.ErrorList { var allErrs field.ErrorList allErrs = append(allErrs, validateRequestNameRef(result.Request, fldPath.Child("request"), requestNames)...) allErrs = append(allErrs, validateDriverName(result.Driver, fldPath.Child("driver"))...) @@ -360,7 +453,7 @@ func validateDeviceRequestAllocationResult(result resource.DeviceRequestAllocati return allErrs } -func validateDeviceAllocationConfiguration(config resource.DeviceAllocationConfiguration, fldPath *field.Path, requestNames sets.Set[string], stored bool) field.ErrorList { +func validateDeviceAllocationConfiguration(config resource.DeviceAllocationConfiguration, fldPath *field.Path, requestNames requestNames, stored bool) field.ErrorList { var allErrs field.ErrorList allErrs = append(allErrs, validateAllocationConfigSource(config.Source, fldPath.Child("source"))...) allErrs = append(allErrs, validateSet(config.Requests, resource.DeviceRequestsMaxSize, diff --git a/pkg/apis/resource/validation/validation_resourceclaim_test.go b/pkg/apis/resource/validation/validation_resourceclaim_test.go index 5f3fe3308a8e2..1a243920e4f4c 100644 --- a/pkg/apis/resource/validation/validation_resourceclaim_test.go +++ b/pkg/apis/resource/validation/validation_resourceclaim_test.go @@ -46,13 +46,17 @@ func testClaim(name, namespace string, spec resource.ResourceClaimSpec) *resourc } const ( - goodName = "foo" - badName = "!@#$%^" - goodNS = "ns" + goodName = "foo" + goodName2 = "bar" + badName = "!@#$%^" + goodNS = "ns" + badSubrequestName = "&^%$" ) var ( - validClaimSpec = resource.ResourceClaimSpec{ + badRequestFormat = fmt.Sprintf("%s/%s/%s", goodName, goodName, goodName) + badFullSubrequestName = fmt.Sprintf("%s/%s", badName, badSubrequestName) + validClaimSpec = resource.ResourceClaimSpec{ Devices: resource.DeviceClaim{ Requests: []resource.DeviceRequest{{ Name: goodName, @@ -62,6 +66,34 @@ var ( }}, }, } + validClaimSpecWithFirstAvailable = resource.ResourceClaimSpec{ + Devices: resource.DeviceClaim{ + Requests: []resource.DeviceRequest{{ + Name: goodName, + FirstAvailable: []resource.DeviceSubRequest{ + { + Name: goodName, + DeviceClassName: goodName, + AllocationMode: resource.DeviceAllocationModeExactCount, + Count: 1, + }, + { + Name: goodName2, + DeviceClassName: goodName, + AllocationMode: resource.DeviceAllocationModeExactCount, + Count: 1, + }, + }, + }}, + }, + } + validSelector = []resource.DeviceSelector{ + { + CEL: &resource.CELDeviceSelector{ + Expression: `device.driver == "dra.example.com"`, + }, + }, + } validClaim = testClaim(goodName, goodNS, validClaimSpec) ) @@ -213,8 +245,8 @@ func TestValidateClaim(t *testing.T) { return claim }(), }, - "missing-classname": { - wantFailures: field.ErrorList{field.Required(field.NewPath("spec", "devices", "requests").Index(0).Child("deviceClassName"), "")}, + "missing-classname-and-firstavailable": { + wantFailures: field.ErrorList{field.Required(field.NewPath("spec", "devices", "requests").Index(0), "exactly one of `deviceClassName` or `firstAvailable` must be specified")}, claim: func() *resource.ResourceClaim { claim := testClaim(goodName, goodNS, validClaimSpec) claim.Spec.Devices.Requests[0].DeviceClassName = "" @@ -225,14 +257,14 @@ func TestValidateClaim(t *testing.T) { wantFailures: field.ErrorList{ field.TooMany(field.NewPath("spec", "devices", "requests"), resource.DeviceRequestsMaxSize+1, resource.DeviceRequestsMaxSize), field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"), - field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim"), + field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"), field.TypeInvalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "missing-domain", "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"), field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), resource.FullyQualifiedName("missing-domain"), "must include a domain"), field.Required(field.NewPath("spec", "devices", "constraints").Index(1).Child("matchAttribute"), "name required"), field.Required(field.NewPath("spec", "devices", "constraints").Index(2).Child("matchAttribute"), ""), field.TooMany(field.NewPath("spec", "devices", "constraints"), resource.DeviceConstraintsMaxSize+1, resource.DeviceConstraintsMaxSize), field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"), - field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim"), + field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"), field.TooMany(field.NewPath("spec", "devices", "config"), resource.DeviceConfigMaxSize+1, resource.DeviceConfigMaxSize), }, claim: func() *resource.ResourceClaim { @@ -312,13 +344,13 @@ func TestValidateClaim(t *testing.T) { "invalid-spec": { wantFailures: field.ErrorList{ field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"), - field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim"), + field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"), field.TypeInvalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "missing-domain", "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"), field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), resource.FullyQualifiedName("missing-domain"), "must include a domain"), field.Required(field.NewPath("spec", "devices", "constraints").Index(1).Child("matchAttribute"), "name required"), field.Required(field.NewPath("spec", "devices", "constraints").Index(2).Child("matchAttribute"), ""), field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"), - field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim"), + field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"), }, claim: func() *resource.ResourceClaim { claim := testClaim(goodName, goodNS, validClaimSpec) @@ -537,6 +569,172 @@ func TestValidateClaim(t *testing.T) { return claim }(), }, + "prioritized-list-valid": { + wantFailures: nil, + claim: func() *resource.ResourceClaim { + claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable) + return claim + }(), + }, + "prioritized-list-field-on-parent": { + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices", "requests").Index(0), nil, "exactly one of `deviceClassName` or `firstAvailable` must be specified"), + }, + claim: func() *resource.ResourceClaim { + claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable) + claim.Spec.Devices.Requests[0].DeviceClassName = goodName + claim.Spec.Devices.Requests[0].Selectors = validSelector + claim.Spec.Devices.Requests[0].AllocationMode = resource.DeviceAllocationModeAll + claim.Spec.Devices.Requests[0].Count = 2 + claim.Spec.Devices.Requests[0].AdminAccess = ptr.To(true) + return claim + }(), + }, + "prioritized-list-invalid-nested-request": { + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("name"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"), + field.Required(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("deviceClassName"), ""), + field.NotSupported(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("allocationMode"), resource.DeviceAllocationMode(""), []resource.DeviceAllocationMode{resource.DeviceAllocationModeAll, resource.DeviceAllocationModeExactCount}), + }, + claim: func() *resource.ResourceClaim { + claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable) + claim.Spec.Devices.Requests[0].FirstAvailable[0] = resource.DeviceSubRequest{ + Name: badName, + } + return claim + }(), + }, + "prioritized-list-nested-requests-same-name": { + wantFailures: field.ErrorList{ + field.Duplicate(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(1).Child("name"), "foo"), + }, + claim: func() *resource.ResourceClaim { + claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable) + claim.Spec.Devices.Requests[0].FirstAvailable[1].Name = goodName + return claim + }(), + }, + "prioritized-list-too-many-subrequests": { + wantFailures: field.ErrorList{ + field.TooMany(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable"), 9, 8), + }, + claim: func() *resource.ResourceClaim { + claim := testClaim(goodName, goodNS, validClaimSpec) + claim.Spec.Devices.Requests[0].DeviceClassName = "" + claim.Spec.Devices.Requests[0].AllocationMode = "" + claim.Spec.Devices.Requests[0].Count = 0 + var subRequests []resource.DeviceSubRequest + for i := 0; i <= 8; i++ { + subRequests = append(subRequests, resource.DeviceSubRequest{ + Name: fmt.Sprintf("subreq-%d", i), + DeviceClassName: goodName, + AllocationMode: resource.DeviceAllocationModeExactCount, + Count: 1, + }) + } + claim.Spec.Devices.Requests[0].FirstAvailable = subRequests + return claim + }(), + }, + "prioritized-list-config-requests-with-subrequest-reference": { + wantFailures: nil, + claim: func() *resource.ResourceClaim { + claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable) + claim.Spec.Devices.Config = []resource.DeviceClaimConfiguration{ + { + Requests: []string{"foo/bar"}, + DeviceConfiguration: resource.DeviceConfiguration{ + Opaque: &resource.OpaqueDeviceConfiguration{ + Driver: "dra.example.com", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"kind": "foo", "apiVersion": "dra.example.com/v1"}`), + }, + }, + }, + }, + } + return claim + }(), + }, + "prioritized-list-config-requests-with-parent-request-reference": { + wantFailures: nil, + claim: func() *resource.ResourceClaim { + claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable) + claim.Spec.Devices.Config = []resource.DeviceClaimConfiguration{ + { + Requests: []string{"foo"}, + DeviceConfiguration: resource.DeviceConfiguration{ + Opaque: &resource.OpaqueDeviceConfiguration{ + Driver: "dra.example.com", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"kind": "foo", "apiVersion": "dra.example.com/v1"}`), + }, + }, + }, + }, + } + return claim + }(), + }, + "prioritized-list-config-requests-with-invalid-subrequest-reference": { + wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(0), "foo/baz", "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'")}, + claim: func() *resource.ResourceClaim { + claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable) + claim.Spec.Devices.Config = []resource.DeviceClaimConfiguration{ + { + Requests: []string{"foo/baz"}, + DeviceConfiguration: resource.DeviceConfiguration{ + Opaque: &resource.OpaqueDeviceConfiguration{ + Driver: "dra.example.com", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"kind": "foo", "apiVersion": "dra.example.com/v1"}`), + }, + }, + }, + }, + } + return claim + }(), + }, + "prioritized-list-constraints-requests-with-subrequest-reference": { + wantFailures: nil, + claim: func() *resource.ResourceClaim { + claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable) + claim.Spec.Devices.Constraints = []resource.DeviceConstraint{ + { + Requests: []string{"foo/bar"}, + MatchAttribute: ptr.To(resource.FullyQualifiedName("dra.example.com/driverVersion")), + }, + } + return claim + }(), + }, + "prioritized-list-constraints-requests-with-parent-request-reference": { + wantFailures: nil, + claim: func() *resource.ResourceClaim { + claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable) + claim.Spec.Devices.Constraints = []resource.DeviceConstraint{ + { + Requests: []string{"foo"}, + MatchAttribute: ptr.To(resource.FullyQualifiedName("dra.example.com/driverVersion")), + }, + } + return claim + }(), + }, + "prioritized-list-constraints-requests-with-invalid-subrequest-reference": { + wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(0), "foo/baz", "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'")}, + claim: func() *resource.ResourceClaim { + claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable) + claim.Spec.Devices.Constraints = []resource.DeviceConstraint{ + { + Requests: []string{"foo/baz"}, + MatchAttribute: ptr.To(resource.FullyQualifiedName("dra.example.com/driverVersion")), + }, + } + return claim + }(), + }, } for name, scenario := range scenarios { @@ -617,11 +815,12 @@ func TestValidateClaimStatusUpdate(t *testing.T) { validAllocatedClaimOld.Status.Allocation.Devices.Results[0].AdminAccess = nil // Not required in 1.31. scenarios := map[string]struct { - adminAccess bool - deviceStatusFeatureGate bool - oldClaim *resource.ResourceClaim - update func(claim *resource.ResourceClaim) *resource.ResourceClaim - wantFailures field.ErrorList + adminAccess bool + deviceStatusFeatureGate bool + prioritizedListFeatureGate bool + oldClaim *resource.ResourceClaim + update func(claim *resource.ResourceClaim) *resource.ResourceClaim + wantFailures field.ErrorList }{ "valid-no-op-update": { oldClaim: validClaim, @@ -654,7 +853,7 @@ func TestValidateClaimStatusUpdate(t *testing.T) { "invalid-add-allocation-bad-request": { wantFailures: field.ErrorList{ field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"), - field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badName, "must be the name of a request in the claim"), + field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badName, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"), }, oldClaim: validClaim, update: func(claim *resource.ResourceClaim) *resource.ResourceClaim { @@ -862,7 +1061,7 @@ func TestValidateClaimStatusUpdate(t *testing.T) { "invalid-request-name": { wantFailures: field.ErrorList{ field.Invalid(field.NewPath("status", "allocation", "devices", "config").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"), - field.Invalid(field.NewPath("status", "allocation", "devices", "config").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim"), + field.Invalid(field.NewPath("status", "allocation", "devices", "config").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"), }, oldClaim: validClaim, update: func(claim *resource.ResourceClaim) *resource.ResourceClaim { @@ -1334,12 +1533,115 @@ func TestValidateClaimStatusUpdate(t *testing.T) { return claim }, }, + "valid-add-allocation-with-sub-requests": { + oldClaim: testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable), + update: func(claim *resource.ResourceClaim) *resource.ResourceClaim { + claim.Status.Allocation = &resource.AllocationResult{ + Devices: resource.DeviceAllocationResult{ + Results: []resource.DeviceRequestAllocationResult{{ + Request: fmt.Sprintf("%s/%s", goodName, goodName), + Driver: goodName, + Pool: goodName, + Device: goodName, + AdminAccess: ptr.To(false), + }}, + }, + } + return claim + }, + prioritizedListFeatureGate: true, + }, + "invalid-add-allocation-with-sub-requests-invalid-format": { + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badRequestFormat, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"), + }, + oldClaim: testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable), + update: func(claim *resource.ResourceClaim) *resource.ResourceClaim { + claim.Status.Allocation = &resource.AllocationResult{ + Devices: resource.DeviceAllocationResult{ + Results: []resource.DeviceRequestAllocationResult{{ + Request: badRequestFormat, + Driver: goodName, + Pool: goodName, + Device: goodName, + AdminAccess: ptr.To(false), + }}, + }, + } + return claim + }, + prioritizedListFeatureGate: true, + }, + "invalid-add-allocation-with-sub-requests-no-corresponding-sub-request": { + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), "foo/baz", "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"), + }, + oldClaim: testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable), + update: func(claim *resource.ResourceClaim) *resource.ResourceClaim { + claim.Status.Allocation = &resource.AllocationResult{ + Devices: resource.DeviceAllocationResult{ + Results: []resource.DeviceRequestAllocationResult{{ + Request: "foo/baz", + Driver: goodName, + Pool: goodName, + Device: goodName, + AdminAccess: ptr.To(false), + }}, + }, + } + return claim + }, + prioritizedListFeatureGate: true, + }, + "invalid-add-allocation-with-sub-requests-invalid-request-names": { + wantFailures: field.ErrorList{ + field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"), + field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badSubrequestName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"), + field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badFullSubrequestName, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"), + }, + oldClaim: testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable), + update: func(claim *resource.ResourceClaim) *resource.ResourceClaim { + claim.Status.Allocation = &resource.AllocationResult{ + Devices: resource.DeviceAllocationResult{ + Results: []resource.DeviceRequestAllocationResult{{ + Request: badFullSubrequestName, + Driver: goodName, + Pool: goodName, + Device: goodName, + AdminAccess: ptr.To(false), + }}, + }, + } + return claim + }, + prioritizedListFeatureGate: true, + }, + "add-allocation-old-claim-with-prioritized-list": { + wantFailures: nil, + oldClaim: testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable), + update: func(claim *resource.ResourceClaim) *resource.ResourceClaim { + claim.Status.Allocation = &resource.AllocationResult{ + Devices: resource.DeviceAllocationResult{ + Results: []resource.DeviceRequestAllocationResult{{ + Request: "foo/bar", + Driver: goodName, + Pool: goodName, + Device: goodName, + AdminAccess: ptr.To(false), + }}, + }, + } + return claim + }, + prioritizedListFeatureGate: false, + }, } for name, scenario := range scenarios { t.Run(name, func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, scenario.adminAccess) featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAResourceClaimDeviceStatus, scenario.deviceStatusFeatureGate) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAPrioritizedList, scenario.prioritizedListFeatureGate) scenario.oldClaim.ResourceVersion = "1" errs := ValidateResourceClaimStatusUpdate(scenario.update(scenario.oldClaim.DeepCopy()), scenario.oldClaim) diff --git a/pkg/apis/resource/validation/validation_resourceclaimtemplate_test.go b/pkg/apis/resource/validation/validation_resourceclaimtemplate_test.go index a22cc776cb2ab..2e24d766bbcb7 100644 --- a/pkg/apis/resource/validation/validation_resourceclaimtemplate_test.go +++ b/pkg/apis/resource/validation/validation_resourceclaimtemplate_test.go @@ -185,6 +185,26 @@ func TestValidateClaimTemplate(t *testing.T) { return template }(), }, + "prioritized-list": { + wantFailures: nil, + template: testClaimTemplate(goodName, goodNS, validClaimSpecWithFirstAvailable), + }, + "proritized-list-class-name-on-parent": { + wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "spec", "devices", "requests").Index(0), nil, "exactly one of `deviceClassName` or `firstAvailable` must be specified")}, + template: func() *resource.ResourceClaimTemplate { + template := testClaimTemplate(goodName, goodNS, validClaimSpecWithFirstAvailable) + template.Spec.Spec.Devices.Requests[0].DeviceClassName = goodName + return template + }(), + }, + "prioritized-list-bad-class-name-on-subrequest": { + wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("deviceClassName"), badName, "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')")}, + template: func() *resource.ResourceClaimTemplate { + template := testClaimTemplate(goodName, goodNS, validClaimSpecWithFirstAvailable) + template.Spec.Spec.Devices.Requests[0].FirstAvailable[0].DeviceClassName = badName + return template + }(), + }, } for name, scenario := range scenarios { @@ -219,6 +239,18 @@ func TestValidateClaimTemplateUpdate(t *testing.T) { return template }, }, + "prioritized-listinvalid-update-class": { + wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec"), func() resource.ResourceClaimTemplateSpec { + template := testClaimTemplate(goodName, goodNS, validClaimSpecWithFirstAvailable) + template.Spec.Spec.Devices.Requests[0].FirstAvailable[0].DeviceClassName += "2" + return template.Spec + }(), "field is immutable")}, + oldClaimTemplate: testClaimTemplate(goodName, goodNS, validClaimSpecWithFirstAvailable), + update: func(template *resource.ResourceClaimTemplate) *resource.ResourceClaimTemplate { + template.Spec.Spec.Devices.Requests[0].FirstAvailable[0].DeviceClassName += "2" + return template + }, + }, } for name, scenario := range scenarios { diff --git a/pkg/apis/resource/zz_generated.deepcopy.go b/pkg/apis/resource/zz_generated.deepcopy.go index edbff8751e21d..fb893b8528ee3 100644 --- a/pkg/apis/resource/zz_generated.deepcopy.go +++ b/pkg/apis/resource/zz_generated.deepcopy.go @@ -482,6 +482,13 @@ func (in *DeviceRequest) DeepCopyInto(out *DeviceRequest) { *out = new(bool) **out = **in } + if in.FirstAvailable != nil { + in, out := &in.FirstAvailable, &out.FirstAvailable + *out = make([]DeviceSubRequest, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -537,6 +544,29 @@ func (in *DeviceSelector) DeepCopy() *DeviceSelector { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeviceSubRequest) DeepCopyInto(out *DeviceSubRequest) { + *out = *in + if in.Selectors != nil { + in, out := &in.Selectors, &out.Selectors + *out = make([]DeviceSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceSubRequest. +func (in *DeviceSubRequest) DeepCopy() *DeviceSubRequest { + if in == nil { + return nil + } + out := new(DeviceSubRequest) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkDeviceData) DeepCopyInto(out *NetworkDeviceData) { *out = *in diff --git a/pkg/controller/resourceclaim/controller.go b/pkg/controller/resourceclaim/controller.go index 3df0211078af4..6fb1365ee7e8a 100644 --- a/pkg/controller/resourceclaim/controller.go +++ b/pkg/controller/resourceclaim/controller.go @@ -71,8 +71,8 @@ const ( // Controller creates ResourceClaims for ResourceClaimTemplates in a pod spec. type Controller struct { - // adminAccessEnabled matches the DRAAdminAccess feature gate state. - adminAccessEnabled bool + // features defines the feature gates that are enabled. + features Features // kubeClient is the kube API client used to communicate with the API // server. @@ -118,25 +118,31 @@ const ( podKeyPrefix = "pod:" ) +// Features defines which features should be enabled in the controller. +type Features struct { + AdminAccess bool + PrioritizedList bool +} + // NewController creates a ResourceClaim controller. func NewController( logger klog.Logger, - adminAccessEnabled bool, + features Features, kubeClient clientset.Interface, podInformer v1informers.PodInformer, claimInformer resourceinformers.ResourceClaimInformer, templateInformer resourceinformers.ResourceClaimTemplateInformer) (*Controller, error) { ec := &Controller{ - adminAccessEnabled: adminAccessEnabled, - kubeClient: kubeClient, - podLister: podInformer.Lister(), - podIndexer: podInformer.Informer().GetIndexer(), - podSynced: podInformer.Informer().HasSynced, - claimLister: claimInformer.Lister(), - claimsSynced: claimInformer.Informer().HasSynced, - templateLister: templateInformer.Lister(), - templatesSynced: templateInformer.Informer().HasSynced, + features: features, + kubeClient: kubeClient, + podLister: podInformer.Lister(), + podIndexer: podInformer.Informer().GetIndexer(), + podSynced: podInformer.Informer().HasSynced, + claimLister: claimInformer.Lister(), + claimsSynced: claimInformer.Informer().HasSynced, + templateLister: templateInformer.Lister(), + templatesSynced: templateInformer.Informer().HasSynced, queue: workqueue.NewTypedRateLimitingQueueWithConfig( workqueue.DefaultTypedControllerRateLimiter[string](), workqueue.TypedRateLimitingQueueConfig[string]{Name: "resource_claim"}, @@ -617,10 +623,14 @@ func (ec *Controller) handleClaim(ctx context.Context, pod *v1.Pod, podClaim v1. return fmt.Errorf("resource claim template %q: %v", *templateName, err) } - if !ec.adminAccessEnabled && needsAdminAccess(template) { + if !ec.features.AdminAccess && needsAdminAccess(template) { return errors.New("admin access is requested, but the feature is disabled") } + if !ec.features.PrioritizedList && hasPrioritizedList(template) { + return errors.New("template includes a prioritized list of subrequests, but the feature is disabled") + } + // Create the ResourceClaim with pod as owner, with a generated name that uses // - as base. isTrue := true @@ -688,6 +698,15 @@ func needsAdminAccess(claimTemplate *resourceapi.ResourceClaimTemplate) bool { return false } +func hasPrioritizedList(claimTemplate *resourceapi.ResourceClaimTemplate) bool { + for _, request := range claimTemplate.Spec.Spec.Devices.Requests { + if len(request.FirstAvailable) > 0 { + return true + } + } + return false +} + // findPodResourceClaim looks for an existing ResourceClaim with the right // annotation (ties it to the pod claim) and the right ownership (ties it to // the pod). diff --git a/pkg/controller/resourceclaim/controller_test.go b/pkg/controller/resourceclaim/controller_test.go index f2b5b78cbc999..8aa5d5316ea65 100644 --- a/pkg/controller/resourceclaim/controller_test.go +++ b/pkg/controller/resourceclaim/controller_test.go @@ -82,18 +82,19 @@ var ( func TestSyncHandler(t *testing.T) { tests := []struct { - name string - key string - adminAccessEnabled bool - claims []*resourceapi.ResourceClaim - claimsInCache []*resourceapi.ResourceClaim - pods []*v1.Pod - podsLater []*v1.Pod - templates []*resourceapi.ResourceClaimTemplate - expectedClaims []resourceapi.ResourceClaim - expectedStatuses map[string][]v1.PodResourceClaimStatus - expectedError bool - expectedMetrics expectedMetrics + name string + key string + adminAccessEnabled bool + prioritizedListEnabled bool + claims []*resourceapi.ResourceClaim + claimsInCache []*resourceapi.ResourceClaim + pods []*v1.Pod + podsLater []*v1.Pod + templates []*resourceapi.ResourceClaimTemplate + expectedClaims []resourceapi.ResourceClaim + expectedStatuses map[string][]v1.PodResourceClaimStatus + expectedError bool + expectedMetrics expectedMetrics }{ { name: "create", @@ -390,7 +391,11 @@ func TestSyncHandler(t *testing.T) { claimInformer := informerFactory.Resource().V1beta1().ResourceClaims() templateInformer := informerFactory.Resource().V1beta1().ResourceClaimTemplates() - ec, err := NewController(tCtx.Logger(), tc.adminAccessEnabled, fakeKubeClient, podInformer, claimInformer, templateInformer) + features := Features{ + AdminAccess: tc.adminAccessEnabled, + PrioritizedList: tc.prioritizedListEnabled, + } + ec, err := NewController(tCtx.Logger(), features, fakeKubeClient, podInformer, claimInformer, templateInformer) if err != nil { t.Fatalf("error creating ephemeral controller : %v", err) } @@ -465,7 +470,7 @@ func TestResourceClaimEventHandler(t *testing.T) { templateInformer := informerFactory.Resource().V1beta1().ResourceClaimTemplates() claimClient := fakeKubeClient.ResourceV1beta1().ResourceClaims(testNamespace) - _, err := NewController(tCtx.Logger(), false /* admin access */, fakeKubeClient, podInformer, claimInformer, templateInformer) + _, err := NewController(tCtx.Logger(), Features{}, fakeKubeClient, podInformer, claimInformer, templateInformer) tCtx.ExpectNoError(err, "creating ephemeral controller") informerFactory.Start(tCtx.Done()) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 8a995ca3ee657..61fc97bd14ced 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -201,6 +201,14 @@ const ( // is to move it into a separate KEP. DRAAdminAccess featuregate.Feature = "DRAAdminAccess" + // owner: @mortent + // kep: http://kep.k8s.io/4816 + // + // Enables support for providing a prioritized list of requests + // for resources. The first entry that can be satisfied will + // be selected. + DRAPrioritizedList featuregate.Feature = "DRAPrioritizedList" + // owner: @pohly // kep: http://kep.k8s.io/4381 // diff --git a/pkg/features/versioned_kube_features.go b/pkg/features/versioned_kube_features.go index 0002c18d07a28..5c4f6d2c08374 100644 --- a/pkg/features/versioned_kube_features.go +++ b/pkg/features/versioned_kube_features.go @@ -173,6 +173,10 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Alpha}, }, + DRAPrioritizedList: { + {Version: version.MustParse("1.33"), Default: false, PreRelease: featuregate.Alpha}, + }, + DynamicResourceAllocation: { {Version: version.MustParse("1.26"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.32"), Default: false, PreRelease: featuregate.Beta}, diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index ae02265df4e39..709b1d31afff2 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -930,6 +930,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "k8s.io/api/resource/v1alpha3.DeviceRequest": schema_k8sio_api_resource_v1alpha3_DeviceRequest(ref), "k8s.io/api/resource/v1alpha3.DeviceRequestAllocationResult": schema_k8sio_api_resource_v1alpha3_DeviceRequestAllocationResult(ref), "k8s.io/api/resource/v1alpha3.DeviceSelector": schema_k8sio_api_resource_v1alpha3_DeviceSelector(ref), + "k8s.io/api/resource/v1alpha3.DeviceSubRequest": schema_k8sio_api_resource_v1alpha3_DeviceSubRequest(ref), "k8s.io/api/resource/v1alpha3.NetworkDeviceData": schema_k8sio_api_resource_v1alpha3_NetworkDeviceData(ref), "k8s.io/api/resource/v1alpha3.OpaqueDeviceConfiguration": schema_k8sio_api_resource_v1alpha3_OpaqueDeviceConfiguration(ref), "k8s.io/api/resource/v1alpha3.ResourceClaim": schema_k8sio_api_resource_v1alpha3_ResourceClaim(ref), @@ -964,6 +965,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "k8s.io/api/resource/v1beta1.DeviceRequest": schema_k8sio_api_resource_v1beta1_DeviceRequest(ref), "k8s.io/api/resource/v1beta1.DeviceRequestAllocationResult": schema_k8sio_api_resource_v1beta1_DeviceRequestAllocationResult(ref), "k8s.io/api/resource/v1beta1.DeviceSelector": schema_k8sio_api_resource_v1beta1_DeviceSelector(ref), + "k8s.io/api/resource/v1beta1.DeviceSubRequest": schema_k8sio_api_resource_v1beta1_DeviceSubRequest(ref), "k8s.io/api/resource/v1beta1.NetworkDeviceData": schema_k8sio_api_resource_v1beta1_NetworkDeviceData(ref), "k8s.io/api/resource/v1beta1.OpaqueDeviceConfiguration": schema_k8sio_api_resource_v1beta1_OpaqueDeviceConfiguration(ref), "k8s.io/api/resource/v1beta1.ResourceClaim": schema_k8sio_api_resource_v1beta1_ResourceClaim(ref), @@ -47070,7 +47072,7 @@ func schema_k8sio_api_resource_v1alpha3_DeviceAllocationConfiguration(ref common }, }, SchemaProps: spec.SchemaProps{ - Description: "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.", + Description: "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -47278,7 +47280,7 @@ func schema_k8sio_api_resource_v1alpha3_DeviceClaimConfiguration(ref common.Refe }, }, SchemaProps: spec.SchemaProps{ - Description: "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.", + Description: "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -47509,7 +47511,7 @@ func schema_k8sio_api_resource_v1alpha3_DeviceConstraint(ref common.ReferenceCal }, }, SchemaProps: spec.SchemaProps{ - Description: "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.", + Description: "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the constraint applies to all subrequests.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -47539,7 +47541,7 @@ func schema_k8sio_api_resource_v1alpha3_DeviceRequest(ref common.ReferenceCallba return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nA DeviceClassName is currently required. Clients must check that it is indeed set. It's absence indicates that something changed in a way that is not supported by the client yet, in which case it must refuse to handle the request.", + Description: "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "name": { @@ -47552,7 +47554,7 @@ func schema_k8sio_api_resource_v1alpha3_DeviceRequest(ref common.ReferenceCallba }, "deviceClassName": { SchemaProps: spec.SchemaProps{ - Description: "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + Description: "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required if no subrequests are specified in the firstAvailable list and no class can be set if subrequests are specified in the firstAvailable list. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", Default: "", Type: []string{"string"}, Format: "", @@ -47565,7 +47567,7 @@ func schema_k8sio_api_resource_v1alpha3_DeviceRequest(ref common.ReferenceCallba }, }, SchemaProps: spec.SchemaProps{ - Description: "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", + Description: "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -47579,31 +47581,50 @@ func schema_k8sio_api_resource_v1alpha3_DeviceRequest(ref common.ReferenceCallba }, "allocationMode": { SchemaProps: spec.SchemaProps{ - Description: "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + Description: "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", Type: []string{"string"}, Format: "", }, }, "count": { SchemaProps: spec.SchemaProps{ - Description: "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", + Description: "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", Type: []string{"integer"}, Format: "int64", }, }, "adminAccess": { SchemaProps: spec.SchemaProps{ - Description: "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + Description: "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", Type: []string{"boolean"}, Format: "", }, }, + "firstAvailable": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "FirstAvailable contains subrequests, of which exactly one will be satisfied by the scheduler to satisfy this request. It tries to satisfy them in the order in which they are listed here. So if there are two entries in the list, the scheduler will only check the second one if it determines that the first one cannot be used.\n\nThis field may only be set in the entries of DeviceClaim.Requests.\n\nDRA does not yet implement scoring, so the scheduler will select the first set of devices that satisfies all the requests in the claim. And if the requirements can be satisfied on more than one node, other scheduling features will determine which node is chosen. This means that the set of devices allocated to a claim might not be the optimal set available to the cluster. Scoring will be implemented later.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/resource/v1alpha3.DeviceSubRequest"), + }, + }, + }, + }, + }, }, - Required: []string{"name", "deviceClassName"}, + Required: []string{"name"}, }, }, Dependencies: []string{ - "k8s.io/api/resource/v1alpha3.DeviceSelector"}, + "k8s.io/api/resource/v1alpha3.DeviceSelector", "k8s.io/api/resource/v1alpha3.DeviceSubRequest"}, } } @@ -47616,7 +47637,7 @@ func schema_k8sio_api_resource_v1alpha3_DeviceRequestAllocationResult(ref common Properties: map[string]spec.Schema{ "request": { SchemaProps: spec.SchemaProps{ - Description: "Request is the name of the request in the claim which caused this device to be allocated. Multiple devices may have been allocated per request.", + Description: "Request is the name of the request in the claim which caused this device to be allocated. If it references a subrequest in the firstAvailable list on a DeviceRequest, this field must include both the name of the main request and the subrequest using the format
/.\n\nMultiple devices may have been allocated per request.", Default: "", Type: []string{"string"}, Format: "", @@ -47681,6 +47702,71 @@ func schema_k8sio_api_resource_v1alpha3_DeviceSelector(ref common.ReferenceCallb } } +func schema_k8sio_api_resource_v1alpha3_DeviceSubRequest(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "DeviceSubRequest describes a request for device provided in the claim.spec.devices.requests[].firstAvailable array. Each is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nDeviceSubRequest is similar to Request, but doesn't expose the AdminAccess or FirstAvailable fields, as those can only be set on the top-level request. AdminAccess is not supported for requests with a prioritized list, and recursive FirstAvailable fields are not supported.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name can be used to reference this subrequest in the list of constraints or the list of configurations for the claim. References must use the format
/.\n\nMust be a DNS label.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "deviceClassName": { + SchemaProps: spec.SchemaProps{ + Description: "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this subrequest.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "selectors": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/resource/v1alpha3.DeviceSelector"), + }, + }, + }, + }, + }, + "allocationMode": { + SchemaProps: spec.SchemaProps{ + Description: "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AlloctionMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + Type: []string{"string"}, + Format: "", + }, + }, + "count": { + SchemaProps: spec.SchemaProps{ + Description: "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + Required: []string{"name", "deviceClassName"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/resource/v1alpha3.DeviceSelector"}, + } +} + func schema_k8sio_api_resource_v1alpha3_NetworkDeviceData(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -48545,7 +48631,7 @@ func schema_k8sio_api_resource_v1beta1_DeviceAllocationConfiguration(ref common. }, }, SchemaProps: spec.SchemaProps{ - Description: "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.", + Description: "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -48775,7 +48861,7 @@ func schema_k8sio_api_resource_v1beta1_DeviceClaimConfiguration(ref common.Refer }, }, SchemaProps: spec.SchemaProps{ - Description: "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.", + Description: "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -49006,7 +49092,7 @@ func schema_k8sio_api_resource_v1beta1_DeviceConstraint(ref common.ReferenceCall }, }, SchemaProps: spec.SchemaProps{ - Description: "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.", + Description: "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the constraint applies to all subrequests.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -49036,12 +49122,12 @@ func schema_k8sio_api_resource_v1beta1_DeviceRequest(ref common.ReferenceCallbac return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nA DeviceClassName is currently required. Clients must check that it is indeed set. It's absence indicates that something changed in a way that is not supported by the client yet, in which case it must refuse to handle the request.", + Description: "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.", Type: []string{"object"}, Properties: map[string]spec.Schema{ "name": { SchemaProps: spec.SchemaProps{ - Description: "Name can be used to reference this request in a pod.spec.containers[].resources.claims entry and in a constraint of the claim.\n\nMust be a DNS label.", + Description: "Name can be used to reference this request in a pod.spec.containers[].resources.claims entry and in a constraint of the claim.\n\nMust be a DNS label and unique among all DeviceRequests in a ResourceClaim.", Default: "", Type: []string{"string"}, Format: "", @@ -49049,7 +49135,7 @@ func schema_k8sio_api_resource_v1beta1_DeviceRequest(ref common.ReferenceCallbac }, "deviceClassName": { SchemaProps: spec.SchemaProps{ - Description: "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + Description: "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required if no subrequests are specified in the firstAvailable list and no class can be set if subrequests are specified in the firstAvailable list. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", Default: "", Type: []string{"string"}, Format: "", @@ -49062,7 +49148,7 @@ func schema_k8sio_api_resource_v1beta1_DeviceRequest(ref common.ReferenceCallbac }, }, SchemaProps: spec.SchemaProps{ - Description: "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", + Description: "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -49076,31 +49162,50 @@ func schema_k8sio_api_resource_v1beta1_DeviceRequest(ref common.ReferenceCallbac }, "allocationMode": { SchemaProps: spec.SchemaProps{ - Description: "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + Description: "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", Type: []string{"string"}, Format: "", }, }, "count": { SchemaProps: spec.SchemaProps{ - Description: "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", + Description: "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", Type: []string{"integer"}, Format: "int64", }, }, "adminAccess": { SchemaProps: spec.SchemaProps{ - Description: "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + Description: "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", Type: []string{"boolean"}, Format: "", }, }, + "firstAvailable": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "FirstAvailable contains subrequests, of which exactly one will be satisfied by the scheduler to satisfy this request. It tries to satisfy them in the order in which they are listed here. So if there are two entries in the list, the scheduler will only check the second one if it determines that the first one cannot be used.\n\nThis field may only be set in the entries of DeviceClaim.Requests.\n\nDRA does not yet implement scoring, so the scheduler will select the first set of devices that satisfies all the requests in the claim. And if the requirements can be satisfied on more than one node, other scheduling features will determine which node is chosen. This means that the set of devices allocated to a claim might not be the optimal set available to the cluster. Scoring will be implemented later.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/resource/v1beta1.DeviceSubRequest"), + }, + }, + }, + }, + }, }, - Required: []string{"name", "deviceClassName"}, + Required: []string{"name"}, }, }, Dependencies: []string{ - "k8s.io/api/resource/v1beta1.DeviceSelector"}, + "k8s.io/api/resource/v1beta1.DeviceSelector", "k8s.io/api/resource/v1beta1.DeviceSubRequest"}, } } @@ -49113,7 +49218,7 @@ func schema_k8sio_api_resource_v1beta1_DeviceRequestAllocationResult(ref common. Properties: map[string]spec.Schema{ "request": { SchemaProps: spec.SchemaProps{ - Description: "Request is the name of the request in the claim which caused this device to be allocated. Multiple devices may have been allocated per request.", + Description: "Request is the name of the request in the claim which caused this device to be allocated. If it references a subrequest in the firstAvailable list on a DeviceRequest, this field must include both the name of the main request and the subrequest using the format
/.\n\nMultiple devices may have been allocated per request.", Default: "", Type: []string{"string"}, Format: "", @@ -49178,6 +49283,71 @@ func schema_k8sio_api_resource_v1beta1_DeviceSelector(ref common.ReferenceCallba } } +func schema_k8sio_api_resource_v1beta1_DeviceSubRequest(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "DeviceSubRequest describes a request for device provided in the claim.spec.devices.requests[].firstAvailable array. Each is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nDeviceSubRequest is similar to Request, but doesn't expose the AdminAccess or FirstAvailable fields, as those can only be set on the top-level request. AdminAccess is not supported for requests with a prioritized list, and recursive FirstAvailable fields are not supported.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name can be used to reference this subrequest in the list of constraints or the list of configurations for the claim. References must use the format
/.\n\nMust be a DNS label.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "deviceClassName": { + SchemaProps: spec.SchemaProps{ + Description: "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this subrequest.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "selectors": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this subrequest. All selectors must be satisfied for a device to be considered.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/resource/v1beta1.DeviceSelector"), + }, + }, + }, + }, + }, + "allocationMode": { + SchemaProps: spec.SchemaProps{ + Description: "AllocationMode and its related fields define how devices are allocated to satisfy this subrequest. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This subrequest is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AlloctionMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other subrequests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + Type: []string{"string"}, + Format: "", + }, + }, + "count": { + SchemaProps: spec.SchemaProps{ + Description: "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + Required: []string{"name", "deviceClassName"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/resource/v1beta1.DeviceSelector"}, + } +} + func schema_k8sio_api_resource_v1beta1_NetworkDeviceData(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/quota/v1/evaluator/core/resource_claims.go b/pkg/quota/v1/evaluator/core/resource_claims.go index f649640884f21..587bf647e857f 100644 --- a/pkg/quota/v1/evaluator/core/resource_claims.go +++ b/pkg/quota/v1/evaluator/core/resource_claims.go @@ -114,6 +114,38 @@ func (p *claimEvaluator) Usage(item runtime.Object) (corev1.ResourceList, error) // charge for claim result[ClaimObjectCountName] = *(resource.NewQuantity(1, resource.DecimalSI)) for _, request := range claim.Spec.Devices.Requests { + if len(request.FirstAvailable) > 0 { + // If there are subrequests, we want to use the worst case per device class + // to quota. So for each device class, we need to find the max number of + // devices that might be allocated. + maxQuantityByDeviceClassClaim := make(map[corev1.ResourceName]resource.Quantity) + for _, subrequest := range request.FirstAvailable { + deviceClassClaim := V1ResourceByDeviceClass(subrequest.DeviceClassName) + var numDevices int64 + switch subrequest.AllocationMode { + case resourceapi.DeviceAllocationModeExactCount: + numDevices = subrequest.Count + case resourceapi.DeviceAllocationModeAll: + // Worst case... + numDevices = resourceapi.AllocationResultsMaxSize + default: + // Could happen after a downgrade. Unknown modes + // don't count towards the quota and users shouldn't + // expect that when downgrading. + } + + q := resource.NewQuantity(numDevices, resource.DecimalSI) + if q.Cmp(maxQuantityByDeviceClassClaim[deviceClassClaim]) > 0 { + maxQuantityByDeviceClassClaim[deviceClassClaim] = *q + } + } + for deviceClassClaim, q := range maxQuantityByDeviceClassClaim { + quantity := result[deviceClassClaim] + quantity.Add(q) + result[deviceClassClaim] = quantity + } + continue + } deviceClassClaim := V1ResourceByDeviceClass(request.DeviceClassName) var numDevices int64 switch request.AllocationMode { diff --git a/pkg/quota/v1/evaluator/core/resource_claims_test.go b/pkg/quota/v1/evaluator/core/resource_claims_test.go index 33bbbbbb314d1..2f137e49edcba 100644 --- a/pkg/quota/v1/evaluator/core/resource_claims_test.go +++ b/pkg/quota/v1/evaluator/core/resource_claims_test.go @@ -39,7 +39,25 @@ func testResourceClaim(name string, namespace string, spec api.ResourceClaimSpec func TestResourceClaimEvaluatorUsage(t *testing.T) { classGpu := "gpu" + classTpu := "tpu" validClaim := testResourceClaim("foo", "ns", api.ResourceClaimSpec{Devices: api.DeviceClaim{Requests: []api.DeviceRequest{{Name: "req-0", DeviceClassName: classGpu, AllocationMode: api.DeviceAllocationModeExactCount, Count: 1}}}}) + validClaimWithPrioritizedList := testResourceClaim("foo", "ns", api.ResourceClaimSpec{ + Devices: api.DeviceClaim{ + Requests: []api.DeviceRequest{ + { + Name: "req-0", + FirstAvailable: []api.DeviceSubRequest{ + { + Name: "subreq-0", + DeviceClassName: classGpu, + AllocationMode: api.DeviceAllocationModeExactCount, + Count: 1, + }, + }, + }, + }, + }, + }) evaluator := NewResourceClaimEvaluator(nil) testCases := map[string]struct { @@ -112,6 +130,61 @@ func TestResourceClaimEvaluatorUsage(t *testing.T) { "gpu.deviceclass.resource.k8s.io/devices": resource.MustParse("1"), }, }, + "prioritized-list": { + claim: validClaimWithPrioritizedList, + usage: corev1.ResourceList{ + "count/resourceclaims.resource.k8s.io": resource.MustParse("1"), + "gpu.deviceclass.resource.k8s.io/devices": resource.MustParse("1"), + }, + }, + "prioritized-list-multiple-subrequests": { + claim: func() *api.ResourceClaim { + claim := validClaimWithPrioritizedList.DeepCopy() + claim.Spec.Devices.Requests[0].FirstAvailable[0].Count = 2 + claim.Spec.Devices.Requests[0].FirstAvailable = append(claim.Spec.Devices.Requests[0].FirstAvailable, api.DeviceSubRequest{ + Name: "subreq-1", + DeviceClassName: classGpu, + AllocationMode: api.DeviceAllocationModeExactCount, + Count: 1, + }) + return claim + }(), + usage: corev1.ResourceList{ + "count/resourceclaims.resource.k8s.io": resource.MustParse("1"), + "gpu.deviceclass.resource.k8s.io/devices": resource.MustParse("2"), + }, + }, + "prioritized-list-multiple-subrequests-allocation-mode-all": { + claim: func() *api.ResourceClaim { + claim := validClaimWithPrioritizedList.DeepCopy() + claim.Spec.Devices.Requests[0].FirstAvailable = append(claim.Spec.Devices.Requests[0].FirstAvailable, api.DeviceSubRequest{ + Name: "subreq-1", + DeviceClassName: classGpu, + AllocationMode: api.DeviceAllocationModeAll, + }) + return claim + }(), + usage: corev1.ResourceList{ + "count/resourceclaims.resource.k8s.io": resource.MustParse("1"), + "gpu.deviceclass.resource.k8s.io/devices": resource.MustParse("32"), + }, + }, + "prioritized-list-multiple-subrequests-different-device-classes": { + claim: func() *api.ResourceClaim { + claim := validClaimWithPrioritizedList.DeepCopy() + claim.Spec.Devices.Requests[0].FirstAvailable = append(claim.Spec.Devices.Requests[0].FirstAvailable, api.DeviceSubRequest{ + Name: "subreq-1", + DeviceClassName: classTpu, + AllocationMode: api.DeviceAllocationModeAll, + }) + return claim + }(), + usage: corev1.ResourceList{ + "count/resourceclaims.resource.k8s.io": resource.MustParse("1"), + "gpu.deviceclass.resource.k8s.io/devices": resource.MustParse("1"), + "tpu.deviceclass.resource.k8s.io/devices": resource.MustParse("32"), + }, + }, } for testName, testCase := range testCases { t.Run(testName, func(t *testing.T) { diff --git a/pkg/registry/resource/resourceclaim/strategy.go b/pkg/registry/resource/resourceclaim/strategy.go index 336f09b15157d..cb770b2a6e1a4 100644 --- a/pkg/registry/resource/resourceclaim/strategy.go +++ b/pkg/registry/resource/resourceclaim/strategy.go @@ -183,10 +183,38 @@ func toSelectableFields(claim *resource.ResourceClaim) fields.Set { // dropDisabledFields removes fields which are covered by a feature gate. func dropDisabledFields(newClaim, oldClaim *resource.ResourceClaim) { + dropDisabledDRAPrioritizedListFields(newClaim, oldClaim) dropDisabledDRAAdminAccessFields(newClaim, oldClaim) dropDisabledDRAResourceClaimDeviceStatusFields(newClaim, oldClaim) } +func dropDisabledDRAPrioritizedListFields(newClaim, oldClaim *resource.ResourceClaim) { + if utilfeature.DefaultFeatureGate.Enabled(features.DRAPrioritizedList) { + return + } + if draPrioritizedListFeatureInUse(oldClaim) { + return + } + + for i := range newClaim.Spec.Devices.Requests { + newClaim.Spec.Devices.Requests[i].FirstAvailable = nil + } +} + +func draPrioritizedListFeatureInUse(claim *resource.ResourceClaim) bool { + if claim == nil { + return false + } + + for _, request := range claim.Spec.Devices.Requests { + if len(request.FirstAvailable) > 0 { + return true + } + } + + return false +} + func dropDisabledDRAAdminAccessFields(newClaim, oldClaim *resource.ResourceClaim) { if utilfeature.DefaultFeatureGate.Enabled(features.DRAAdminAccess) { // No need to drop anything. diff --git a/pkg/registry/resource/resourceclaim/strategy_test.go b/pkg/registry/resource/resourceclaim/strategy_test.go index c65092cb5a42f..fb855f0bf969d 100644 --- a/pkg/registry/resource/resourceclaim/strategy_test.go +++ b/pkg/registry/resource/resourceclaim/strategy_test.go @@ -133,6 +133,30 @@ var objWithAdminAccessStatus = &resource.ResourceClaim{ }, } +var objWithPrioritizedList = &resource.ResourceClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-claim", + Namespace: "default", + }, + Spec: resource.ResourceClaimSpec{ + Devices: resource.DeviceClaim{ + Requests: []resource.DeviceRequest{ + { + Name: "req-0", + FirstAvailable: []resource.DeviceSubRequest{ + { + Name: "subreq-0", + DeviceClassName: "class", + AllocationMode: resource.DeviceAllocationModeExactCount, + Count: 1, + }, + }, + }, + }, + }, + }, +} + const ( testRequest = "test-request" testDriver = "test-driver" @@ -155,6 +179,7 @@ func TestStrategyCreate(t *testing.T) { testcases := map[string]struct { obj *resource.ResourceClaim adminAccess bool + prioritizedList bool expectValidationError bool expectObj *resource.ResourceClaim }{ @@ -180,11 +205,22 @@ func TestStrategyCreate(t *testing.T) { adminAccess: true, expectObj: objWithAdminAccess, }, + "drop-fields-prioritized-list": { + obj: objWithPrioritizedList, + prioritizedList: false, + expectValidationError: true, + }, + "keep-fields-prioritized-list": { + obj: objWithPrioritizedList, + prioritizedList: true, + expectObj: objWithPrioritizedList, + }, } for name, tc := range testcases { t.Run(name, func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, tc.adminAccess) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAPrioritizedList, tc.prioritizedList) obj := tc.obj.DeepCopy() Strategy.PrepareForCreate(ctx, obj) @@ -212,6 +248,7 @@ func TestStrategyUpdate(t *testing.T) { oldObj *resource.ResourceClaim newObj *resource.ResourceClaim adminAccess bool + prioritizedList bool expectValidationError bool expectObj *resource.ResourceClaim }{ @@ -247,11 +284,36 @@ func TestStrategyUpdate(t *testing.T) { adminAccess: true, expectObj: objWithAdminAccess, }, + "drop-fields-prioritized-list": { + oldObj: obj, + newObj: objWithPrioritizedList, + prioritizedList: false, + expectValidationError: true, + }, + "keep-fields-prioritized-list": { + oldObj: obj, + newObj: objWithPrioritizedList, + prioritizedList: true, + expectValidationError: true, // Spec is immutable. + }, + "keep-existing-fields-prioritized-list": { + oldObj: objWithPrioritizedList, + newObj: objWithPrioritizedList, + prioritizedList: true, + expectObj: objWithPrioritizedList, + }, + "keep-existing-fields-prioritized-list-disabled-feature": { + oldObj: objWithPrioritizedList, + newObj: objWithPrioritizedList, + prioritizedList: false, + expectObj: objWithPrioritizedList, + }, } for name, tc := range testcases { t.Run(name, func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, tc.adminAccess) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAPrioritizedList, tc.prioritizedList) oldObj := tc.oldObj.DeepCopy() newObj := tc.newObj.DeepCopy() diff --git a/pkg/registry/resource/resourceclaimtemplate/strategy.go b/pkg/registry/resource/resourceclaimtemplate/strategy.go index 3677683e9f7b8..3fa78b2a7f07e 100644 --- a/pkg/registry/resource/resourceclaimtemplate/strategy.go +++ b/pkg/registry/resource/resourceclaimtemplate/strategy.go @@ -100,9 +100,37 @@ func toSelectableFields(template *resource.ResourceClaimTemplate) fields.Set { } func dropDisabledFields(newClaimTemplate, oldClaimTemplate *resource.ResourceClaimTemplate) { + dropDisabledDRAPrioritizedListFields(newClaimTemplate, oldClaimTemplate) dropDisabledDRAAdminAccessFields(newClaimTemplate, oldClaimTemplate) } +func dropDisabledDRAPrioritizedListFields(newClaimTemplate, oldClaimTemplate *resource.ResourceClaimTemplate) { + if utilfeature.DefaultFeatureGate.Enabled(features.DRAPrioritizedList) { + return + } + if draPrioritizedListFeatureInUse(oldClaimTemplate) { + return + } + + for i := range newClaimTemplate.Spec.Spec.Devices.Requests { + newClaimTemplate.Spec.Spec.Devices.Requests[i].FirstAvailable = nil + } +} + +func draPrioritizedListFeatureInUse(claimTemplate *resource.ResourceClaimTemplate) bool { + if claimTemplate == nil { + return false + } + + for _, request := range claimTemplate.Spec.Spec.Devices.Requests { + if len(request.FirstAvailable) > 0 { + return true + } + } + + return false +} + func dropDisabledDRAAdminAccessFields(newClaimTemplate, oldClaimTemplate *resource.ResourceClaimTemplate) { if utilfeature.DefaultFeatureGate.Enabled(features.DRAAdminAccess) { // No need to drop anything. diff --git a/pkg/registry/resource/resourceclaimtemplate/strategy_test.go b/pkg/registry/resource/resourceclaimtemplate/strategy_test.go index b48f78d7beeb2..eb547cfe46969 100644 --- a/pkg/registry/resource/resourceclaimtemplate/strategy_test.go +++ b/pkg/registry/resource/resourceclaimtemplate/strategy_test.go @@ -71,6 +71,32 @@ var objWithAdminAccess = &resource.ResourceClaimTemplate{ }, } +var objWithPrioritizedList = &resource.ResourceClaimTemplate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-claim-template", + Namespace: "default", + }, + Spec: resource.ResourceClaimTemplateSpec{ + Spec: resource.ResourceClaimSpec{ + Devices: resource.DeviceClaim{ + Requests: []resource.DeviceRequest{ + { + Name: "req-0", + FirstAvailable: []resource.DeviceSubRequest{ + { + Name: "subreq-0", + DeviceClassName: "class", + AllocationMode: resource.DeviceAllocationModeExactCount, + Count: 1, + }, + }, + }, + }, + }, + }, + }, +} + func TestClaimTemplateStrategy(t *testing.T) { if !Strategy.NamespaceScoped() { t.Errorf("ResourceClaimTemplate must be namespace scoped") @@ -86,6 +112,7 @@ func TestClaimTemplateStrategyCreate(t *testing.T) { testcases := map[string]struct { obj *resource.ResourceClaimTemplate adminAccess bool + prioritizedList bool expectValidationError bool expectObj *resource.ResourceClaimTemplate }{ @@ -111,11 +138,22 @@ func TestClaimTemplateStrategyCreate(t *testing.T) { adminAccess: true, expectObj: objWithAdminAccess, }, + "drop-fields-prioritized-list": { + obj: objWithPrioritizedList, + prioritizedList: false, + expectValidationError: true, + }, + "keep-fields-prioritized-list": { + obj: objWithPrioritizedList, + prioritizedList: true, + expectObj: objWithPrioritizedList, + }, } for name, tc := range testcases { t.Run(name, func(t *testing.T) { featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, tc.adminAccess) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAPrioritizedList, tc.prioritizedList) obj := tc.obj.DeepCopy() Strategy.PrepareForCreate(ctx, obj) diff --git a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go index 7b5d1fbc27b04..171e17e7c6869 100644 --- a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go +++ b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "slices" + "strings" "sync" "github.com/google/go-cmp/cmp" //nolint:depguard @@ -103,6 +104,7 @@ type informationForClaim struct { type DynamicResources struct { enabled bool enableAdminAccess bool + enablePrioritizedList bool enableSchedulingQueueHint bool fh framework.Handle @@ -121,6 +123,7 @@ func New(ctx context.Context, plArgs runtime.Object, fh framework.Handle, fts fe pl := &DynamicResources{ enabled: true, enableAdminAccess: fts.EnableDRAAdminAccess, + enablePrioritizedList: fts.EnableDRAPrioritizedList, enableSchedulingQueueHint: fts.EnableSchedulingQueueHint, fh: fh, @@ -405,20 +408,19 @@ func (pl *DynamicResources) PreFilter(ctx context.Context, state *framework.Cycl // initial set of potential nodes before we ask the // driver(s) for information about the specific pod. for _, request := range claim.Spec.Devices.Requests { - if request.DeviceClassName == "" { - return nil, statusError(logger, fmt.Errorf("request %s: unsupported request type", request.Name)) - } - - _, err := pl.draManager.DeviceClasses().Get(request.DeviceClassName) - if err != nil { - // If the class cannot be retrieved, allocation cannot proceed. - if apierrors.IsNotFound(err) { - // Here we mark the pod as "unschedulable", so it'll sleep in - // the unscheduleable queue until a DeviceClass event occurs. - return nil, statusUnschedulable(logger, fmt.Sprintf("request %s: device class %s does not exist", request.Name, request.DeviceClassName)) + // The requirements differ depending on whether the request has a list of + // alternative subrequests defined in the firstAvailable field. + if len(request.FirstAvailable) == 0 { + if status := pl.validateDeviceClass(logger, request.DeviceClassName, request.Name); status != nil { + return nil, status + } + } else { + for _, subRequest := range request.FirstAvailable { + qualRequestName := strings.Join([]string{request.Name, subRequest.Name}, "/") + if status := pl.validateDeviceClass(logger, subRequest.DeviceClassName, qualRequestName); status != nil { + return nil, status + } } - // Other error, retry with backoff. - return nil, statusError(logger, fmt.Errorf("request %s: look up device class: %w", request.Name, err)) } } } @@ -447,7 +449,7 @@ func (pl *DynamicResources) PreFilter(ctx context.Context, state *framework.Cycl if err != nil { return nil, statusError(logger, err) } - allocator, err := structured.NewAllocator(ctx, pl.enableAdminAccess, allocateClaims, allAllocatedDevices, pl.draManager.DeviceClasses(), slices, pl.celCache) + allocator, err := structured.NewAllocator(ctx, pl.enableAdminAccess, pl.enablePrioritizedList, allocateClaims, allAllocatedDevices, pl.draManager.DeviceClasses(), slices, pl.celCache) if err != nil { return nil, statusError(logger, err) } @@ -459,6 +461,23 @@ func (pl *DynamicResources) PreFilter(ctx context.Context, state *framework.Cycl return nil, nil } +func (pl *DynamicResources) validateDeviceClass(logger klog.Logger, deviceClassName, requestName string) *framework.Status { + if deviceClassName == "" { + return statusError(logger, fmt.Errorf("request %s: unsupported request type", requestName)) + } + + _, err := pl.draManager.DeviceClasses().Get(deviceClassName) + if err != nil { + // If the class cannot be retrieved, allocation cannot proceed. + if apierrors.IsNotFound(err) { + // Here we mark the pod as "unschedulable", so it'll sleep in + // the unscheduleable queue until a DeviceClass event occurs. + return statusUnschedulable(logger, fmt.Sprintf("request %s: device class %s does not exist", requestName, deviceClassName)) + } + } + return nil +} + // PreFilterExtensions returns prefilter extensions, pod add and remove. func (pl *DynamicResources) PreFilterExtensions() framework.PreFilterExtensions { return nil diff --git a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go index 9d2fb08ed9775..dd79e6dcacabf 100644 --- a/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go +++ b/pkg/scheduler/framework/plugins/dynamicresources/dynamicresources_test.go @@ -117,9 +117,17 @@ var ( Namespace(namespace). Request(className). Obj() + claimWithPrioritzedList = st.MakeResourceClaim(). + Name(claimName). + Namespace(namespace). + RequestWithPrioritizedList(className). + Obj() pendingClaim = st.FromResourceClaim(claim). OwnerReference(podName, podUID, podKind). Obj() + pendingClaimWithPrioritizedList = st.FromResourceClaim(claimWithPrioritzedList). + OwnerReference(podName, podUID, podKind). + Obj() allocationResult = &resourceapi.AllocationResult{ Devices: resourceapi.DeviceAllocationResult{ Results: []resourceapi.DeviceRequestAllocationResult{{ @@ -133,13 +141,33 @@ var ( return st.MakeNodeSelector().In("metadata.name", []string{nodeName}, st.NodeSelectorTypeMatchFields).Obj() }(), } + allocationResultWithPrioritizedList = &resourceapi.AllocationResult{ + Devices: resourceapi.DeviceAllocationResult{ + Results: []resourceapi.DeviceRequestAllocationResult{{ + Driver: driver, + Pool: nodeName, + Device: "instance-1", + Request: "req-1/subreq-1", + }}, + }, + NodeSelector: func() *v1.NodeSelector { + return st.MakeNodeSelector().In("metadata.name", []string{nodeName}, st.NodeSelectorTypeMatchFields).Obj() + }(), + } inUseClaim = st.FromResourceClaim(pendingClaim). Allocation(allocationResult). ReservedForPod(podName, types.UID(podUID)). Obj() + inUseClaimWithPrioritizedList = st.FromResourceClaim(pendingClaimWithPrioritizedList). + Allocation(allocationResultWithPrioritizedList). + ReservedForPod(podName, types.UID(podUID)). + Obj() allocatedClaim = st.FromResourceClaim(pendingClaim). Allocation(allocationResult). Obj() + allocatedClaimWithPrioritizedList = st.FromResourceClaim(pendingClaimWithPrioritizedList). + Allocation(allocationResultWithPrioritizedList). + Obj() allocatedClaimWithWrongTopology = st.FromResourceClaim(allocatedClaim). Allocation(&resourceapi.AllocationResult{NodeSelector: st.MakeNodeSelector().In("no-such-label", []string{"no-such-value"}, st.NodeSelectorTypeMatchExpressions).Obj()}). @@ -201,6 +229,24 @@ func breakCELInClass(class *resourceapi.DeviceClass) *resourceapi.DeviceClass { return class } +func updateDeviceClassName(claim *resourceapi.ResourceClaim, deviceClassName string) *resourceapi.ResourceClaim { + claim = claim.DeepCopy() + for i := range claim.Spec.Devices.Requests { + // If the firstAvailable list is empty we update the device class name + // on the base request. + if len(claim.Spec.Devices.Requests[i].FirstAvailable) == 0 { + claim.Spec.Devices.Requests[i].DeviceClassName = deviceClassName + } else { + // If subrequests are specified, update the device class name on + // all of them. + for j := range claim.Spec.Devices.Requests[i].FirstAvailable { + claim.Spec.Devices.Requests[i].FirstAvailable[j].DeviceClassName = deviceClassName + } + } + } + return claim +} + // result defines the expected outcome of some operation. It covers // operation's status and the state of the world (= objects). type result struct { @@ -295,6 +341,8 @@ func TestPlugin(t *testing.T) { // Feature gates. False is chosen so that the uncommon case // doesn't need to be set. disableDRA bool + + enableDRAPrioritizedList bool }{ "empty": { pod: st.MakePod().Name("foo").Namespace("default").Obj(), @@ -795,6 +843,69 @@ func TestPlugin(t *testing.T) { }, disableDRA: true, }, + "claim-with-request-with-unknown-device-class": { + pod: podWithClaimName, + claims: []*resourceapi.ResourceClaim{updateDeviceClassName(claim, "does-not-exist")}, + want: want{ + prefilter: result{ + status: framework.NewStatus(framework.Unschedulable, `request req-1: device class does-not-exist does not exist`), + }, + postfilter: result{ + status: framework.NewStatus(framework.Unschedulable, `no new claims to deallocate`), + }, + }, + }, + "claim-with-prioritized-list-feature-disabled": { + enableDRAPrioritizedList: false, + pod: podWithClaimName, + claims: []*resourceapi.ResourceClaim{claimWithPrioritzedList}, + classes: []*resourceapi.DeviceClass{deviceClass}, + want: want{ + filter: perNodeResult{ + workerNode.Name: { + status: framework.NewStatus(framework.UnschedulableAndUnresolvable, `claim default/my-pod-my-resource, request req-1: has subrequests, but the feature is disabled`), + }, + }, + }, + }, + "claim-with-prioritized-list-unknown-device-class": { + enableDRAPrioritizedList: true, + pod: podWithClaimName, + claims: []*resourceapi.ResourceClaim{updateDeviceClassName(claimWithPrioritzedList, "does-not-exist")}, + want: want{ + prefilter: result{ + status: framework.NewStatus(framework.Unschedulable, `request req-1/subreq-1: device class does-not-exist does not exist`), + }, + postfilter: result{ + status: framework.NewStatus(framework.Unschedulable, `no new claims to deallocate`), + }, + }, + }, + "claim-with-prioritized-list": { + enableDRAPrioritizedList: true, + pod: podWithClaimName, + claims: []*resourceapi.ResourceClaim{pendingClaimWithPrioritizedList}, + classes: []*resourceapi.DeviceClass{deviceClass}, + objs: []apiruntime.Object{workerNodeSlice}, + want: want{ + reserve: result{ + inFlightClaim: allocatedClaimWithPrioritizedList, + }, + prebind: result{ + assumedClaim: reserve(allocatedClaimWithPrioritizedList, podWithClaimName), + changes: change{ + claim: func(claim *resourceapi.ResourceClaim) *resourceapi.ResourceClaim { + if claim.Name == claimName { + claim = claim.DeepCopy() + claim.Finalizers = allocatedClaimWithPrioritizedList.Finalizers + claim.Status = inUseClaimWithPrioritizedList.Status + } + return claim + }, + }, + }, + }, + }, } for name, tc := range testcases { @@ -809,6 +920,7 @@ func TestPlugin(t *testing.T) { features := feature.Features{ EnableDRAAdminAccess: tc.enableDRAAdminAccess, EnableDynamicResourceAllocation: !tc.disableDRA, + EnableDRAPrioritizedList: tc.enableDRAPrioritizedList, } testCtx := setup(t, nodes, tc.claims, tc.classes, tc.objs, features) initialObjects := testCtx.listAll(t) diff --git a/pkg/scheduler/framework/plugins/feature/feature.go b/pkg/scheduler/framework/plugins/feature/feature.go index 4f201bf9da024..0b5985250015d 100644 --- a/pkg/scheduler/framework/plugins/feature/feature.go +++ b/pkg/scheduler/framework/plugins/feature/feature.go @@ -20,6 +20,7 @@ package feature // This struct allows us to break the dependency of the plugins on // the internal k8s features pkg. type Features struct { + EnableDRAPrioritizedList bool EnableDRAAdminAccess bool EnableDynamicResourceAllocation bool EnableVolumeCapacityPriority bool diff --git a/pkg/scheduler/framework/plugins/registry.go b/pkg/scheduler/framework/plugins/registry.go index af06e5952e34e..1579ba9b1ecb0 100644 --- a/pkg/scheduler/framework/plugins/registry.go +++ b/pkg/scheduler/framework/plugins/registry.go @@ -46,6 +46,7 @@ import ( // through the WithFrameworkOutOfTreeRegistry option. func NewInTreeRegistry() runtime.Registry { fts := plfeature.Features{ + EnableDRAPrioritizedList: feature.DefaultFeatureGate.Enabled(features.DRAPrioritizedList), EnableDRAAdminAccess: feature.DefaultFeatureGate.Enabled(features.DRAAdminAccess), EnableDynamicResourceAllocation: feature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation), EnableVolumeCapacityPriority: feature.DefaultFeatureGate.Enabled(features.VolumeCapacityPriority), diff --git a/pkg/scheduler/testing/wrappers.go b/pkg/scheduler/testing/wrappers.go index 3270bc58e6dcf..1a6633163a059 100644 --- a/pkg/scheduler/testing/wrappers.go +++ b/pkg/scheduler/testing/wrappers.go @@ -1104,6 +1104,28 @@ func (wrapper *ResourceClaimWrapper) Request(deviceClassName string) *ResourceCl return wrapper } +// RequestWithPrioritizedList adds one device request with one subrequest +// per provided deviceClassName. +func (wrapper *ResourceClaimWrapper) RequestWithPrioritizedList(deviceClassNames ...string) *ResourceClaimWrapper { + var prioritizedList []resourceapi.DeviceSubRequest + for i, deviceClassName := range deviceClassNames { + prioritizedList = append(prioritizedList, resourceapi.DeviceSubRequest{ + Name: fmt.Sprintf("subreq-%d", i+1), + AllocationMode: resourceapi.DeviceAllocationModeExactCount, + Count: 1, + DeviceClassName: deviceClassName, + }) + } + + wrapper.Spec.Devices.Requests = append(wrapper.Spec.Devices.Requests, + resourceapi.DeviceRequest{ + Name: fmt.Sprintf("req-%d", len(wrapper.Spec.Devices.Requests)+1), + FirstAvailable: prioritizedList, + }, + ) + return wrapper +} + // Allocation sets the allocation of the inner object. func (wrapper *ResourceClaimWrapper) Allocation(allocation *resourceapi.AllocationResult) *ResourceClaimWrapper { if !slices.Contains(wrapper.ResourceClaim.Finalizers, resourceapi.Finalizer) { diff --git a/staging/src/k8s.io/api/resource/v1alpha3/generated.pb.go b/staging/src/k8s.io/api/resource/v1alpha3/generated.pb.go index 9339406f4d4f9..ec3586e55e9f1 100644 --- a/staging/src/k8s.io/api/resource/v1alpha3/generated.pb.go +++ b/staging/src/k8s.io/api/resource/v1alpha3/generated.pb.go @@ -582,10 +582,38 @@ func (m *DeviceSelector) XXX_DiscardUnknown() { var xxx_messageInfo_DeviceSelector proto.InternalMessageInfo +func (m *DeviceSubRequest) Reset() { *m = DeviceSubRequest{} } +func (*DeviceSubRequest) ProtoMessage() {} +func (*DeviceSubRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_66649ee9bbcd89d2, []int{19} +} +func (m *DeviceSubRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DeviceSubRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *DeviceSubRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeviceSubRequest.Merge(m, src) +} +func (m *DeviceSubRequest) XXX_Size() int { + return m.Size() +} +func (m *DeviceSubRequest) XXX_DiscardUnknown() { + xxx_messageInfo_DeviceSubRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_DeviceSubRequest proto.InternalMessageInfo + func (m *NetworkDeviceData) Reset() { *m = NetworkDeviceData{} } func (*NetworkDeviceData) ProtoMessage() {} func (*NetworkDeviceData) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{19} + return fileDescriptor_66649ee9bbcd89d2, []int{20} } func (m *NetworkDeviceData) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -613,7 +641,7 @@ var xxx_messageInfo_NetworkDeviceData proto.InternalMessageInfo func (m *OpaqueDeviceConfiguration) Reset() { *m = OpaqueDeviceConfiguration{} } func (*OpaqueDeviceConfiguration) ProtoMessage() {} func (*OpaqueDeviceConfiguration) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{20} + return fileDescriptor_66649ee9bbcd89d2, []int{21} } func (m *OpaqueDeviceConfiguration) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -641,7 +669,7 @@ var xxx_messageInfo_OpaqueDeviceConfiguration proto.InternalMessageInfo func (m *ResourceClaim) Reset() { *m = ResourceClaim{} } func (*ResourceClaim) ProtoMessage() {} func (*ResourceClaim) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{21} + return fileDescriptor_66649ee9bbcd89d2, []int{22} } func (m *ResourceClaim) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -669,7 +697,7 @@ var xxx_messageInfo_ResourceClaim proto.InternalMessageInfo func (m *ResourceClaimConsumerReference) Reset() { *m = ResourceClaimConsumerReference{} } func (*ResourceClaimConsumerReference) ProtoMessage() {} func (*ResourceClaimConsumerReference) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{22} + return fileDescriptor_66649ee9bbcd89d2, []int{23} } func (m *ResourceClaimConsumerReference) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -697,7 +725,7 @@ var xxx_messageInfo_ResourceClaimConsumerReference proto.InternalMessageInfo func (m *ResourceClaimList) Reset() { *m = ResourceClaimList{} } func (*ResourceClaimList) ProtoMessage() {} func (*ResourceClaimList) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{23} + return fileDescriptor_66649ee9bbcd89d2, []int{24} } func (m *ResourceClaimList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -725,7 +753,7 @@ var xxx_messageInfo_ResourceClaimList proto.InternalMessageInfo func (m *ResourceClaimSpec) Reset() { *m = ResourceClaimSpec{} } func (*ResourceClaimSpec) ProtoMessage() {} func (*ResourceClaimSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{24} + return fileDescriptor_66649ee9bbcd89d2, []int{25} } func (m *ResourceClaimSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -753,7 +781,7 @@ var xxx_messageInfo_ResourceClaimSpec proto.InternalMessageInfo func (m *ResourceClaimStatus) Reset() { *m = ResourceClaimStatus{} } func (*ResourceClaimStatus) ProtoMessage() {} func (*ResourceClaimStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{25} + return fileDescriptor_66649ee9bbcd89d2, []int{26} } func (m *ResourceClaimStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -781,7 +809,7 @@ var xxx_messageInfo_ResourceClaimStatus proto.InternalMessageInfo func (m *ResourceClaimTemplate) Reset() { *m = ResourceClaimTemplate{} } func (*ResourceClaimTemplate) ProtoMessage() {} func (*ResourceClaimTemplate) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{26} + return fileDescriptor_66649ee9bbcd89d2, []int{27} } func (m *ResourceClaimTemplate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -809,7 +837,7 @@ var xxx_messageInfo_ResourceClaimTemplate proto.InternalMessageInfo func (m *ResourceClaimTemplateList) Reset() { *m = ResourceClaimTemplateList{} } func (*ResourceClaimTemplateList) ProtoMessage() {} func (*ResourceClaimTemplateList) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{27} + return fileDescriptor_66649ee9bbcd89d2, []int{28} } func (m *ResourceClaimTemplateList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -837,7 +865,7 @@ var xxx_messageInfo_ResourceClaimTemplateList proto.InternalMessageInfo func (m *ResourceClaimTemplateSpec) Reset() { *m = ResourceClaimTemplateSpec{} } func (*ResourceClaimTemplateSpec) ProtoMessage() {} func (*ResourceClaimTemplateSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{28} + return fileDescriptor_66649ee9bbcd89d2, []int{29} } func (m *ResourceClaimTemplateSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -865,7 +893,7 @@ var xxx_messageInfo_ResourceClaimTemplateSpec proto.InternalMessageInfo func (m *ResourcePool) Reset() { *m = ResourcePool{} } func (*ResourcePool) ProtoMessage() {} func (*ResourcePool) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{29} + return fileDescriptor_66649ee9bbcd89d2, []int{30} } func (m *ResourcePool) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -893,7 +921,7 @@ var xxx_messageInfo_ResourcePool proto.InternalMessageInfo func (m *ResourceSlice) Reset() { *m = ResourceSlice{} } func (*ResourceSlice) ProtoMessage() {} func (*ResourceSlice) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{30} + return fileDescriptor_66649ee9bbcd89d2, []int{31} } func (m *ResourceSlice) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -921,7 +949,7 @@ var xxx_messageInfo_ResourceSlice proto.InternalMessageInfo func (m *ResourceSliceList) Reset() { *m = ResourceSliceList{} } func (*ResourceSliceList) ProtoMessage() {} func (*ResourceSliceList) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{31} + return fileDescriptor_66649ee9bbcd89d2, []int{32} } func (m *ResourceSliceList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -949,7 +977,7 @@ var xxx_messageInfo_ResourceSliceList proto.InternalMessageInfo func (m *ResourceSliceSpec) Reset() { *m = ResourceSliceSpec{} } func (*ResourceSliceSpec) ProtoMessage() {} func (*ResourceSliceSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_66649ee9bbcd89d2, []int{32} + return fileDescriptor_66649ee9bbcd89d2, []int{33} } func (m *ResourceSliceSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -996,6 +1024,7 @@ func init() { proto.RegisterType((*DeviceRequest)(nil), "k8s.io.api.resource.v1alpha3.DeviceRequest") proto.RegisterType((*DeviceRequestAllocationResult)(nil), "k8s.io.api.resource.v1alpha3.DeviceRequestAllocationResult") proto.RegisterType((*DeviceSelector)(nil), "k8s.io.api.resource.v1alpha3.DeviceSelector") + proto.RegisterType((*DeviceSubRequest)(nil), "k8s.io.api.resource.v1alpha3.DeviceSubRequest") proto.RegisterType((*NetworkDeviceData)(nil), "k8s.io.api.resource.v1alpha3.NetworkDeviceData") proto.RegisterType((*OpaqueDeviceConfiguration)(nil), "k8s.io.api.resource.v1alpha3.OpaqueDeviceConfiguration") proto.RegisterType((*ResourceClaim)(nil), "k8s.io.api.resource.v1alpha3.ResourceClaim") @@ -1017,134 +1046,138 @@ func init() { } var fileDescriptor_66649ee9bbcd89d2 = []byte{ - // 2031 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x19, 0x4b, 0x73, 0x1c, 0x47, - 0x59, 0xb3, 0xb3, 0x7a, 0x7d, 0xab, 0x97, 0xdb, 0x38, 0xc8, 0x22, 0xec, 0xca, 0x13, 0x0a, 0xe4, - 0xc4, 0xd9, 0x8d, 0xe5, 0x54, 0x12, 0x30, 0x07, 0x34, 0x92, 0x62, 0x64, 0x6c, 0x59, 0x6e, 0x25, - 0x2e, 0x0c, 0xc1, 0xd0, 0x9a, 0x6d, 0x49, 0x83, 0x66, 0x67, 0x36, 0xd3, 0x3d, 0x72, 0x74, 0xa1, - 0x52, 0x70, 0x77, 0xf1, 0x07, 0xa8, 0xdc, 0xa8, 0xe2, 0x02, 0xfc, 0x03, 0xa8, 0x82, 0x2a, 0x5c, - 0x70, 0x71, 0x15, 0x1c, 0x72, 0x5a, 0xe2, 0xa5, 0x38, 0x73, 0xf7, 0x89, 0xea, 0x9e, 0x9e, 0xe7, - 0xee, 0xc8, 0xb3, 0xa9, 0xa0, 0x32, 0xb7, 0x9d, 0xef, 0xdd, 0xdf, 0xbb, 0x7b, 0xe1, 0xca, 0xd1, - 0x3b, 0xac, 0x69, 0x7b, 0x2d, 0xd2, 0xb5, 0x5b, 0x3e, 0x65, 0x5e, 0xe0, 0x5b, 0xb4, 0x75, 0x7c, - 0x95, 0x38, 0xdd, 0x43, 0x72, 0xad, 0x75, 0x40, 0x5d, 0xea, 0x13, 0x4e, 0xdb, 0xcd, 0xae, 0xef, - 0x71, 0x0f, 0xbd, 0x1c, 0x52, 0x37, 0x49, 0xd7, 0x6e, 0x46, 0xd4, 0xcd, 0x88, 0x7a, 0xe9, 0xf5, - 0x03, 0x9b, 0x1f, 0x06, 0x7b, 0x4d, 0xcb, 0xeb, 0xb4, 0x0e, 0xbc, 0x03, 0xaf, 0x25, 0x99, 0xf6, - 0x82, 0x7d, 0xf9, 0x25, 0x3f, 0xe4, 0xaf, 0x50, 0xd8, 0x92, 0x91, 0x52, 0x6d, 0x79, 0xbe, 0x50, - 0x9b, 0x57, 0xb8, 0xf4, 0x66, 0x42, 0xd3, 0x21, 0xd6, 0xa1, 0xed, 0x52, 0xff, 0xa4, 0xd5, 0x3d, - 0x3a, 0xc8, 0xda, 0x3b, 0x0a, 0x17, 0x6b, 0x75, 0x28, 0x27, 0xc3, 0x74, 0xb5, 0x8a, 0xb8, 0xfc, - 0xc0, 0xe5, 0x76, 0x67, 0x50, 0xcd, 0x5b, 0xcf, 0x63, 0x60, 0xd6, 0x21, 0xed, 0x90, 0x3c, 0x9f, - 0xf1, 0x89, 0x0e, 0x17, 0xd6, 0x1c, 0xc7, 0xb3, 0x04, 0x6c, 0x83, 0x1e, 0xdb, 0x16, 0xdd, 0xe5, - 0x84, 0x07, 0x0c, 0x7d, 0x1d, 0x26, 0xda, 0xbe, 0x7d, 0x4c, 0xfd, 0x45, 0x6d, 0x59, 0x5b, 0x99, - 0x36, 0xe7, 0x1e, 0xf7, 0x1a, 0x63, 0xfd, 0x5e, 0x63, 0x62, 0x43, 0x42, 0xb1, 0xc2, 0xa2, 0x65, - 0xa8, 0x76, 0x3d, 0xcf, 0x59, 0xac, 0x48, 0xaa, 0x19, 0x45, 0x55, 0xdd, 0xf1, 0x3c, 0x07, 0x4b, - 0x8c, 0x94, 0x24, 0x25, 0x2f, 0xea, 0x39, 0x49, 0x12, 0x8a, 0x15, 0x16, 0x59, 0x00, 0x96, 0xe7, - 0xb6, 0x6d, 0x6e, 0x7b, 0x2e, 0x5b, 0xac, 0x2e, 0xeb, 0x2b, 0xb5, 0xd5, 0x56, 0x33, 0x09, 0x73, - 0x7c, 0xb0, 0x66, 0xf7, 0xe8, 0x40, 0x00, 0x58, 0x53, 0xf8, 0xaf, 0x79, 0x7c, 0xb5, 0xb9, 0x1e, - 0xf1, 0x99, 0x48, 0x09, 0x87, 0x18, 0xc4, 0x70, 0x4a, 0x2c, 0xfa, 0x1e, 0x54, 0xdb, 0x84, 0x93, - 0xc5, 0xf1, 0x65, 0x6d, 0xa5, 0xb6, 0xfa, 0x7a, 0xa1, 0x78, 0xe5, 0xb7, 0x26, 0x26, 0x0f, 0x37, - 0x3f, 0xe2, 0xd4, 0x65, 0x42, 0xf8, 0x94, 0x38, 0xd9, 0x06, 0xe1, 0x04, 0x4b, 0x21, 0x68, 0x0f, - 0x6a, 0x2e, 0xe5, 0x0f, 0x3d, 0xff, 0x48, 0x00, 0x17, 0x27, 0xa4, 0xcc, 0xb4, 0xc9, 0x83, 0x99, - 0xd9, 0xdc, 0x56, 0x0c, 0xf2, 0xcc, 0x82, 0xcd, 0x9c, 0xef, 0xf7, 0x1a, 0xb5, 0xed, 0x44, 0x0e, - 0x4e, 0x0b, 0x35, 0xfe, 0xa6, 0xc1, 0x82, 0x8a, 0x90, 0xed, 0xb9, 0x98, 0xb2, 0xc0, 0xe1, 0xe8, - 0xc7, 0x30, 0x19, 0x3a, 0x8d, 0xc9, 0xe8, 0xd4, 0x56, 0xdf, 0x3c, 0x5d, 0x69, 0xa8, 0x2d, 0x2f, - 0xc6, 0x9c, 0x57, 0xce, 0x9a, 0x0c, 0xf1, 0x0c, 0x47, 0x52, 0xd1, 0x3d, 0x98, 0x71, 0xbd, 0x36, - 0xdd, 0xa5, 0x0e, 0xb5, 0xb8, 0xe7, 0xcb, 0xc8, 0xd5, 0x56, 0x97, 0xd3, 0x5a, 0x44, 0x9d, 0x08, - 0xdf, 0x6f, 0xa7, 0xe8, 0xcc, 0x85, 0x7e, 0xaf, 0x31, 0x93, 0x86, 0xe0, 0x8c, 0x1c, 0xe3, 0x33, - 0x1d, 0x6a, 0x26, 0x61, 0xb6, 0x15, 0x6a, 0x44, 0x3f, 0x03, 0x20, 0x9c, 0xfb, 0xf6, 0x5e, 0xc0, - 0xe5, 0x59, 0x44, 0xcc, 0xbf, 0x79, 0xfa, 0x59, 0x52, 0xec, 0xcd, 0xb5, 0x98, 0x77, 0xd3, 0xe5, - 0xfe, 0x89, 0xf9, 0x4a, 0x14, 0xfd, 0x04, 0xf1, 0xf3, 0x7f, 0x36, 0x66, 0xef, 0x06, 0xc4, 0xb1, - 0xf7, 0x6d, 0xda, 0xde, 0x26, 0x1d, 0x8a, 0x53, 0x1a, 0xd1, 0x31, 0x4c, 0x59, 0xa4, 0x4b, 0x2c, - 0x9b, 0x9f, 0x2c, 0x56, 0xa4, 0xf6, 0xb7, 0xcb, 0x6b, 0x5f, 0x57, 0x9c, 0xa1, 0xee, 0x4b, 0x4a, - 0xf7, 0x54, 0x04, 0x1e, 0xd4, 0x1c, 0xeb, 0x5a, 0x72, 0x60, 0x3e, 0x67, 0x3b, 0x5a, 0x00, 0xfd, - 0x88, 0x9e, 0x84, 0xd5, 0x86, 0xc5, 0x4f, 0xb4, 0x0e, 0xe3, 0xc7, 0xc4, 0x09, 0xa8, 0xac, 0xad, - 0x6c, 0xb2, 0x16, 0xc7, 0x38, 0x92, 0x8a, 0x43, 0xde, 0x6f, 0x55, 0xde, 0xd1, 0x96, 0x8e, 0x60, - 0x36, 0x63, 0xeb, 0x10, 0x5d, 0x1b, 0x59, 0x5d, 0xcd, 0xd3, 0xea, 0x2e, 0x51, 0x7e, 0x37, 0x20, - 0x2e, 0xb7, 0xf9, 0x49, 0x4a, 0x99, 0x71, 0x03, 0xce, 0xad, 0x6f, 0xde, 0x52, 0xbd, 0x44, 0xc5, - 0x1d, 0xad, 0x02, 0xd0, 0x8f, 0xba, 0x3e, 0x65, 0xa2, 0x8e, 0x54, 0x47, 0x89, 0x4b, 0x75, 0x33, - 0xc6, 0xe0, 0x14, 0x95, 0x71, 0x0c, 0xaa, 0x43, 0x88, 0x1e, 0xe3, 0x92, 0x0e, 0x55, 0x7c, 0x71, - 0x8f, 0x91, 0x3e, 0x95, 0x18, 0x74, 0x13, 0xc6, 0xf7, 0x44, 0x64, 0x94, 0xf9, 0x97, 0x4b, 0x07, - 0xd1, 0x9c, 0xee, 0xf7, 0x1a, 0xe3, 0x12, 0x80, 0x43, 0x11, 0xc6, 0xa3, 0x0a, 0x7c, 0x35, 0x5f, - 0x30, 0xeb, 0x9e, 0xbb, 0x6f, 0x1f, 0x04, 0xbe, 0xfc, 0x40, 0xdf, 0x81, 0x89, 0x50, 0xa4, 0xb2, - 0x68, 0x25, 0xea, 0x68, 0xbb, 0x12, 0xfa, 0xac, 0xd7, 0x78, 0x29, 0xcf, 0x1a, 0x62, 0xb0, 0xe2, - 0x43, 0x2b, 0x30, 0xe5, 0xd3, 0x0f, 0x03, 0xca, 0x38, 0x93, 0x79, 0x37, 0x6d, 0xce, 0x88, 0xd4, - 0xc1, 0x0a, 0x86, 0x63, 0x2c, 0xfa, 0x58, 0x83, 0xf3, 0x61, 0x55, 0x66, 0x6c, 0x50, 0x15, 0x79, - 0xb5, 0x4c, 0x4e, 0x64, 0x18, 0xcd, 0xaf, 0x28, 0x63, 0xcf, 0x0f, 0x41, 0xe2, 0x61, 0xaa, 0x8c, - 0x7f, 0x6b, 0xf0, 0xd2, 0xf0, 0x0e, 0x82, 0xf6, 0x61, 0xd2, 0x97, 0xbf, 0xa2, 0xe2, 0xbd, 0x5e, - 0xc6, 0x20, 0x75, 0xcc, 0xe2, 0x7e, 0x14, 0x7e, 0x33, 0x1c, 0x09, 0x47, 0x16, 0x4c, 0x58, 0xd2, - 0x26, 0x55, 0xa5, 0xd7, 0x47, 0xeb, 0x77, 0x59, 0x0f, 0xc4, 0x03, 0x28, 0x04, 0x63, 0x25, 0xda, - 0xf8, 0x8d, 0x06, 0xf3, 0xb9, 0x2a, 0x42, 0x75, 0xd0, 0x6d, 0x97, 0xcb, 0xb4, 0xd2, 0xc3, 0x18, - 0x6d, 0xb9, 0xfc, 0x9e, 0x48, 0x76, 0x2c, 0x10, 0xe8, 0x12, 0x54, 0xf7, 0xc4, 0xf8, 0x13, 0xe1, - 0x98, 0x32, 0x67, 0xfb, 0xbd, 0xc6, 0xb4, 0xe9, 0x79, 0x4e, 0x48, 0x21, 0x51, 0xe8, 0x1b, 0x30, - 0xc1, 0xb8, 0x6f, 0xbb, 0x07, 0x8b, 0x55, 0x99, 0x2d, 0xb2, 0xdf, 0xef, 0x4a, 0x48, 0x48, 0xa6, - 0xd0, 0xe8, 0x55, 0x98, 0x3c, 0xa6, 0xbe, 0xac, 0x90, 0x71, 0x49, 0x29, 0xbb, 0xe9, 0xbd, 0x10, - 0x14, 0x92, 0x46, 0x04, 0xc6, 0xef, 0x2a, 0x50, 0x53, 0x01, 0x74, 0x88, 0xdd, 0x41, 0xf7, 0x53, - 0x09, 0x15, 0x46, 0xe2, 0xb5, 0x11, 0x22, 0x61, 0x2e, 0x44, 0xcd, 0x6b, 0x48, 0x06, 0x52, 0xa8, - 0x59, 0x9e, 0xcb, 0xb8, 0x4f, 0x6c, 0x57, 0xa5, 0x6b, 0xb6, 0x41, 0x9c, 0x96, 0x78, 0x8a, 0xcd, - 0x3c, 0xaf, 0x14, 0xd4, 0x12, 0x18, 0xc3, 0x69, 0xb9, 0xe8, 0x41, 0x1c, 0x62, 0x5d, 0x6a, 0x78, - 0xab, 0x94, 0x06, 0x71, 0xf8, 0x72, 0xd1, 0xfd, 0x8b, 0x06, 0x8b, 0x45, 0x4c, 0x99, 0x7a, 0xd4, - 0x3e, 0x57, 0x3d, 0x56, 0xce, 0xae, 0x1e, 0xff, 0xa8, 0xa5, 0x62, 0xcf, 0x18, 0xfa, 0x09, 0x4c, - 0x89, 0x45, 0x48, 0xee, 0x35, 0xe1, 0x3a, 0xf0, 0x46, 0xb9, 0xb5, 0xe9, 0xce, 0xde, 0x4f, 0xa9, - 0xc5, 0x6f, 0x53, 0x4e, 0x92, 0x66, 0x9c, 0xc0, 0x70, 0x2c, 0x15, 0xdd, 0x81, 0x2a, 0xeb, 0x52, - 0x6b, 0x94, 0x41, 0x24, 0x4d, 0xdb, 0xed, 0x52, 0x2b, 0xe9, 0xd7, 0xe2, 0x0b, 0x4b, 0x41, 0xc6, - 0xaf, 0xd2, 0xc1, 0x60, 0x2c, 0x1b, 0x8c, 0x22, 0x17, 0x6b, 0x67, 0xe7, 0xe2, 0x3f, 0xc4, 0xad, - 0x40, 0xda, 0x77, 0xcb, 0x66, 0x1c, 0x7d, 0x30, 0xe0, 0xe6, 0x66, 0x39, 0x37, 0x0b, 0x6e, 0xe9, - 0xe4, 0xb8, 0xca, 0x22, 0x48, 0xca, 0xc5, 0xdb, 0x30, 0x6e, 0x73, 0xda, 0x89, 0xea, 0xeb, 0x72, - 0x69, 0x1f, 0x9b, 0xb3, 0x4a, 0xea, 0xf8, 0x96, 0xe0, 0xc7, 0xa1, 0x18, 0xe3, 0x49, 0xf6, 0x04, - 0xc2, 0xf7, 0xe8, 0x47, 0x30, 0xcd, 0xd4, 0x44, 0x8e, 0xba, 0xc4, 0x95, 0x32, 0x7a, 0xe2, 0xf5, - 0xee, 0x9c, 0x52, 0x35, 0x1d, 0x41, 0x18, 0x4e, 0x24, 0xa6, 0x2a, 0xb8, 0x32, 0x52, 0x05, 0xe7, - 0xe2, 0x5f, 0x58, 0xc1, 0x3e, 0x0c, 0x0b, 0x20, 0xfa, 0x21, 0x4c, 0x78, 0x5d, 0xf2, 0x61, 0x40, - 0x55, 0x54, 0x9e, 0xb3, 0xc1, 0xdd, 0x91, 0xb4, 0xc3, 0xd2, 0x04, 0x84, 0xce, 0x10, 0x8d, 0x95, - 0x48, 0xe3, 0x91, 0x06, 0x0b, 0xf9, 0x66, 0x36, 0x42, 0xb7, 0xd8, 0x81, 0xb9, 0x0e, 0xe1, 0xd6, - 0x61, 0x3c, 0x50, 0xd4, 0x3d, 0x69, 0xa5, 0xdf, 0x6b, 0xcc, 0xdd, 0xce, 0x60, 0x9e, 0xf5, 0x1a, - 0xe8, 0xdd, 0xc0, 0x71, 0x4e, 0xb2, 0x3b, 0x63, 0x8e, 0xdf, 0xf8, 0x85, 0x0e, 0xb3, 0x99, 0xde, - 0x5d, 0x62, 0x3b, 0x5a, 0x83, 0xf9, 0x76, 0xe2, 0x6c, 0x81, 0x50, 0x66, 0x7c, 0x59, 0x11, 0xa7, - 0x33, 0x45, 0xf2, 0xe5, 0xe9, 0xb3, 0xa9, 0xa3, 0x7f, 0xe1, 0xa9, 0x73, 0x0f, 0xe6, 0x48, 0x3c, - 0xad, 0x6f, 0x7b, 0x6d, 0xaa, 0x66, 0x65, 0x53, 0x71, 0xcd, 0xad, 0x65, 0xb0, 0xcf, 0x7a, 0x8d, - 0x2f, 0xe5, 0x67, 0xbc, 0x80, 0xe3, 0x9c, 0x14, 0xf4, 0x0a, 0x8c, 0x5b, 0x5e, 0xe0, 0x72, 0x39, - 0x50, 0xf5, 0xa4, 0x54, 0xd6, 0x05, 0x10, 0x87, 0x38, 0x74, 0x15, 0x6a, 0xa4, 0xdd, 0xb1, 0xdd, - 0x35, 0xcb, 0xa2, 0x8c, 0xc9, 0x6b, 0xdc, 0x54, 0x38, 0xa5, 0xd7, 0x12, 0x30, 0x4e, 0xd3, 0x18, - 0xff, 0xd1, 0xa2, 0x1d, 0xb1, 0x60, 0x97, 0x41, 0x97, 0xc5, 0x66, 0x24, 0x51, 0x2a, 0x30, 0xa9, - 0xe5, 0x46, 0x82, 0x71, 0x84, 0x4f, 0x5d, 0xb5, 0x2b, 0xa5, 0xae, 0xda, 0x7a, 0x89, 0xab, 0x76, - 0xf5, 0xd4, 0xab, 0x76, 0xee, 0xc4, 0xe3, 0x25, 0x4e, 0xfc, 0x01, 0xcc, 0xe5, 0x76, 0xfa, 0x9b, - 0xa0, 0x5b, 0xd4, 0x51, 0x45, 0xf7, 0x9c, 0x5b, 0xef, 0xc0, 0x8d, 0xc0, 0x9c, 0xec, 0xf7, 0x1a, - 0xfa, 0xfa, 0xe6, 0x2d, 0x2c, 0x84, 0x18, 0xbf, 0xd5, 0xe0, 0xdc, 0xc0, 0xcd, 0x18, 0x5d, 0x87, - 0x59, 0xdb, 0xe5, 0xd4, 0xdf, 0x27, 0x16, 0xdd, 0x4e, 0x52, 0xfc, 0x82, 0x3a, 0xd5, 0xec, 0x56, - 0x1a, 0x89, 0xb3, 0xb4, 0xe8, 0x22, 0xe8, 0x76, 0x37, 0xda, 0xae, 0xa5, 0xb6, 0xad, 0x1d, 0x86, - 0x05, 0x4c, 0xd4, 0xc3, 0x21, 0xf1, 0xdb, 0x0f, 0x89, 0x4f, 0xd7, 0xda, 0x6d, 0x71, 0xdf, 0x50, - 0x3e, 0x8d, 0xeb, 0xe1, 0xbb, 0x59, 0x34, 0xce, 0xd3, 0x1b, 0xbf, 0xd6, 0xe0, 0x62, 0x61, 0x27, - 0x29, 0xfd, 0x78, 0x42, 0x00, 0xba, 0xc4, 0x27, 0x1d, 0xca, 0xa9, 0xcf, 0x86, 0x4c, 0xd7, 0x12, - 0x6f, 0x12, 0xf1, 0xe0, 0xde, 0x89, 0x05, 0xe1, 0x94, 0x50, 0xe3, 0x93, 0x0a, 0xcc, 0x62, 0x15, - 0x8f, 0x70, 0x55, 0xfc, 0xdf, 0xaf, 0x0b, 0x77, 0x33, 0xeb, 0xc2, 0x73, 0x52, 0x23, 0x63, 0x5c, - 0xd1, 0xc2, 0x80, 0xee, 0x8b, 0x25, 0x9a, 0xf0, 0x80, 0x95, 0xbb, 0xf8, 0x64, 0x85, 0x4a, 0xc6, - 0x24, 0x08, 0xe1, 0x37, 0x56, 0x02, 0x8d, 0xbe, 0x06, 0xf5, 0x0c, 0xbd, 0xe8, 0xf4, 0x41, 0x87, - 0xfa, 0x98, 0xee, 0x53, 0x9f, 0xba, 0x16, 0x45, 0x57, 0x60, 0x8a, 0x74, 0xed, 0x1b, 0xbe, 0x17, - 0x74, 0x55, 0x44, 0xe3, 0x51, 0xbe, 0xb6, 0xb3, 0x25, 0xe1, 0x38, 0xa6, 0x10, 0xd4, 0x91, 0x45, - 0x2a, 0xaf, 0x52, 0xeb, 0x75, 0x08, 0xc7, 0x31, 0x45, 0xdc, 0xbe, 0xab, 0x85, 0xed, 0xdb, 0x04, - 0x3d, 0xb0, 0xdb, 0xea, 0x4e, 0xf0, 0x86, 0x22, 0xd0, 0xdf, 0xdf, 0xda, 0x78, 0xd6, 0x6b, 0x5c, - 0x2a, 0x7a, 0xf8, 0xe3, 0x27, 0x5d, 0xca, 0x9a, 0xef, 0x6f, 0x6d, 0x60, 0xc1, 0x6c, 0xfc, 0x49, - 0x83, 0x73, 0x99, 0x43, 0x9e, 0xc1, 0x4a, 0xb3, 0x93, 0x5d, 0x69, 0x5e, 0x1b, 0x21, 0x64, 0x05, - 0x4b, 0x8d, 0x9d, 0x3b, 0x84, 0xdc, 0x6a, 0xde, 0xcb, 0x3f, 0x86, 0x5d, 0x2e, 0x7d, 0x73, 0x28, - 0x7e, 0x01, 0x33, 0xfe, 0x5a, 0x81, 0xf3, 0x43, 0xb2, 0x08, 0x3d, 0x00, 0x48, 0x66, 0xcc, 0x10, - 0xa7, 0x0d, 0x51, 0x38, 0x70, 0xcf, 0x9d, 0x93, 0x4f, 0x54, 0x09, 0x34, 0x25, 0x11, 0x31, 0xa8, - 0xf9, 0x94, 0x51, 0xff, 0x98, 0xb6, 0xdf, 0xf5, 0x7c, 0xe5, 0xba, 0x6f, 0x8f, 0xe0, 0xba, 0x81, - 0xec, 0x4d, 0xee, 0x5e, 0x38, 0x11, 0x8c, 0xd3, 0x5a, 0xd0, 0x83, 0xc4, 0x85, 0xe1, 0xbb, 0xeb, - 0xb5, 0x52, 0x27, 0xca, 0x3e, 0x19, 0x9f, 0xe2, 0xcc, 0x7f, 0x68, 0x70, 0x21, 0x63, 0xe4, 0x7b, - 0xb4, 0xd3, 0x75, 0x08, 0xa7, 0x67, 0xd0, 0x8c, 0xee, 0x67, 0x9a, 0xd1, 0xdb, 0x23, 0x78, 0x32, - 0x32, 0xb2, 0xf0, 0x16, 0xf3, 0x77, 0x0d, 0x2e, 0x0e, 0xe5, 0x38, 0x83, 0xe2, 0xfa, 0x7e, 0xb6, - 0xb8, 0xae, 0x7d, 0x8e, 0x73, 0x15, 0xdf, 0x1c, 0x2e, 0x16, 0xfa, 0xe1, 0xff, 0x72, 0x7a, 0x18, - 0xbf, 0xd7, 0x60, 0x26, 0xa2, 0x14, 0xeb, 0x52, 0x89, 0x9d, 0x79, 0x15, 0x40, 0xfd, 0x59, 0x12, - 0xdd, 0xee, 0xf5, 0xc4, 0xee, 0x1b, 0x31, 0x06, 0xa7, 0xa8, 0xd0, 0x4d, 0x40, 0x91, 0x85, 0xbb, - 0x8e, 0x5c, 0x0a, 0xc4, 0xea, 0xa9, 0x4b, 0xde, 0x25, 0xc5, 0x8b, 0xf0, 0x00, 0x05, 0x1e, 0xc2, - 0x65, 0xfc, 0x59, 0x4b, 0xe6, 0xb6, 0x04, 0xbf, 0xa8, 0x9e, 0x97, 0xc6, 0x15, 0x7a, 0x3e, 0x3d, - 0x77, 0x24, 0xe5, 0x0b, 0x3b, 0x77, 0xa4, 0x75, 0x05, 0x25, 0xf1, 0x48, 0xcf, 0x9d, 0x42, 0x96, - 0x42, 0xd9, 0x2d, 0xef, 0x56, 0xea, 0x2f, 0xb2, 0xda, 0xea, 0xab, 0xe5, 0xcc, 0x11, 0x69, 0x3a, - 0x74, 0xc7, 0xbf, 0x02, 0x53, 0xae, 0xd7, 0x0e, 0xf7, 0xe1, 0xdc, 0x76, 0xb1, 0xad, 0xe0, 0x38, - 0xa6, 0x18, 0xf8, 0x23, 0xa7, 0xfa, 0xc5, 0xfc, 0x91, 0x23, 0x37, 0x22, 0xc7, 0x11, 0x04, 0xd1, - 0xf5, 0x21, 0xd9, 0x88, 0x14, 0x1c, 0xc7, 0x14, 0xe8, 0x4e, 0x32, 0x5f, 0x26, 0x64, 0x4c, 0xbe, - 0x56, 0x66, 0x44, 0x17, 0x0f, 0x14, 0xd3, 0x7c, 0xfc, 0xb4, 0x3e, 0xf6, 0xe4, 0x69, 0x7d, 0xec, - 0xd3, 0xa7, 0xf5, 0xb1, 0x8f, 0xfb, 0x75, 0xed, 0x71, 0xbf, 0xae, 0x3d, 0xe9, 0xd7, 0xb5, 0x4f, - 0xfb, 0x75, 0xed, 0xb3, 0x7e, 0x5d, 0xfb, 0xe5, 0xbf, 0xea, 0x63, 0x3f, 0x78, 0xf9, 0xb4, 0x7f, - 0x94, 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xba, 0x87, 0x1a, 0xe0, 0x70, 0x1e, 0x00, 0x00, + // 2087 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x3a, 0xcb, 0x73, 0x1c, 0x47, + 0xf9, 0x9a, 0x9d, 0xd5, 0xeb, 0x5b, 0xbd, 0xdc, 0xfe, 0xd9, 0xbf, 0xb5, 0x08, 0xbb, 0xf2, 0x84, + 0x02, 0x39, 0x71, 0x76, 0x63, 0x39, 0x95, 0x04, 0xcc, 0x01, 0xad, 0x24, 0x1b, 0x19, 0x5b, 0x96, + 0x5b, 0x89, 0x0b, 0x43, 0x30, 0xb4, 0x66, 0x5b, 0xd2, 0xa0, 0xd9, 0x99, 0xcd, 0x74, 0xcf, 0x3a, + 0xba, 0x50, 0x29, 0xfe, 0x00, 0x17, 0xff, 0x00, 0x95, 0x1b, 0x55, 0x5c, 0x80, 0xff, 0x00, 0xaa, + 0xa0, 0x0a, 0x17, 0x5c, 0x5c, 0x15, 0x0e, 0x39, 0x2d, 0xf1, 0x52, 0x9c, 0x39, 0x70, 0xf3, 0x89, + 0xea, 0x9e, 0x9e, 0xe7, 0xee, 0x48, 0xa3, 0x10, 0x54, 0xa1, 0x8a, 0x9b, 0xe6, 0x7b, 0xf7, 0xf7, + 0xea, 0xef, 0xeb, 0x15, 0x5c, 0x3d, 0x7c, 0x9b, 0x35, 0x2c, 0xb7, 0x49, 0xba, 0x56, 0xd3, 0xa3, + 0xcc, 0xf5, 0x3d, 0x93, 0x36, 0x7b, 0xd7, 0x88, 0xdd, 0x3d, 0x20, 0xd7, 0x9b, 0xfb, 0xd4, 0xa1, + 0x1e, 0xe1, 0xb4, 0xdd, 0xe8, 0x7a, 0x2e, 0x77, 0xd1, 0x4b, 0x01, 0x75, 0x83, 0x74, 0xad, 0x46, + 0x48, 0xdd, 0x08, 0xa9, 0x17, 0x5f, 0xdb, 0xb7, 0xf8, 0x81, 0xbf, 0xdb, 0x30, 0xdd, 0x4e, 0x73, + 0xdf, 0xdd, 0x77, 0x9b, 0x92, 0x69, 0xd7, 0xdf, 0x93, 0x5f, 0xf2, 0x43, 0xfe, 0x15, 0x08, 0x5b, + 0x34, 0x12, 0xaa, 0x4d, 0xd7, 0x13, 0x6a, 0xb3, 0x0a, 0x17, 0xdf, 0x88, 0x69, 0x3a, 0xc4, 0x3c, + 0xb0, 0x1c, 0xea, 0x1d, 0x35, 0xbb, 0x87, 0xfb, 0x69, 0x7b, 0x4f, 0xc3, 0xc5, 0x9a, 0x1d, 0xca, + 0xc9, 0x28, 0x5d, 0xcd, 0x3c, 0x2e, 0xcf, 0x77, 0xb8, 0xd5, 0x19, 0x56, 0xf3, 0xe6, 0x49, 0x0c, + 0xcc, 0x3c, 0xa0, 0x1d, 0x92, 0xe5, 0x33, 0x3e, 0xd2, 0xe1, 0xc2, 0xaa, 0x6d, 0xbb, 0xa6, 0x80, + 0xad, 0xd3, 0x9e, 0x65, 0xd2, 0x1d, 0x4e, 0xb8, 0xcf, 0xd0, 0x57, 0x61, 0xa2, 0xed, 0x59, 0x3d, + 0xea, 0x55, 0xb5, 0x25, 0x6d, 0x79, 0xba, 0x35, 0xf7, 0xb4, 0x5f, 0x1f, 0x1b, 0xf4, 0xeb, 0x13, + 0xeb, 0x12, 0x8a, 0x15, 0x16, 0x2d, 0x41, 0xb9, 0xeb, 0xba, 0x76, 0xb5, 0x24, 0xa9, 0x66, 0x14, + 0x55, 0x79, 0xdb, 0x75, 0x6d, 0x2c, 0x31, 0x52, 0x92, 0x94, 0x5c, 0xd5, 0x33, 0x92, 0x24, 0x14, + 0x2b, 0x2c, 0x32, 0x01, 0x4c, 0xd7, 0x69, 0x5b, 0xdc, 0x72, 0x1d, 0x56, 0x2d, 0x2f, 0xe9, 0xcb, + 0x95, 0x95, 0x66, 0x23, 0x0e, 0x73, 0x74, 0xb0, 0x46, 0xf7, 0x70, 0x5f, 0x00, 0x58, 0x43, 0xf8, + 0xaf, 0xd1, 0xbb, 0xd6, 0x58, 0x0b, 0xf9, 0x5a, 0x48, 0x09, 0x87, 0x08, 0xc4, 0x70, 0x42, 0x2c, + 0xfa, 0x0e, 0x94, 0xdb, 0x84, 0x93, 0xea, 0xf8, 0x92, 0xb6, 0x5c, 0x59, 0x79, 0x2d, 0x57, 0xbc, + 0xf2, 0x5b, 0x03, 0x93, 0xc7, 0x1b, 0x1f, 0x70, 0xea, 0x30, 0x21, 0x7c, 0x4a, 0x9c, 0x6c, 0x9d, + 0x70, 0x82, 0xa5, 0x10, 0xb4, 0x0b, 0x15, 0x87, 0xf2, 0xc7, 0xae, 0x77, 0x28, 0x80, 0xd5, 0x09, + 0x29, 0x33, 0x69, 0xf2, 0x70, 0x66, 0x36, 0xb6, 0x14, 0x83, 0x3c, 0xb3, 0x60, 0x6b, 0xcd, 0x0f, + 0xfa, 0xf5, 0xca, 0x56, 0x2c, 0x07, 0x27, 0x85, 0x1a, 0x7f, 0xd6, 0x60, 0x41, 0x45, 0xc8, 0x72, + 0x1d, 0x4c, 0x99, 0x6f, 0x73, 0xf4, 0x43, 0x98, 0x0c, 0x9c, 0xc6, 0x64, 0x74, 0x2a, 0x2b, 0x6f, + 0x1c, 0xaf, 0x34, 0xd0, 0x96, 0x15, 0xd3, 0x9a, 0x57, 0xce, 0x9a, 0x0c, 0xf0, 0x0c, 0x87, 0x52, + 0xd1, 0x03, 0x98, 0x71, 0xdc, 0x36, 0xdd, 0xa1, 0x36, 0x35, 0xb9, 0xeb, 0xc9, 0xc8, 0x55, 0x56, + 0x96, 0x92, 0x5a, 0x44, 0x9d, 0x08, 0xdf, 0x6f, 0x25, 0xe8, 0x5a, 0x0b, 0x83, 0x7e, 0x7d, 0x26, + 0x09, 0xc1, 0x29, 0x39, 0xc6, 0xa7, 0x3a, 0x54, 0x5a, 0x84, 0x59, 0x66, 0xa0, 0x11, 0xfd, 0x04, + 0x80, 0x70, 0xee, 0x59, 0xbb, 0x3e, 0x97, 0x67, 0x11, 0x31, 0xff, 0xfa, 0xf1, 0x67, 0x49, 0xb0, + 0x37, 0x56, 0x23, 0xde, 0x0d, 0x87, 0x7b, 0x47, 0xad, 0x97, 0xc3, 0xe8, 0xc7, 0x88, 0x9f, 0xfe, + 0xb5, 0x3e, 0x7b, 0xdf, 0x27, 0xb6, 0xb5, 0x67, 0xd1, 0xf6, 0x16, 0xe9, 0x50, 0x9c, 0xd0, 0x88, + 0x7a, 0x30, 0x65, 0x92, 0x2e, 0x31, 0x2d, 0x7e, 0x54, 0x2d, 0x49, 0xed, 0x6f, 0x15, 0xd7, 0xbe, + 0xa6, 0x38, 0x03, 0xdd, 0x97, 0x95, 0xee, 0xa9, 0x10, 0x3c, 0xac, 0x39, 0xd2, 0xb5, 0x68, 0xc3, + 0x7c, 0xc6, 0x76, 0xb4, 0x00, 0xfa, 0x21, 0x3d, 0x0a, 0xaa, 0x0d, 0x8b, 0x3f, 0xd1, 0x1a, 0x8c, + 0xf7, 0x88, 0xed, 0x53, 0x59, 0x5b, 0xe9, 0x64, 0xcd, 0x8f, 0x71, 0x28, 0x15, 0x07, 0xbc, 0xdf, + 0x28, 0xbd, 0xad, 0x2d, 0x1e, 0xc2, 0x6c, 0xca, 0xd6, 0x11, 0xba, 0xd6, 0xd3, 0xba, 0x1a, 0xc7, + 0xd5, 0x5d, 0xac, 0xfc, 0xbe, 0x4f, 0x1c, 0x6e, 0xf1, 0xa3, 0x84, 0x32, 0xe3, 0x16, 0x9c, 0x5b, + 0xdb, 0xb8, 0xa3, 0x7a, 0x89, 0x8a, 0x3b, 0x5a, 0x01, 0xa0, 0x1f, 0x74, 0x3d, 0xca, 0x44, 0x1d, + 0xa9, 0x8e, 0x12, 0x95, 0xea, 0x46, 0x84, 0xc1, 0x09, 0x2a, 0xa3, 0x07, 0xaa, 0x43, 0x88, 0x1e, + 0xe3, 0x90, 0x0e, 0x55, 0x7c, 0x51, 0x8f, 0x91, 0x3e, 0x95, 0x18, 0x74, 0x1b, 0xc6, 0x77, 0x45, + 0x64, 0x94, 0xf9, 0x57, 0x0a, 0x07, 0xb1, 0x35, 0x3d, 0xe8, 0xd7, 0xc7, 0x25, 0x00, 0x07, 0x22, + 0x8c, 0x27, 0x25, 0xf8, 0x72, 0xb6, 0x60, 0xd6, 0x5c, 0x67, 0xcf, 0xda, 0xf7, 0x3d, 0xf9, 0x81, + 0xbe, 0x05, 0x13, 0x81, 0x48, 0x65, 0xd1, 0x72, 0xd8, 0xd1, 0x76, 0x24, 0xf4, 0x45, 0xbf, 0x7e, + 0x31, 0xcb, 0x1a, 0x60, 0xb0, 0xe2, 0x43, 0xcb, 0x30, 0xe5, 0xd1, 0xf7, 0x7d, 0xca, 0x38, 0x93, + 0x79, 0x37, 0xdd, 0x9a, 0x11, 0xa9, 0x83, 0x15, 0x0c, 0x47, 0x58, 0xf4, 0xa1, 0x06, 0xe7, 0x83, + 0xaa, 0x4c, 0xd9, 0xa0, 0x2a, 0xf2, 0x5a, 0x91, 0x9c, 0x48, 0x31, 0xb6, 0xbe, 0xa4, 0x8c, 0x3d, + 0x3f, 0x02, 0x89, 0x47, 0xa9, 0x32, 0xfe, 0xae, 0xc1, 0xc5, 0xd1, 0x1d, 0x04, 0xed, 0xc1, 0xa4, + 0x27, 0xff, 0x0a, 0x8b, 0xf7, 0x46, 0x11, 0x83, 0xd4, 0x31, 0xf3, 0xfb, 0x51, 0xf0, 0xcd, 0x70, + 0x28, 0x1c, 0x99, 0x30, 0x61, 0x4a, 0x9b, 0x54, 0x95, 0xde, 0x38, 0x5d, 0xbf, 0x4b, 0x7b, 0x20, + 0xba, 0x80, 0x02, 0x30, 0x56, 0xa2, 0x8d, 0x5f, 0x6a, 0x30, 0x9f, 0xa9, 0x22, 0x54, 0x03, 0xdd, + 0x72, 0xb8, 0x4c, 0x2b, 0x3d, 0x88, 0xd1, 0xa6, 0xc3, 0x1f, 0x88, 0x64, 0xc7, 0x02, 0x81, 0x2e, + 0x43, 0x79, 0x57, 0x5c, 0x7f, 0x22, 0x1c, 0x53, 0xad, 0xd9, 0x41, 0xbf, 0x3e, 0xdd, 0x72, 0x5d, + 0x3b, 0xa0, 0x90, 0x28, 0xf4, 0x35, 0x98, 0x60, 0xdc, 0xb3, 0x9c, 0xfd, 0x6a, 0x59, 0x66, 0x8b, + 0xec, 0xf7, 0x3b, 0x12, 0x12, 0x90, 0x29, 0x34, 0x7a, 0x05, 0x26, 0x7b, 0xd4, 0x93, 0x15, 0x32, + 0x2e, 0x29, 0x65, 0x37, 0x7d, 0x10, 0x80, 0x02, 0xd2, 0x90, 0xc0, 0xf8, 0x75, 0x09, 0x2a, 0x2a, + 0x80, 0x36, 0xb1, 0x3a, 0xe8, 0x61, 0x22, 0xa1, 0x82, 0x48, 0xbc, 0x7a, 0x8a, 0x48, 0xb4, 0x16, + 0xc2, 0xe6, 0x35, 0x22, 0x03, 0x29, 0x54, 0x4c, 0xd7, 0x61, 0xdc, 0x23, 0x96, 0xa3, 0xd2, 0x35, + 0xdd, 0x20, 0x8e, 0x4b, 0x3c, 0xc5, 0xd6, 0x3a, 0xaf, 0x14, 0x54, 0x62, 0x18, 0xc3, 0x49, 0xb9, + 0xe8, 0x51, 0x14, 0x62, 0x5d, 0x6a, 0x78, 0xb3, 0x90, 0x06, 0x71, 0xf8, 0x62, 0xd1, 0xfd, 0xa3, + 0x06, 0xd5, 0x3c, 0xa6, 0x54, 0x3d, 0x6a, 0x9f, 0xa9, 0x1e, 0x4b, 0x67, 0x57, 0x8f, 0xbf, 0xd3, + 0x12, 0xb1, 0x67, 0x0c, 0xfd, 0x08, 0xa6, 0xc4, 0x20, 0x24, 0xe7, 0x9a, 0x60, 0x1c, 0x78, 0xbd, + 0xd8, 0xd8, 0x74, 0x6f, 0xf7, 0xc7, 0xd4, 0xe4, 0x77, 0x29, 0x27, 0x71, 0x33, 0x8e, 0x61, 0x38, + 0x92, 0x8a, 0xee, 0x41, 0x99, 0x75, 0xa9, 0x79, 0x9a, 0x8b, 0x48, 0x9a, 0xb6, 0xd3, 0xa5, 0x66, + 0xdc, 0xaf, 0xc5, 0x17, 0x96, 0x82, 0x8c, 0x9f, 0x27, 0x83, 0xc1, 0x58, 0x3a, 0x18, 0x79, 0x2e, + 0xd6, 0xce, 0xce, 0xc5, 0xbf, 0x8d, 0x5a, 0x81, 0xb4, 0xef, 0x8e, 0xc5, 0x38, 0x7a, 0x6f, 0xc8, + 0xcd, 0x8d, 0x62, 0x6e, 0x16, 0xdc, 0xd2, 0xc9, 0x51, 0x95, 0x85, 0x90, 0x84, 0x8b, 0xb7, 0x60, + 0xdc, 0xe2, 0xb4, 0x13, 0xd6, 0xd7, 0x95, 0xc2, 0x3e, 0x6e, 0xcd, 0x2a, 0xa9, 0xe3, 0x9b, 0x82, + 0x1f, 0x07, 0x62, 0x8c, 0x67, 0xe9, 0x13, 0x08, 0xdf, 0xa3, 0x1f, 0xc0, 0x34, 0x53, 0x37, 0x72, + 0xd8, 0x25, 0xae, 0x16, 0xd1, 0x13, 0x8d, 0x77, 0xe7, 0x94, 0xaa, 0xe9, 0x10, 0xc2, 0x70, 0x2c, + 0x31, 0x51, 0xc1, 0xa5, 0x53, 0x55, 0x70, 0x26, 0xfe, 0xb9, 0x15, 0xec, 0xc1, 0xa8, 0x00, 0xa2, + 0xef, 0xc3, 0x84, 0xdb, 0x25, 0xef, 0xfb, 0x54, 0x45, 0xe5, 0x84, 0x09, 0xee, 0x9e, 0xa4, 0x1d, + 0x95, 0x26, 0x20, 0x74, 0x06, 0x68, 0xac, 0x44, 0x1a, 0x4f, 0x34, 0x58, 0xc8, 0x36, 0xb3, 0x53, + 0x74, 0x8b, 0x6d, 0x98, 0xeb, 0x10, 0x6e, 0x1e, 0x44, 0x17, 0x8a, 0xda, 0x93, 0x96, 0x07, 0xfd, + 0xfa, 0xdc, 0xdd, 0x14, 0xe6, 0x45, 0xbf, 0x8e, 0x6e, 0xfa, 0xb6, 0x7d, 0x94, 0x9e, 0x19, 0x33, + 0xfc, 0xc6, 0x3f, 0x75, 0x98, 0x4d, 0xf5, 0xee, 0x02, 0xd3, 0xd1, 0x2a, 0xcc, 0xb7, 0x63, 0x67, + 0x0b, 0x84, 0x32, 0xe3, 0xff, 0x15, 0x71, 0x32, 0x53, 0x24, 0x5f, 0x96, 0x3e, 0x9d, 0x3a, 0xfa, + 0xe7, 0x9e, 0x3a, 0x0f, 0x60, 0x8e, 0x44, 0xb7, 0xf5, 0x5d, 0xb7, 0x4d, 0xd5, 0x5d, 0xd9, 0x50, + 0x5c, 0x73, 0xab, 0x29, 0xec, 0x8b, 0x7e, 0xfd, 0xff, 0xb2, 0x77, 0xbc, 0x80, 0xe3, 0x8c, 0x14, + 0xf4, 0x32, 0x8c, 0x9b, 0xae, 0xef, 0x70, 0x79, 0xa1, 0xea, 0x71, 0xa9, 0xac, 0x09, 0x20, 0x0e, + 0x70, 0xe8, 0x1a, 0x54, 0x48, 0xbb, 0x63, 0x39, 0xab, 0xa6, 0x49, 0x19, 0x93, 0x6b, 0xdc, 0x54, + 0x70, 0x4b, 0xaf, 0xc6, 0x60, 0x9c, 0xa4, 0x41, 0x0e, 0xcc, 0xed, 0x59, 0x1e, 0xe3, 0xab, 0x3d, + 0x62, 0xd9, 0x64, 0xd7, 0xa6, 0xd5, 0xc9, 0xe2, 0xd7, 0xe2, 0x8e, 0xbf, 0x1b, 0xde, 0xbb, 0x17, + 0xc3, 0xf3, 0xdd, 0x4c, 0x49, 0xc3, 0x19, 0xe9, 0xc6, 0x3f, 0xb4, 0x70, 0x26, 0xcd, 0x99, 0x9d, + 0xd0, 0x15, 0x31, 0x89, 0x49, 0x94, 0x4a, 0x84, 0xc4, 0x30, 0x25, 0xc1, 0x38, 0xc4, 0x27, 0x56, + 0xfb, 0x52, 0xa1, 0xd5, 0x5e, 0x2f, 0xb0, 0xda, 0x97, 0x8f, 0x5d, 0xed, 0x33, 0x1e, 0x1e, 0x3f, + 0xd9, 0xc3, 0xc6, 0x7b, 0x30, 0x97, 0xd9, 0x21, 0x6e, 0x83, 0x6e, 0x52, 0x5b, 0x15, 0xf9, 0x09, + 0x5b, 0xf6, 0xd0, 0x06, 0xd2, 0x9a, 0x1c, 0xf4, 0xeb, 0xfa, 0xda, 0xc6, 0x1d, 0x2c, 0x84, 0x18, + 0x1f, 0x97, 0xc2, 0xb2, 0x8e, 0x83, 0xf1, 0xbf, 0x42, 0xfa, 0x37, 0x0b, 0xc9, 0xf8, 0x95, 0x06, + 0xe7, 0x86, 0xde, 0x37, 0xd0, 0x0d, 0x98, 0xb5, 0x1c, 0x4e, 0xbd, 0x3d, 0x62, 0xd2, 0xad, 0xd8, + 0xbf, 0x17, 0x94, 0x88, 0xd9, 0xcd, 0x24, 0x12, 0xa7, 0x69, 0xd1, 0x25, 0xd0, 0xad, 0x6e, 0xb8, + 0x23, 0xc9, 0x18, 0x6e, 0x6e, 0x33, 0x2c, 0x60, 0x22, 0x18, 0x07, 0xc4, 0x6b, 0x3f, 0x26, 0x1e, + 0x5d, 0x6d, 0xb7, 0xc5, 0xd6, 0xa8, 0x32, 0x35, 0x0a, 0xc6, 0xb7, 0xd3, 0x68, 0x9c, 0xa5, 0x37, + 0x7e, 0xa1, 0xc1, 0xa5, 0xdc, 0xfb, 0xa0, 0xf0, 0x13, 0x18, 0x01, 0xe8, 0x12, 0x8f, 0x74, 0x28, + 0xa7, 0x1e, 0x1b, 0x31, 0x23, 0x15, 0x78, 0x59, 0x8a, 0xc6, 0xaf, 0xed, 0x48, 0x10, 0x4e, 0x08, + 0x35, 0x3e, 0x2a, 0xc1, 0x2c, 0x56, 0x99, 0x11, 0x0c, 0xfc, 0xff, 0xf9, 0xa1, 0xef, 0x7e, 0x6a, + 0xe8, 0x3b, 0xa1, 0xe0, 0x52, 0xc6, 0xe5, 0x8d, 0x7d, 0xe8, 0xa1, 0x58, 0x85, 0x08, 0xf7, 0x59, + 0xb1, 0xf5, 0x35, 0x2d, 0x54, 0x32, 0xc6, 0x41, 0x08, 0xbe, 0xb1, 0x12, 0x68, 0x0c, 0x34, 0xa8, + 0xa5, 0xe8, 0xc5, 0x7d, 0xed, 0x77, 0xa8, 0x87, 0xe9, 0x1e, 0xf5, 0xa8, 0x63, 0x52, 0x74, 0x15, + 0xa6, 0x48, 0xd7, 0xba, 0xe5, 0xb9, 0x7e, 0x57, 0x45, 0x34, 0x1a, 0xc8, 0x56, 0xb7, 0x37, 0x25, + 0x1c, 0x47, 0x14, 0x82, 0x3a, 0xb4, 0x48, 0xe5, 0x55, 0x62, 0x49, 0x0a, 0xe0, 0x38, 0xa2, 0x88, + 0x7a, 0x47, 0x39, 0xb7, 0x77, 0xb4, 0x40, 0xf7, 0xad, 0xb6, 0xda, 0xec, 0x5e, 0x57, 0x04, 0xfa, + 0xbb, 0x9b, 0xeb, 0x2f, 0xfa, 0xf5, 0xcb, 0x79, 0xcf, 0xb7, 0xfc, 0xa8, 0x4b, 0x59, 0xe3, 0xdd, + 0xcd, 0x75, 0x2c, 0x98, 0x8d, 0xdf, 0x6b, 0x70, 0x2e, 0x75, 0xc8, 0x33, 0x18, 0x4c, 0xb7, 0xd3, + 0x83, 0xe9, 0xab, 0xa7, 0x08, 0x59, 0xce, 0x68, 0x6a, 0x65, 0x0e, 0x21, 0x67, 0xd3, 0x77, 0xb2, + 0x4f, 0x9a, 0x57, 0x0a, 0xef, 0x7f, 0xf9, 0xef, 0x98, 0xc6, 0x9f, 0x4a, 0x70, 0x7e, 0x44, 0x16, + 0xa1, 0x47, 0x00, 0x71, 0x83, 0x1b, 0xe1, 0xb4, 0x11, 0x0a, 0x87, 0x5e, 0x2b, 0xe6, 0xe4, 0x43, + 0x63, 0x0c, 0x4d, 0x48, 0x44, 0x0c, 0x2a, 0x1e, 0x65, 0xd4, 0xeb, 0xd1, 0xf6, 0x4d, 0xd7, 0x53, + 0xae, 0xfb, 0xe6, 0x29, 0x5c, 0x37, 0x94, 0xbd, 0xf1, 0x06, 0x8d, 0x63, 0xc1, 0x38, 0xa9, 0x05, + 0x3d, 0x8a, 0x5d, 0x18, 0xbc, 0x9e, 0x5f, 0x2f, 0x74, 0xa2, 0xf4, 0xc3, 0xff, 0x31, 0xce, 0xfc, + 0x8b, 0x06, 0x17, 0x52, 0x46, 0xbe, 0x43, 0x3b, 0x5d, 0x9b, 0x70, 0x7a, 0x06, 0xcd, 0xe8, 0x61, + 0xaa, 0x19, 0xbd, 0x75, 0x0a, 0x4f, 0x86, 0x46, 0xe6, 0xee, 0xa2, 0x1f, 0x6b, 0x70, 0x69, 0x24, + 0xc7, 0x19, 0x14, 0xd7, 0x77, 0xd3, 0xc5, 0x75, 0xfd, 0x33, 0x9c, 0x2b, 0x7f, 0xff, 0xbb, 0x94, + 0xeb, 0x87, 0xff, 0xca, 0xdb, 0xc3, 0xf8, 0x8d, 0x06, 0x33, 0x21, 0xa5, 0x18, 0x42, 0x0b, 0x0c, + 0x6c, 0x2b, 0x00, 0xea, 0x27, 0xaf, 0xf0, 0x8d, 0x46, 0x8f, 0xed, 0xbe, 0x15, 0x61, 0x70, 0x82, + 0x0a, 0xdd, 0x06, 0x14, 0x5a, 0xb8, 0x63, 0xcb, 0xa1, 0x40, 0xcc, 0x3d, 0xba, 0xe4, 0x5d, 0x54, + 0xbc, 0x08, 0x0f, 0x51, 0xe0, 0x11, 0x5c, 0xc6, 0x1f, 0xb4, 0xf8, 0xde, 0x96, 0xe0, 0x2f, 0xaa, + 0xe7, 0xa5, 0x71, 0xb9, 0x9e, 0x4f, 0xde, 0x3b, 0x92, 0xf2, 0x0b, 0x7b, 0xef, 0x48, 0xeb, 0x72, + 0x4a, 0xe2, 0x89, 0x9e, 0x39, 0x85, 0x2c, 0x85, 0xa2, 0x53, 0xde, 0x9d, 0xc4, 0x0f, 0x9d, 0x95, + 0x95, 0x57, 0x8a, 0x99, 0x23, 0xd2, 0x74, 0xe4, 0xe6, 0x74, 0x15, 0xa6, 0x1c, 0xb7, 0x1d, 0xcc, + 0xc3, 0x99, 0xe9, 0x62, 0x4b, 0xc1, 0x71, 0x44, 0x31, 0xf4, 0x73, 0x5c, 0xf9, 0xf3, 0xf9, 0x39, + 0x4e, 0x4e, 0x44, 0xb6, 0x2d, 0x08, 0xc2, 0xa5, 0x2c, 0x9e, 0x88, 0x14, 0x1c, 0x47, 0x14, 0xe8, + 0x5e, 0x7c, 0xbf, 0x4c, 0xc8, 0x98, 0x7c, 0xa5, 0xc8, 0x15, 0x9d, 0x7f, 0xa1, 0xb4, 0x5a, 0x4f, + 0x9f, 0xd7, 0xc6, 0x9e, 0x3d, 0xaf, 0x8d, 0x7d, 0xf2, 0xbc, 0x36, 0xf6, 0xe1, 0xa0, 0xa6, 0x3d, + 0x1d, 0xd4, 0xb4, 0x67, 0x83, 0x9a, 0xf6, 0xc9, 0xa0, 0xa6, 0x7d, 0x3a, 0xa8, 0x69, 0x3f, 0xfb, + 0x5b, 0x6d, 0xec, 0x7b, 0x2f, 0x1d, 0xf7, 0x7f, 0x01, 0xff, 0x0a, 0x00, 0x00, 0xff, 0xff, 0xc2, + 0x62, 0xe6, 0x4b, 0x36, 0x20, 0x00, 0x00, } func (m *AllocatedDeviceStatus) Marshal() (dAtA []byte, err error) { @@ -1942,6 +1975,20 @@ func (m *DeviceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.FirstAvailable) > 0 { + for iNdEx := len(m.FirstAvailable) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.FirstAvailable[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + } if m.AdminAccess != nil { i-- if *m.AdminAccess { @@ -2075,6 +2122,61 @@ func (m *DeviceSelector) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *DeviceSubRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceSubRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DeviceSubRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i = encodeVarintGenerated(dAtA, i, uint64(m.Count)) + i-- + dAtA[i] = 0x28 + i -= len(m.AllocationMode) + copy(dAtA[i:], m.AllocationMode) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.AllocationMode))) + i-- + dAtA[i] = 0x22 + if len(m.Selectors) > 0 { + for iNdEx := len(m.Selectors) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Selectors[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + i -= len(m.DeviceClassName) + copy(dAtA[i:], m.DeviceClassName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.DeviceClassName))) + i-- + dAtA[i] = 0x12 + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *NetworkDeviceData) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -3059,6 +3161,12 @@ func (m *DeviceRequest) Size() (n int) { if m.AdminAccess != nil { n += 2 } + if len(m.FirstAvailable) > 0 { + for _, e := range m.FirstAvailable { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -3095,6 +3203,28 @@ func (m *DeviceSelector) Size() (n int) { return n } +func (m *DeviceSubRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.DeviceClassName) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Selectors) > 0 { + for _, e := range m.Selectors { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = len(m.AllocationMode) + n += 1 + l + sovGenerated(uint64(l)) + n += 1 + sovGenerated(uint64(m.Count)) + return n +} + func (m *NetworkDeviceData) Size() (n int) { if m == nil { return 0 @@ -3585,6 +3715,11 @@ func (this *DeviceRequest) String() string { repeatedStringForSelectors += strings.Replace(strings.Replace(f.String(), "DeviceSelector", "DeviceSelector", 1), `&`, ``, 1) + "," } repeatedStringForSelectors += "}" + repeatedStringForFirstAvailable := "[]DeviceSubRequest{" + for _, f := range this.FirstAvailable { + repeatedStringForFirstAvailable += strings.Replace(strings.Replace(f.String(), "DeviceSubRequest", "DeviceSubRequest", 1), `&`, ``, 1) + "," + } + repeatedStringForFirstAvailable += "}" s := strings.Join([]string{`&DeviceRequest{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `DeviceClassName:` + fmt.Sprintf("%v", this.DeviceClassName) + `,`, @@ -3592,6 +3727,7 @@ func (this *DeviceRequest) String() string { `AllocationMode:` + fmt.Sprintf("%v", this.AllocationMode) + `,`, `Count:` + fmt.Sprintf("%v", this.Count) + `,`, `AdminAccess:` + valueToStringGenerated(this.AdminAccess) + `,`, + `FirstAvailable:` + repeatedStringForFirstAvailable + `,`, `}`, }, "") return s @@ -3620,6 +3756,25 @@ func (this *DeviceSelector) String() string { }, "") return s } +func (this *DeviceSubRequest) String() string { + if this == nil { + return "nil" + } + repeatedStringForSelectors := "[]DeviceSelector{" + for _, f := range this.Selectors { + repeatedStringForSelectors += strings.Replace(strings.Replace(f.String(), "DeviceSelector", "DeviceSelector", 1), `&`, ``, 1) + "," + } + repeatedStringForSelectors += "}" + s := strings.Join([]string{`&DeviceSubRequest{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `DeviceClassName:` + fmt.Sprintf("%v", this.DeviceClassName) + `,`, + `Selectors:` + repeatedStringForSelectors + `,`, + `AllocationMode:` + fmt.Sprintf("%v", this.AllocationMode) + `,`, + `Count:` + fmt.Sprintf("%v", this.Count) + `,`, + `}`, + }, "") + return s +} func (this *NetworkDeviceData) String() string { if this == nil { return "nil" @@ -6223,6 +6378,40 @@ func (m *DeviceRequest) Unmarshal(dAtA []byte) error { } b := bool(v != 0) m.AdminAccess = &b + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FirstAvailable", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FirstAvailable = append(m.FirstAvailable, DeviceSubRequest{}) + if err := m.FirstAvailable[len(m.FirstAvailable)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -6529,6 +6718,205 @@ func (m *DeviceSelector) Unmarshal(dAtA []byte) error { } return nil } +func (m *DeviceSubRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceSubRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceSubRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeviceClassName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DeviceClassName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Selectors", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Selectors = append(m.Selectors, DeviceSelector{}) + if err := m.Selectors[len(m.Selectors)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllocationMode", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllocationMode = DeviceAllocationMode(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType) + } + m.Count = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Count |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *NetworkDeviceData) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/staging/src/k8s.io/api/resource/v1alpha3/generated.proto b/staging/src/k8s.io/api/resource/v1alpha3/generated.proto index 19ac86c77229f..714973183b1ae 100644 --- a/staging/src/k8s.io/api/resource/v1alpha3/generated.proto +++ b/staging/src/k8s.io/api/resource/v1alpha3/generated.proto @@ -200,6 +200,10 @@ message DeviceAllocationConfiguration { // Requests lists the names of requests where the configuration applies. // If empty, its applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic repeated string requests = 2; @@ -286,6 +290,10 @@ message DeviceClaimConfiguration { // Requests lists the names of requests where the configuration applies. // If empty, it applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic repeated string requests = 1; @@ -370,6 +378,10 @@ message DeviceConstraint { // constraint. If this is not specified, this constraint applies to all // requests in this claim. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the constraint applies to all subrequests. + // // +optional // +listType=atomic repeated string requests = 1; @@ -395,11 +407,6 @@ message DeviceConstraint { // DeviceRequest is a request for devices required for a claim. // This is typically a request for a single resource like a device, but can // also ask for several identical devices. -// -// A DeviceClassName is currently required. Clients must check that it is -// indeed set. It's absence indicates that something changed in a way that -// is not supported by the client yet, in which case it must refuse to -// handle the request. message DeviceRequest { // Name can be used to reference this request in a pod.spec.containers[].resources.claims // entry and in a constraint of the claim. @@ -413,7 +420,10 @@ message DeviceRequest { // additional configuration and selectors to be inherited by this // request. // - // A class is required. Which classes are available depends on the cluster. + // A class is required if no subrequests are specified in the + // firstAvailable list and no class can be set if subrequests + // are specified in the firstAvailable list. + // Which classes are available depends on the cluster. // // Administrators may use this to restrict which devices may get // requested by only installing classes with selectors for permitted @@ -421,7 +431,8 @@ message DeviceRequest { // then administrators can create an empty DeviceClass for users // to reference. // - // +required + // +optional + // +oneOf=deviceRequestType optional string deviceClassName = 2; // Selectors define criteria which must be satisfied by a specific @@ -429,6 +440,9 @@ message DeviceRequest { // request. All selectors must be satisfied for a device to be // considered. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // +optional // +listType=atomic repeated DeviceSelector selectors = 3; @@ -449,6 +463,9 @@ message DeviceRequest { // the mode is ExactCount and count is not specified, the default count is // one. Any other requests must specify this field. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // More modes may get added in the future. Clients must refuse to handle // requests with unknown modes. // @@ -458,6 +475,9 @@ message DeviceRequest { // Count is used only when the count mode is "ExactCount". Must be greater than zero. // If AllocationMode is ExactCount and this field is not specified, the default is one. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // +optional // +oneOf=AllocationMode optional int64 count = 5; @@ -468,6 +488,9 @@ message DeviceRequest { // all ordinary claims to the device with respect to access modes and // any resource allocations. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // This is an alpha field and requires enabling the DRAAdminAccess // feature gate. Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. @@ -475,13 +498,39 @@ message DeviceRequest { // +optional // +featureGate=DRAAdminAccess optional bool adminAccess = 6; + + // FirstAvailable contains subrequests, of which exactly one will be + // satisfied by the scheduler to satisfy this request. It tries to + // satisfy them in the order in which they are listed here. So if + // there are two entries in the list, the scheduler will only check + // the second one if it determines that the first one cannot be used. + // + // This field may only be set in the entries of DeviceClaim.Requests. + // + // DRA does not yet implement scoring, so the scheduler will + // select the first set of devices that satisfies all the + // requests in the claim. And if the requirements can + // be satisfied on more than one node, other scheduling features + // will determine which node is chosen. This means that the set of + // devices allocated to a claim might not be the optimal set + // available to the cluster. Scoring will be implemented later. + // + // +optional + // +oneOf=deviceRequestType + // +listType=atomic + // +featureGate=DRAPrioritizedList + repeated DeviceSubRequest firstAvailable = 7; } // DeviceRequestAllocationResult contains the allocation result for one request. message DeviceRequestAllocationResult { // Request is the name of the request in the claim which caused this - // device to be allocated. Multiple devices may have been allocated - // per request. + // device to be allocated. If it references a subrequest in the + // firstAvailable list on a DeviceRequest, this field must + // include both the name of the main request and the subrequest + // using the format
/. + // + // Multiple devices may have been allocated per request. // // +required optional string request = 1; @@ -533,6 +582,78 @@ message DeviceSelector { optional CELDeviceSelector cel = 1; } +// DeviceSubRequest describes a request for device provided in the +// claim.spec.devices.requests[].firstAvailable array. Each +// is typically a request for a single resource like a device, but can +// also ask for several identical devices. +// +// DeviceSubRequest is similar to Request, but doesn't expose the AdminAccess +// or FirstAvailable fields, as those can only be set on the top-level request. +// AdminAccess is not supported for requests with a prioritized list, and +// recursive FirstAvailable fields are not supported. +message DeviceSubRequest { + // Name can be used to reference this subrequest in the list of constraints + // or the list of configurations for the claim. References must use the + // format
/. + // + // Must be a DNS label. + // + // +required + optional string name = 1; + + // DeviceClassName references a specific DeviceClass, which can define + // additional configuration and selectors to be inherited by this + // subrequest. + // + // A class is required. Which classes are available depends on the cluster. + // + // Administrators may use this to restrict which devices may get + // requested by only installing classes with selectors for permitted + // devices. If users are free to request anything without restrictions, + // then administrators can create an empty DeviceClass for users + // to reference. + // + // +required + optional string deviceClassName = 2; + + // Selectors define criteria which must be satisfied by a specific + // device in order for that device to be considered for this + // request. All selectors must be satisfied for a device to be + // considered. + // + // +optional + // +listType=atomic + repeated DeviceSelector selectors = 3; + + // AllocationMode and its related fields define how devices are allocated + // to satisfy this request. Supported values are: + // + // - ExactCount: This request is for a specific number of devices. + // This is the default. The exact number is provided in the + // count field. + // + // - All: This request is for all of the matching devices in a pool. + // Allocation will fail if some devices are already allocated, + // unless adminAccess is requested. + // + // If AlloctionMode is not specified, the default mode is ExactCount. If + // the mode is ExactCount and count is not specified, the default count is + // one. Any other requests must specify this field. + // + // More modes may get added in the future. Clients must refuse to handle + // requests with unknown modes. + // + // +optional + optional string allocationMode = 4; + + // Count is used only when the count mode is "ExactCount". Must be greater than zero. + // If AllocationMode is ExactCount and this field is not specified, the default is one. + // + // +optional + // +oneOf=AllocationMode + optional int64 count = 5; +} + // NetworkDeviceData provides network-related details for the allocated device. // This information may be filled by drivers or other components to configure // or identify the device within a network context. diff --git a/staging/src/k8s.io/api/resource/v1alpha3/types.go b/staging/src/k8s.io/api/resource/v1alpha3/types.go index e6471d058517c..b6c6c31840e6b 100644 --- a/staging/src/k8s.io/api/resource/v1alpha3/types.go +++ b/staging/src/k8s.io/api/resource/v1alpha3/types.go @@ -386,11 +386,6 @@ const ( // DeviceRequest is a request for devices required for a claim. // This is typically a request for a single resource like a device, but can // also ask for several identical devices. -// -// A DeviceClassName is currently required. Clients must check that it is -// indeed set. It's absence indicates that something changed in a way that -// is not supported by the client yet, in which case it must refuse to -// handle the request. type DeviceRequest struct { // Name can be used to reference this request in a pod.spec.containers[].resources.claims // entry and in a constraint of the claim. @@ -404,7 +399,10 @@ type DeviceRequest struct { // additional configuration and selectors to be inherited by this // request. // - // A class is required. Which classes are available depends on the cluster. + // A class is required if no subrequests are specified in the + // firstAvailable list and no class can be set if subrequests + // are specified in the firstAvailable list. + // Which classes are available depends on the cluster. // // Administrators may use this to restrict which devices may get // requested by only installing classes with selectors for permitted @@ -412,7 +410,8 @@ type DeviceRequest struct { // then administrators can create an empty DeviceClass for users // to reference. // - // +required + // +optional + // +oneOf=deviceRequestType DeviceClassName string `json:"deviceClassName" protobuf:"bytes,2,name=deviceClassName"` // Selectors define criteria which must be satisfied by a specific @@ -420,6 +419,9 @@ type DeviceRequest struct { // request. All selectors must be satisfied for a device to be // considered. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // +optional // +listType=atomic Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"` @@ -440,6 +442,9 @@ type DeviceRequest struct { // the mode is ExactCount and count is not specified, the default count is // one. Any other requests must specify this field. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // More modes may get added in the future. Clients must refuse to handle // requests with unknown modes. // @@ -449,6 +454,9 @@ type DeviceRequest struct { // Count is used only when the count mode is "ExactCount". Must be greater than zero. // If AllocationMode is ExactCount and this field is not specified, the default is one. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // +optional // +oneOf=AllocationMode Count int64 `json:"count,omitempty" protobuf:"bytes,5,opt,name=count"` @@ -459,6 +467,9 @@ type DeviceRequest struct { // all ordinary claims to the device with respect to access modes and // any resource allocations. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // This is an alpha field and requires enabling the DRAAdminAccess // feature gate. Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. @@ -466,10 +477,105 @@ type DeviceRequest struct { // +optional // +featureGate=DRAAdminAccess AdminAccess *bool `json:"adminAccess,omitempty" protobuf:"bytes,6,opt,name=adminAccess"` + + // FirstAvailable contains subrequests, of which exactly one will be + // satisfied by the scheduler to satisfy this request. It tries to + // satisfy them in the order in which they are listed here. So if + // there are two entries in the list, the scheduler will only check + // the second one if it determines that the first one cannot be used. + // + // This field may only be set in the entries of DeviceClaim.Requests. + // + // DRA does not yet implement scoring, so the scheduler will + // select the first set of devices that satisfies all the + // requests in the claim. And if the requirements can + // be satisfied on more than one node, other scheduling features + // will determine which node is chosen. This means that the set of + // devices allocated to a claim might not be the optimal set + // available to the cluster. Scoring will be implemented later. + // + // +optional + // +oneOf=deviceRequestType + // +listType=atomic + // +featureGate=DRAPrioritizedList + FirstAvailable []DeviceSubRequest `json:"firstAvailable,omitempty" protobuf:"bytes,7,name=firstAvailable"` +} + +// DeviceSubRequest describes a request for device provided in the +// claim.spec.devices.requests[].firstAvailable array. Each +// is typically a request for a single resource like a device, but can +// also ask for several identical devices. +// +// DeviceSubRequest is similar to Request, but doesn't expose the AdminAccess +// or FirstAvailable fields, as those can only be set on the top-level request. +// AdminAccess is not supported for requests with a prioritized list, and +// recursive FirstAvailable fields are not supported. +type DeviceSubRequest struct { + // Name can be used to reference this subrequest in the list of constraints + // or the list of configurations for the claim. References must use the + // format
/. + // + // Must be a DNS label. + // + // +required + Name string `json:"name" protobuf:"bytes,1,name=name"` + + // DeviceClassName references a specific DeviceClass, which can define + // additional configuration and selectors to be inherited by this + // subrequest. + // + // A class is required. Which classes are available depends on the cluster. + // + // Administrators may use this to restrict which devices may get + // requested by only installing classes with selectors for permitted + // devices. If users are free to request anything without restrictions, + // then administrators can create an empty DeviceClass for users + // to reference. + // + // +required + DeviceClassName string `json:"deviceClassName" protobuf:"bytes,2,name=deviceClassName"` + + // Selectors define criteria which must be satisfied by a specific + // device in order for that device to be considered for this + // request. All selectors must be satisfied for a device to be + // considered. + // + // +optional + // +listType=atomic + Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"` + + // AllocationMode and its related fields define how devices are allocated + // to satisfy this request. Supported values are: + // + // - ExactCount: This request is for a specific number of devices. + // This is the default. The exact number is provided in the + // count field. + // + // - All: This request is for all of the matching devices in a pool. + // Allocation will fail if some devices are already allocated, + // unless adminAccess is requested. + // + // If AlloctionMode is not specified, the default mode is ExactCount. If + // the mode is ExactCount and count is not specified, the default count is + // one. Any other requests must specify this field. + // + // More modes may get added in the future. Clients must refuse to handle + // requests with unknown modes. + // + // +optional + AllocationMode DeviceAllocationMode `json:"allocationMode,omitempty" protobuf:"bytes,4,opt,name=allocationMode"` + + // Count is used only when the count mode is "ExactCount". Must be greater than zero. + // If AllocationMode is ExactCount and this field is not specified, the default is one. + // + // +optional + // +oneOf=AllocationMode + Count int64 `json:"count,omitempty" protobuf:"bytes,5,opt,name=count"` } const ( - DeviceSelectorsMaxSize = 32 + DeviceSelectorsMaxSize = 32 + FirstAvailableDeviceRequestMaxSize = 8 ) type DeviceAllocationMode string @@ -582,6 +688,10 @@ type DeviceConstraint struct { // constraint. If this is not specified, this constraint applies to all // requests in this claim. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the constraint applies to all subrequests. + // // +optional // +listType=atomic Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"` @@ -619,6 +729,10 @@ type DeviceClaimConfiguration struct { // Requests lists the names of requests where the configuration applies. // If empty, it applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"` @@ -791,8 +905,12 @@ const AllocationResultsMaxSize = 32 // DeviceRequestAllocationResult contains the allocation result for one request. type DeviceRequestAllocationResult struct { // Request is the name of the request in the claim which caused this - // device to be allocated. Multiple devices may have been allocated - // per request. + // device to be allocated. If it references a subrequest in the + // firstAvailable list on a DeviceRequest, this field must + // include both the name of the main request and the subrequest + // using the format
/. + // + // Multiple devices may have been allocated per request. // // +required Request string `json:"request" protobuf:"bytes,1,name=request"` @@ -847,6 +965,10 @@ type DeviceAllocationConfiguration struct { // Requests lists the names of requests where the configuration applies. // If empty, its applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic Requests []string `json:"requests,omitempty" protobuf:"bytes,2,opt,name=requests"` diff --git a/staging/src/k8s.io/api/resource/v1alpha3/types_swagger_doc_generated.go b/staging/src/k8s.io/api/resource/v1alpha3/types_swagger_doc_generated.go index ecc83f1566286..e7388eef7fe74 100644 --- a/staging/src/k8s.io/api/resource/v1alpha3/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/resource/v1alpha3/types_swagger_doc_generated.go @@ -83,7 +83,7 @@ func (Device) SwaggerDoc() map[string]string { var map_DeviceAllocationConfiguration = map[string]string{ "": "DeviceAllocationConfiguration gets embedded in an AllocationResult.", "source": "Source records whether the configuration comes from a class and thus is not something that a normal user would have been able to set or from a claim.", - "requests": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.", + "requests": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", } func (DeviceAllocationConfiguration) SwaggerDoc() map[string]string { @@ -125,7 +125,7 @@ func (DeviceClaim) SwaggerDoc() map[string]string { var map_DeviceClaimConfiguration = map[string]string{ "": "DeviceClaimConfiguration is used for configuration parameters in DeviceClaim.", - "requests": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.", + "requests": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", } func (DeviceClaimConfiguration) SwaggerDoc() map[string]string { @@ -181,7 +181,7 @@ func (DeviceConfiguration) SwaggerDoc() map[string]string { var map_DeviceConstraint = map[string]string{ "": "DeviceConstraint must have exactly one field set besides Requests.", - "requests": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.", + "requests": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the constraint applies to all subrequests.", "matchAttribute": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", } @@ -190,13 +190,14 @@ func (DeviceConstraint) SwaggerDoc() map[string]string { } var map_DeviceRequest = map[string]string{ - "": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nA DeviceClassName is currently required. Clients must check that it is indeed set. It's absence indicates that something changed in a way that is not supported by the client yet, in which case it must refuse to handle the request.", + "": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.", "name": "Name can be used to reference this request in a pod.spec.containers[].resources.claims entry and in a constraint of the claim.\n\nMust be a DNS label.", - "deviceClassName": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", - "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", - "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", - "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", - "adminAccess": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "deviceClassName": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required if no subrequests are specified in the firstAvailable list and no class can be set if subrequests are specified in the firstAvailable list. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", + "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", + "adminAccess": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "firstAvailable": "FirstAvailable contains subrequests, of which exactly one will be satisfied by the scheduler to satisfy this request. It tries to satisfy them in the order in which they are listed here. So if there are two entries in the list, the scheduler will only check the second one if it determines that the first one cannot be used.\n\nThis field may only be set in the entries of DeviceClaim.Requests.\n\nDRA does not yet implement scoring, so the scheduler will select the first set of devices that satisfies all the requests in the claim. And if the requirements can be satisfied on more than one node, other scheduling features will determine which node is chosen. This means that the set of devices allocated to a claim might not be the optimal set available to the cluster. Scoring will be implemented later.", } func (DeviceRequest) SwaggerDoc() map[string]string { @@ -205,7 +206,7 @@ func (DeviceRequest) SwaggerDoc() map[string]string { var map_DeviceRequestAllocationResult = map[string]string{ "": "DeviceRequestAllocationResult contains the allocation result for one request.", - "request": "Request is the name of the request in the claim which caused this device to be allocated. Multiple devices may have been allocated per request.", + "request": "Request is the name of the request in the claim which caused this device to be allocated. If it references a subrequest in the firstAvailable list on a DeviceRequest, this field must include both the name of the main request and the subrequest using the format
/.\n\nMultiple devices may have been allocated per request.", "driver": "Driver specifies the name of the DRA driver whose kubelet plugin should be invoked to process the allocation once the claim is needed on a node.\n\nMust be a DNS subdomain and should end with a DNS domain owned by the vendor of the driver.", "pool": "This name together with the driver name and the device name field identify which device was allocated (`//`).\n\nMust not be longer than 253 characters and may contain one or more DNS sub-domains separated by slashes.", "device": "Device references one device instance via its name in the driver's resource pool. It must be a DNS label.", @@ -225,6 +226,19 @@ func (DeviceSelector) SwaggerDoc() map[string]string { return map_DeviceSelector } +var map_DeviceSubRequest = map[string]string{ + "": "DeviceSubRequest describes a request for device provided in the claim.spec.devices.requests[].firstAvailable array. Each is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nDeviceSubRequest is similar to Request, but doesn't expose the AdminAccess or FirstAvailable fields, as those can only be set on the top-level request. AdminAccess is not supported for requests with a prioritized list, and recursive FirstAvailable fields are not supported.", + "name": "Name can be used to reference this subrequest in the list of constraints or the list of configurations for the claim. References must use the format
/.\n\nMust be a DNS label.", + "deviceClassName": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this subrequest.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", + "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AlloctionMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", +} + +func (DeviceSubRequest) SwaggerDoc() map[string]string { + return map_DeviceSubRequest +} + var map_NetworkDeviceData = map[string]string{ "": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", "interfaceName": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.", diff --git a/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.deepcopy.go b/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.deepcopy.go index 9b26b79efe627..6f604acf6c307 100644 --- a/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/resource/v1alpha3/zz_generated.deepcopy.go @@ -466,6 +466,13 @@ func (in *DeviceRequest) DeepCopyInto(out *DeviceRequest) { *out = new(bool) **out = **in } + if in.FirstAvailable != nil { + in, out := &in.FirstAvailable, &out.FirstAvailable + *out = make([]DeviceSubRequest, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -521,6 +528,29 @@ func (in *DeviceSelector) DeepCopy() *DeviceSelector { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeviceSubRequest) DeepCopyInto(out *DeviceSubRequest) { + *out = *in + if in.Selectors != nil { + in, out := &in.Selectors, &out.Selectors + *out = make([]DeviceSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceSubRequest. +func (in *DeviceSubRequest) DeepCopy() *DeviceSubRequest { + if in == nil { + return nil + } + out := new(DeviceSubRequest) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkDeviceData) DeepCopyInto(out *NetworkDeviceData) { *out = *in diff --git a/staging/src/k8s.io/api/resource/v1beta1/generated.pb.go b/staging/src/k8s.io/api/resource/v1beta1/generated.pb.go index 4812f9ecf4491..9d2ecffd67c48 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/resource/v1beta1/generated.pb.go @@ -609,10 +609,38 @@ func (m *DeviceSelector) XXX_DiscardUnknown() { var xxx_messageInfo_DeviceSelector proto.InternalMessageInfo +func (m *DeviceSubRequest) Reset() { *m = DeviceSubRequest{} } +func (*DeviceSubRequest) ProtoMessage() {} +func (*DeviceSubRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ba331e3ec6484c27, []int{20} +} +func (m *DeviceSubRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DeviceSubRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *DeviceSubRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_DeviceSubRequest.Merge(m, src) +} +func (m *DeviceSubRequest) XXX_Size() int { + return m.Size() +} +func (m *DeviceSubRequest) XXX_DiscardUnknown() { + xxx_messageInfo_DeviceSubRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_DeviceSubRequest proto.InternalMessageInfo + func (m *NetworkDeviceData) Reset() { *m = NetworkDeviceData{} } func (*NetworkDeviceData) ProtoMessage() {} func (*NetworkDeviceData) Descriptor() ([]byte, []int) { - return fileDescriptor_ba331e3ec6484c27, []int{20} + return fileDescriptor_ba331e3ec6484c27, []int{21} } func (m *NetworkDeviceData) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -640,7 +668,7 @@ var xxx_messageInfo_NetworkDeviceData proto.InternalMessageInfo func (m *OpaqueDeviceConfiguration) Reset() { *m = OpaqueDeviceConfiguration{} } func (*OpaqueDeviceConfiguration) ProtoMessage() {} func (*OpaqueDeviceConfiguration) Descriptor() ([]byte, []int) { - return fileDescriptor_ba331e3ec6484c27, []int{21} + return fileDescriptor_ba331e3ec6484c27, []int{22} } func (m *OpaqueDeviceConfiguration) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -668,7 +696,7 @@ var xxx_messageInfo_OpaqueDeviceConfiguration proto.InternalMessageInfo func (m *ResourceClaim) Reset() { *m = ResourceClaim{} } func (*ResourceClaim) ProtoMessage() {} func (*ResourceClaim) Descriptor() ([]byte, []int) { - return fileDescriptor_ba331e3ec6484c27, []int{22} + return fileDescriptor_ba331e3ec6484c27, []int{23} } func (m *ResourceClaim) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -696,7 +724,7 @@ var xxx_messageInfo_ResourceClaim proto.InternalMessageInfo func (m *ResourceClaimConsumerReference) Reset() { *m = ResourceClaimConsumerReference{} } func (*ResourceClaimConsumerReference) ProtoMessage() {} func (*ResourceClaimConsumerReference) Descriptor() ([]byte, []int) { - return fileDescriptor_ba331e3ec6484c27, []int{23} + return fileDescriptor_ba331e3ec6484c27, []int{24} } func (m *ResourceClaimConsumerReference) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -724,7 +752,7 @@ var xxx_messageInfo_ResourceClaimConsumerReference proto.InternalMessageInfo func (m *ResourceClaimList) Reset() { *m = ResourceClaimList{} } func (*ResourceClaimList) ProtoMessage() {} func (*ResourceClaimList) Descriptor() ([]byte, []int) { - return fileDescriptor_ba331e3ec6484c27, []int{24} + return fileDescriptor_ba331e3ec6484c27, []int{25} } func (m *ResourceClaimList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -752,7 +780,7 @@ var xxx_messageInfo_ResourceClaimList proto.InternalMessageInfo func (m *ResourceClaimSpec) Reset() { *m = ResourceClaimSpec{} } func (*ResourceClaimSpec) ProtoMessage() {} func (*ResourceClaimSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_ba331e3ec6484c27, []int{25} + return fileDescriptor_ba331e3ec6484c27, []int{26} } func (m *ResourceClaimSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -780,7 +808,7 @@ var xxx_messageInfo_ResourceClaimSpec proto.InternalMessageInfo func (m *ResourceClaimStatus) Reset() { *m = ResourceClaimStatus{} } func (*ResourceClaimStatus) ProtoMessage() {} func (*ResourceClaimStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_ba331e3ec6484c27, []int{26} + return fileDescriptor_ba331e3ec6484c27, []int{27} } func (m *ResourceClaimStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -808,7 +836,7 @@ var xxx_messageInfo_ResourceClaimStatus proto.InternalMessageInfo func (m *ResourceClaimTemplate) Reset() { *m = ResourceClaimTemplate{} } func (*ResourceClaimTemplate) ProtoMessage() {} func (*ResourceClaimTemplate) Descriptor() ([]byte, []int) { - return fileDescriptor_ba331e3ec6484c27, []int{27} + return fileDescriptor_ba331e3ec6484c27, []int{28} } func (m *ResourceClaimTemplate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -836,7 +864,7 @@ var xxx_messageInfo_ResourceClaimTemplate proto.InternalMessageInfo func (m *ResourceClaimTemplateList) Reset() { *m = ResourceClaimTemplateList{} } func (*ResourceClaimTemplateList) ProtoMessage() {} func (*ResourceClaimTemplateList) Descriptor() ([]byte, []int) { - return fileDescriptor_ba331e3ec6484c27, []int{28} + return fileDescriptor_ba331e3ec6484c27, []int{29} } func (m *ResourceClaimTemplateList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -864,7 +892,7 @@ var xxx_messageInfo_ResourceClaimTemplateList proto.InternalMessageInfo func (m *ResourceClaimTemplateSpec) Reset() { *m = ResourceClaimTemplateSpec{} } func (*ResourceClaimTemplateSpec) ProtoMessage() {} func (*ResourceClaimTemplateSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_ba331e3ec6484c27, []int{29} + return fileDescriptor_ba331e3ec6484c27, []int{30} } func (m *ResourceClaimTemplateSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -892,7 +920,7 @@ var xxx_messageInfo_ResourceClaimTemplateSpec proto.InternalMessageInfo func (m *ResourcePool) Reset() { *m = ResourcePool{} } func (*ResourcePool) ProtoMessage() {} func (*ResourcePool) Descriptor() ([]byte, []int) { - return fileDescriptor_ba331e3ec6484c27, []int{30} + return fileDescriptor_ba331e3ec6484c27, []int{31} } func (m *ResourcePool) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -920,7 +948,7 @@ var xxx_messageInfo_ResourcePool proto.InternalMessageInfo func (m *ResourceSlice) Reset() { *m = ResourceSlice{} } func (*ResourceSlice) ProtoMessage() {} func (*ResourceSlice) Descriptor() ([]byte, []int) { - return fileDescriptor_ba331e3ec6484c27, []int{31} + return fileDescriptor_ba331e3ec6484c27, []int{32} } func (m *ResourceSlice) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -948,7 +976,7 @@ var xxx_messageInfo_ResourceSlice proto.InternalMessageInfo func (m *ResourceSliceList) Reset() { *m = ResourceSliceList{} } func (*ResourceSliceList) ProtoMessage() {} func (*ResourceSliceList) Descriptor() ([]byte, []int) { - return fileDescriptor_ba331e3ec6484c27, []int{32} + return fileDescriptor_ba331e3ec6484c27, []int{33} } func (m *ResourceSliceList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -976,7 +1004,7 @@ var xxx_messageInfo_ResourceSliceList proto.InternalMessageInfo func (m *ResourceSliceSpec) Reset() { *m = ResourceSliceSpec{} } func (*ResourceSliceSpec) ProtoMessage() {} func (*ResourceSliceSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_ba331e3ec6484c27, []int{33} + return fileDescriptor_ba331e3ec6484c27, []int{34} } func (m *ResourceSliceSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1024,6 +1052,7 @@ func init() { proto.RegisterType((*DeviceRequest)(nil), "k8s.io.api.resource.v1beta1.DeviceRequest") proto.RegisterType((*DeviceRequestAllocationResult)(nil), "k8s.io.api.resource.v1beta1.DeviceRequestAllocationResult") proto.RegisterType((*DeviceSelector)(nil), "k8s.io.api.resource.v1beta1.DeviceSelector") + proto.RegisterType((*DeviceSubRequest)(nil), "k8s.io.api.resource.v1beta1.DeviceSubRequest") proto.RegisterType((*NetworkDeviceData)(nil), "k8s.io.api.resource.v1beta1.NetworkDeviceData") proto.RegisterType((*OpaqueDeviceConfiguration)(nil), "k8s.io.api.resource.v1beta1.OpaqueDeviceConfiguration") proto.RegisterType((*ResourceClaim)(nil), "k8s.io.api.resource.v1beta1.ResourceClaim") @@ -1045,136 +1074,139 @@ func init() { } var fileDescriptor_ba331e3ec6484c27 = []byte{ - // 2051 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x19, 0x4b, 0x8f, 0x1b, 0x49, - 0x79, 0xda, 0xed, 0x79, 0x7d, 0x9e, 0x57, 0x2a, 0x64, 0x71, 0x26, 0xc2, 0x9e, 0x74, 0x24, 0xf0, - 0x66, 0xb3, 0xed, 0x8d, 0x81, 0x28, 0xca, 0x5e, 0x70, 0xcf, 0xcc, 0x06, 0xb3, 0xc9, 0x64, 0xb6, - 0x86, 0x0d, 0xd1, 0xb2, 0x41, 0xd4, 0xb4, 0x6b, 0x66, 0x9a, 0xb1, 0xbb, 0x9d, 0xee, 0xea, 0xc9, - 0xce, 0x01, 0x81, 0xf6, 0xbc, 0x42, 0xdc, 0x11, 0x1c, 0x91, 0x90, 0x10, 0xe2, 0x17, 0x80, 0x04, - 0x42, 0x44, 0x1c, 0x60, 0x05, 0x97, 0x15, 0x07, 0x43, 0xbc, 0x3f, 0x80, 0x7b, 0x4e, 0xa8, 0xaa, - 0xab, 0x9f, 0x76, 0x9b, 0x1e, 0xb4, 0x8c, 0xc2, 0xcd, 0xfd, 0xbd, 0xeb, 0x7b, 0x57, 0x19, 0x5e, - 0x3b, 0xbe, 0xed, 0xe9, 0x96, 0xd3, 0x24, 0x03, 0xab, 0xe9, 0x52, 0xcf, 0xf1, 0x5d, 0x93, 0x36, - 0x4f, 0x6e, 0xee, 0x53, 0x46, 0x6e, 0x36, 0x0f, 0xa9, 0x4d, 0x5d, 0xc2, 0x68, 0x57, 0x1f, 0xb8, - 0x0e, 0x73, 0xd0, 0x95, 0x80, 0x58, 0x27, 0x03, 0x4b, 0x0f, 0x89, 0x75, 0x49, 0xbc, 0xfe, 0xfa, - 0xa1, 0xc5, 0x8e, 0xfc, 0x7d, 0xdd, 0x74, 0xfa, 0xcd, 0x43, 0xe7, 0xd0, 0x69, 0x0a, 0x9e, 0x7d, - 0xff, 0x40, 0x7c, 0x89, 0x0f, 0xf1, 0x2b, 0x90, 0xb5, 0xae, 0x25, 0x14, 0x9b, 0x8e, 0xcb, 0x95, - 0x66, 0xf5, 0xad, 0x7f, 0x25, 0xa6, 0xe9, 0x13, 0xf3, 0xc8, 0xb2, 0xa9, 0x7b, 0xda, 0x1c, 0x1c, - 0x1f, 0xa6, 0xad, 0x3d, 0x0b, 0x97, 0xd7, 0xec, 0x53, 0x46, 0x26, 0xe9, 0x6a, 0xe6, 0x71, 0xb9, - 0xbe, 0xcd, 0xac, 0xfe, 0xb8, 0x9a, 0x5b, 0xff, 0x89, 0xc1, 0x33, 0x8f, 0x68, 0x9f, 0x64, 0xf9, - 0xb4, 0x9f, 0xa9, 0x70, 0xa9, 0xdd, 0xeb, 0x39, 0x26, 0x87, 0x6d, 0xd1, 0x13, 0xcb, 0xa4, 0x7b, - 0x8c, 0x30, 0xdf, 0x43, 0x5f, 0x84, 0xb9, 0xae, 0x6b, 0x9d, 0x50, 0xb7, 0xaa, 0x6c, 0x28, 0x8d, - 0x45, 0x63, 0xe5, 0xd9, 0xb0, 0x3e, 0x33, 0x1a, 0xd6, 0xe7, 0xb6, 0x04, 0x14, 0x4b, 0x2c, 0xda, - 0x80, 0xf2, 0xc0, 0x71, 0x7a, 0xd5, 0x92, 0xa0, 0x5a, 0x92, 0x54, 0xe5, 0x5d, 0xc7, 0xe9, 0x61, - 0x81, 0x11, 0x92, 0x84, 0xe4, 0xaa, 0x9a, 0x91, 0x24, 0xa0, 0x58, 0x62, 0x91, 0x09, 0x60, 0x3a, - 0x76, 0xd7, 0x62, 0x96, 0x63, 0x7b, 0xd5, 0xf2, 0x86, 0xda, 0xa8, 0xb4, 0x9a, 0x7a, 0x1c, 0xe5, - 0xe8, 0x60, 0xfa, 0xe0, 0xf8, 0x90, 0x03, 0x3c, 0x9d, 0xfb, 0x4f, 0x3f, 0xb9, 0xa9, 0x6f, 0x86, - 0x7c, 0x06, 0x92, 0xc2, 0x21, 0x02, 0x79, 0x38, 0x21, 0x16, 0xbd, 0x0d, 0xe5, 0x2e, 0x61, 0xa4, - 0x3a, 0xbb, 0xa1, 0x34, 0x2a, 0xad, 0xd7, 0x73, 0xc5, 0x4b, 0xbf, 0xe9, 0x98, 0x3c, 0xdd, 0xfe, - 0x80, 0x51, 0xdb, 0xe3, 0xc2, 0x17, 0xf8, 0xc9, 0xb6, 0x08, 0x23, 0x58, 0x08, 0x41, 0x04, 0x2a, - 0x36, 0x65, 0x4f, 0x1d, 0xf7, 0x98, 0x03, 0xab, 0x73, 0x42, 0xa6, 0xae, 0x4f, 0x49, 0x4c, 0x7d, - 0x47, 0xd2, 0x8b, 0x23, 0x73, 0x2e, 0x63, 0x75, 0x34, 0xac, 0x57, 0x76, 0x62, 0x31, 0x38, 0x29, - 0x53, 0xfb, 0x93, 0x02, 0x6b, 0x32, 0x40, 0x96, 0x63, 0x63, 0xea, 0xf9, 0x3d, 0x86, 0xbe, 0x03, - 0xf3, 0x81, 0xcf, 0x3c, 0x11, 0x9c, 0x4a, 0xeb, 0xcb, 0x53, 0x75, 0x06, 0xca, 0xb2, 0x52, 0x8c, - 0x55, 0xe9, 0xaa, 0xf9, 0x00, 0xef, 0xe1, 0x50, 0x28, 0x7a, 0x08, 0x4b, 0xb6, 0xd3, 0xa5, 0x7b, - 0xb4, 0x47, 0x4d, 0xe6, 0xb8, 0x22, 0x6e, 0x95, 0xd6, 0x46, 0x52, 0x09, 0xaf, 0x12, 0xee, 0xf9, - 0x9d, 0x04, 0x9d, 0xb1, 0x36, 0x1a, 0xd6, 0x97, 0x92, 0x10, 0x9c, 0x92, 0xa3, 0xfd, 0x5d, 0x85, - 0x8a, 0x41, 0x3c, 0xcb, 0x0c, 0x34, 0xa2, 0xef, 0x03, 0x10, 0xc6, 0x5c, 0x6b, 0xdf, 0x67, 0xe2, - 0x28, 0x3c, 0xe2, 0xb7, 0xa7, 0x1e, 0x25, 0xc1, 0xad, 0xb7, 0x23, 0xd6, 0x6d, 0x9b, 0xb9, 0xa7, - 0xc6, 0xb5, 0x30, 0xf4, 0x31, 0xe2, 0xc3, 0x7f, 0xd4, 0x97, 0xdf, 0xf1, 0x49, 0xcf, 0x3a, 0xb0, - 0x68, 0x77, 0x87, 0xf4, 0x29, 0x4e, 0x28, 0x44, 0x3e, 0x2c, 0x98, 0x64, 0x40, 0x4c, 0x8b, 0x9d, - 0x56, 0x4b, 0x42, 0xf9, 0xad, 0xc2, 0xca, 0x37, 0x25, 0x63, 0xa0, 0xfa, 0xaa, 0x54, 0xbd, 0x10, - 0x82, 0xc7, 0x15, 0x47, 0xaa, 0xd6, 0x8f, 0x61, 0x35, 0x63, 0x3a, 0x5a, 0x03, 0xf5, 0x98, 0x9e, - 0x06, 0x95, 0x86, 0xf9, 0x4f, 0x64, 0xc0, 0xec, 0x09, 0xe9, 0xf9, 0x54, 0xd4, 0x55, 0xa5, 0x75, - 0xa3, 0x48, 0x80, 0x43, 0xa1, 0x38, 0x60, 0xbd, 0x53, 0xba, 0xad, 0xac, 0x1f, 0xc1, 0x72, 0xca, - 0xd4, 0x09, 0xaa, 0xda, 0x69, 0x55, 0xaf, 0x15, 0x50, 0x15, 0x8a, 0x4c, 0x68, 0xd2, 0xee, 0xc2, - 0x85, 0xcd, 0xed, 0x7b, 0xb2, 0x87, 0xc8, 0x88, 0xa3, 0x16, 0x00, 0xfd, 0x60, 0xe0, 0x52, 0x8f, - 0xd7, 0x8f, 0xec, 0x24, 0x51, 0x89, 0x6e, 0x47, 0x18, 0x9c, 0xa0, 0xd2, 0x7c, 0x90, 0x9d, 0x81, - 0xf7, 0x16, 0x9b, 0xf4, 0xa9, 0xe4, 0x8b, 0x7a, 0x8b, 0xf0, 0xa7, 0xc0, 0xa0, 0x0e, 0xcc, 0xee, - 0xf3, 0xa8, 0x48, 0xdb, 0x1b, 0x45, 0xe3, 0x67, 0x2c, 0x8e, 0x86, 0xf5, 0x59, 0x01, 0xc0, 0x81, - 0x04, 0xed, 0xa3, 0x12, 0x7c, 0x21, 0x5b, 0x29, 0x9b, 0x8e, 0x7d, 0x60, 0x1d, 0xfa, 0xae, 0xf8, - 0x40, 0x5f, 0x83, 0xb9, 0x40, 0xa2, 0x34, 0xa8, 0x11, 0x36, 0xb2, 0x3d, 0x01, 0x7d, 0x31, 0xac, - 0xbf, 0x92, 0x65, 0x0d, 0x30, 0x58, 0xf2, 0xa1, 0x06, 0x2c, 0xb8, 0xf4, 0x89, 0x4f, 0x3d, 0xe6, - 0x89, 0x8c, 0x5b, 0x34, 0x96, 0x78, 0xd6, 0x60, 0x09, 0xc3, 0x11, 0x16, 0xfd, 0x00, 0x2e, 0x06, - 0xd5, 0x98, 0x32, 0x41, 0x56, 0xe2, 0x1b, 0x45, 0x42, 0x94, 0xe4, 0x33, 0xae, 0x48, 0x53, 0x2f, - 0x4e, 0x40, 0xe2, 0x49, 0x9a, 0xb4, 0x4f, 0x15, 0x78, 0x65, 0x72, 0xe3, 0x40, 0x14, 0xe6, 0x5d, - 0xf1, 0x2b, 0xac, 0xd9, 0x3b, 0x05, 0xec, 0x91, 0x67, 0xcc, 0xef, 0x42, 0xc1, 0xb7, 0x87, 0x43, - 0xd9, 0x68, 0x1f, 0xe6, 0x4c, 0x61, 0x92, 0x2c, 0xce, 0x3b, 0x67, 0x6a, 0x72, 0xe9, 0xf3, 0x47, - 0x33, 0x27, 0x00, 0x63, 0x29, 0x59, 0xfb, 0x85, 0x02, 0xab, 0x99, 0xea, 0x41, 0x35, 0x50, 0x2d, - 0x9b, 0x89, 0x8c, 0x52, 0x83, 0xf8, 0x74, 0x6c, 0xf6, 0x90, 0xe7, 0x39, 0xe6, 0x08, 0x74, 0x15, - 0xca, 0xfb, 0x7c, 0xe2, 0xf1, 0x58, 0x2c, 0x18, 0xcb, 0xa3, 0x61, 0x7d, 0xd1, 0x70, 0x9c, 0x5e, - 0x40, 0x21, 0x50, 0xe8, 0x4b, 0x30, 0xe7, 0x31, 0xd7, 0xb2, 0x0f, 0xab, 0x65, 0x91, 0x29, 0xa2, - 0xc7, 0xef, 0x09, 0x48, 0x40, 0x26, 0xd1, 0xe8, 0x3a, 0xcc, 0x9f, 0x50, 0x57, 0x14, 0xc7, 0xac, - 0xa0, 0x14, 0x2d, 0xf4, 0x61, 0x00, 0x0a, 0x48, 0x43, 0x02, 0x8d, 0xc2, 0x4a, 0xba, 0xfa, 0xd0, - 0x5e, 0x58, 0xb9, 0xca, 0xd8, 0xe4, 0x19, 0x1b, 0x96, 0xb1, 0xc7, 0xde, 0xf1, 0x89, 0xcd, 0x2c, - 0x76, 0x6a, 0x2c, 0x4b, 0xa7, 0xcc, 0x06, 0x8a, 0x02, 0x59, 0xda, 0x2f, 0x4b, 0x50, 0x91, 0x7a, - 0x7a, 0xc4, 0xea, 0xa3, 0x47, 0x89, 0x9c, 0x0d, 0xc2, 0x7d, 0xbd, 0x78, 0xb8, 0x8d, 0xb5, 0xb0, - 0x33, 0x4e, 0xc8, 0xf1, 0x2e, 0x54, 0x4c, 0xc7, 0xf6, 0x98, 0x4b, 0x2c, 0x5b, 0x16, 0x44, 0x7a, - 0x24, 0x4f, 0xc9, 0x6d, 0xc9, 0x65, 0x5c, 0x94, 0xf2, 0x2b, 0x31, 0xcc, 0xc3, 0x49, 0xb1, 0xe8, - 0x71, 0x94, 0x46, 0xaa, 0x50, 0xf0, 0xd5, 0x22, 0x0a, 0xf8, 0xc9, 0x8b, 0x65, 0xd0, 0x1f, 0x14, - 0xa8, 0xe6, 0x31, 0xa5, 0xea, 0x5d, 0xf9, 0x6f, 0xea, 0xbd, 0x74, 0x6e, 0xf5, 0xfe, 0x5b, 0x25, - 0x11, 0x76, 0xcf, 0x43, 0xdf, 0x85, 0x05, 0xbe, 0x5d, 0x89, 0x65, 0x49, 0x19, 0xb3, 0x62, 0xca, - 0x2e, 0xf6, 0x60, 0xff, 0x7b, 0xd4, 0x64, 0xf7, 0x29, 0x23, 0x71, 0xa7, 0x8f, 0x61, 0x38, 0x92, - 0x8a, 0x76, 0xa0, 0xec, 0x0d, 0xa8, 0x79, 0x86, 0x09, 0x27, 0x2c, 0xdb, 0x1b, 0x50, 0x33, 0x9e, - 0x05, 0xfc, 0x0b, 0x0b, 0x39, 0xda, 0x4f, 0x92, 0x91, 0xf0, 0xbc, 0x74, 0x24, 0x72, 0xfc, 0xab, - 0x9c, 0x9b, 0x7f, 0x7f, 0x13, 0x75, 0x1a, 0x61, 0xdd, 0x3d, 0xcb, 0x63, 0xe8, 0xfd, 0x31, 0x1f, - 0xeb, 0xc5, 0x7c, 0xcc, 0xb9, 0x85, 0x87, 0xa3, 0xf2, 0x0a, 0x21, 0x09, 0xff, 0xde, 0x87, 0x59, - 0x8b, 0xd1, 0x7e, 0x58, 0x58, 0x8d, 0xa2, 0x0e, 0x8e, 0xfb, 0x42, 0x87, 0xb3, 0xe3, 0x40, 0x8a, - 0xf6, 0xe7, 0xf4, 0x01, 0xb8, 0xe3, 0xd1, 0xfb, 0xb0, 0xe8, 0xc9, 0x51, 0x1f, 0x36, 0x87, 0x22, - 0xeb, 0x43, 0xb4, 0x30, 0x5e, 0x90, 0x9a, 0x16, 0x43, 0x88, 0x87, 0x63, 0x81, 0x89, 0xca, 0x2d, - 0x9d, 0xa5, 0x72, 0x33, 0xa1, 0xcf, 0xad, 0xdc, 0x27, 0x30, 0x29, 0x7a, 0xe8, 0x3d, 0x98, 0x73, - 0x06, 0xe4, 0x49, 0xd4, 0x55, 0xa7, 0xef, 0x84, 0x0f, 0x04, 0xe9, 0xa4, 0x14, 0x01, 0xae, 0x32, - 0x40, 0x63, 0x29, 0x51, 0xfb, 0x91, 0x02, 0x6b, 0xd9, 0x16, 0x76, 0x86, 0x26, 0xb1, 0x0b, 0x2b, - 0x7d, 0xc2, 0xcc, 0xa3, 0x68, 0x56, 0xc9, 0x5b, 0x57, 0x63, 0x34, 0xac, 0xaf, 0xdc, 0x4f, 0x61, - 0x5e, 0x0c, 0xeb, 0xe8, 0x2d, 0xbf, 0xd7, 0x3b, 0x4d, 0x6f, 0xa1, 0x19, 0x7e, 0xed, 0x43, 0x15, - 0x96, 0x53, 0x0d, 0xbb, 0xc0, 0xce, 0xd5, 0x86, 0xd5, 0x6e, 0xec, 0x6b, 0x8e, 0x90, 0x66, 0x7c, - 0x5e, 0x12, 0x27, 0xd3, 0x44, 0xf0, 0x65, 0xe9, 0xd3, 0x79, 0xa3, 0x7e, 0xd6, 0x79, 0xf3, 0x10, - 0x56, 0x48, 0xb4, 0x07, 0xdc, 0x77, 0xba, 0x54, 0x4e, 0x61, 0x5d, 0x72, 0xad, 0xb4, 0x53, 0xd8, - 0x17, 0xc3, 0xfa, 0xe7, 0xb2, 0xdb, 0x03, 0x87, 0xe3, 0x8c, 0x14, 0x74, 0x0d, 0x66, 0x4d, 0xc7, - 0xb7, 0x99, 0x18, 0xd5, 0x6a, 0x5c, 0x26, 0x9b, 0x1c, 0x88, 0x03, 0x1c, 0xba, 0x09, 0x15, 0xd2, - 0xed, 0x5b, 0x76, 0xdb, 0x34, 0xa9, 0xe7, 0x89, 0x3b, 0xe1, 0x42, 0x30, 0xff, 0xdb, 0x31, 0x18, - 0x27, 0x69, 0xb4, 0x7f, 0x29, 0xe1, 0xe6, 0x99, 0xb3, 0x24, 0xa1, 0x57, 0xf9, 0xc6, 0x25, 0x50, - 0x32, 0x2e, 0x89, 0xad, 0x49, 0x80, 0x71, 0x88, 0x4f, 0xdc, 0xdb, 0x4b, 0x85, 0xee, 0xed, 0x6a, - 0x81, 0x7b, 0x7b, 0x79, 0xea, 0xbd, 0x3d, 0x73, 0xe2, 0xd9, 0x02, 0x27, 0xfe, 0x76, 0xb8, 0xca, - 0x44, 0x17, 0x85, 0x0e, 0xa8, 0x26, 0xed, 0x4d, 0xe8, 0x82, 0xe3, 0xb9, 0x30, 0x76, 0xcb, 0x30, - 0xe6, 0x47, 0xc3, 0xba, 0xba, 0xb9, 0x7d, 0x0f, 0x73, 0x19, 0xda, 0xaf, 0x14, 0xb8, 0x30, 0x76, - 0xcd, 0x46, 0x6f, 0xc2, 0xb2, 0x65, 0x33, 0xea, 0x1e, 0x10, 0x93, 0xee, 0xc4, 0x09, 0x7e, 0x49, - 0x1e, 0x6a, 0xb9, 0x93, 0x44, 0xe2, 0x34, 0x2d, 0xba, 0x0c, 0xaa, 0x35, 0x08, 0x57, 0x76, 0xa1, - 0xad, 0xb3, 0xeb, 0x61, 0x0e, 0xe3, 0xd5, 0x70, 0x44, 0xdc, 0xee, 0x53, 0xe2, 0xd2, 0x76, 0xb7, - 0xcb, 0xef, 0x30, 0xd2, 0xa5, 0x51, 0x35, 0x7c, 0x3d, 0x8d, 0xc6, 0x59, 0x7a, 0xed, 0xe7, 0x0a, - 0x5c, 0xce, 0xed, 0x23, 0x85, 0x1f, 0x62, 0x08, 0xc0, 0x80, 0xb8, 0xa4, 0x4f, 0x19, 0x75, 0x3d, - 0x39, 0x54, 0xcf, 0xf8, 0xbe, 0x11, 0xcd, 0xeb, 0xdd, 0x48, 0x10, 0x4e, 0x08, 0xd5, 0x7e, 0x5a, - 0x82, 0x65, 0x2c, 0xc3, 0x11, 0x2c, 0x87, 0xff, 0xfb, 0x2d, 0x61, 0x37, 0xb5, 0x25, 0x4c, 0xcf, - 0x8c, 0x94, 0x6d, 0x79, 0x7b, 0x02, 0x7a, 0xc4, 0x97, 0x73, 0xc2, 0x7c, 0xaf, 0xd0, 0x6d, 0x2a, - 0x2d, 0x53, 0xf0, 0xc5, 0x21, 0x08, 0xbe, 0xb1, 0x94, 0xa7, 0x8d, 0x14, 0xa8, 0xa5, 0xe8, 0x79, - 0x97, 0xf7, 0xfb, 0xd4, 0xc5, 0xf4, 0x80, 0xba, 0xd4, 0x36, 0x29, 0xba, 0x01, 0x0b, 0x64, 0x60, - 0xdd, 0x75, 0x1d, 0x7f, 0x20, 0xe3, 0x19, 0x8d, 0xf0, 0xf6, 0x6e, 0x47, 0xc0, 0x71, 0x44, 0xc1, - 0xa9, 0x43, 0x83, 0x64, 0x56, 0x25, 0xf6, 0xe9, 0x00, 0x8e, 0x23, 0x8a, 0xa8, 0x75, 0x97, 0x73, - 0x5b, 0xb7, 0x01, 0xaa, 0x6f, 0x75, 0xe5, 0x55, 0xe3, 0x0d, 0x49, 0xa0, 0xbe, 0xdb, 0xd9, 0x7a, - 0x31, 0xac, 0x5f, 0xcd, 0x7b, 0x42, 0x64, 0xa7, 0x03, 0xea, 0xe9, 0xef, 0x76, 0xb6, 0x30, 0x67, - 0xd6, 0x7e, 0xa7, 0xc0, 0x85, 0xd4, 0x21, 0xcf, 0x61, 0x95, 0x79, 0x90, 0x5e, 0x65, 0xae, 0x17, - 0x8f, 0x58, 0xce, 0x32, 0x73, 0x94, 0x39, 0x83, 0xd8, 0x66, 0xf6, 0xb2, 0xcf, 0x6a, 0x8d, 0xa2, - 0x57, 0x85, 0xfc, 0xb7, 0x34, 0xed, 0x8f, 0x25, 0xb8, 0x38, 0x21, 0x87, 0xd0, 0x63, 0x80, 0x78, - 0xbc, 0x48, 0x7d, 0xd3, 0xef, 0x3e, 0x63, 0x57, 0xe7, 0x15, 0xf1, 0xd8, 0x15, 0x43, 0x13, 0x02, - 0x91, 0x0b, 0x15, 0x97, 0x7a, 0xd4, 0x3d, 0xa1, 0xdd, 0xb7, 0x1c, 0x57, 0xfa, 0xed, 0xcd, 0xe2, - 0x7e, 0x1b, 0xcb, 0xdc, 0xf8, 0xa6, 0x85, 0x63, 0xb9, 0x38, 0xa9, 0x04, 0x3d, 0x8e, 0xfd, 0x17, - 0xbc, 0xde, 0xb6, 0x8a, 0x9c, 0x27, 0xfd, 0xee, 0x3c, 0xc5, 0x93, 0x7f, 0x53, 0xe0, 0x52, 0xca, - 0xc6, 0x6f, 0xd2, 0xfe, 0xa0, 0x47, 0x18, 0x3d, 0x87, 0x2e, 0xf4, 0x28, 0xd5, 0x85, 0x6e, 0x15, - 0xf7, 0x63, 0x68, 0x63, 0xee, 0xad, 0xe5, 0xaf, 0x0a, 0x5c, 0x9e, 0xc8, 0x71, 0x0e, 0x65, 0xf5, - 0xad, 0x74, 0x59, 0xb5, 0xce, 0x7e, 0xac, 0x9c, 0xf2, 0xfa, 0x4b, 0xde, 0xa1, 0x44, 0x9d, 0xfd, - 0x1f, 0x0e, 0x0d, 0xed, 0xd7, 0x0a, 0x2c, 0x85, 0x94, 0x7c, 0x47, 0x2a, 0xb0, 0x27, 0xb7, 0x00, - 0xe4, 0xdf, 0x2d, 0xe1, 0x4d, 0x5e, 0x8d, 0xcd, 0xbe, 0x1b, 0x61, 0x70, 0x82, 0x0a, 0x7d, 0x03, - 0x50, 0x68, 0xe0, 0x5e, 0x4f, 0xac, 0x02, 0x7c, 0xdf, 0x54, 0x05, 0xef, 0xba, 0xe4, 0x45, 0x78, - 0x8c, 0x02, 0x4f, 0xe0, 0xd2, 0x7e, 0xaf, 0xc4, 0xd3, 0x5a, 0x80, 0x5f, 0x52, 0xc7, 0x0b, 0xdb, - 0x72, 0x1d, 0x9f, 0x1c, 0x37, 0x82, 0xf2, 0x65, 0x1d, 0x37, 0xc2, 0xb8, 0x9c, 0x7a, 0xf8, 0x48, - 0xcd, 0x1c, 0x42, 0xd4, 0x41, 0xd1, 0xcd, 0xee, 0xed, 0xc4, 0x5f, 0x6c, 0x95, 0xd6, 0xab, 0x85, - 0xac, 0xe1, 0x39, 0x3a, 0x71, 0xab, 0xbf, 0x01, 0x0b, 0xb6, 0xd3, 0x0d, 0x56, 0xe0, 0xcc, 0x4a, - 0xb1, 0x23, 0xe1, 0x38, 0xa2, 0x18, 0xfb, 0x27, 0xa8, 0xfc, 0xd9, 0xfc, 0x13, 0x24, 0xd6, 0xa0, - 0x5e, 0x8f, 0x13, 0x84, 0x17, 0x86, 0x78, 0x0d, 0x92, 0x70, 0x1c, 0x51, 0xa0, 0x9d, 0x78, 0xb0, - 0xcc, 0x89, 0x88, 0x5c, 0x2b, 0x30, 0x98, 0xf3, 0x27, 0x89, 0xd1, 0x7e, 0xf6, 0xbc, 0x36, 0xf3, - 0xf1, 0xf3, 0xda, 0xcc, 0x27, 0xcf, 0x6b, 0x33, 0x3f, 0x1c, 0xd5, 0x94, 0x67, 0xa3, 0x9a, 0xf2, - 0xf1, 0xa8, 0xa6, 0x7c, 0x32, 0xaa, 0x29, 0xff, 0x1c, 0xd5, 0x94, 0x1f, 0x7f, 0x5a, 0x9b, 0x79, - 0xef, 0xca, 0x94, 0x7f, 0xa3, 0xff, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xe1, 0xe6, 0x3e, 0xd5, 0xab, - 0x1e, 0x00, 0x00, + // 2103 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x1a, 0x4b, 0x6f, 0x1c, 0x49, + 0xd9, 0x3d, 0x3d, 0x7e, 0x7d, 0xe3, 0x57, 0x2a, 0x24, 0x4c, 0x1c, 0x31, 0xe3, 0x74, 0x24, 0xf0, + 0x66, 0xb3, 0x33, 0x9b, 0x01, 0xa2, 0x28, 0x7b, 0x61, 0xda, 0x76, 0x82, 0xd9, 0xc4, 0xf1, 0x96, + 0xd9, 0x10, 0x2d, 0x1b, 0x44, 0x4d, 0x4f, 0xd9, 0x6e, 0xdc, 0xd3, 0x3d, 0xe9, 0xae, 0x76, 0xd6, + 0x07, 0x04, 0xe2, 0xbc, 0x42, 0xdc, 0x11, 0x1c, 0x91, 0x90, 0x10, 0xe2, 0x17, 0x80, 0x04, 0x42, + 0x44, 0x1c, 0x60, 0xb5, 0x5c, 0x56, 0x1c, 0x06, 0x32, 0xfb, 0x03, 0x10, 0xd7, 0x9c, 0x50, 0x55, + 0x57, 0x3f, 0x67, 0x7a, 0xb6, 0x0d, 0x8b, 0x15, 0x24, 0x6e, 0xee, 0xef, 0x5d, 0xdf, 0xbb, 0x6a, + 0x0c, 0xaf, 0x1e, 0xdd, 0xf2, 0x1a, 0xa6, 0xd3, 0x24, 0x7d, 0xb3, 0xe9, 0x52, 0xcf, 0xf1, 0x5d, + 0x83, 0x36, 0x8f, 0x6f, 0x74, 0x28, 0x23, 0x37, 0x9a, 0x07, 0xd4, 0xa6, 0x2e, 0x61, 0xb4, 0xdb, + 0xe8, 0xbb, 0x0e, 0x73, 0xd0, 0xe5, 0x80, 0xb8, 0x41, 0xfa, 0x66, 0x23, 0x24, 0x6e, 0x48, 0xe2, + 0xd5, 0xd7, 0x0e, 0x4c, 0x76, 0xe8, 0x77, 0x1a, 0x86, 0xd3, 0x6b, 0x1e, 0x38, 0x07, 0x4e, 0x53, + 0xf0, 0x74, 0xfc, 0x7d, 0xf1, 0x25, 0x3e, 0xc4, 0x5f, 0x81, 0xac, 0x55, 0x2d, 0xa1, 0xd8, 0x70, + 0x5c, 0xae, 0x34, 0xab, 0x6f, 0xf5, 0x4b, 0x31, 0x4d, 0x8f, 0x18, 0x87, 0xa6, 0x4d, 0xdd, 0x93, + 0x66, 0xff, 0xe8, 0x20, 0x6d, 0xed, 0x69, 0xb8, 0xbc, 0x66, 0x8f, 0x32, 0x32, 0x4e, 0x57, 0x33, + 0x8f, 0xcb, 0xf5, 0x6d, 0x66, 0xf6, 0x46, 0xd5, 0xdc, 0xfc, 0x24, 0x06, 0xcf, 0x38, 0xa4, 0x3d, + 0x92, 0xe5, 0xd3, 0x7e, 0xaa, 0xc2, 0x85, 0xb6, 0x65, 0x39, 0x06, 0x87, 0x6d, 0xd2, 0x63, 0xd3, + 0xa0, 0x7b, 0x8c, 0x30, 0xdf, 0x43, 0x9f, 0x87, 0x99, 0xae, 0x6b, 0x1e, 0x53, 0xb7, 0xaa, 0xac, + 0x29, 0xeb, 0xf3, 0xfa, 0xd2, 0xb3, 0x41, 0x7d, 0x6a, 0x38, 0xa8, 0xcf, 0x6c, 0x0a, 0x28, 0x96, + 0x58, 0xb4, 0x06, 0xe5, 0xbe, 0xe3, 0x58, 0xd5, 0x92, 0xa0, 0x5a, 0x90, 0x54, 0xe5, 0x5d, 0xc7, + 0xb1, 0xb0, 0xc0, 0x08, 0x49, 0x42, 0x72, 0x55, 0xcd, 0x48, 0x12, 0x50, 0x2c, 0xb1, 0xc8, 0x00, + 0x30, 0x1c, 0xbb, 0x6b, 0x32, 0xd3, 0xb1, 0xbd, 0x6a, 0x79, 0x4d, 0x5d, 0xaf, 0xb4, 0x9a, 0x8d, + 0x38, 0xca, 0xd1, 0xc1, 0x1a, 0xfd, 0xa3, 0x03, 0x0e, 0xf0, 0x1a, 0xdc, 0x7f, 0x8d, 0xe3, 0x1b, + 0x8d, 0x8d, 0x90, 0x4f, 0x47, 0x52, 0x38, 0x44, 0x20, 0x0f, 0x27, 0xc4, 0xa2, 0x37, 0xa1, 0xdc, + 0x25, 0x8c, 0x54, 0xa7, 0xd7, 0x94, 0xf5, 0x4a, 0xeb, 0xb5, 0x5c, 0xf1, 0xd2, 0x6f, 0x0d, 0x4c, + 0x9e, 0x6e, 0xbd, 0xc7, 0xa8, 0xed, 0x71, 0xe1, 0x73, 0xfc, 0x64, 0x9b, 0x84, 0x11, 0x2c, 0x84, + 0x20, 0x02, 0x15, 0x9b, 0xb2, 0xa7, 0x8e, 0x7b, 0xc4, 0x81, 0xd5, 0x19, 0x21, 0xb3, 0xd1, 0x98, + 0x90, 0x98, 0x8d, 0x1d, 0x49, 0x2f, 0x8e, 0xcc, 0xb9, 0xf4, 0xe5, 0xe1, 0xa0, 0x5e, 0xd9, 0x89, + 0xc5, 0xe0, 0xa4, 0x4c, 0xed, 0x8f, 0x0a, 0xac, 0xc8, 0x00, 0x99, 0x8e, 0x8d, 0xa9, 0xe7, 0x5b, + 0x0c, 0x7d, 0x0b, 0x66, 0x03, 0x9f, 0x79, 0x22, 0x38, 0x95, 0xd6, 0x17, 0x27, 0xea, 0x0c, 0x94, + 0x65, 0xa5, 0xe8, 0xcb, 0xd2, 0x55, 0xb3, 0x01, 0xde, 0xc3, 0xa1, 0x50, 0xf4, 0x10, 0x16, 0x6c, + 0xa7, 0x4b, 0xf7, 0xa8, 0x45, 0x0d, 0xe6, 0xb8, 0x22, 0x6e, 0x95, 0xd6, 0x5a, 0x52, 0x09, 0xaf, + 0x12, 0xee, 0xf9, 0x9d, 0x04, 0x9d, 0xbe, 0x32, 0x1c, 0xd4, 0x17, 0x92, 0x10, 0x9c, 0x92, 0xa3, + 0xfd, 0x55, 0x85, 0x8a, 0x4e, 0x3c, 0xd3, 0x08, 0x34, 0xa2, 0xef, 0x02, 0x10, 0xc6, 0x5c, 0xb3, + 0xe3, 0x33, 0x71, 0x14, 0x1e, 0xf1, 0x5b, 0x13, 0x8f, 0x92, 0xe0, 0x6e, 0xb4, 0x23, 0xd6, 0x2d, + 0x9b, 0xb9, 0x27, 0xfa, 0xd5, 0x30, 0xf4, 0x31, 0xe2, 0x07, 0x7f, 0xab, 0x2f, 0xbe, 0xe5, 0x13, + 0xcb, 0xdc, 0x37, 0x69, 0x77, 0x87, 0xf4, 0x28, 0x4e, 0x28, 0x44, 0x3e, 0xcc, 0x19, 0xa4, 0x4f, + 0x0c, 0x93, 0x9d, 0x54, 0x4b, 0x42, 0xf9, 0xcd, 0xc2, 0xca, 0x37, 0x24, 0x63, 0xa0, 0xfa, 0x8a, + 0x54, 0x3d, 0x17, 0x82, 0x47, 0x15, 0x47, 0xaa, 0x56, 0x8f, 0x60, 0x39, 0x63, 0x3a, 0x5a, 0x01, + 0xf5, 0x88, 0x9e, 0x04, 0x95, 0x86, 0xf9, 0x9f, 0x48, 0x87, 0xe9, 0x63, 0x62, 0xf9, 0x54, 0xd4, + 0x55, 0xa5, 0x75, 0xbd, 0x48, 0x80, 0x43, 0xa1, 0x38, 0x60, 0xbd, 0x5d, 0xba, 0xa5, 0xac, 0x1e, + 0xc2, 0x62, 0xca, 0xd4, 0x31, 0xaa, 0xda, 0x69, 0x55, 0xaf, 0x16, 0x50, 0x15, 0x8a, 0x4c, 0x68, + 0xd2, 0xee, 0xc2, 0xb9, 0x8d, 0xad, 0x7b, 0xb2, 0x87, 0xc8, 0x88, 0xa3, 0x16, 0x00, 0x7d, 0xaf, + 0xef, 0x52, 0x8f, 0xd7, 0x8f, 0xec, 0x24, 0x51, 0x89, 0x6e, 0x45, 0x18, 0x9c, 0xa0, 0xd2, 0x7c, + 0x90, 0x9d, 0x81, 0xf7, 0x16, 0x9b, 0xf4, 0xa8, 0xe4, 0x8b, 0x7a, 0x8b, 0xf0, 0xa7, 0xc0, 0xa0, + 0x6d, 0x98, 0xee, 0xf0, 0xa8, 0x48, 0xdb, 0xd7, 0x8b, 0xc6, 0x4f, 0x9f, 0x1f, 0x0e, 0xea, 0xd3, + 0x02, 0x80, 0x03, 0x09, 0xda, 0xfb, 0x25, 0xf8, 0x5c, 0xb6, 0x52, 0x36, 0x1c, 0x7b, 0xdf, 0x3c, + 0xf0, 0x5d, 0xf1, 0x81, 0xbe, 0x02, 0x33, 0x81, 0x44, 0x69, 0xd0, 0x7a, 0xd8, 0xc8, 0xf6, 0x04, + 0xf4, 0xc5, 0xa0, 0x7e, 0x31, 0xcb, 0x1a, 0x60, 0xb0, 0xe4, 0x43, 0xeb, 0x30, 0xe7, 0xd2, 0x27, + 0x3e, 0xf5, 0x98, 0x27, 0x32, 0x6e, 0x5e, 0x5f, 0xe0, 0x59, 0x83, 0x25, 0x0c, 0x47, 0x58, 0xf4, + 0x3d, 0x38, 0x1f, 0x54, 0x63, 0xca, 0x04, 0x59, 0x89, 0xaf, 0x17, 0x09, 0x51, 0x92, 0x4f, 0xbf, + 0x2c, 0x4d, 0x3d, 0x3f, 0x06, 0x89, 0xc7, 0x69, 0xd2, 0x3e, 0x56, 0xe0, 0xe2, 0xf8, 0xc6, 0x81, + 0x28, 0xcc, 0xba, 0xe2, 0xaf, 0xb0, 0x66, 0x6f, 0x17, 0xb0, 0x47, 0x9e, 0x31, 0xbf, 0x0b, 0x05, + 0xdf, 0x1e, 0x0e, 0x65, 0xa3, 0x0e, 0xcc, 0x18, 0xc2, 0x24, 0x59, 0x9c, 0xb7, 0x4f, 0xd5, 0xe4, + 0xd2, 0xe7, 0x8f, 0x66, 0x4e, 0x00, 0xc6, 0x52, 0xb2, 0xf6, 0x73, 0x05, 0x96, 0x33, 0xd5, 0x83, + 0x6a, 0xa0, 0x9a, 0x36, 0x13, 0x19, 0xa5, 0x06, 0xf1, 0xd9, 0xb6, 0xd9, 0x43, 0x9e, 0xe7, 0x98, + 0x23, 0xd0, 0x15, 0x28, 0x77, 0xf8, 0xc4, 0xe3, 0xb1, 0x98, 0xd3, 0x17, 0x87, 0x83, 0xfa, 0xbc, + 0xee, 0x38, 0x56, 0x40, 0x21, 0x50, 0xe8, 0x0b, 0x30, 0xe3, 0x31, 0xd7, 0xb4, 0x0f, 0xaa, 0x65, + 0x91, 0x29, 0xa2, 0xc7, 0xef, 0x09, 0x48, 0x40, 0x26, 0xd1, 0xe8, 0x1a, 0xcc, 0x1e, 0x53, 0x57, + 0x14, 0xc7, 0xb4, 0xa0, 0x14, 0x2d, 0xf4, 0x61, 0x00, 0x0a, 0x48, 0x43, 0x02, 0x8d, 0xc2, 0x52, + 0xba, 0xfa, 0xd0, 0x5e, 0x58, 0xb9, 0xca, 0xc8, 0xe4, 0x19, 0x19, 0x96, 0xb1, 0xc7, 0xde, 0xf2, + 0x89, 0xcd, 0x4c, 0x76, 0xa2, 0x2f, 0x4a, 0xa7, 0x4c, 0x07, 0x8a, 0x02, 0x59, 0xda, 0x2f, 0x4a, + 0x50, 0x91, 0x7a, 0x2c, 0x62, 0xf6, 0xd0, 0xa3, 0x44, 0xce, 0x06, 0xe1, 0xbe, 0x56, 0x3c, 0xdc, + 0xfa, 0x4a, 0xd8, 0x19, 0xc7, 0xe4, 0x78, 0x17, 0x2a, 0x86, 0x63, 0x7b, 0xcc, 0x25, 0xa6, 0x2d, + 0x0b, 0x22, 0x3d, 0x92, 0x27, 0xe4, 0xb6, 0xe4, 0xd2, 0xcf, 0x4b, 0xf9, 0x95, 0x18, 0xe6, 0xe1, + 0xa4, 0x58, 0xf4, 0x38, 0x4a, 0x23, 0x55, 0x28, 0xf8, 0x72, 0x11, 0x05, 0xfc, 0xe4, 0xc5, 0x32, + 0xe8, 0xf7, 0x0a, 0x54, 0xf3, 0x98, 0x52, 0xf5, 0xae, 0xfc, 0x3b, 0xf5, 0x5e, 0x3a, 0xb3, 0x7a, + 0xff, 0x8d, 0x92, 0x08, 0xbb, 0xe7, 0xa1, 0x6f, 0xc3, 0x1c, 0xdf, 0xae, 0xc4, 0xb2, 0xa4, 0x8c, + 0x58, 0x31, 0x61, 0x17, 0x7b, 0xd0, 0xf9, 0x0e, 0x35, 0xd8, 0x7d, 0xca, 0x48, 0xdc, 0xe9, 0x63, + 0x18, 0x8e, 0xa4, 0xa2, 0x1d, 0x28, 0x7b, 0x7d, 0x6a, 0x9c, 0x62, 0xc2, 0x09, 0xcb, 0xf6, 0xfa, + 0xd4, 0x88, 0x67, 0x01, 0xff, 0xc2, 0x42, 0x8e, 0xf6, 0xe3, 0x64, 0x24, 0x3c, 0x2f, 0x1d, 0x89, + 0x1c, 0xff, 0x2a, 0x67, 0xe6, 0xdf, 0x5f, 0x47, 0x9d, 0x46, 0x58, 0x77, 0xcf, 0xf4, 0x18, 0x7a, + 0x77, 0xc4, 0xc7, 0x8d, 0x62, 0x3e, 0xe6, 0xdc, 0xc2, 0xc3, 0x51, 0x79, 0x85, 0x90, 0x84, 0x7f, + 0xef, 0xc3, 0xb4, 0xc9, 0x68, 0x2f, 0x2c, 0xac, 0xf5, 0xa2, 0x0e, 0x8e, 0xfb, 0xc2, 0x36, 0x67, + 0xc7, 0x81, 0x14, 0xed, 0x4f, 0xe9, 0x03, 0x70, 0xc7, 0xa3, 0x77, 0x61, 0xde, 0x93, 0xa3, 0x3e, + 0x6c, 0x0e, 0x45, 0xd6, 0x87, 0x68, 0x61, 0x3c, 0x27, 0x35, 0xcd, 0x87, 0x10, 0x0f, 0xc7, 0x02, + 0x13, 0x95, 0x5b, 0x3a, 0x4d, 0xe5, 0x66, 0x42, 0x9f, 0x5b, 0xb9, 0x4f, 0x60, 0x5c, 0xf4, 0xd0, + 0x3b, 0x30, 0xe3, 0xf4, 0xc9, 0x93, 0xa8, 0xab, 0x4e, 0xde, 0x09, 0x1f, 0x08, 0xd2, 0x71, 0x29, + 0x02, 0x5c, 0x65, 0x80, 0xc6, 0x52, 0xa2, 0xf6, 0x43, 0x05, 0x56, 0xb2, 0x2d, 0xec, 0x14, 0x4d, + 0x62, 0x17, 0x96, 0x7a, 0x84, 0x19, 0x87, 0xd1, 0xac, 0x92, 0xb7, 0xae, 0xf5, 0xe1, 0xa0, 0xbe, + 0x74, 0x3f, 0x85, 0x79, 0x31, 0xa8, 0xa3, 0x3b, 0xbe, 0x65, 0x9d, 0xa4, 0xb7, 0xd0, 0x0c, 0xbf, + 0xf6, 0x4f, 0x15, 0x16, 0x53, 0x0d, 0xbb, 0xc0, 0xce, 0xd5, 0x86, 0xe5, 0x6e, 0xec, 0x6b, 0x8e, + 0x90, 0x66, 0x7c, 0x56, 0x12, 0x27, 0xd3, 0x44, 0xf0, 0x65, 0xe9, 0xd3, 0x79, 0xa3, 0x7e, 0xda, + 0x79, 0xf3, 0x10, 0x96, 0x48, 0xb4, 0x07, 0xdc, 0x77, 0xba, 0x54, 0x4e, 0xe1, 0x86, 0xe4, 0x5a, + 0x6a, 0xa7, 0xb0, 0x2f, 0x06, 0xf5, 0xcf, 0x64, 0xb7, 0x07, 0x0e, 0xc7, 0x19, 0x29, 0xe8, 0x2a, + 0x4c, 0x1b, 0x8e, 0x6f, 0x33, 0x31, 0xaa, 0xd5, 0xb8, 0x4c, 0x36, 0x38, 0x10, 0x07, 0x38, 0x74, + 0x03, 0x2a, 0xa4, 0xdb, 0x33, 0xed, 0xb6, 0x61, 0x50, 0xcf, 0x13, 0x77, 0xc2, 0xb9, 0x60, 0xfe, + 0xb7, 0x63, 0x30, 0x4e, 0xd2, 0xa0, 0x1e, 0x2c, 0xed, 0x9b, 0xae, 0xc7, 0xda, 0xc7, 0xc4, 0xb4, + 0x48, 0xc7, 0xa2, 0xd5, 0xd9, 0xc2, 0xa3, 0x70, 0xcf, 0xef, 0x84, 0xa3, 0xf6, 0x62, 0x78, 0xbc, + 0x3b, 0x29, 0x61, 0x38, 0x23, 0x5c, 0xfb, 0x87, 0x12, 0x2e, 0xba, 0x39, 0x3b, 0x19, 0x7a, 0x85, + 0x2f, 0x78, 0x02, 0x25, 0xd3, 0x20, 0xb1, 0xa4, 0x09, 0x30, 0x0e, 0xf1, 0x89, 0x67, 0x82, 0x52, + 0xa1, 0x67, 0x02, 0xb5, 0xc0, 0x33, 0x41, 0x79, 0xe2, 0x33, 0x41, 0xc6, 0xc1, 0xd3, 0x9f, 0xec, + 0x60, 0xed, 0x9b, 0xe1, 0xe6, 0x14, 0xdd, 0x4b, 0xb6, 0x41, 0x35, 0xa8, 0x35, 0xa6, 0xe9, 0x8e, + 0xfa, 0x79, 0xe4, 0x52, 0xa3, 0xcf, 0x0e, 0x07, 0x75, 0x75, 0x63, 0xeb, 0x1e, 0xe6, 0x32, 0xb4, + 0x0f, 0x4b, 0x61, 0x4d, 0xc7, 0xb1, 0xf8, 0x7f, 0x15, 0xfd, 0x67, 0x55, 0xa4, 0xfd, 0x52, 0x81, + 0x73, 0x23, 0x4f, 0x25, 0xe8, 0x0d, 0x58, 0x34, 0x6d, 0x46, 0xdd, 0x7d, 0x62, 0xd0, 0x9d, 0xd8, + 0xbd, 0x17, 0xa4, 0x88, 0xc5, 0xed, 0x24, 0x12, 0xa7, 0x69, 0xd1, 0x25, 0x50, 0xcd, 0x7e, 0x78, + 0xed, 0x12, 0x21, 0xdc, 0xde, 0xf5, 0x30, 0x87, 0xf1, 0x58, 0x1c, 0x12, 0xb7, 0xfb, 0x94, 0xb8, + 0xb4, 0xdd, 0xed, 0xf2, 0x7b, 0xa8, 0xcc, 0xd3, 0x28, 0x16, 0x5f, 0x4d, 0xa3, 0x71, 0x96, 0x5e, + 0xfb, 0x99, 0x02, 0x97, 0x72, 0x67, 0x41, 0xe1, 0xc7, 0x34, 0x02, 0xd0, 0x27, 0x2e, 0xe9, 0x51, + 0x46, 0x5d, 0x4f, 0x2e, 0x46, 0xa7, 0x7c, 0xa3, 0x8a, 0x76, 0xae, 0xdd, 0x48, 0x10, 0x4e, 0x08, + 0xd5, 0x7e, 0x52, 0x82, 0x45, 0x2c, 0x13, 0x23, 0x58, 0xf0, 0xff, 0xfb, 0x9b, 0xde, 0x6e, 0x6a, + 0xd3, 0x9b, 0x5c, 0x6e, 0x29, 0xdb, 0xf2, 0x76, 0x3d, 0xf4, 0x88, 0x5f, 0xb0, 0x08, 0xf3, 0xbd, + 0x42, 0x37, 0xe2, 0xb4, 0x4c, 0xc1, 0x17, 0x87, 0x20, 0xf8, 0xc6, 0x52, 0x9e, 0x36, 0x54, 0xa0, + 0x96, 0xa2, 0xe7, 0x93, 0xda, 0xef, 0x51, 0x17, 0xd3, 0x7d, 0xea, 0x52, 0xdb, 0xa0, 0xe8, 0x3a, + 0xcc, 0x91, 0xbe, 0x79, 0xd7, 0x75, 0xfc, 0xbe, 0x8c, 0x67, 0xb4, 0x86, 0xb5, 0x77, 0xb7, 0x05, + 0x1c, 0x47, 0x14, 0x9c, 0x3a, 0x34, 0x48, 0x66, 0x55, 0xe2, 0x4e, 0x14, 0xc0, 0x71, 0x44, 0x11, + 0x35, 0x8e, 0x72, 0x6e, 0xe3, 0xd0, 0x41, 0xf5, 0xcd, 0xae, 0xbc, 0x2e, 0xbe, 0x2e, 0x09, 0xd4, + 0xb7, 0xb7, 0x37, 0x5f, 0x0c, 0xea, 0x57, 0xf2, 0x9e, 0x81, 0xd9, 0x49, 0x9f, 0x7a, 0x8d, 0xb7, + 0xb7, 0x37, 0x31, 0x67, 0xd6, 0x7e, 0xab, 0xc0, 0xb9, 0xd4, 0x21, 0xcf, 0x60, 0x1d, 0x7d, 0x90, + 0x5e, 0x47, 0xaf, 0x15, 0x8f, 0x58, 0xce, 0x42, 0x7a, 0x98, 0x39, 0x83, 0xd8, 0x48, 0xf7, 0xb2, + 0x4f, 0xa3, 0xeb, 0x45, 0xaf, 0x7b, 0xf9, 0xef, 0xa1, 0xda, 0x1f, 0x4a, 0x70, 0x7e, 0x4c, 0x0e, + 0xa1, 0xc7, 0x00, 0x71, 0x73, 0x93, 0xfa, 0x26, 0x0f, 0xed, 0x91, 0xe7, 0x8f, 0x25, 0xf1, 0x60, + 0x19, 0x43, 0x13, 0x02, 0x91, 0x0b, 0x15, 0x97, 0x7a, 0xd4, 0x3d, 0xa6, 0xdd, 0x3b, 0x8e, 0x2b, + 0xfd, 0xf6, 0x46, 0x71, 0xbf, 0x8d, 0x64, 0x6e, 0x7c, 0x5b, 0xc6, 0xb1, 0x5c, 0x9c, 0x54, 0x82, + 0x1e, 0xc7, 0xfe, 0x0b, 0x5e, 0xe0, 0x5b, 0x45, 0xce, 0x93, 0xfe, 0xed, 0x60, 0x82, 0x27, 0xff, + 0xa2, 0xc0, 0x85, 0x94, 0x8d, 0x5f, 0xa7, 0xbd, 0xbe, 0x45, 0x18, 0x3d, 0x83, 0x2e, 0xf4, 0x28, + 0xd5, 0x85, 0x6e, 0x16, 0xf7, 0x63, 0x68, 0x63, 0xee, 0xcd, 0xf3, 0x43, 0x05, 0x2e, 0x8d, 0xe5, + 0x38, 0x83, 0xb2, 0xfa, 0x46, 0xba, 0xac, 0x5a, 0xa7, 0x3f, 0x56, 0x4e, 0x79, 0xfd, 0x39, 0xef, + 0x50, 0xa2, 0xce, 0xfe, 0x07, 0x87, 0x86, 0xf6, 0x2b, 0x05, 0x16, 0x42, 0x4a, 0xbe, 0x78, 0x16, + 0xd8, 0xd2, 0x5a, 0x00, 0xf2, 0x27, 0xb3, 0xf0, 0x35, 0x46, 0x8d, 0xcd, 0xbe, 0x1b, 0x61, 0x70, + 0x82, 0x0a, 0x7d, 0x0d, 0x50, 0x68, 0xe0, 0x9e, 0x25, 0x56, 0x01, 0xbe, 0xed, 0xa8, 0x82, 0x77, + 0x55, 0xf2, 0x22, 0x3c, 0x42, 0x81, 0xc7, 0x70, 0x69, 0xbf, 0x53, 0xe2, 0x69, 0x2d, 0xc0, 0x2f, + 0xa9, 0xe3, 0x85, 0x6d, 0xb9, 0x8e, 0x4f, 0x8e, 0x1b, 0x41, 0xf9, 0xb2, 0x8e, 0x1b, 0x61, 0x5c, + 0x4e, 0x3d, 0xbc, 0xaf, 0x66, 0x0e, 0x21, 0xea, 0xa0, 0xe8, 0x66, 0xf7, 0x66, 0xe2, 0x67, 0xd2, + 0x4a, 0xeb, 0x95, 0x42, 0xd6, 0xf0, 0x1c, 0x1d, 0x7b, 0x55, 0xba, 0x0e, 0x73, 0xb6, 0xd3, 0x0d, + 0x56, 0xe0, 0xcc, 0x4a, 0xb1, 0x23, 0xe1, 0x38, 0xa2, 0x18, 0xf9, 0x35, 0xaf, 0xfc, 0xe9, 0xfc, + 0x9a, 0x27, 0xd6, 0x20, 0xcb, 0xe2, 0x04, 0xe1, 0x2d, 0x2c, 0x5e, 0x83, 0x24, 0x1c, 0x47, 0x14, + 0x68, 0x27, 0x1e, 0x2c, 0x33, 0x22, 0x22, 0x57, 0x0b, 0x0c, 0xe6, 0xfc, 0x49, 0xa2, 0xb7, 0x9f, + 0x3d, 0xaf, 0x4d, 0x7d, 0xf0, 0xbc, 0x36, 0xf5, 0xd1, 0xf3, 0xda, 0xd4, 0xf7, 0x87, 0x35, 0xe5, + 0xd9, 0xb0, 0xa6, 0x7c, 0x30, 0xac, 0x29, 0x1f, 0x0d, 0x6b, 0xca, 0xdf, 0x87, 0x35, 0xe5, 0x47, + 0x1f, 0xd7, 0xa6, 0xde, 0xb9, 0x3c, 0xe1, 0x3f, 0x0a, 0xfe, 0x15, 0x00, 0x00, 0xff, 0xff, 0xa6, + 0xd4, 0xe9, 0x0d, 0x6f, 0x20, 0x00, 0x00, } func (m *AllocatedDeviceStatus) Marshal() (dAtA []byte, err error) { @@ -2005,6 +2037,20 @@ func (m *DeviceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.FirstAvailable) > 0 { + for iNdEx := len(m.FirstAvailable) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.FirstAvailable[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + } if m.AdminAccess != nil { i-- if *m.AdminAccess { @@ -2138,6 +2184,61 @@ func (m *DeviceSelector) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *DeviceSubRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DeviceSubRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DeviceSubRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i = encodeVarintGenerated(dAtA, i, uint64(m.Count)) + i-- + dAtA[i] = 0x28 + i -= len(m.AllocationMode) + copy(dAtA[i:], m.AllocationMode) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.AllocationMode))) + i-- + dAtA[i] = 0x22 + if len(m.Selectors) > 0 { + for iNdEx := len(m.Selectors) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Selectors[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + i -= len(m.DeviceClassName) + copy(dAtA[i:], m.DeviceClassName) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.DeviceClassName))) + i-- + dAtA[i] = 0x12 + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *NetworkDeviceData) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -3133,6 +3234,12 @@ func (m *DeviceRequest) Size() (n int) { if m.AdminAccess != nil { n += 2 } + if len(m.FirstAvailable) > 0 { + for _, e := range m.FirstAvailable { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -3169,6 +3276,28 @@ func (m *DeviceSelector) Size() (n int) { return n } +func (m *DeviceSubRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.DeviceClassName) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Selectors) > 0 { + for _, e := range m.Selectors { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + l = len(m.AllocationMode) + n += 1 + l + sovGenerated(uint64(l)) + n += 1 + sovGenerated(uint64(m.Count)) + return n +} + func (m *NetworkDeviceData) Size() (n int) { if m == nil { return 0 @@ -3669,6 +3798,11 @@ func (this *DeviceRequest) String() string { repeatedStringForSelectors += strings.Replace(strings.Replace(f.String(), "DeviceSelector", "DeviceSelector", 1), `&`, ``, 1) + "," } repeatedStringForSelectors += "}" + repeatedStringForFirstAvailable := "[]DeviceSubRequest{" + for _, f := range this.FirstAvailable { + repeatedStringForFirstAvailable += strings.Replace(strings.Replace(f.String(), "DeviceSubRequest", "DeviceSubRequest", 1), `&`, ``, 1) + "," + } + repeatedStringForFirstAvailable += "}" s := strings.Join([]string{`&DeviceRequest{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `DeviceClassName:` + fmt.Sprintf("%v", this.DeviceClassName) + `,`, @@ -3676,6 +3810,7 @@ func (this *DeviceRequest) String() string { `AllocationMode:` + fmt.Sprintf("%v", this.AllocationMode) + `,`, `Count:` + fmt.Sprintf("%v", this.Count) + `,`, `AdminAccess:` + valueToStringGenerated(this.AdminAccess) + `,`, + `FirstAvailable:` + repeatedStringForFirstAvailable + `,`, `}`, }, "") return s @@ -3704,6 +3839,25 @@ func (this *DeviceSelector) String() string { }, "") return s } +func (this *DeviceSubRequest) String() string { + if this == nil { + return "nil" + } + repeatedStringForSelectors := "[]DeviceSelector{" + for _, f := range this.Selectors { + repeatedStringForSelectors += strings.Replace(strings.Replace(f.String(), "DeviceSelector", "DeviceSelector", 1), `&`, ``, 1) + "," + } + repeatedStringForSelectors += "}" + s := strings.Join([]string{`&DeviceSubRequest{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `DeviceClassName:` + fmt.Sprintf("%v", this.DeviceClassName) + `,`, + `Selectors:` + repeatedStringForSelectors + `,`, + `AllocationMode:` + fmt.Sprintf("%v", this.AllocationMode) + `,`, + `Count:` + fmt.Sprintf("%v", this.Count) + `,`, + `}`, + }, "") + return s +} func (this *NetworkDeviceData) String() string { if this == nil { return "nil" @@ -6390,6 +6544,40 @@ func (m *DeviceRequest) Unmarshal(dAtA []byte) error { } b := bool(v != 0) m.AdminAccess = &b + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FirstAvailable", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FirstAvailable = append(m.FirstAvailable, DeviceSubRequest{}) + if err := m.FirstAvailable[len(m.FirstAvailable)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -6696,6 +6884,205 @@ func (m *DeviceSelector) Unmarshal(dAtA []byte) error { } return nil } +func (m *DeviceSubRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceSubRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceSubRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DeviceClassName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DeviceClassName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Selectors", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Selectors = append(m.Selectors, DeviceSelector{}) + if err := m.Selectors[len(m.Selectors)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllocationMode", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllocationMode = DeviceAllocationMode(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Count", wireType) + } + m.Count = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Count |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *NetworkDeviceData) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/staging/src/k8s.io/api/resource/v1beta1/generated.proto b/staging/src/k8s.io/api/resource/v1beta1/generated.proto index f52413f24564e..c2afd439050a7 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/resource/v1beta1/generated.proto @@ -200,6 +200,10 @@ message DeviceAllocationConfiguration { // Requests lists the names of requests where the configuration applies. // If empty, its applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic repeated string requests = 2; @@ -294,6 +298,10 @@ message DeviceClaimConfiguration { // Requests lists the names of requests where the configuration applies. // If empty, it applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic repeated string requests = 1; @@ -378,6 +386,10 @@ message DeviceConstraint { // constraint. If this is not specified, this constraint applies to all // requests in this claim. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the constraint applies to all subrequests. + // // +optional // +listType=atomic repeated string requests = 1; @@ -403,16 +415,12 @@ message DeviceConstraint { // DeviceRequest is a request for devices required for a claim. // This is typically a request for a single resource like a device, but can // also ask for several identical devices. -// -// A DeviceClassName is currently required. Clients must check that it is -// indeed set. It's absence indicates that something changed in a way that -// is not supported by the client yet, in which case it must refuse to -// handle the request. message DeviceRequest { // Name can be used to reference this request in a pod.spec.containers[].resources.claims // entry and in a constraint of the claim. // - // Must be a DNS label. + // Must be a DNS label and unique among all DeviceRequests in a + // ResourceClaim. // // +required optional string name = 1; @@ -421,7 +429,10 @@ message DeviceRequest { // additional configuration and selectors to be inherited by this // request. // - // A class is required. Which classes are available depends on the cluster. + // A class is required if no subrequests are specified in the + // firstAvailable list and no class can be set if subrequests + // are specified in the firstAvailable list. + // Which classes are available depends on the cluster. // // Administrators may use this to restrict which devices may get // requested by only installing classes with selectors for permitted @@ -429,7 +440,8 @@ message DeviceRequest { // then administrators can create an empty DeviceClass for users // to reference. // - // +required + // +optional + // +oneOf=deviceRequestType optional string deviceClassName = 2; // Selectors define criteria which must be satisfied by a specific @@ -437,6 +449,9 @@ message DeviceRequest { // request. All selectors must be satisfied for a device to be // considered. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // +optional // +listType=atomic repeated DeviceSelector selectors = 3; @@ -457,6 +472,9 @@ message DeviceRequest { // the mode is ExactCount and count is not specified, the default count is // one. Any other requests must specify this field. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // More modes may get added in the future. Clients must refuse to handle // requests with unknown modes. // @@ -466,6 +484,9 @@ message DeviceRequest { // Count is used only when the count mode is "ExactCount". Must be greater than zero. // If AllocationMode is ExactCount and this field is not specified, the default is one. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // +optional // +oneOf=AllocationMode optional int64 count = 5; @@ -476,6 +497,9 @@ message DeviceRequest { // all ordinary claims to the device with respect to access modes and // any resource allocations. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // This is an alpha field and requires enabling the DRAAdminAccess // feature gate. Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. @@ -483,13 +507,39 @@ message DeviceRequest { // +optional // +featureGate=DRAAdminAccess optional bool adminAccess = 6; + + // FirstAvailable contains subrequests, of which exactly one will be + // satisfied by the scheduler to satisfy this request. It tries to + // satisfy them in the order in which they are listed here. So if + // there are two entries in the list, the scheduler will only check + // the second one if it determines that the first one cannot be used. + // + // This field may only be set in the entries of DeviceClaim.Requests. + // + // DRA does not yet implement scoring, so the scheduler will + // select the first set of devices that satisfies all the + // requests in the claim. And if the requirements can + // be satisfied on more than one node, other scheduling features + // will determine which node is chosen. This means that the set of + // devices allocated to a claim might not be the optimal set + // available to the cluster. Scoring will be implemented later. + // + // +optional + // +oneOf=deviceRequestType + // +listType=atomic + // +featureGate=DRAPrioritizedList + repeated DeviceSubRequest firstAvailable = 7; } // DeviceRequestAllocationResult contains the allocation result for one request. message DeviceRequestAllocationResult { // Request is the name of the request in the claim which caused this - // device to be allocated. Multiple devices may have been allocated - // per request. + // device to be allocated. If it references a subrequest in the + // firstAvailable list on a DeviceRequest, this field must + // include both the name of the main request and the subrequest + // using the format
/. + // + // Multiple devices may have been allocated per request. // // +required optional string request = 1; @@ -541,6 +591,78 @@ message DeviceSelector { optional CELDeviceSelector cel = 1; } +// DeviceSubRequest describes a request for device provided in the +// claim.spec.devices.requests[].firstAvailable array. Each +// is typically a request for a single resource like a device, but can +// also ask for several identical devices. +// +// DeviceSubRequest is similar to Request, but doesn't expose the AdminAccess +// or FirstAvailable fields, as those can only be set on the top-level request. +// AdminAccess is not supported for requests with a prioritized list, and +// recursive FirstAvailable fields are not supported. +message DeviceSubRequest { + // Name can be used to reference this subrequest in the list of constraints + // or the list of configurations for the claim. References must use the + // format
/. + // + // Must be a DNS label. + // + // +required + optional string name = 1; + + // DeviceClassName references a specific DeviceClass, which can define + // additional configuration and selectors to be inherited by this + // subrequest. + // + // A class is required. Which classes are available depends on the cluster. + // + // Administrators may use this to restrict which devices may get + // requested by only installing classes with selectors for permitted + // devices. If users are free to request anything without restrictions, + // then administrators can create an empty DeviceClass for users + // to reference. + // + // +required + optional string deviceClassName = 2; + + // Selectors define criteria which must be satisfied by a specific + // device in order for that device to be considered for this + // subrequest. All selectors must be satisfied for a device to be + // considered. + // + // +optional + // +listType=atomic + repeated DeviceSelector selectors = 3; + + // AllocationMode and its related fields define how devices are allocated + // to satisfy this subrequest. Supported values are: + // + // - ExactCount: This request is for a specific number of devices. + // This is the default. The exact number is provided in the + // count field. + // + // - All: This subrequest is for all of the matching devices in a pool. + // Allocation will fail if some devices are already allocated, + // unless adminAccess is requested. + // + // If AlloctionMode is not specified, the default mode is ExactCount. If + // the mode is ExactCount and count is not specified, the default count is + // one. Any other subrequests must specify this field. + // + // More modes may get added in the future. Clients must refuse to handle + // requests with unknown modes. + // + // +optional + optional string allocationMode = 4; + + // Count is used only when the count mode is "ExactCount". Must be greater than zero. + // If AllocationMode is ExactCount and this field is not specified, the default is one. + // + // +optional + // +oneOf=AllocationMode + optional int64 count = 5; +} + // NetworkDeviceData provides network-related details for the allocated device. // This information may be filled by drivers or other components to configure // or identify the device within a network context. diff --git a/staging/src/k8s.io/api/resource/v1beta1/types.go b/staging/src/k8s.io/api/resource/v1beta1/types.go index 558665386e7c8..cc1f02161f53c 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/types.go +++ b/staging/src/k8s.io/api/resource/v1beta1/types.go @@ -394,16 +394,12 @@ const ( // DeviceRequest is a request for devices required for a claim. // This is typically a request for a single resource like a device, but can // also ask for several identical devices. -// -// A DeviceClassName is currently required. Clients must check that it is -// indeed set. It's absence indicates that something changed in a way that -// is not supported by the client yet, in which case it must refuse to -// handle the request. type DeviceRequest struct { // Name can be used to reference this request in a pod.spec.containers[].resources.claims // entry and in a constraint of the claim. // - // Must be a DNS label. + // Must be a DNS label and unique among all DeviceRequests in a + // ResourceClaim. // // +required Name string `json:"name" protobuf:"bytes,1,name=name"` @@ -412,7 +408,10 @@ type DeviceRequest struct { // additional configuration and selectors to be inherited by this // request. // - // A class is required. Which classes are available depends on the cluster. + // A class is required if no subrequests are specified in the + // firstAvailable list and no class can be set if subrequests + // are specified in the firstAvailable list. + // Which classes are available depends on the cluster. // // Administrators may use this to restrict which devices may get // requested by only installing classes with selectors for permitted @@ -420,7 +419,8 @@ type DeviceRequest struct { // then administrators can create an empty DeviceClass for users // to reference. // - // +required + // +optional + // +oneOf=deviceRequestType DeviceClassName string `json:"deviceClassName" protobuf:"bytes,2,name=deviceClassName"` // Selectors define criteria which must be satisfied by a specific @@ -428,6 +428,9 @@ type DeviceRequest struct { // request. All selectors must be satisfied for a device to be // considered. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // +optional // +listType=atomic Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"` @@ -448,6 +451,9 @@ type DeviceRequest struct { // the mode is ExactCount and count is not specified, the default count is // one. Any other requests must specify this field. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // More modes may get added in the future. Clients must refuse to handle // requests with unknown modes. // @@ -457,6 +463,9 @@ type DeviceRequest struct { // Count is used only when the count mode is "ExactCount". Must be greater than zero. // If AllocationMode is ExactCount and this field is not specified, the default is one. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // +optional // +oneOf=AllocationMode Count int64 `json:"count,omitempty" protobuf:"bytes,5,opt,name=count"` @@ -467,6 +476,9 @@ type DeviceRequest struct { // all ordinary claims to the device with respect to access modes and // any resource allocations. // + // This field can only be set when deviceClassName is set and no subrequests + // are specified in the firstAvailable list. + // // This is an alpha field and requires enabling the DRAAdminAccess // feature gate. Admin access is disabled if this field is unset or // set to false, otherwise it is enabled. @@ -474,6 +486,100 @@ type DeviceRequest struct { // +optional // +featureGate=DRAAdminAccess AdminAccess *bool `json:"adminAccess,omitempty" protobuf:"bytes,6,opt,name=adminAccess"` + + // FirstAvailable contains subrequests, of which exactly one will be + // satisfied by the scheduler to satisfy this request. It tries to + // satisfy them in the order in which they are listed here. So if + // there are two entries in the list, the scheduler will only check + // the second one if it determines that the first one cannot be used. + // + // This field may only be set in the entries of DeviceClaim.Requests. + // + // DRA does not yet implement scoring, so the scheduler will + // select the first set of devices that satisfies all the + // requests in the claim. And if the requirements can + // be satisfied on more than one node, other scheduling features + // will determine which node is chosen. This means that the set of + // devices allocated to a claim might not be the optimal set + // available to the cluster. Scoring will be implemented later. + // + // +optional + // +oneOf=deviceRequestType + // +listType=atomic + // +featureGate=DRAPrioritizedList + FirstAvailable []DeviceSubRequest `json:"firstAvailable,omitempty" protobuf:"bytes,7,name=firstAvailable"` +} + +// DeviceSubRequest describes a request for device provided in the +// claim.spec.devices.requests[].firstAvailable array. Each +// is typically a request for a single resource like a device, but can +// also ask for several identical devices. +// +// DeviceSubRequest is similar to Request, but doesn't expose the AdminAccess +// or FirstAvailable fields, as those can only be set on the top-level request. +// AdminAccess is not supported for requests with a prioritized list, and +// recursive FirstAvailable fields are not supported. +type DeviceSubRequest struct { + // Name can be used to reference this subrequest in the list of constraints + // or the list of configurations for the claim. References must use the + // format
/. + // + // Must be a DNS label. + // + // +required + Name string `json:"name" protobuf:"bytes,1,name=name"` + + // DeviceClassName references a specific DeviceClass, which can define + // additional configuration and selectors to be inherited by this + // subrequest. + // + // A class is required. Which classes are available depends on the cluster. + // + // Administrators may use this to restrict which devices may get + // requested by only installing classes with selectors for permitted + // devices. If users are free to request anything without restrictions, + // then administrators can create an empty DeviceClass for users + // to reference. + // + // +required + DeviceClassName string `json:"deviceClassName" protobuf:"bytes,2,name=deviceClassName"` + + // Selectors define criteria which must be satisfied by a specific + // device in order for that device to be considered for this + // subrequest. All selectors must be satisfied for a device to be + // considered. + // + // +optional + // +listType=atomic + Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"` + + // AllocationMode and its related fields define how devices are allocated + // to satisfy this subrequest. Supported values are: + // + // - ExactCount: This request is for a specific number of devices. + // This is the default. The exact number is provided in the + // count field. + // + // - All: This subrequest is for all of the matching devices in a pool. + // Allocation will fail if some devices are already allocated, + // unless adminAccess is requested. + // + // If AlloctionMode is not specified, the default mode is ExactCount. If + // the mode is ExactCount and count is not specified, the default count is + // one. Any other subrequests must specify this field. + // + // More modes may get added in the future. Clients must refuse to handle + // requests with unknown modes. + // + // +optional + AllocationMode DeviceAllocationMode `json:"allocationMode,omitempty" protobuf:"bytes,4,opt,name=allocationMode"` + + // Count is used only when the count mode is "ExactCount". Must be greater than zero. + // If AllocationMode is ExactCount and this field is not specified, the default is one. + // + // +optional + // +oneOf=AllocationMode + Count int64 `json:"count,omitempty" protobuf:"bytes,5,opt,name=count"` } const ( @@ -590,6 +696,10 @@ type DeviceConstraint struct { // constraint. If this is not specified, this constraint applies to all // requests in this claim. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the constraint applies to all subrequests. + // // +optional // +listType=atomic Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"` @@ -627,6 +737,10 @@ type DeviceClaimConfiguration struct { // Requests lists the names of requests where the configuration applies. // If empty, it applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"` @@ -799,8 +913,12 @@ const AllocationResultsMaxSize = 32 // DeviceRequestAllocationResult contains the allocation result for one request. type DeviceRequestAllocationResult struct { // Request is the name of the request in the claim which caused this - // device to be allocated. Multiple devices may have been allocated - // per request. + // device to be allocated. If it references a subrequest in the + // firstAvailable list on a DeviceRequest, this field must + // include both the name of the main request and the subrequest + // using the format
/. + // + // Multiple devices may have been allocated per request. // // +required Request string `json:"request" protobuf:"bytes,1,name=request"` @@ -855,6 +973,10 @@ type DeviceAllocationConfiguration struct { // Requests lists the names of requests where the configuration applies. // If empty, its applies to all requests. // + // References to subrequests must include the name of the main request + // and may include the subrequest using the format
[/]. If just + // the main request is given, the configuration applies to all subrequests. + // // +optional // +listType=atomic Requests []string `json:"requests,omitempty" protobuf:"bytes,2,opt,name=requests"` diff --git a/staging/src/k8s.io/api/resource/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/resource/v1beta1/types_swagger_doc_generated.go index fac9987e3ae55..b9695d696593b 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/resource/v1beta1/types_swagger_doc_generated.go @@ -83,7 +83,7 @@ func (Device) SwaggerDoc() map[string]string { var map_DeviceAllocationConfiguration = map[string]string{ "": "DeviceAllocationConfiguration gets embedded in an AllocationResult.", "source": "Source records whether the configuration comes from a class and thus is not something that a normal user would have been able to set or from a claim.", - "requests": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.", + "requests": "Requests lists the names of requests where the configuration applies. If empty, its applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", } func (DeviceAllocationConfiguration) SwaggerDoc() map[string]string { @@ -134,7 +134,7 @@ func (DeviceClaim) SwaggerDoc() map[string]string { var map_DeviceClaimConfiguration = map[string]string{ "": "DeviceClaimConfiguration is used for configuration parameters in DeviceClaim.", - "requests": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.", + "requests": "Requests lists the names of requests where the configuration applies. If empty, it applies to all requests.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the configuration applies to all subrequests.", } func (DeviceClaimConfiguration) SwaggerDoc() map[string]string { @@ -190,7 +190,7 @@ func (DeviceConfiguration) SwaggerDoc() map[string]string { var map_DeviceConstraint = map[string]string{ "": "DeviceConstraint must have exactly one field set besides Requests.", - "requests": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.", + "requests": "Requests is a list of the one or more requests in this claim which must co-satisfy this constraint. If a request is fulfilled by multiple devices, then all of the devices must satisfy the constraint. If this is not specified, this constraint applies to all requests in this claim.\n\nReferences to subrequests must include the name of the main request and may include the subrequest using the format
[/]. If just the main request is given, the constraint applies to all subrequests.", "matchAttribute": "MatchAttribute requires that all devices in question have this attribute and that its type and value are the same across those devices.\n\nFor example, if you specified \"dra.example.com/numa\" (a hypothetical example!), then only devices in the same NUMA node will be chosen. A device which does not have that attribute will not be chosen. All devices should use a value of the same type for this attribute because that is part of its specification, but if one device doesn't, then it also will not be chosen.\n\nMust include the domain qualifier.", } @@ -199,13 +199,14 @@ func (DeviceConstraint) SwaggerDoc() map[string]string { } var map_DeviceRequest = map[string]string{ - "": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nA DeviceClassName is currently required. Clients must check that it is indeed set. It's absence indicates that something changed in a way that is not supported by the client yet, in which case it must refuse to handle the request.", - "name": "Name can be used to reference this request in a pod.spec.containers[].resources.claims entry and in a constraint of the claim.\n\nMust be a DNS label.", - "deviceClassName": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", - "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.", - "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", - "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", - "adminAccess": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "": "DeviceRequest is a request for devices required for a claim. This is typically a request for a single resource like a device, but can also ask for several identical devices.", + "name": "Name can be used to reference this request in a pod.spec.containers[].resources.claims entry and in a constraint of the claim.\n\nMust be a DNS label and unique among all DeviceRequests in a ResourceClaim.", + "deviceClassName": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this request.\n\nA class is required if no subrequests are specified in the firstAvailable list and no class can be set if subrequests are specified in the firstAvailable list. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this request. All selectors must be satisfied for a device to be considered.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", + "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this request. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This request is for all of the matching devices in a pool.\n At least one device must exist on the node for the allocation to succeed.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AllocationMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other requests must specify this field.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.", + "adminAccess": "AdminAccess indicates that this is a claim for administrative access to the device(s). Claims with AdminAccess are expected to be used for monitoring or other management services for a device. They ignore all ordinary claims to the device with respect to access modes and any resource allocations.\n\nThis field can only be set when deviceClassName is set and no subrequests are specified in the firstAvailable list.\n\nThis is an alpha field and requires enabling the DRAAdminAccess feature gate. Admin access is disabled if this field is unset or set to false, otherwise it is enabled.", + "firstAvailable": "FirstAvailable contains subrequests, of which exactly one will be satisfied by the scheduler to satisfy this request. It tries to satisfy them in the order in which they are listed here. So if there are two entries in the list, the scheduler will only check the second one if it determines that the first one cannot be used.\n\nThis field may only be set in the entries of DeviceClaim.Requests.\n\nDRA does not yet implement scoring, so the scheduler will select the first set of devices that satisfies all the requests in the claim. And if the requirements can be satisfied on more than one node, other scheduling features will determine which node is chosen. This means that the set of devices allocated to a claim might not be the optimal set available to the cluster. Scoring will be implemented later.", } func (DeviceRequest) SwaggerDoc() map[string]string { @@ -214,7 +215,7 @@ func (DeviceRequest) SwaggerDoc() map[string]string { var map_DeviceRequestAllocationResult = map[string]string{ "": "DeviceRequestAllocationResult contains the allocation result for one request.", - "request": "Request is the name of the request in the claim which caused this device to be allocated. Multiple devices may have been allocated per request.", + "request": "Request is the name of the request in the claim which caused this device to be allocated. If it references a subrequest in the firstAvailable list on a DeviceRequest, this field must include both the name of the main request and the subrequest using the format
/.\n\nMultiple devices may have been allocated per request.", "driver": "Driver specifies the name of the DRA driver whose kubelet plugin should be invoked to process the allocation once the claim is needed on a node.\n\nMust be a DNS subdomain and should end with a DNS domain owned by the vendor of the driver.", "pool": "This name together with the driver name and the device name field identify which device was allocated (`//`).\n\nMust not be longer than 253 characters and may contain one or more DNS sub-domains separated by slashes.", "device": "Device references one device instance via its name in the driver's resource pool. It must be a DNS label.", @@ -234,6 +235,19 @@ func (DeviceSelector) SwaggerDoc() map[string]string { return map_DeviceSelector } +var map_DeviceSubRequest = map[string]string{ + "": "DeviceSubRequest describes a request for device provided in the claim.spec.devices.requests[].firstAvailable array. Each is typically a request for a single resource like a device, but can also ask for several identical devices.\n\nDeviceSubRequest is similar to Request, but doesn't expose the AdminAccess or FirstAvailable fields, as those can only be set on the top-level request. AdminAccess is not supported for requests with a prioritized list, and recursive FirstAvailable fields are not supported.", + "name": "Name can be used to reference this subrequest in the list of constraints or the list of configurations for the claim. References must use the format
/.\n\nMust be a DNS label.", + "deviceClassName": "DeviceClassName references a specific DeviceClass, which can define additional configuration and selectors to be inherited by this subrequest.\n\nA class is required. Which classes are available depends on the cluster.\n\nAdministrators may use this to restrict which devices may get requested by only installing classes with selectors for permitted devices. If users are free to request anything without restrictions, then administrators can create an empty DeviceClass for users to reference.", + "selectors": "Selectors define criteria which must be satisfied by a specific device in order for that device to be considered for this subrequest. All selectors must be satisfied for a device to be considered.", + "allocationMode": "AllocationMode and its related fields define how devices are allocated to satisfy this subrequest. Supported values are:\n\n- ExactCount: This request is for a specific number of devices.\n This is the default. The exact number is provided in the\n count field.\n\n- All: This subrequest is for all of the matching devices in a pool.\n Allocation will fail if some devices are already allocated,\n unless adminAccess is requested.\n\nIf AlloctionMode is not specified, the default mode is ExactCount. If the mode is ExactCount and count is not specified, the default count is one. Any other subrequests must specify this field.\n\nMore modes may get added in the future. Clients must refuse to handle requests with unknown modes.", + "count": "Count is used only when the count mode is \"ExactCount\". Must be greater than zero. If AllocationMode is ExactCount and this field is not specified, the default is one.", +} + +func (DeviceSubRequest) SwaggerDoc() map[string]string { + return map_DeviceSubRequest +} + var map_NetworkDeviceData = map[string]string{ "": "NetworkDeviceData provides network-related details for the allocated device. This information may be filled by drivers or other components to configure or identify the device within a network context.", "interfaceName": "InterfaceName specifies the name of the network interface associated with the allocated device. This might be the name of a physical or virtual network interface being configured in the pod.\n\nMust not be longer than 256 characters.", diff --git a/staging/src/k8s.io/api/resource/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/resource/v1beta1/zz_generated.deepcopy.go index ec08c15b0afcd..f2592e39b39d8 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/resource/v1beta1/zz_generated.deepcopy.go @@ -482,6 +482,13 @@ func (in *DeviceRequest) DeepCopyInto(out *DeviceRequest) { *out = new(bool) **out = **in } + if in.FirstAvailable != nil { + in, out := &in.FirstAvailable, &out.FirstAvailable + *out = make([]DeviceSubRequest, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -537,6 +544,29 @@ func (in *DeviceSelector) DeepCopy() *DeviceSelector { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeviceSubRequest) DeepCopyInto(out *DeviceSubRequest) { + *out = *in + if in.Selectors != nil { + in, out := &in.Selectors, &out.Selectors + *out = make([]DeviceSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeviceSubRequest. +func (in *DeviceSubRequest) DeepCopy() *DeviceSubRequest { + if in == nil { + return nil + } + out := new(DeviceSubRequest) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkDeviceData) DeepCopyInto(out *NetworkDeviceData) { *out = *in diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaim.json b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaim.json index f61f244ae7e43..b3a610c4b2fbb 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaim.json +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaim.json @@ -58,7 +58,22 @@ ], "allocationMode": "allocationModeValue", "count": 5, - "adminAccess": true + "adminAccess": true, + "firstAvailable": [ + { + "name": "nameValue", + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 5 + } + ] } ], "constraints": [ diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaim.pb b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaim.pb index 30923fd8e35ce..e70287ec054f0 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaim.pb and b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaim.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaim.yaml b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaim.yaml index bc7855856b182..c2c12181b3c55 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaim.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaim.yaml @@ -55,6 +55,14 @@ spec: allocationMode: allocationModeValue count: 5 deviceClassName: deviceClassNameValue + firstAvailable: + - allocationMode: allocationModeValue + count: 5 + deviceClassName: deviceClassNameValue + name: nameValue + selectors: + - cel: + expression: expressionValue name: nameValue selectors: - cel: diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaimTemplate.json b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaimTemplate.json index 0669af6df4672..5dabc4458f485 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaimTemplate.json +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaimTemplate.json @@ -101,7 +101,22 @@ ], "allocationMode": "allocationModeValue", "count": 5, - "adminAccess": true + "adminAccess": true, + "firstAvailable": [ + { + "name": "nameValue", + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 5 + } + ] } ], "constraints": [ diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaimTemplate.pb b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaimTemplate.pb index fab18a8e9e5f8..832be05eb9452 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaimTemplate.pb and b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaimTemplate.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaimTemplate.yaml b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaimTemplate.yaml index b0e5939dd5fe9..13b06c07493ee 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaimTemplate.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1alpha3.ResourceClaimTemplate.yaml @@ -88,6 +88,14 @@ spec: allocationMode: allocationModeValue count: 5 deviceClassName: deviceClassNameValue + firstAvailable: + - allocationMode: allocationModeValue + count: 5 + deviceClassName: deviceClassNameValue + name: nameValue + selectors: + - cel: + expression: expressionValue name: nameValue selectors: - cel: diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaim.json b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaim.json index f0f993218bc02..7982a35a1c8b8 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaim.json +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaim.json @@ -58,7 +58,22 @@ ], "allocationMode": "allocationModeValue", "count": 5, - "adminAccess": true + "adminAccess": true, + "firstAvailable": [ + { + "name": "nameValue", + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 5 + } + ] } ], "constraints": [ diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaim.pb b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaim.pb index 9265e325d91c2..ed044a4d13ddf 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaim.pb and b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaim.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaim.yaml b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaim.yaml index 71d30617a5631..5e56d97b58756 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaim.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaim.yaml @@ -55,6 +55,14 @@ spec: allocationMode: allocationModeValue count: 5 deviceClassName: deviceClassNameValue + firstAvailable: + - allocationMode: allocationModeValue + count: 5 + deviceClassName: deviceClassNameValue + name: nameValue + selectors: + - cel: + expression: expressionValue name: nameValue selectors: - cel: diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaimTemplate.json b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaimTemplate.json index 9342cb4faa037..bf5f56abe70c1 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaimTemplate.json +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaimTemplate.json @@ -101,7 +101,22 @@ ], "allocationMode": "allocationModeValue", "count": 5, - "adminAccess": true + "adminAccess": true, + "firstAvailable": [ + { + "name": "nameValue", + "deviceClassName": "deviceClassNameValue", + "selectors": [ + { + "cel": { + "expression": "expressionValue" + } + } + ], + "allocationMode": "allocationModeValue", + "count": 5 + } + ] } ], "constraints": [ diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaimTemplate.pb b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaimTemplate.pb index af0631e6030b6..6b6cd72e38c94 100644 Binary files a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaimTemplate.pb and b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaimTemplate.pb differ diff --git a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaimTemplate.yaml b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaimTemplate.yaml index 3d2209315d1a1..6711b1d802d73 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaimTemplate.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/resource.k8s.io.v1beta1.ResourceClaimTemplate.yaml @@ -88,6 +88,14 @@ spec: allocationMode: allocationModeValue count: 5 deviceClassName: deviceClassNameValue + firstAvailable: + - allocationMode: allocationModeValue + count: 5 + deviceClassName: deviceClassNameValue + name: nameValue + selectors: + - cel: + expression: expressionValue name: nameValue selectors: - cel: diff --git a/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go b/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go index ae67f4ad81ba8..c116160535f6d 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go @@ -12708,6 +12708,12 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: string default: "" + - name: firstAvailable + type: + list: + elementType: + namedType: io.k8s.api.resource.v1alpha3.DeviceSubRequest + elementRelationship: atomic - name: name type: scalar: string @@ -12746,6 +12752,29 @@ var schemaYAML = typed.YAMLObject(`types: - name: cel type: namedType: io.k8s.api.resource.v1alpha3.CELDeviceSelector +- name: io.k8s.api.resource.v1alpha3.DeviceSubRequest + map: + fields: + - name: allocationMode + type: + scalar: string + - name: count + type: + scalar: numeric + - name: deviceClassName + type: + scalar: string + default: "" + - name: name + type: + scalar: string + default: "" + - name: selectors + type: + list: + elementType: + namedType: io.k8s.api.resource.v1alpha3.DeviceSelector + elementRelationship: atomic - name: io.k8s.api.resource.v1alpha3.NetworkDeviceData map: fields: @@ -13147,6 +13176,12 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: string default: "" + - name: firstAvailable + type: + list: + elementType: + namedType: io.k8s.api.resource.v1beta1.DeviceSubRequest + elementRelationship: atomic - name: name type: scalar: string @@ -13185,6 +13220,29 @@ var schemaYAML = typed.YAMLObject(`types: - name: cel type: namedType: io.k8s.api.resource.v1beta1.CELDeviceSelector +- name: io.k8s.api.resource.v1beta1.DeviceSubRequest + map: + fields: + - name: allocationMode + type: + scalar: string + - name: count + type: + scalar: numeric + - name: deviceClassName + type: + scalar: string + default: "" + - name: name + type: + scalar: string + default: "" + - name: selectors + type: + list: + elementType: + namedType: io.k8s.api.resource.v1beta1.DeviceSelector + elementRelationship: atomic - name: io.k8s.api.resource.v1beta1.NetworkDeviceData map: fields: diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicerequest.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicerequest.go index e5c87efe47538..f91c8b41b267a 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicerequest.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicerequest.go @@ -31,6 +31,7 @@ type DeviceRequestApplyConfiguration struct { AllocationMode *resourcev1alpha3.DeviceAllocationMode `json:"allocationMode,omitempty"` Count *int64 `json:"count,omitempty"` AdminAccess *bool `json:"adminAccess,omitempty"` + FirstAvailable []DeviceSubRequestApplyConfiguration `json:"firstAvailable,omitempty"` } // DeviceRequestApplyConfiguration constructs a declarative configuration of the DeviceRequest type for use with @@ -91,3 +92,16 @@ func (b *DeviceRequestApplyConfiguration) WithAdminAccess(value bool) *DeviceReq b.AdminAccess = &value return b } + +// WithFirstAvailable adds the given value to the FirstAvailable field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the FirstAvailable field. +func (b *DeviceRequestApplyConfiguration) WithFirstAvailable(values ...*DeviceSubRequestApplyConfiguration) *DeviceRequestApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithFirstAvailable") + } + b.FirstAvailable = append(b.FirstAvailable, *values[i]) + } + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicesubrequest.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicesubrequest.go new file mode 100644 index 0000000000000..408c0daa55b2e --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1alpha3/devicesubrequest.go @@ -0,0 +1,84 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha3 + +import ( + resourcev1alpha3 "k8s.io/api/resource/v1alpha3" +) + +// DeviceSubRequestApplyConfiguration represents a declarative configuration of the DeviceSubRequest type for use +// with apply. +type DeviceSubRequestApplyConfiguration struct { + Name *string `json:"name,omitempty"` + DeviceClassName *string `json:"deviceClassName,omitempty"` + Selectors []DeviceSelectorApplyConfiguration `json:"selectors,omitempty"` + AllocationMode *resourcev1alpha3.DeviceAllocationMode `json:"allocationMode,omitempty"` + Count *int64 `json:"count,omitempty"` +} + +// DeviceSubRequestApplyConfiguration constructs a declarative configuration of the DeviceSubRequest type for use with +// apply. +func DeviceSubRequest() *DeviceSubRequestApplyConfiguration { + return &DeviceSubRequestApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *DeviceSubRequestApplyConfiguration) WithName(value string) *DeviceSubRequestApplyConfiguration { + b.Name = &value + return b +} + +// WithDeviceClassName sets the DeviceClassName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeviceClassName field is set to the value of the last call. +func (b *DeviceSubRequestApplyConfiguration) WithDeviceClassName(value string) *DeviceSubRequestApplyConfiguration { + b.DeviceClassName = &value + return b +} + +// WithSelectors adds the given value to the Selectors field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Selectors field. +func (b *DeviceSubRequestApplyConfiguration) WithSelectors(values ...*DeviceSelectorApplyConfiguration) *DeviceSubRequestApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithSelectors") + } + b.Selectors = append(b.Selectors, *values[i]) + } + return b +} + +// WithAllocationMode sets the AllocationMode field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AllocationMode field is set to the value of the last call. +func (b *DeviceSubRequestApplyConfiguration) WithAllocationMode(value resourcev1alpha3.DeviceAllocationMode) *DeviceSubRequestApplyConfiguration { + b.AllocationMode = &value + return b +} + +// WithCount sets the Count field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Count field is set to the value of the last call. +func (b *DeviceSubRequestApplyConfiguration) WithCount(value int64) *DeviceSubRequestApplyConfiguration { + b.Count = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicerequest.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicerequest.go index ea454a275c01a..1099b6dd01f9d 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicerequest.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicerequest.go @@ -31,6 +31,7 @@ type DeviceRequestApplyConfiguration struct { AllocationMode *resourcev1beta1.DeviceAllocationMode `json:"allocationMode,omitempty"` Count *int64 `json:"count,omitempty"` AdminAccess *bool `json:"adminAccess,omitempty"` + FirstAvailable []DeviceSubRequestApplyConfiguration `json:"firstAvailable,omitempty"` } // DeviceRequestApplyConfiguration constructs a declarative configuration of the DeviceRequest type for use with @@ -91,3 +92,16 @@ func (b *DeviceRequestApplyConfiguration) WithAdminAccess(value bool) *DeviceReq b.AdminAccess = &value return b } + +// WithFirstAvailable adds the given value to the FirstAvailable field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the FirstAvailable field. +func (b *DeviceRequestApplyConfiguration) WithFirstAvailable(values ...*DeviceSubRequestApplyConfiguration) *DeviceRequestApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithFirstAvailable") + } + b.FirstAvailable = append(b.FirstAvailable, *values[i]) + } + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicesubrequest.go b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicesubrequest.go new file mode 100644 index 0000000000000..0cc96d7699068 --- /dev/null +++ b/staging/src/k8s.io/client-go/applyconfigurations/resource/v1beta1/devicesubrequest.go @@ -0,0 +1,84 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta1 + +import ( + resourcev1beta1 "k8s.io/api/resource/v1beta1" +) + +// DeviceSubRequestApplyConfiguration represents a declarative configuration of the DeviceSubRequest type for use +// with apply. +type DeviceSubRequestApplyConfiguration struct { + Name *string `json:"name,omitempty"` + DeviceClassName *string `json:"deviceClassName,omitempty"` + Selectors []DeviceSelectorApplyConfiguration `json:"selectors,omitempty"` + AllocationMode *resourcev1beta1.DeviceAllocationMode `json:"allocationMode,omitempty"` + Count *int64 `json:"count,omitempty"` +} + +// DeviceSubRequestApplyConfiguration constructs a declarative configuration of the DeviceSubRequest type for use with +// apply. +func DeviceSubRequest() *DeviceSubRequestApplyConfiguration { + return &DeviceSubRequestApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *DeviceSubRequestApplyConfiguration) WithName(value string) *DeviceSubRequestApplyConfiguration { + b.Name = &value + return b +} + +// WithDeviceClassName sets the DeviceClassName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeviceClassName field is set to the value of the last call. +func (b *DeviceSubRequestApplyConfiguration) WithDeviceClassName(value string) *DeviceSubRequestApplyConfiguration { + b.DeviceClassName = &value + return b +} + +// WithSelectors adds the given value to the Selectors field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Selectors field. +func (b *DeviceSubRequestApplyConfiguration) WithSelectors(values ...*DeviceSelectorApplyConfiguration) *DeviceSubRequestApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithSelectors") + } + b.Selectors = append(b.Selectors, *values[i]) + } + return b +} + +// WithAllocationMode sets the AllocationMode field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AllocationMode field is set to the value of the last call. +func (b *DeviceSubRequestApplyConfiguration) WithAllocationMode(value resourcev1beta1.DeviceAllocationMode) *DeviceSubRequestApplyConfiguration { + b.AllocationMode = &value + return b +} + +// WithCount sets the Count field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Count field is set to the value of the last call. +func (b *DeviceSubRequestApplyConfiguration) WithCount(value int64) *DeviceSubRequestApplyConfiguration { + b.Count = &value + return b +} diff --git a/staging/src/k8s.io/client-go/applyconfigurations/utils.go b/staging/src/k8s.io/client-go/applyconfigurations/utils.go index 5a09a8850317a..00bb2644d28f0 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/utils.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/utils.go @@ -1634,6 +1634,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &resourcev1alpha3.DeviceRequestAllocationResultApplyConfiguration{} case v1alpha3.SchemeGroupVersion.WithKind("DeviceSelector"): return &resourcev1alpha3.DeviceSelectorApplyConfiguration{} + case v1alpha3.SchemeGroupVersion.WithKind("DeviceSubRequest"): + return &resourcev1alpha3.DeviceSubRequestApplyConfiguration{} case v1alpha3.SchemeGroupVersion.WithKind("NetworkDeviceData"): return &resourcev1alpha3.NetworkDeviceDataApplyConfiguration{} case v1alpha3.SchemeGroupVersion.WithKind("OpaqueDeviceConfiguration"): @@ -1696,6 +1698,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &applyconfigurationsresourcev1beta1.DeviceRequestAllocationResultApplyConfiguration{} case resourcev1beta1.SchemeGroupVersion.WithKind("DeviceSelector"): return &applyconfigurationsresourcev1beta1.DeviceSelectorApplyConfiguration{} + case resourcev1beta1.SchemeGroupVersion.WithKind("DeviceSubRequest"): + return &applyconfigurationsresourcev1beta1.DeviceSubRequestApplyConfiguration{} case resourcev1beta1.SchemeGroupVersion.WithKind("NetworkDeviceData"): return &applyconfigurationsresourcev1beta1.NetworkDeviceDataApplyConfiguration{} case resourcev1beta1.SchemeGroupVersion.WithKind("OpaqueDeviceConfiguration"): diff --git a/staging/src/k8s.io/dynamic-resource-allocation/resourceclaim/resourceclaim.go b/staging/src/k8s.io/dynamic-resource-allocation/resourceclaim/resourceclaim.go index e2c895f349941..d25c70f5e1a61 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/resourceclaim/resourceclaim.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/resourceclaim/resourceclaim.go @@ -26,6 +26,8 @@ package resourceclaim import ( "errors" "fmt" + "slices" + "strings" v1 "k8s.io/api/core/v1" resourceapi "k8s.io/api/resource/v1beta1" @@ -106,3 +108,36 @@ func CanBeReserved(claim *resourceapi.ResourceClaim) bool { // Currently no restrictions on sharing... return true } + +// BaseRequestRef returns the request name if the reference is to a top-level +// request and the name of the parent request if the reference is to a subrequest. +func BaseRequestRef(requestRef string) string { + segments := strings.Split(requestRef, "/") + return segments[0] +} + +// ConfigForResult returns the configs that are applicable to device +// allocated for the provided result. +func ConfigForResult(deviceConfigurations []resourceapi.DeviceAllocationConfiguration, result resourceapi.DeviceRequestAllocationResult) []resourceapi.DeviceAllocationConfiguration { + var configs []resourceapi.DeviceAllocationConfiguration + for _, deviceConfiguration := range deviceConfigurations { + if deviceConfiguration.Opaque != nil && + isMatch(deviceConfiguration.Requests, result.Request) { + configs = append(configs, deviceConfiguration) + } + } + return configs +} + +func isMatch(requests []string, requestRef string) bool { + if len(requests) == 0 { + return true + } + + if slices.Contains(requests, requestRef) { + return true + } + + baseRequestRef := BaseRequestRef(requestRef) + return slices.Contains(requests, baseRequestRef) +} diff --git a/staging/src/k8s.io/dynamic-resource-allocation/resourceclaim/resourceclaim_test.go b/staging/src/k8s.io/dynamic-resource-allocation/resourceclaim/resourceclaim_test.go index 9f9804c7fce6d..1adcfc8f723e9 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/resourceclaim/resourceclaim_test.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/resourceclaim/resourceclaim_test.go @@ -332,3 +332,247 @@ func TestName(t *testing.T) { }) } } + +func TestBaseRequestRef(t *testing.T) { + testcases := map[string]struct { + requestRef string + expectedBaseRequestRef string + }{ + "valid-no-subrequest": { + requestRef: "foo", + expectedBaseRequestRef: "foo", + }, + "valid-subrequest": { + requestRef: "foo/bar", + expectedBaseRequestRef: "foo", + }, + } + + for name, tc := range testcases { + t.Run(name, func(t *testing.T) { + baseRequestRef := BaseRequestRef(tc.requestRef) + assert.Equal(t, tc.expectedBaseRequestRef, baseRequestRef) + }) + } +} + +func TestConfigForResult(t *testing.T) { + testcases := map[string]struct { + deviceConfigurations []resourceapi.DeviceAllocationConfiguration + result resourceapi.DeviceRequestAllocationResult + expectedConfigs []resourceapi.DeviceAllocationConfiguration + }{ + "opaque-nil": { + deviceConfigurations: []resourceapi.DeviceAllocationConfiguration{ + { + Source: resourceapi.AllocationConfigSourceClass, + Requests: []string{}, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: nil, + }, + }, + }, + result: resourceapi.DeviceRequestAllocationResult{ + Request: "foo", + Device: "device-1", + }, + expectedConfigs: nil, + }, + "empty-requests-match-all": { + deviceConfigurations: []resourceapi.DeviceAllocationConfiguration{ + { + Source: resourceapi.AllocationConfigSourceClass, + Requests: []string{}, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: "driver-a", + }, + }, + }, + }, + result: resourceapi.DeviceRequestAllocationResult{ + Request: "foo", + Device: "device-1", + }, + expectedConfigs: []resourceapi.DeviceAllocationConfiguration{ + { + Source: resourceapi.AllocationConfigSourceClass, + Requests: []string{}, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: "driver-a", + }, + }, + }, + }, + }, + "match-regular-request": { + deviceConfigurations: []resourceapi.DeviceAllocationConfiguration{ + { + Source: resourceapi.AllocationConfigSourceClass, + Requests: []string{ + "foo", + }, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: "driver-a", + }, + }, + }, + }, + result: resourceapi.DeviceRequestAllocationResult{ + Request: "foo", + Device: "device-1", + }, + expectedConfigs: []resourceapi.DeviceAllocationConfiguration{ + { + Source: resourceapi.AllocationConfigSourceClass, + Requests: []string{ + "foo", + }, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: "driver-a", + }, + }, + }, + }, + }, + "match-parent-request-for-subrequest": { + deviceConfigurations: []resourceapi.DeviceAllocationConfiguration{ + { + Source: resourceapi.AllocationConfigSourceClass, + Requests: []string{ + "foo", + }, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: "driver-a", + }, + }, + }, + }, + result: resourceapi.DeviceRequestAllocationResult{ + Request: "foo/bar", + Device: "device-1", + }, + expectedConfigs: []resourceapi.DeviceAllocationConfiguration{ + { + Source: resourceapi.AllocationConfigSourceClass, + Requests: []string{ + "foo", + }, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: "driver-a", + }, + }, + }, + }, + }, + "match-subrequest": { + deviceConfigurations: []resourceapi.DeviceAllocationConfiguration{ + { + Source: resourceapi.AllocationConfigSourceClass, + Requests: []string{ + "foo/bar", + }, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: "driver-a", + }, + }, + }, + { + Source: resourceapi.AllocationConfigSourceClass, + Requests: []string{ + "foo/not-bar", + }, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: "driver-a", + }, + }, + }, + }, + result: resourceapi.DeviceRequestAllocationResult{ + Request: "foo/bar", + Device: "device-1", + }, + expectedConfigs: []resourceapi.DeviceAllocationConfiguration{ + { + Source: resourceapi.AllocationConfigSourceClass, + Requests: []string{ + "foo/bar", + }, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: "driver-a", + }, + }, + }, + }, + }, + "match-both-source-class-and-claim": { + deviceConfigurations: []resourceapi.DeviceAllocationConfiguration{ + { + Source: resourceapi.AllocationConfigSourceClass, + Requests: []string{ + "foo", + }, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: "driver-a", + }, + }, + }, + { + Source: resourceapi.AllocationConfigSourceClaim, + Requests: []string{ + "foo/bar", + }, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: "driver-a", + }, + }, + }, + }, + result: resourceapi.DeviceRequestAllocationResult{ + Request: "foo/bar", + Device: "device-1", + }, + expectedConfigs: []resourceapi.DeviceAllocationConfiguration{ + { + Source: resourceapi.AllocationConfigSourceClass, + Requests: []string{ + "foo", + }, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: "driver-a", + }, + }, + }, + { + Source: resourceapi.AllocationConfigSourceClaim, + Requests: []string{ + "foo/bar", + }, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: "driver-a", + }, + }, + }, + }, + }, + } + + for name, tc := range testcases { + t.Run(name, func(t *testing.T) { + configs := ConfigForResult(tc.deviceConfigurations, tc.result) + assert.Equal(t, tc.expectedConfigs, configs) + }) + } +} diff --git a/staging/src/k8s.io/dynamic-resource-allocation/structured/allocator.go b/staging/src/k8s.io/dynamic-resource-allocation/structured/allocator.go index 6e05234074aa0..0068a0e5e7953 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/structured/allocator.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/structured/allocator.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "math" + "slices" "strings" v1 "k8s.io/api/core/v1" @@ -46,12 +47,13 @@ type deviceClassLister interface { // available and the current state of the cluster (claims, classes, resource // slices). type Allocator struct { - adminAccessEnabled bool - claimsToAllocate []*resourceapi.ResourceClaim - allocatedDevices sets.Set[DeviceID] - classLister deviceClassLister - slices []*resourceapi.ResourceSlice - celCache *cel.Cache + adminAccessEnabled bool + prioritizedListEnabled bool + claimsToAllocate []*resourceapi.ResourceClaim + allocatedDevices sets.Set[DeviceID] + classLister deviceClassLister + slices []*resourceapi.ResourceSlice + celCache *cel.Cache } // NewAllocator returns an allocator for a certain set of claims or an error if @@ -60,6 +62,7 @@ type Allocator struct { // The returned Allocator can be used multiple times and is thread-safe. func NewAllocator(ctx context.Context, adminAccessEnabled bool, + prioritizedListEnabled bool, claimsToAllocate []*resourceapi.ResourceClaim, allocatedDevices sets.Set[DeviceID], classLister deviceClassLister, @@ -67,12 +70,13 @@ func NewAllocator(ctx context.Context, celCache *cel.Cache, ) (*Allocator, error) { return &Allocator{ - adminAccessEnabled: adminAccessEnabled, - claimsToAllocate: claimsToAllocate, - allocatedDevices: allocatedDevices, - classLister: classLister, - slices: slices, - celCache: celCache, + adminAccessEnabled: adminAccessEnabled, + prioritizedListEnabled: prioritizedListEnabled, + claimsToAllocate: claimsToAllocate, + allocatedDevices: allocatedDevices, + classLister: classLister, + slices: slices, + celCache: celCache, }, nil } @@ -148,9 +152,9 @@ func (a *Allocator) Allocate(ctx context.Context, node *v1.Node) (finalResult [] // and their requests. For each claim we determine how many devices // need to be allocated. If not all can be stored in the result, the // claim cannot be allocated. - numDevicesTotal := 0 + minDevicesTotal := 0 for claimIndex, claim := range alloc.claimsToAllocate { - numDevicesPerClaim := 0 + minDevicesPerClaim := 0 // If we have any any request that wants "all" devices, we need to // figure out how much "all" is. If some pool is incomplete, we stop @@ -161,92 +165,57 @@ func (a *Allocator) Allocate(ctx context.Context, node *v1.Node) (finalResult [] // has some matching device. for requestIndex := range claim.Spec.Devices.Requests { request := &claim.Spec.Devices.Requests[requestIndex] - for i, selector := range request.Selectors { - if selector.CEL == nil { - // Unknown future selector type! - return nil, fmt.Errorf("claim %s, request %s, selector #%d: CEL expression empty (unsupported selector type?)", klog.KObj(claim), request.Name, i) - } - } + requestKey := requestIndices{claimIndex: claimIndex, requestIndex: requestIndex} + hasSubRequests := len(request.FirstAvailable) > 0 - if !a.adminAccessEnabled && request.AdminAccess != nil { - return nil, fmt.Errorf("claim %s, request %s: admin access is requested, but the feature is disabled", klog.KObj(claim), request.Name) + // Error out if the prioritizedList feature is not enabled and the request + // has subrequests. This is to avoid surprising behavior for users. + if !a.prioritizedListEnabled && hasSubRequests { + return nil, fmt.Errorf("claim %s, request %s: has subrequests, but the feature is disabled", klog.KObj(claim), request.Name) } - // Should be set. If it isn't, something changed and we should refuse to proceed. - if request.DeviceClassName == "" { - return nil, fmt.Errorf("claim %s, request %s: missing device class name (unsupported request type?)", klog.KObj(claim), request.Name) - } - class, err := alloc.classLister.Get(request.DeviceClassName) - if err != nil { - return nil, fmt.Errorf("claim %s, request %s: could not retrieve device class %s: %w", klog.KObj(claim), request.Name, request.DeviceClassName, err) - } - - // Start collecting information about the request. - // The class must be set and stored before calling isSelectable. - requestData := requestData{ - class: class, - } - requestKey := requestIndices{claimIndex: claimIndex, requestIndex: requestIndex} - alloc.requestData[requestKey] = requestData - - switch request.AllocationMode { - case resourceapi.DeviceAllocationModeExactCount: - numDevices := request.Count - if numDevices > math.MaxInt { - // Allowed by API validation, but doesn't make sense. - return nil, fmt.Errorf("claim %s, request %s: exact count %d is too large", klog.KObj(claim), request.Name, numDevices) - } - requestData.numDevices = int(numDevices) - case resourceapi.DeviceAllocationModeAll: - requestData.allDevices = make([]deviceWithID, 0, resourceapi.AllocationResultsMaxSize) - for _, pool := range pools { - if pool.IsIncomplete { - return nil, fmt.Errorf("claim %s, request %s: asks for all devices, but resource pool %s is currently being updated", klog.KObj(claim), request.Name, pool.PoolID) - } - if pool.IsInvalid { - return nil, fmt.Errorf("claim %s, request %s: asks for all devices, but resource pool %s is currently invalid", klog.KObj(claim), request.Name, pool.PoolID) + if hasSubRequests { + // We need to find the minimum number of devices that can be allocated + // for the request, so setting this to a high number so we can do the + // easy comparison in the loop. + minDevicesPerRequest := math.MaxInt + + // A request with subrequests gets one entry per subrequest in alloc.requestData. + // We can only predict a lower number of devices because it depends on which + // subrequest gets chosen. + for i, subReq := range request.FirstAvailable { + reqData, err := alloc.validateDeviceRequest(&deviceSubRequestAccessor{subRequest: &subReq}, + &deviceRequestAccessor{request: request}, requestKey, pools) + if err != nil { + return nil, err } - - for _, slice := range pool.Slices { - for deviceIndex := range slice.Spec.Devices { - selectable, err := alloc.isSelectable(requestKey, slice, deviceIndex) - if err != nil { - return nil, err - } - if selectable { - device := deviceWithID{ - id: DeviceID{Driver: slice.Spec.Driver, Pool: slice.Spec.Pool.Name, Device: slice.Spec.Devices[deviceIndex].Name}, - basic: slice.Spec.Devices[deviceIndex].Basic, - slice: slice, - } - requestData.allDevices = append(requestData.allDevices, device) - } - } + requestKey.subRequestIndex = i + alloc.requestData[requestKey] = reqData + if reqData.numDevices < minDevicesPerRequest { + minDevicesPerRequest = reqData.numDevices } } - // At least one device is required for 'All' allocation mode. - if len(requestData.allDevices) == 0 { - alloc.logger.V(6).Info("Allocation for 'all' devices didn't succeed: no devices found", "claim", klog.KObj(claim), "request", request.Name) - return nil, nil + minDevicesPerClaim += minDevicesPerRequest + } else { + reqData, err := alloc.validateDeviceRequest(&deviceRequestAccessor{request: request}, nil, requestKey, pools) + if err != nil { + return nil, err } - requestData.numDevices = len(requestData.allDevices) - alloc.logger.V(6).Info("Request for 'all' devices", "claim", klog.KObj(claim), "request", request.Name, "numDevicesPerRequest", requestData.numDevices) - default: - return nil, fmt.Errorf("claim %s, request %s: unsupported count mode %s", klog.KObj(claim), request.Name, request.AllocationMode) + alloc.requestData[requestKey] = reqData + minDevicesPerClaim += reqData.numDevices } - alloc.requestData[requestKey] = requestData - numDevicesPerClaim += requestData.numDevices } - alloc.logger.V(6).Info("Checked claim", "claim", klog.KObj(claim), "numDevices", numDevicesPerClaim) - + alloc.logger.V(6).Info("Checked claim", "claim", klog.KObj(claim), "minDevices", minDevicesPerClaim) // Check that we don't end up with too many results. - if numDevicesPerClaim > resourceapi.AllocationResultsMaxSize { - return nil, fmt.Errorf("claim %s: number of requested devices %d exceeds the claim limit of %d", klog.KObj(claim), numDevicesPerClaim, resourceapi.AllocationResultsMaxSize) + // This isn't perfectly reliable because numDevicesPerClaim is + // only a lower bound, so allocation also has to check this. + if minDevicesPerClaim > resourceapi.AllocationResultsMaxSize { + return nil, fmt.Errorf("claim %s: number of requested devices %d exceeds the claim limit of %d", klog.KObj(claim), minDevicesPerClaim, resourceapi.AllocationResultsMaxSize) } // If we don't, then we can pre-allocate the result slices for // appending the actual results later. - alloc.result[claimIndex].devices = make([]internalDeviceResult, 0, numDevicesPerClaim) + alloc.result[claimIndex].devices = make([]internalDeviceResult, 0, minDevicesPerClaim) // Constraints are assumed to be monotonic: once a constraint returns // false, adding more devices will not cause it to return true. This @@ -273,7 +242,7 @@ func (a *Allocator) Allocate(ctx context.Context, node *v1.Node) (finalResult [] } } alloc.constraints[claimIndex] = constraints - numDevicesTotal += numDevicesPerClaim + minDevicesTotal += minDevicesPerClaim } // Selecting a device for a request is independent of what has been @@ -284,9 +253,9 @@ func (a *Allocator) Allocate(ctx context.Context, node *v1.Node) (finalResult [] alloc.deviceMatchesRequest = make(map[matchKey]bool) // We can estimate the size based on what we need to allocate. - alloc.allocatingDevices = make(map[DeviceID]bool, numDevicesTotal) + alloc.allocatingDevices = make(map[DeviceID]bool, minDevicesTotal) - alloc.logger.V(6).Info("Gathered information about devices", "numAllocated", len(alloc.allocatedDevices), "toBeAllocated", numDevicesTotal) + alloc.logger.V(6).Info("Gathered information about devices", "numAllocated", len(alloc.allocatedDevices), "minDevicesToBeAllocated", minDevicesTotal) // In practice, there aren't going to be many different CEL // expressions. Most likely, there is going to be handful of different @@ -301,7 +270,7 @@ func (a *Allocator) Allocate(ctx context.Context, node *v1.Node) (finalResult [] // All errors get created such that they can be returned by Allocate // without further wrapping. - done, err := alloc.allocateOne(deviceIndices{}) + done, err := alloc.allocateOne(deviceIndices{}, false) if errors.Is(err, errStop) { return nil, nil } @@ -319,7 +288,7 @@ func (a *Allocator) Allocate(ctx context.Context, node *v1.Node) (finalResult [] allocationResult.Devices.Results = make([]resourceapi.DeviceRequestAllocationResult, len(internalResult.devices)) for i, internal := range internalResult.devices { allocationResult.Devices.Results[i] = resourceapi.DeviceRequestAllocationResult{ - Request: internal.request, + Request: internal.requestName(), Driver: internal.id.Driver.String(), Pool: internal.id.Pool.String(), Device: internal.id.Device.String(), @@ -329,7 +298,15 @@ func (a *Allocator) Allocate(ctx context.Context, node *v1.Node) (finalResult [] // Populate configs. for requestIndex := range claim.Spec.Devices.Requests { - class := alloc.requestData[requestIndices{claimIndex: claimIndex, requestIndex: requestIndex}].class + requestKey := requestIndices{claimIndex: claimIndex, requestIndex: requestIndex} + requestData := alloc.requestData[requestKey] + if requestData.parentRequest != nil { + // We need the class of the selected subrequest. + requestKey.subRequestIndex = requestData.selectedSubRequestIndex + requestData = alloc.requestData[requestKey] + } + + class := requestData.class if class != nil { for _, config := range class.Spec.Config { allocationResult.Devices.Config = append(allocationResult.Devices.Config, resourceapi.DeviceAllocationConfiguration{ @@ -341,11 +318,42 @@ func (a *Allocator) Allocate(ctx context.Context, node *v1.Node) (finalResult [] } } for _, config := range claim.Spec.Devices.Config { - allocationResult.Devices.Config = append(allocationResult.Devices.Config, resourceapi.DeviceAllocationConfiguration{ - Source: resourceapi.AllocationConfigSourceClaim, - Requests: config.Requests, - DeviceConfiguration: config.DeviceConfiguration, - }) + // If Requests are empty, it applies to all. So it can just be included. + if len(config.Requests) == 0 { + allocationResult.Devices.Config = append(allocationResult.Devices.Config, resourceapi.DeviceAllocationConfiguration{ + Source: resourceapi.AllocationConfigSourceClaim, + Requests: config.Requests, + DeviceConfiguration: config.DeviceConfiguration, + }) + continue + } + + for i, request := range claim.Spec.Devices.Requests { + if slices.Contains(config.Requests, request.Name) { + allocationResult.Devices.Config = append(allocationResult.Devices.Config, resourceapi.DeviceAllocationConfiguration{ + Source: resourceapi.AllocationConfigSourceClaim, + Requests: config.Requests, + DeviceConfiguration: config.DeviceConfiguration, + }) + continue + } + + requestKey := requestIndices{claimIndex: claimIndex, requestIndex: i} + requestData := alloc.requestData[requestKey] + if requestData.parentRequest == nil { + continue + } + + subRequest := request.FirstAvailable[requestData.selectedSubRequestIndex] + subRequestName := fmt.Sprintf("%s/%s", request.Name, subRequest.Name) + if slices.Contains(config.Requests, subRequestName) { + allocationResult.Devices.Config = append(allocationResult.Devices.Config, resourceapi.DeviceAllocationConfiguration{ + Source: resourceapi.AllocationConfigSourceClaim, + Requests: config.Requests, + DeviceConfiguration: config.DeviceConfiguration, + }) + } + } } // Determine node selector. @@ -359,6 +367,86 @@ func (a *Allocator) Allocate(ctx context.Context, node *v1.Node) (finalResult [] return result, nil } +func (a *allocator) validateDeviceRequest(request requestAccessor, parentRequest requestAccessor, requestKey requestIndices, pools []*Pool) (requestData, error) { + claim := a.claimsToAllocate[requestKey.claimIndex] + requestData := requestData{ + request: request, + parentRequest: parentRequest, + } + for i, selector := range request.selectors() { + if selector.CEL == nil { + // Unknown future selector type! + return requestData, fmt.Errorf("claim %s, request %s, selector #%d: CEL expression empty (unsupported selector type?)", klog.KObj(claim), request.name(), i) + } + } + + if !a.adminAccessEnabled && request.hasAdminAccess() { + return requestData, fmt.Errorf("claim %s, request %s: admin access is requested, but the feature is disabled", klog.KObj(claim), request.name()) + } + + // Should be set. If it isn't, something changed and we should refuse to proceed. + if request.deviceClassName() == "" { + return requestData, fmt.Errorf("claim %s, request %s: missing device class name (unsupported request type?)", klog.KObj(claim), request.name()) + } + class, err := a.classLister.Get(request.deviceClassName()) + if err != nil { + return requestData, fmt.Errorf("claim %s, request %s: could not retrieve device class %s: %w", klog.KObj(claim), request.name(), request.deviceClassName(), err) + } + + // Start collecting information about the request. + // The class must be set and stored before calling isSelectable. + requestData.class = class + + switch request.allocationMode() { + case resourceapi.DeviceAllocationModeExactCount: + numDevices := request.count() + if numDevices > math.MaxInt { + // Allowed by API validation, but doesn't make sense. + return requestData, fmt.Errorf("claim %s, request %s: exact count %d is too large", klog.KObj(claim), request.name(), numDevices) + } + requestData.numDevices = int(numDevices) + case resourceapi.DeviceAllocationModeAll: + // If we have any any request that wants "all" devices, we need to + // figure out how much "all" is. If some pool is incomplete, we stop + // here because allocation cannot succeed. Once we do scoring, we should + // stop in all cases, not just when "all" devices are needed, because + // pulling from an incomplete might not pick the best solution and it's + // better to wait. This does not matter yet as long the incomplete pool + // has some matching device. + requestData.allDevices = make([]deviceWithID, 0, resourceapi.AllocationResultsMaxSize) + for _, pool := range pools { + if pool.IsIncomplete { + return requestData, fmt.Errorf("claim %s, request %s: asks for all devices, but resource pool %s is currently being updated", klog.KObj(claim), request.name(), pool.PoolID) + } + if pool.IsInvalid { + return requestData, fmt.Errorf("claim %s, request %s: asks for all devices, but resource pool %s is currently invalid", klog.KObj(claim), request.name(), pool.PoolID) + } + + for _, slice := range pool.Slices { + for deviceIndex := range slice.Spec.Devices { + selectable, err := a.isSelectable(requestKey, requestData, slice, deviceIndex) + if err != nil { + return requestData, err + } + if selectable { + device := deviceWithID{ + id: DeviceID{Driver: slice.Spec.Driver, Pool: slice.Spec.Pool.Name, Device: slice.Spec.Devices[deviceIndex].Name}, + basic: slice.Spec.Devices[deviceIndex].Basic, + slice: slice, + } + requestData.allDevices = append(requestData.allDevices, device) + } + } + } + } + requestData.numDevices = len(requestData.allDevices) + a.logger.V(6).Info("Request for 'all' devices", "claim", klog.KObj(claim), "request", request.name(), "numDevicesPerRequest", requestData.numDevices) + default: + return requestData, fmt.Errorf("claim %s, request %s: unsupported count mode %s", klog.KObj(claim), request.name(), request.allocationMode()) + } + return requestData, nil +} + // errStop is a special error that gets returned by allocateOne if it detects // that allocation cannot succeed. var errStop = errors.New("stop allocation") @@ -372,7 +460,7 @@ type allocator struct { pools []*Pool deviceMatchesRequest map[matchKey]bool constraints [][]constraint // one list of constraints per claim - requestData map[requestIndices]requestData // one entry per request + requestData map[requestIndices]requestData // one entry per request with no subrequests and one entry per subrequest allocatingDevices map[DeviceID]bool result []internalAllocationResult } @@ -383,21 +471,38 @@ type matchKey struct { requestIndices } -// requestIndices identifies one specific request by its -// claim and request index. +// requestIndices identifies one specific request +// or subrequest by three properties: +// +// - claimIndex: The index of the claim in the requestData map. +// - requestIndex: The index of the request in the claim. +// - subRequestIndex: The index of the subrequest in the parent request. type requestIndices struct { claimIndex, requestIndex int + subRequestIndex int } // deviceIndices identifies one specific required device inside -// a request of a certain claim. +// a request or subrequest of a certain claim. type deviceIndices struct { - claimIndex, requestIndex, deviceIndex int + claimIndex int // The index of the claim in the allocator. + requestIndex int // The index of the request in the claim. + subRequestIndex int // The index of the subrequest within the request (ignored if subRequest is false). + deviceIndex int // The index of a device within a request or subrequest. } type requestData struct { - class *resourceapi.DeviceClass - numDevices int + // The request or subrequest which needs to be allocated. + // Never nil. + request requestAccessor + // The parent of a subrequest, nil if not a subrequest. + parentRequest requestAccessor + class *resourceapi.DeviceClass + numDevices int + + // selectedSubRequestIndex is set for the entry with requestIndices.subRequestIndex == 0. + // It is the index of the subrequest which got picked during allocation. + selectedSubRequestIndex int // pre-determined set of devices for allocating "all" devices allDevices []deviceWithID @@ -414,21 +519,29 @@ type internalAllocationResult struct { } type internalDeviceResult struct { - request string - id DeviceID - slice *draapi.ResourceSlice - adminAccess *bool + request string // name of the request (if no subrequests) or the subrequest + parentRequest string // name of the request which contains the subrequest, empty otherwise + id DeviceID + slice *draapi.ResourceSlice + adminAccess *bool +} + +func (i internalDeviceResult) requestName() string { + if i.parentRequest == "" { + return i.request + } + return fmt.Sprintf("%s/%s", i.parentRequest, i.request) } type constraint interface { // add is called whenever a device is about to be allocated. It must // check whether the device matches the constraint and if yes, // track that it is allocated. - add(requestName string, device *draapi.BasicDevice, deviceID DeviceID) bool + add(requestName, subRequestName string, device *draapi.BasicDevice, deviceID DeviceID) bool // For every successful add there is exactly one matching removed call // with the exact same parameters. - remove(requestName string, device *draapi.BasicDevice, deviceID DeviceID) + remove(requestName, subRequestName string, device *draapi.BasicDevice, deviceID DeviceID) } // matchAttributeConstraint compares an attribute value across devices. @@ -447,8 +560,8 @@ type matchAttributeConstraint struct { numDevices int } -func (m *matchAttributeConstraint) add(requestName string, device *draapi.BasicDevice, deviceID DeviceID) bool { - if m.requestNames.Len() > 0 && !m.requestNames.Has(requestName) { +func (m *matchAttributeConstraint) add(requestName, subRequestName string, device *draapi.BasicDevice, deviceID DeviceID) bool { + if m.requestNames.Len() > 0 && !m.matches(requestName, subRequestName) { // Device not affected by constraint. m.logger.V(7).Info("Constraint does not apply to request", "request", requestName) return true @@ -504,8 +617,8 @@ func (m *matchAttributeConstraint) add(requestName string, device *draapi.BasicD return true } -func (m *matchAttributeConstraint) remove(requestName string, device *draapi.BasicDevice, deviceID DeviceID) { - if m.requestNames.Len() > 0 && !m.requestNames.Has(requestName) { +func (m *matchAttributeConstraint) remove(requestName, subRequestName string, device *draapi.BasicDevice, deviceID DeviceID) { + if m.requestNames.Len() > 0 && !m.matches(requestName, subRequestName) { // Device not affected by constraint. return } @@ -514,6 +627,15 @@ func (m *matchAttributeConstraint) remove(requestName string, device *draapi.Bas m.logger.V(7).Info("Device removed from constraint set", "device", deviceID, "numDevices", m.numDevices) } +func (m *matchAttributeConstraint) matches(requestName, subRequestName string) bool { + if subRequestName == "" { + return m.requestNames.Has(requestName) + } else { + fullSubRequestName := fmt.Sprintf("%s/%s", requestName, subRequestName) + return m.requestNames.Has(requestName) || m.requestNames.Has(fullSubRequestName) + } +} + func lookupAttribute(device *draapi.BasicDevice, deviceID DeviceID, attributeName draapi.FullyQualifiedName) *draapi.DeviceAttribute { // Fully-qualified match? if attr, ok := device.Attributes[draapi.QualifiedName(attributeName)]; ok { @@ -542,7 +664,11 @@ func lookupAttribute(device *draapi.BasicDevice, deviceID DeviceID, attributeNam // allocateOne iterates over all eligible devices (not in use, match selector, // satisfy constraints) for a specific required device. It returns true if // everything got allocated, an error if allocation needs to stop. -func (alloc *allocator) allocateOne(r deviceIndices) (bool, error) { +// +// allocateSubRequest is true when trying to allocate one particular subrequest. +// This allows the logic for subrequests to call allocateOne with the same +// device index without causing infinite recursion. +func (alloc *allocator) allocateOne(r deviceIndices, allocateSubRequest bool) (bool, error) { if r.claimIndex >= len(alloc.claimsToAllocate) { // Done! If we were doing scoring, we would compare the current allocation result // against the previous one, keep the best, and continue. Without scoring, we stop @@ -554,20 +680,73 @@ func (alloc *allocator) allocateOne(r deviceIndices) (bool, error) { claim := alloc.claimsToAllocate[r.claimIndex] if r.requestIndex >= len(claim.Spec.Devices.Requests) { // Done with the claim, continue with the next one. - return alloc.allocateOne(deviceIndices{claimIndex: r.claimIndex + 1}) + return alloc.allocateOne(deviceIndices{claimIndex: r.claimIndex + 1}, false) + } + + // r.subRequestIndex is zero unless the for loop below is in the + // recursion chain. + requestKey := requestIndices{claimIndex: r.claimIndex, requestIndex: r.requestIndex, subRequestIndex: r.subRequestIndex} + requestData := alloc.requestData[requestKey] + + // Subrequests are special: we only need to allocate one of them, then + // we can move on to the next request. We enter this for loop when + // hitting the first subrequest, but not if we are already working on a + // specific subrequest. + if !allocateSubRequest && requestData.parentRequest != nil { + for subRequestIndex := 0; ; subRequestIndex++ { + nextSubRequestKey := requestKey + nextSubRequestKey.subRequestIndex = subRequestIndex + if _, ok := alloc.requestData[nextSubRequestKey]; !ok { + // Past the end of the subrequests without finding a solution -> give up. + return false, nil + } + + r.subRequestIndex = subRequestIndex + success, err := alloc.allocateOne(r, true /* prevent infinite recusion */) + if err != nil { + return false, err + } + // If allocation with a subrequest succeeds, return without + // attempting the remaining subrequests. + if success { + // Store the index of the selected subrequest + requestData.selectedSubRequestIndex = subRequestIndex + alloc.requestData[requestKey] = requestData + return true, nil + } + } + // This is unreachable, so no need to have a return statement here. + } + + // Look up the current request that we are attempting to satisfy. This can + // be either a request or a subrequest. + request := requestData.request + doAllDevices := request.allocationMode() == resourceapi.DeviceAllocationModeAll + + // At least one device is required for 'All' allocation mode. + if doAllDevices && len(requestData.allDevices) == 0 { + alloc.logger.V(6).Info("Allocation for 'all' devices didn't succeed: no devices found", "claim", klog.KObj(claim), "request", requestData.request.name()) + return false, nil } // We already know how many devices per request are needed. - // Ready to move on to the next request? - requestData := alloc.requestData[requestIndices{claimIndex: r.claimIndex, requestIndex: r.requestIndex}] if r.deviceIndex >= requestData.numDevices { - return alloc.allocateOne(deviceIndices{claimIndex: r.claimIndex, requestIndex: r.requestIndex + 1}) + // Done with request, continue with next one. We have completed the work for + // the request or subrequest, so we can no longer be allocating devices for + // a subrequest. + return alloc.allocateOne(deviceIndices{claimIndex: r.claimIndex, requestIndex: r.requestIndex + 1}, false) } - request := &alloc.claimsToAllocate[r.claimIndex].Spec.Devices.Requests[r.requestIndex] - doAllDevices := request.AllocationMode == resourceapi.DeviceAllocationModeAll - alloc.logger.V(6).Info("Allocating one device", "currentClaim", r.claimIndex, "totalClaims", len(alloc.claimsToAllocate), "currentRequest", r.requestIndex, "totalRequestsPerClaim", len(claim.Spec.Devices.Requests), "currentDevice", r.deviceIndex, "devicesPerRequest", requestData.numDevices, "allDevices", doAllDevices, "adminAccess", request.AdminAccess) + // Before trying to allocate devices, check if allocating the devices + // in the current request will put us over the threshold. + numDevicesAfterAlloc := len(alloc.result[r.claimIndex].devices) + requestData.numDevices + if numDevicesAfterAlloc > resourceapi.AllocationResultsMaxSize { + // Don't return an error here since we want to keep searching for + // a solution that works. + return false, nil + } + alloc.logger.V(6).Info("Allocating one device", "currentClaim", r.claimIndex, "totalClaims", len(alloc.claimsToAllocate), "currentRequest", r.requestIndex, "currentSubRequest", r.subRequestIndex, "totalRequestsPerClaim", len(claim.Spec.Devices.Requests), "currentDevice", r.deviceIndex, "devicesPerRequest", requestData.numDevices, "allDevices", doAllDevices, "adminAccess", request.adminAccess()) if doAllDevices { // For "all" devices we already know which ones we need. We // just need to check whether we can use them. @@ -580,9 +759,9 @@ func (alloc *allocator) allocateOne(r deviceIndices) (bool, error) { // The order in which we allocate "all" devices doesn't matter, // so we only try with the one which was up next. If we couldn't // get all of them, then there is no solution and we have to stop. - return false, errStop + return false, nil } - done, err := alloc.allocateOne(deviceIndices{claimIndex: r.claimIndex, requestIndex: r.requestIndex, deviceIndex: r.deviceIndex + 1}) + done, err := alloc.allocateOne(deviceIndices{claimIndex: r.claimIndex, requestIndex: r.requestIndex, deviceIndex: r.deviceIndex + 1}, allocateSubRequest) if err != nil { return false, err } @@ -606,13 +785,14 @@ func (alloc *allocator) allocateOne(r deviceIndices) (bool, error) { deviceID := DeviceID{Driver: pool.Driver, Pool: pool.Pool, Device: slice.Spec.Devices[deviceIndex].Name} // Checking for "in use" is cheap and thus gets done first. - if !ptr.Deref(request.AdminAccess, false) && (alloc.allocatedDevices.Has(deviceID) || alloc.allocatingDevices[deviceID]) { + if !request.adminAccess() && (alloc.allocatedDevices.Has(deviceID) || alloc.allocatingDevices[deviceID]) { alloc.logger.V(7).Info("Device in use", "device", deviceID) continue } // Next check selectors. - selectable, err := alloc.isSelectable(requestIndices{claimIndex: r.claimIndex, requestIndex: r.requestIndex}, slice, deviceIndex) + requestKey := requestIndices{claimIndex: r.claimIndex, requestIndex: r.requestIndex, subRequestIndex: r.subRequestIndex} + selectable, err := alloc.isSelectable(requestKey, requestData, slice, deviceIndex) if err != nil { return false, err } @@ -636,7 +816,13 @@ func (alloc *allocator) allocateOne(r deviceIndices) (bool, error) { alloc.logger.V(7).Info("Device not usable", "device", deviceID) continue } - done, err := alloc.allocateOne(deviceIndices{claimIndex: r.claimIndex, requestIndex: r.requestIndex, deviceIndex: r.deviceIndex + 1}) + deviceKey := deviceIndices{ + claimIndex: r.claimIndex, + requestIndex: r.requestIndex, + subRequestIndex: r.subRequestIndex, + deviceIndex: r.deviceIndex + 1, + } + done, err := alloc.allocateOne(deviceKey, allocateSubRequest) if err != nil { return false, err } @@ -657,7 +843,7 @@ func (alloc *allocator) allocateOne(r deviceIndices) (bool, error) { } // isSelectable checks whether a device satisfies the request and class selectors. -func (alloc *allocator) isSelectable(r requestIndices, slice *draapi.ResourceSlice, deviceIndex int) (bool, error) { +func (alloc *allocator) isSelectable(r requestIndices, requestData requestData, slice *draapi.ResourceSlice, deviceIndex int) (bool, error) { // This is the only supported device type at the moment. device := slice.Spec.Devices[deviceIndex].Basic if device == nil { @@ -672,7 +858,6 @@ func (alloc *allocator) isSelectable(r requestIndices, slice *draapi.ResourceSli return matches, nil } - requestData := alloc.requestData[r] if requestData.class != nil { match, err := alloc.selectorsMatch(r, device, deviceID, requestData.class, requestData.class.Spec.Selectors) if err != nil { @@ -684,8 +869,8 @@ func (alloc *allocator) isSelectable(r requestIndices, slice *draapi.ResourceSli } } - request := &alloc.claimsToAllocate[r.claimIndex].Spec.Devices.Requests[r.requestIndex] - match, err := alloc.selectorsMatch(r, device, deviceID, nil, request.Selectors) + request := requestData.request + match, err := alloc.selectorsMatch(r, device, deviceID, nil, request.selectors()) if err != nil { return false, err } @@ -752,26 +937,38 @@ func (alloc *allocator) selectorsMatch(r requestIndices, device *draapi.BasicDev // restore the previous state. func (alloc *allocator) allocateDevice(r deviceIndices, device deviceWithID, must bool) (bool, func(), error) { claim := alloc.claimsToAllocate[r.claimIndex] - request := &claim.Spec.Devices.Requests[r.requestIndex] - adminAccess := ptr.Deref(request.AdminAccess, false) - if !adminAccess && (alloc.allocatedDevices.Has(device.id) || alloc.allocatingDevices[device.id]) { + requestKey := requestIndices{claimIndex: r.claimIndex, requestIndex: r.requestIndex, subRequestIndex: r.subRequestIndex} + requestData := alloc.requestData[requestKey] + request := requestData.request + if !request.adminAccess() && (alloc.allocatedDevices.Has(device.id) || alloc.allocatingDevices[device.id]) { alloc.logger.V(7).Info("Device in use", "device", device.id) return false, nil, nil } + var parentRequestName string + var baseRequestName string + var subRequestName string + if requestData.parentRequest == nil { + baseRequestName = requestData.request.name() + } else { + parentRequestName = requestData.parentRequest.name() + baseRequestName = parentRequestName + subRequestName = requestData.request.name() + } + // It's available. Now check constraints. for i, constraint := range alloc.constraints[r.claimIndex] { - added := constraint.add(request.Name, device.basic, device.id) + added := constraint.add(baseRequestName, subRequestName, device.basic, device.id) if !added { if must { // It does not make sense to declare a claim where a constraint prevents getting // all devices. Treat this as an error. - return false, nil, fmt.Errorf("claim %s, request %s: cannot add device %s because a claim constraint would not be satisfied", klog.KObj(claim), request.Name, device.id) + return false, nil, fmt.Errorf("claim %s, request %s: cannot add device %s because a claim constraint would not be satisfied", klog.KObj(claim), request.name(), device.id) } // Roll back for all previous constraints before we return. for e := 0; e < i; e++ { - alloc.constraints[r.claimIndex][e].remove(request.Name, device.basic, device.id) + alloc.constraints[r.claimIndex][e].remove(baseRequestName, subRequestName, device.basic, device.id) } return false, nil, nil } @@ -780,25 +977,26 @@ func (alloc *allocator) allocateDevice(r deviceIndices, device deviceWithID, mus // All constraints satisfied. Mark as in use (unless we do admin access) // and record the result. alloc.logger.V(7).Info("Device allocated", "device", device.id) - if !adminAccess { + if !request.adminAccess() { alloc.allocatingDevices[device.id] = true } result := internalDeviceResult{ - request: request.Name, - id: device.id, - slice: device.slice, + request: request.name(), + parentRequest: parentRequestName, + id: device.id, + slice: device.slice, } - if adminAccess { - result.adminAccess = &adminAccess + if request.adminAccess() { + result.adminAccess = ptr.To(request.adminAccess()) } previousNumResults := len(alloc.result[r.claimIndex].devices) alloc.result[r.claimIndex].devices = append(alloc.result[r.claimIndex].devices, result) return true, func() { for _, constraint := range alloc.constraints[r.claimIndex] { - constraint.remove(request.Name, device.basic, device.id) + constraint.remove(baseRequestName, subRequestName, device.basic, device.id) } - if !adminAccess { + if !request.adminAccess() { alloc.allocatingDevices[device.id] = false } // Truncate, but keep the underlying slice. @@ -855,6 +1053,88 @@ func (alloc *allocator) createNodeSelector(result []internalDeviceResult) (*v1.N return nil, nil } +// requestAccessor is an interface for accessing either +// DeviceRequests or DeviceSubRequests. It lets most +// of the allocator code work with either DeviceRequests +// or DeviceSubRequests. +type requestAccessor interface { + name() string + deviceClassName() string + allocationMode() resourceapi.DeviceAllocationMode + count() int64 + adminAccess() bool + hasAdminAccess() bool + selectors() []resourceapi.DeviceSelector +} + +// deviceRequestAccessor is an implementation of the +// requestAccessor interface for DeviceRequests. +type deviceRequestAccessor struct { + request *resourceapi.DeviceRequest +} + +func (d *deviceRequestAccessor) name() string { + return d.request.Name +} + +func (d *deviceRequestAccessor) deviceClassName() string { + return d.request.DeviceClassName +} + +func (d *deviceRequestAccessor) allocationMode() resourceapi.DeviceAllocationMode { + return d.request.AllocationMode +} + +func (d *deviceRequestAccessor) count() int64 { + return d.request.Count +} + +func (d *deviceRequestAccessor) adminAccess() bool { + return ptr.Deref(d.request.AdminAccess, false) +} + +func (d *deviceRequestAccessor) hasAdminAccess() bool { + return d.request.AdminAccess != nil +} + +func (d *deviceRequestAccessor) selectors() []resourceapi.DeviceSelector { + return d.request.Selectors +} + +// deviceSubRequestAccessor is an implementation of the +// requestAccessor interface for DeviceSubRequests. +type deviceSubRequestAccessor struct { + subRequest *resourceapi.DeviceSubRequest +} + +func (d *deviceSubRequestAccessor) name() string { + return d.subRequest.Name +} + +func (d *deviceSubRequestAccessor) deviceClassName() string { + return d.subRequest.DeviceClassName +} + +func (d *deviceSubRequestAccessor) allocationMode() resourceapi.DeviceAllocationMode { + return d.subRequest.AllocationMode +} + +func (d *deviceSubRequestAccessor) count() int64 { + return d.subRequest.Count +} + +func (d *deviceSubRequestAccessor) adminAccess() bool { + return false +} + +func (d *deviceSubRequestAccessor) hasAdminAccess() bool { + return false +} + +func (d *deviceSubRequestAccessor) selectors() []resourceapi.DeviceSelector { + return d.subRequest.Selectors +} + func addNewNodeSelectorRequirements(from []v1.NodeSelectorRequirement, to *[]v1.NodeSelectorRequirement) { for _, requirement := range from { if !containsNodeSelectorRequirement(*to, requirement) { diff --git a/staging/src/k8s.io/dynamic-resource-allocation/structured/allocator_test.go b/staging/src/k8s.io/dynamic-resource-allocation/structured/allocator_test.go index 95b59240dec45..52b1f6dc8ed05 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/structured/allocator_test.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/structured/allocator_test.go @@ -41,29 +41,36 @@ import ( ) const ( - region1 = "region-1" - region2 = "region-2" - node1 = "node-1" - node2 = "node-2" - classA = "class-a" - classB = "class-b" - driverA = "driver-a" - driverB = "driver-b" - pool1 = "pool-1" - pool2 = "pool-2" - pool3 = "pool-3" - pool4 = "pool-4" - req0 = "req-0" - req1 = "req-1" - req2 = "req-2" - req3 = "req-3" - claim0 = "claim-0" - claim1 = "claim-1" - slice1 = "slice-1" - slice2 = "slice-2" - device1 = "device-1" - device2 = "device-2" - device3 = "device-3" + region1 = "region-1" + region2 = "region-2" + node1 = "node-1" + node2 = "node-2" + classA = "class-a" + classB = "class-b" + driverA = "driver-a" + driverB = "driver-b" + pool1 = "pool-1" + pool2 = "pool-2" + pool3 = "pool-3" + pool4 = "pool-4" + req0 = "req-0" + req1 = "req-1" + req2 = "req-2" + req3 = "req-3" + subReq0 = "subReq-0" + subReq1 = "subReq-1" + req0SubReq0 = "req-0/subReq-0" + req0SubReq1 = "req-0/subReq-1" + req1SubReq0 = "req-1/subReq-0" + req1SubReq1 = "req-1/subReq-1" + claim0 = "claim-0" + claim1 = "claim-1" + slice1 = "slice-1" + slice2 = "slice-2" + device1 = "device-1" + device2 = "device-2" + device3 = "device-3" + device4 = "device-4" ) func init() { @@ -165,6 +172,24 @@ func request(name, class string, count int64, selectors ...resourceapi.DeviceSel } } +func subRequest(name, class string, count int64, selectors ...resourceapi.DeviceSelector) resourceapi.DeviceSubRequest { + return resourceapi.DeviceSubRequest{ + Name: name, + Count: count, + AllocationMode: resourceapi.DeviceAllocationModeExactCount, + DeviceClassName: class, + Selectors: selectors, + } +} + +// genereate a DeviceRequest with the given name and list of prioritized requests. +func requestWithPrioritizedList(name string, prioritizedRequests ...resourceapi.DeviceSubRequest) resourceapi.DeviceRequest { + return resourceapi.DeviceRequest{ + Name: name, + FirstAvailable: prioritizedRequests, + } +} + // generate a ResourceClaim object with the given name, request and class. func claim(name, req, class string, constraints ...resourceapi.DeviceConstraint) *resourceapi.ResourceClaim { claim := claimWithRequests(name, constraints, request(req, class, 1)) @@ -183,6 +208,19 @@ func claimWithDeviceConfig(name, request, class, driver, attribute string) *reso return claim } +func claimWithAll(name string, requests []resourceapi.DeviceRequest, constraints []resourceapi.DeviceConstraint, configs []resourceapi.DeviceClaimConfiguration) *resourceapi.ResourceClaim { + claim := claimWithRequests(name, constraints, requests...) + claim.Spec.Devices.Config = configs + return claim +} + +func deviceClaimConfig(requests []string, deviceConfig resourceapi.DeviceConfiguration) resourceapi.DeviceClaimConfiguration { + return resourceapi.DeviceClaimConfiguration{ + Requests: requests, + DeviceConfiguration: deviceConfig, + } +} + // generate a Device object with the given name, capacity and attributes. func device(name string, capacity map[resourceapi.QualifiedName]resource.Quantity, attributes map[resourceapi.QualifiedName]resourceapi.DeviceAttribute) resourceapi.Device { device := resourceapi.Device{ @@ -334,6 +372,16 @@ func allocationResultWithConfig(selector *v1.NodeSelector, driver string, source } } +func allocationResultWithConfigs(selector *v1.NodeSelector, results []resourceapi.DeviceRequestAllocationResult, configs []resourceapi.DeviceAllocationConfiguration) resourceapi.AllocationResult { + return resourceapi.AllocationResult{ + Devices: resourceapi.DeviceAllocationResult{ + Results: results, + Config: configs, + }, + NodeSelector: selector, + } +} + // Helpers // convert a list of objects to a slice @@ -351,6 +399,15 @@ func sliceWithOneDevice(name string, nodeSelection any, pool, driver string) *re return slice(name, nodeSelection, pool, driver, device(device1, nil, nil)) } +// generate a ResourceSclie object with the given parameters and the specified number of devices. +func sliceWithMultipleDevices(name string, nodeSelection any, pool, driver string, count int) *resourceapi.ResourceSlice { + var devices []resourceapi.Device + for i := 0; i < count; i++ { + devices = append(devices, device(fmt.Sprintf("device-%d", i), nil, nil)) + } + return slice(name, nodeSelection, pool, driver, devices...) +} + func TestAllocator(t *testing.T) { nonExistentAttribute := resourceapi.FullyQualifiedName(driverA + "/" + "NonExistentAttribute") boolAttribute := resourceapi.FullyQualifiedName(driverA + "/" + "boolAttribute") @@ -360,6 +417,7 @@ func TestAllocator(t *testing.T) { testcases := map[string]struct { adminAccess bool + prioritizedList bool claimsToAllocate []*resourceapi.ResourceClaim allocatedDevices []DeviceID classes []*resourceapi.DeviceClass @@ -917,6 +975,86 @@ func TestAllocator(t *testing.T) { deviceAllocationResult(req0, driverA, pool1, device2, true), )}, }, + "all-devices-slice-without-devices-prioritized-list": { + prioritizedList: true, + claimsToAllocate: objects( + func() *resourceapi.ResourceClaim { + claim := claimWithRequests(claim0, nil, + requestWithPrioritizedList(req0, + subRequest(subReq0, classA, 1), + subRequest(subReq1, classB, 1), + ), + ) + claim.Spec.Devices.Requests[0].FirstAvailable[0].AllocationMode = resourceapi.DeviceAllocationModeAll + claim.Spec.Devices.Requests[0].FirstAvailable[0].Count = 0 + return claim + }(), + ), + classes: objects(class(classA, driverA), class(classB, driverB)), + slices: objects( + sliceWithNoDevices(slice1, node1, pool1, driverA), + sliceWithOneDevice(slice2, node1, pool2, driverB), + ), + node: node(node1, region1), + expectResults: []any{allocationResult( + localNodeSelector(node1), + deviceAllocationResult(req0SubReq1, driverB, pool2, device1, false), + )}, + }, + "all-devices-no-slices-prioritized-list": { + prioritizedList: true, + claimsToAllocate: objects( + func() *resourceapi.ResourceClaim { + claim := claimWithRequests(claim0, nil, + requestWithPrioritizedList(req0, + subRequest(subReq0, classA, 1), + subRequest(subReq1, classB, 1), + ), + ) + claim.Spec.Devices.Requests[0].FirstAvailable[0].AllocationMode = resourceapi.DeviceAllocationModeAll + claim.Spec.Devices.Requests[0].FirstAvailable[0].Count = 0 + return claim + }(), + ), + classes: objects(class(classA, driverA), class(classB, driverB)), + slices: objects( + sliceWithOneDevice(slice2, node1, pool2, driverB), + ), + node: node(node1, region1), + expectResults: []any{allocationResult( + localNodeSelector(node1), + deviceAllocationResult(req0SubReq1, driverB, pool2, device1, false), + )}, + }, + "all-devices-some-allocated-prioritized-list": { + prioritizedList: true, + claimsToAllocate: objects( + func() *resourceapi.ResourceClaim { + claim := claimWithRequests(claim0, nil, + requestWithPrioritizedList(req0, + subRequest(subReq0, classA, 1), + subRequest(subReq1, classB, 1), + ), + ) + claim.Spec.Devices.Requests[0].FirstAvailable[0].AllocationMode = resourceapi.DeviceAllocationModeAll + claim.Spec.Devices.Requests[0].FirstAvailable[0].Count = 0 + return claim + }(), + ), + allocatedDevices: []DeviceID{ + MakeDeviceID(driverA, pool1, device1), + }, + classes: objects(class(classA, driverA), class(classB, driverB)), + slices: objects( + slice(slice1, node1, pool1, driverA, device(device1, nil, nil), device(device2, nil, nil)), + sliceWithOneDevice(slice2, node1, pool2, driverB), + ), + node: node(node1, region1), + expectResults: []any{allocationResult( + localNodeSelector(node1), + deviceAllocationResult(req0SubReq1, driverB, pool2, device1, false), + )}, + }, "network-attached-device": { claimsToAllocate: objects(claim(claim0, req0, classA)), classes: objects(class(classA, driverA)), @@ -1417,6 +1555,8 @@ func TestAllocator(t *testing.T) { ), ), classes: objects(class(classA, driverA)), + slices: objects(sliceWithMultipleDevices(slice1, node1, pool1, driverA, resourceapi.AllocationResultsMaxSize+1)), + node: node(node1, region1), expectError: gomega.MatchError(gomega.ContainSubstring("exceeds the claim limit")), }, @@ -1426,6 +1566,478 @@ func TestAllocator(t *testing.T) { expectError: gomega.MatchError(gomega.ContainSubstring("exceeds the claim limit")), }, + "prioritized-list-first-unavailable": { + prioritizedList: true, + claimsToAllocate: objects(claimWithRequests(claim0, nil, requestWithPrioritizedList(req0, + subRequest(subReq0, classB, 1), + subRequest(subReq1, classA, 1), + ))), + classes: objects(class(classA, driverA), class(classB, driverB)), + slices: objects(sliceWithOneDevice(slice1, node1, pool1, driverA)), + node: node(node1, region1), + + expectResults: []any{allocationResult( + localNodeSelector(node1), + deviceAllocationResult(req0SubReq1, driverA, pool1, device1, false), + )}, + }, + "prioritized-list-non-available": { + prioritizedList: true, + claimsToAllocate: objects(claimWithRequests(claim0, nil, requestWithPrioritizedList(req0, + subRequest(subReq0, classB, 2), + subRequest(subReq1, classA, 2), + ))), + classes: objects(class(classA, driverA), class(classB, driverB)), + slices: objects( + sliceWithOneDevice(slice1, node1, pool1, driverA), + sliceWithOneDevice(slice2, node1, pool2, driverB), + ), + node: node(node1, region1), + + expectResults: nil, + }, + "prioritized-list-device-config": { + prioritizedList: true, + claimsToAllocate: objects( + claimWithAll(claim0, + objects( + requestWithPrioritizedList(req0, + subRequest(subReq0, classA, 1), + subRequest(subReq1, classB, 2), + ), + ), + nil, + objects( + deviceClaimConfig([]string{req0SubReq0}, deviceConfiguration(driverA, "foo")), + deviceClaimConfig([]string{req0SubReq1}, deviceConfiguration(driverB, "bar")), + ), + ), + ), + classes: objects(class(classA, driverA), class(classB, driverB)), + slices: objects(slice(slice1, node1, pool1, driverB, + device(device1, nil, map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{}), + device(device2, nil, map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{}), + )), + node: node(node1, region1), + + expectResults: []any{allocationResultWithConfigs( + localNodeSelector(node1), + objects( + deviceAllocationResult(req0SubReq1, driverB, pool1, device1, false), + deviceAllocationResult(req0SubReq1, driverB, pool1, device2, false), + ), + []resourceapi.DeviceAllocationConfiguration{ + { + Source: resourceapi.AllocationConfigSourceClaim, + Requests: []string{ + req0SubReq1, + }, + DeviceConfiguration: deviceConfiguration(driverB, "bar"), + }, + }, + )}, + }, + "prioritized-list-class-config": { + prioritizedList: true, + claimsToAllocate: objects(claimWithRequests(claim0, nil, requestWithPrioritizedList(req0, + subRequest(subReq0, classA, 2), + subRequest(subReq1, classB, 2), + ))), + classes: objects( + classWithConfig(classA, driverA, "foo"), + classWithConfig(classB, driverB, "bar"), + ), + slices: objects( + slice(slice1, node1, pool1, driverB, + device(device1, nil, nil), + device(device2, nil, nil), + ), + slice(slice2, node1, pool2, driverA, + device(device3, nil, nil), + ), + ), + node: node(node1, region1), + + expectResults: []any{allocationResultWithConfigs( + localNodeSelector(node1), + objects( + deviceAllocationResult(req0SubReq1, driverB, pool1, device1, false), + deviceAllocationResult(req0SubReq1, driverB, pool1, device2, false), + ), + []resourceapi.DeviceAllocationConfiguration{ + { + Source: resourceapi.AllocationConfigSourceClass, + Requests: nil, + DeviceConfiguration: deviceConfiguration(driverB, "bar"), + }, + }, + )}, + }, + "prioritized-list-subrequests-with-expressions": { + prioritizedList: true, + claimsToAllocate: objects( + claimWithRequests(claim0, nil, + request(req0, classA, 1, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("1Gi")) >= 0`, driverA), + }}, + ), + requestWithPrioritizedList(req1, + subRequest(subReq0, classA, 1, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("4Gi")) >= 0`, driverA), + }}), + subRequest(subReq1, classA, 2, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("2Gi")) >= 0`, driverA), + }}), + ), + ), + ), + classes: objects(class(classA, driverA)), + slices: objects(slice(slice1, node1, pool1, driverA, + device(device1, map[resourceapi.QualifiedName]resource.Quantity{ + "memory": resource.MustParse("2Gi"), + }, nil), + device(device2, map[resourceapi.QualifiedName]resource.Quantity{ + "memory": resource.MustParse("2Gi"), + }, nil), + device(device3, map[resourceapi.QualifiedName]resource.Quantity{ + "memory": resource.MustParse("1Gi"), + }, nil), + )), + node: node(node1, region1), + + expectResults: []any{allocationResult( + localNodeSelector(node1), + deviceAllocationResult(req0, driverA, pool1, device3, false), + deviceAllocationResult(req1SubReq1, driverA, pool1, device1, false), + deviceAllocationResult(req1SubReq1, driverA, pool1, device2, false), + )}, + }, + "prioritized-list-subrequests-with-constraints-ref-parent-request": { + prioritizedList: true, + claimsToAllocate: objects( + claimWithRequests(claim0, + []resourceapi.DeviceConstraint{ + { + Requests: []string{req0, req1}, + MatchAttribute: &versionAttribute, + }, + }, + request(req0, classA, 1, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("8Gi")) >= 0`, driverA), + }}, + ), + requestWithPrioritizedList(req1, + subRequest(subReq0, classA, 1, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("2Gi")) >= 0`, driverA), + }}, + ), + subRequest(subReq1, classA, 1, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("1Gi")) >= 0`, driverA), + }}, + ), + ), + ), + ), + classes: objects(class(classA, driverA)), + slices: objects( + slice(slice1, node1, pool1, driverA, + device(device1, + map[resourceapi.QualifiedName]resource.Quantity{ + "memory": resource.MustParse("8Gi"), + }, + map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + "driverVersion": {VersionValue: ptr.To("1.0.0")}, + }, + ), + ), + slice(slice2, node1, pool2, driverA, + device(device2, + map[resourceapi.QualifiedName]resource.Quantity{ + "memory": resource.MustParse("2Gi"), + }, + map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + "driverVersion": {VersionValue: ptr.To("2.0.0")}, + }, + ), + device(device3, + map[resourceapi.QualifiedName]resource.Quantity{ + "memory": resource.MustParse("1Gi"), + }, + map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + "driverVersion": {VersionValue: ptr.To("1.0.0")}, + }, + ), + ), + ), + node: node(node1, region1), + + expectResults: []any{allocationResult( + localNodeSelector(node1), + deviceAllocationResult(req0, driverA, pool1, device1, false), + deviceAllocationResult(req1SubReq1, driverA, pool2, device3, false), + )}, + }, + "prioritized-list-subrequests-with-constraints-ref-sub-request": { + prioritizedList: true, + claimsToAllocate: objects( + claimWithRequests(claim0, + []resourceapi.DeviceConstraint{ + { + Requests: []string{req0, req1SubReq0}, + MatchAttribute: &versionAttribute, + }, + }, + request(req0, classA, 1, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("8Gi")) >= 0`, driverA), + }}, + ), + requestWithPrioritizedList(req1, + subRequest(subReq0, classA, 1, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("2Gi")) >= 0`, driverA), + }}, + ), + subRequest(subReq1, classA, 1, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("1Gi")) >= 0`, driverA), + }}, + ), + ), + ), + ), + classes: objects(class(classA, driverA)), + slices: objects( + slice(slice1, node1, pool1, driverA, + device(device1, + map[resourceapi.QualifiedName]resource.Quantity{ + "memory": resource.MustParse("8Gi"), + }, + map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + "driverVersion": {VersionValue: ptr.To("1.0.0")}, + }, + ), + ), + slice(slice2, node1, pool2, driverA, + device(device2, + map[resourceapi.QualifiedName]resource.Quantity{ + "memory": resource.MustParse("2Gi"), + }, + map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + "driverVersion": {VersionValue: ptr.To("2.0.0")}, + }, + ), + ), + ), + node: node(node1, region1), + + expectResults: []any{allocationResult( + localNodeSelector(node1), + deviceAllocationResult(req0, driverA, pool1, device1, false), + deviceAllocationResult(req1SubReq1, driverA, pool2, device2, false), + )}, + }, + "prioritized-list-subrequests-with-allocation-mode-all": { + prioritizedList: true, + claimsToAllocate: objects( + func() *resourceapi.ResourceClaim { + claim := claimWithRequests(claim0, nil, + requestWithPrioritizedList(req0, + subRequest(subReq0, classA, 1), + subRequest(subReq1, classA, 1), + ), + ) + claim.Spec.Devices.Requests[0].FirstAvailable[0].AllocationMode = resourceapi.DeviceAllocationModeAll + claim.Spec.Devices.Requests[0].FirstAvailable[0].Count = 0 + return claim + }(), + ), + classes: objects(class(classA, driverA)), + slices: objects( + slice(slice1, node1, pool1, driverA, + device(device1, nil, nil), + device(device2, nil, nil), + ), + ), + allocatedDevices: []DeviceID{ + MakeDeviceID(driverA, pool1, device1), + }, + node: node(node1, region1), + + expectResults: []any{allocationResult( + localNodeSelector(node1), + deviceAllocationResult(req0SubReq1, driverA, pool1, device2, false), + )}, + }, + "prioritized-list-allocation-mode-all-multiple-requests": { + prioritizedList: true, + claimsToAllocate: objects( + claimWithRequests(claim0, nil, + request(req0, classA, 1), + requestWithPrioritizedList(req1, + func() resourceapi.DeviceSubRequest { + subReq := subRequest(subReq0, classA, 1) + subReq.AllocationMode = resourceapi.DeviceAllocationModeAll + subReq.Count = 0 + return subReq + }(), + subRequest(subReq1, classA, 1), + ), + ), + ), + classes: objects(class(classA, driverA)), + slices: objects( + slice(slice1, node1, pool1, driverA, + device(device1, nil, nil), + device(device2, nil, nil), + ), + ), + node: node(node1, region1), + + expectResults: []any{allocationResult( + localNodeSelector(node1), + deviceAllocationResult(req0, driverA, pool1, device1, false), + deviceAllocationResult(req1SubReq1, driverA, pool1, device2, false), + )}, + }, + "prioritized-list-disabled": { + prioritizedList: false, + claimsToAllocate: objects( + func() *resourceapi.ResourceClaim { + claim := claimWithRequests(claim0, nil, + requestWithPrioritizedList(req0, + subRequest(subReq0, classA, 1), + ), + ) + return claim + }(), + ), + classes: objects(class(classA, driverA)), + slices: objects(sliceWithOneDevice(slice1, node1, pool1, driverA)), + node: node(node1, region1), + + expectResults: nil, + expectError: gomega.MatchError(gomega.ContainSubstring("claim claim-0, request req-0: has subrequests, but the feature is disabled")), + }, + "prioritized-list-multi-request": { + prioritizedList: true, + claimsToAllocate: objects( + claimWithRequests(claim0, nil, + request(req1, classA, 1, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("8Gi")) >= 0`, driverA), + }}, + ), + requestWithPrioritizedList(req0, + subRequest(subReq0, classA, 1, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("8Gi")) >= 0`, driverA), + }}, + ), + subRequest(subReq1, classA, 1, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("4Gi")) >= 0`, driverA), + }}, + ), + ), + ), + ), + classes: objects(class(classA, driverA)), + slices: objects( + slice(slice1, node1, pool1, driverA, + device(device1, + map[resourceapi.QualifiedName]resource.Quantity{ + "memory": resource.MustParse("8Gi"), + }, + map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{}, + ), + ), + slice(slice2, node1, pool2, driverA, + device(device2, + map[resourceapi.QualifiedName]resource.Quantity{ + "memory": resource.MustParse("4Gi"), + }, + map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{}, + ), + ), + ), + node: node(node1, region1), + expectResults: []any{allocationResult( + localNodeSelector(node1), + deviceAllocationResult(req1, driverA, pool1, device1, false), + deviceAllocationResult(req0SubReq1, driverA, pool2, device2, false), + )}, + }, + "prioritized-list-with-backtracking": { + prioritizedList: true, + claimsToAllocate: objects( + claimWithRequests(claim0, nil, + requestWithPrioritizedList(req0, + subRequest(subReq0, classA, 1, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("8Gi")) >= 0`, driverA), + }}, + ), + subRequest(subReq1, classA, 1, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("4Gi")) >= 0`, driverA), + }}, + ), + ), + request(req1, classA, 1, resourceapi.DeviceSelector{ + CEL: &resourceapi.CELDeviceSelector{ + Expression: fmt.Sprintf(`device.capacity["%s"].memory.compareTo(quantity("8Gi")) >= 0`, driverA), + }}, + ), + ), + ), + classes: objects(class(classA, driverA)), + slices: objects( + slice(slice1, node1, pool1, driverA, + device(device1, + map[resourceapi.QualifiedName]resource.Quantity{ + "memory": resource.MustParse("8Gi"), + }, + map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{}, + ), + ), + slice(slice2, node1, pool2, driverA, + device(device2, + map[resourceapi.QualifiedName]resource.Quantity{ + "memory": resource.MustParse("4Gi"), + }, + map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{}, + ), + ), + ), + node: node(node1, region1), + expectResults: []any{allocationResult( + localNodeSelector(node1), + deviceAllocationResult(req0SubReq1, driverA, pool2, device2, false), + deviceAllocationResult(req1, driverA, pool1, device1, false), + )}, + }, + "prioritized-list-too-many-in-first-subrequest": { + prioritizedList: true, + claimsToAllocate: objects(claimWithRequests(claim0, nil, requestWithPrioritizedList(req0, + subRequest(subReq0, classB, 500), + subRequest(subReq1, classA, 1), + ))), + classes: objects(class(classA, driverA), class(classB, driverB)), + slices: objects(sliceWithOneDevice(slice1, node1, pool1, driverA)), + node: node(node1, region1), + + expectResults: []any{allocationResult( + localNodeSelector(node1), + deviceAllocationResult(req0SubReq1, driverA, pool1, device1, false), + )}, + }, } for name, tc := range testcases { @@ -1444,7 +2056,7 @@ func TestAllocator(t *testing.T) { allocatedDevices := slices.Clone(tc.allocatedDevices) slices := slices.Clone(tc.slices) - allocator, err := NewAllocator(ctx, tc.adminAccess, claimsToAllocate, sets.New(allocatedDevices...), classLister, slices, cel.NewCache(1)) + allocator, err := NewAllocator(ctx, tc.adminAccess, tc.prioritizedList, claimsToAllocate, sets.New(allocatedDevices...), classLister, slices, cel.NewCache(1)) g.Expect(err).ToNot(gomega.HaveOccurred()) results, err := allocator.Allocate(ctx, tc.node) diff --git a/test/e2e/dra/dra.go b/test/e2e/dra/dra.go index 4b8250f2fb7e9..e03479ecaa29e 100644 --- a/test/e2e/dra/dra.go +++ b/test/e2e/dra/dra.go @@ -863,6 +863,411 @@ var _ = framework.SIGDescribe("node")("DRA", feature.DynamicResourceAllocation, }) } + prioritizedListTests := func() { + nodes := NewNodes(f, 1, 1) + + driver1Params, driver1Env := `{"driver":"1"}`, []string{"admin_driver", "1"} + driver2Params, driver2Env := `{"driver":"2"}`, []string{"admin_driver", "2"} + + driver1 := NewDriver(f, nodes, perNode(-1, nodes), []map[string]map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + { + "device-1-1": { + "dra.example.com/version": {StringValue: ptr.To("1.0.0")}, + "dra.example.com/pcieRoot": {StringValue: ptr.To("bar")}, + }, + "device-1-2": { + "dra.example.com/version": {StringValue: ptr.To("2.0.0")}, + "dra.example.com/pcieRoot": {StringValue: ptr.To("foo")}, + }, + }, + }...) + driver1.NameSuffix = "-1" + b1 := newBuilder(f, driver1) + b1.classParameters = driver1Params + + driver2 := NewDriver(f, nodes, perNode(-1, nodes), []map[string]map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{ + { + "device-2-1": { + "dra.example.com/version": {StringValue: ptr.To("1.0.0")}, + "dra.example.com/pcieRoot": {StringValue: ptr.To("foo")}, + }, + }, + }...) + driver2.NameSuffix = "-2" + b2 := newBuilder(f, driver2) + b2.classParameters = driver2Params + + f.It("selects the first subrequest that can be satisfied", feature.DRAPrioritizedList, func(ctx context.Context) { + name := "external-multiclaim" + params := `{"a":"b"}` + claim := &resourceapi.ResourceClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: resourceapi.ResourceClaimSpec{ + Devices: resourceapi.DeviceClaim{ + Requests: []resourceapi.DeviceRequest{{ + Name: "request-1", + FirstAvailable: []resourceapi.DeviceSubRequest{ + { + Name: "sub-request-1", + DeviceClassName: b1.className(), + AllocationMode: resourceapi.DeviceAllocationModeExactCount, + Count: 3, + }, + { + Name: "sub-request-2", + DeviceClassName: b1.className(), + AllocationMode: resourceapi.DeviceAllocationModeExactCount, + Count: 2, + }, + { + Name: "sub-request-3", + DeviceClassName: b1.className(), + AllocationMode: resourceapi.DeviceAllocationModeExactCount, + Count: 1, + }, + }, + }}, + Config: []resourceapi.DeviceClaimConfiguration{ + { + Requests: []string{"request-1"}, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: b1.driver.Name, + Parameters: runtime.RawExtension{ + Raw: []byte(params), + }, + }, + }, + }, + }, + }, + }, + } + pod := b1.podExternal() + podClaimName := "resource-claim" + externalClaimName := "external-multiclaim" + pod.Spec.ResourceClaims = []v1.PodResourceClaim{ + { + Name: podClaimName, + ResourceClaimName: &externalClaimName, + }, + } + b1.create(ctx, claim, pod) + b1.testPod(ctx, f, pod) + + var allocatedResourceClaim *resourceapi.ResourceClaim + gomega.Eventually(ctx, func(ctx context.Context) (*resourceapi.ResourceClaim, error) { + var err error + allocatedResourceClaim, err = f.ClientSet.ResourceV1beta1().ResourceClaims(f.Namespace.Name).Get(ctx, claim.Name, metav1.GetOptions{}) + return allocatedResourceClaim, err + }).WithTimeout(f.Timeouts.PodDelete).ShouldNot(gomega.HaveField("Status.Allocation", (*resourceapi.AllocationResult)(nil))) + results := allocatedResourceClaim.Status.Allocation.Devices.Results + gomega.Expect(results).To(gomega.HaveLen(2)) + gomega.Expect(results[0].Request).To(gomega.Equal("request-1/sub-request-2")) + gomega.Expect(results[1].Request).To(gomega.Equal("request-1/sub-request-2")) + }) + + f.It("uses the config for the selected subrequest", feature.DRAPrioritizedList, func(ctx context.Context) { + name := "external-multiclaim" + parentReqParams, parentReqEnv := `{"a":"b"}`, []string{"user_a", "b"} + subReq1Params := `{"c":"d"}` + subReq2Params, subReq2Env := `{"e":"f"}`, []string{"user_e", "f"} + claim := &resourceapi.ResourceClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: resourceapi.ResourceClaimSpec{ + Devices: resourceapi.DeviceClaim{ + Requests: []resourceapi.DeviceRequest{{ + Name: "request-1", + FirstAvailable: []resourceapi.DeviceSubRequest{ + { + Name: "sub-request-1", + DeviceClassName: b1.className(), + AllocationMode: resourceapi.DeviceAllocationModeExactCount, + Count: 3, + }, + { + Name: "sub-request-2", + DeviceClassName: b1.className(), + AllocationMode: resourceapi.DeviceAllocationModeExactCount, + Count: 2, + }, + }, + }}, + Config: []resourceapi.DeviceClaimConfiguration{ + { + Requests: []string{"request-1"}, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: b1.driver.Name, + Parameters: runtime.RawExtension{ + Raw: []byte(parentReqParams), + }, + }, + }, + }, + { + Requests: []string{"request-1/sub-request-1"}, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: b1.driver.Name, + Parameters: runtime.RawExtension{ + Raw: []byte(subReq1Params), + }, + }, + }, + }, + { + Requests: []string{"request-1/sub-request-2"}, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: b1.driver.Name, + Parameters: runtime.RawExtension{ + Raw: []byte(subReq2Params), + }, + }, + }, + }, + }, + }, + }, + } + pod := b1.podExternal() + podClaimName := "resource-claim" + externalClaimName := "external-multiclaim" + pod.Spec.ResourceClaims = []v1.PodResourceClaim{ + { + Name: podClaimName, + ResourceClaimName: &externalClaimName, + }, + } + b1.create(ctx, claim, pod) + var expectedEnv []string + expectedEnv = append(expectedEnv, parentReqEnv...) + expectedEnv = append(expectedEnv, subReq2Env...) + b1.testPod(ctx, f, pod, expectedEnv...) + }) + + f.It("chooses the correct subrequest subject to constraints", feature.DRAPrioritizedList, func(ctx context.Context) { + name := "external-multiclaim" + params := `{"a":"b"}` + claim := &resourceapi.ResourceClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: resourceapi.ResourceClaimSpec{ + Devices: resourceapi.DeviceClaim{ + Requests: []resourceapi.DeviceRequest{ + { + Name: "request-1", + FirstAvailable: []resourceapi.DeviceSubRequest{ + { + Name: "sub-request-1", + DeviceClassName: b1.className(), + AllocationMode: resourceapi.DeviceAllocationModeExactCount, + Count: 1, + }, + { + Name: "sub-request-2", + DeviceClassName: b1.className(), + AllocationMode: resourceapi.DeviceAllocationModeExactCount, + Count: 1, + }, + }, + }, + { + Name: "request-2", + DeviceClassName: b2.className(), + AllocationMode: resourceapi.DeviceAllocationModeExactCount, + Count: 1, + }, + }, + Constraints: []resourceapi.DeviceConstraint{ + { + Requests: []string{"request-1", "request-2"}, + MatchAttribute: ptr.To(resourceapi.FullyQualifiedName("dra.example.com/version")), + }, + { + Requests: []string{"request-1/sub-request-1", "request-2"}, + MatchAttribute: ptr.To(resourceapi.FullyQualifiedName("dra.example.com/pcieRoot")), + }, + }, + Config: []resourceapi.DeviceClaimConfiguration{ + { + Requests: []string{}, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: b1.driver.Name, + Parameters: runtime.RawExtension{ + Raw: []byte(params), + }, + }, + }, + }, + }, + }, + }, + } + pod := b1.podExternal() + podClaimName := "resource-claim" + externalClaimName := "external-multiclaim" + pod.Spec.ResourceClaims = []v1.PodResourceClaim{ + { + Name: podClaimName, + ResourceClaimName: &externalClaimName, + }, + } + b1.create(ctx, claim, pod) + b1.testPod(ctx, f, pod) + + var allocatedResourceClaim *resourceapi.ResourceClaim + gomega.Eventually(ctx, func(ctx context.Context) (*resourceapi.ResourceClaim, error) { + var err error + allocatedResourceClaim, err = f.ClientSet.ResourceV1beta1().ResourceClaims(f.Namespace.Name).Get(ctx, claim.Name, metav1.GetOptions{}) + return allocatedResourceClaim, err + }).WithTimeout(f.Timeouts.PodDelete).ShouldNot(gomega.HaveField("Status.Allocation", (*resourceapi.AllocationResult)(nil))) + results := allocatedResourceClaim.Status.Allocation.Devices.Results + gomega.Expect(results).To(gomega.HaveLen(2)) + gomega.Expect(results[0].Request).To(gomega.Equal("request-1/sub-request-2")) + gomega.Expect(results[1].Request).To(gomega.Equal("request-2")) + }) + + f.It("filters config correctly for multiple devices", feature.DRAPrioritizedList, func(ctx context.Context) { + name := "external-multiclaim" + req1Params, req1Env := `{"a":"b"}`, []string{"user_a", "b"} + req1subReq1Params, _ := `{"c":"d"}`, []string{"user_d", "d"} + req1subReq2Params, req1subReq2Env := `{"e":"f"}`, []string{"user_e", "f"} + req2Params, req2Env := `{"g":"h"}`, []string{"user_g", "h"} + claim := &resourceapi.ResourceClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: resourceapi.ResourceClaimSpec{ + Devices: resourceapi.DeviceClaim{ + Requests: []resourceapi.DeviceRequest{ + { + Name: "request-1", + FirstAvailable: []resourceapi.DeviceSubRequest{ + { + Name: "sub-request-1", + DeviceClassName: b1.className(), + AllocationMode: resourceapi.DeviceAllocationModeExactCount, + Count: 20, // Requests more than are available. + }, + { + Name: "sub-request-2", + DeviceClassName: b1.className(), + AllocationMode: resourceapi.DeviceAllocationModeExactCount, + Count: 1, + }, + }, + }, + { + Name: "request-2", + DeviceClassName: b2.className(), + AllocationMode: resourceapi.DeviceAllocationModeExactCount, + Count: 1, + }, + }, + Config: []resourceapi.DeviceClaimConfiguration{ + { + Requests: []string{"request-1"}, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: b1.driver.Name, + Parameters: runtime.RawExtension{ + Raw: []byte(req1Params), + }, + }, + }, + }, + { + Requests: []string{"request-1/sub-request-1"}, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: b1.driver.Name, + Parameters: runtime.RawExtension{ + Raw: []byte(req1subReq1Params), + }, + }, + }, + }, + { + Requests: []string{"request-1/sub-request-2"}, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: b1.driver.Name, + Parameters: runtime.RawExtension{ + Raw: []byte(req1subReq2Params), + }, + }, + }, + }, + { + Requests: []string{"request-2"}, + DeviceConfiguration: resourceapi.DeviceConfiguration{ + Opaque: &resourceapi.OpaqueDeviceConfiguration{ + Driver: b2.driver.Name, + Parameters: runtime.RawExtension{ + Raw: []byte(req2Params), + }, + }, + }, + }, + }, + }, + }, + } + pod := b1.pod() + pod.Spec.Containers = append(pod.Spec.Containers, *pod.Spec.Containers[0].DeepCopy()) + pod.Spec.Containers[0].Name = "with-resource-0" + pod.Spec.Containers[1].Name = "with-resource-1" + pod.Spec.ResourceClaims = []v1.PodResourceClaim{ + { + Name: name, + ResourceClaimName: &name, + }, + } + pod.Spec.Containers[0].Resources.Claims = []v1.ResourceClaim{{Name: name, Request: "request-1"}} + pod.Spec.Containers[1].Resources.Claims = []v1.ResourceClaim{{Name: name, Request: "request-2"}} + + b1.create(ctx, claim, pod) + err := e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod) + framework.ExpectNoError(err, "start pod") + + var allocatedResourceClaim *resourceapi.ResourceClaim + gomega.Eventually(ctx, func(ctx context.Context) (*resourceapi.ResourceClaim, error) { + var err error + allocatedResourceClaim, err = f.ClientSet.ResourceV1beta1().ResourceClaims(f.Namespace.Name).Get(ctx, claim.Name, metav1.GetOptions{}) + return allocatedResourceClaim, err + }).WithTimeout(f.Timeouts.PodDelete).ShouldNot(gomega.HaveField("Status.Allocation", (*resourceapi.AllocationResult)(nil))) + results := allocatedResourceClaim.Status.Allocation.Devices.Results + gomega.Expect(results).To(gomega.HaveLen(2)) + gomega.Expect(results[0].Request).To(gomega.Equal("request-1/sub-request-2")) + gomega.Expect(results[1].Request).To(gomega.Equal("request-2")) + + req1ExpectedEnv := []string{ + "claim_external_multiclaim_request_1", + "true", + } + req1ExpectedEnv = append(req1ExpectedEnv, req1Env...) + req1ExpectedEnv = append(req1ExpectedEnv, req1subReq2Env...) + req1ExpectedEnv = append(req1ExpectedEnv, driver1Env...) + testContainerEnv(ctx, f, pod, "with-resource-0", true, req1ExpectedEnv...) + + req2ExpectedEnv := []string{ + "claim_external_multiclaim_request_2", + "true", + } + req2ExpectedEnv = append(req2ExpectedEnv, req2Env...) + req2ExpectedEnv = append(req2ExpectedEnv, driver2Env...) + testContainerEnv(ctx, f, pod, "with-resource-1", true, req2ExpectedEnv...) + }) + } + ginkgo.Context("on single node", func() { singleNodeTests() }) @@ -871,6 +1276,10 @@ var _ = framework.SIGDescribe("node")("DRA", feature.DynamicResourceAllocation, multiNodeTests() }) + ginkgo.Context("with prioritized list", func() { + prioritizedListTests() + }) + // TODO (https://github.com/kubernetes/kubernetes/issues/123699): move most of the test below into `testDriver` so that they get // executed with different parameters. diff --git a/test/e2e/dra/test-driver/app/kubeletplugin.go b/test/e2e/dra/test-driver/app/kubeletplugin.go index 9fec1b9c9130c..863477c54e741 100644 --- a/test/e2e/dra/test-driver/app/kubeletplugin.go +++ b/test/e2e/dra/test-driver/app/kubeletplugin.go @@ -24,7 +24,6 @@ import ( "os" "path/filepath" "regexp" - "slices" "sort" "strings" "sync" @@ -38,6 +37,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/kubernetes" "k8s.io/dynamic-resource-allocation/kubeletplugin" + "k8s.io/dynamic-resource-allocation/resourceclaim" "k8s.io/dynamic-resource-allocation/resourceslice" "k8s.io/klog/v2" drapbv1alpha4 "k8s.io/kubelet/pkg/apis/dra/v1alpha4" @@ -103,7 +103,8 @@ var _ drapb.DRAPluginServer = &ExamplePlugin{} // getJSONFilePath returns the absolute path where CDI file is/should be. func (ex *ExamplePlugin) getJSONFilePath(claimUID string, requestName string) string { - return filepath.Join(ex.cdiDir, fmt.Sprintf("%s-%s-%s.json", ex.driverName, claimUID, requestName)) + baseRequestRef := resourceclaim.BaseRequestRef(requestName) + return filepath.Join(ex.cdiDir, fmt.Sprintf("%s-%s-%s.json", ex.driverName, claimUID, baseRequestRef)) } // FileOperations defines optional callbacks for handling CDI files @@ -328,15 +329,20 @@ func (ex *ExamplePlugin) nodePrepareResource(ctx context.Context, claimReq *drap var devices []Device for _, result := range claim.Status.Allocation.Devices.Results { - requestName := result.Request + // Only handle allocations for the current driver. + if ex.driverName != result.Driver { + continue + } + + baseRequestName := resourceclaim.BaseRequestRef(result.Request) // The driver joins all env variables in the order in which // they appear in results (last one wins). + configs := resourceclaim.ConfigForResult(claim.Status.Allocation.Devices.Config, result) env := make(map[string]string) - for i, config := range claim.Status.Allocation.Devices.Config { - if config.Opaque == nil || - config.Opaque.Driver != ex.driverName || - len(config.Requests) > 0 && !slices.Contains(config.Requests, requestName) { + for i, config := range configs { + // Only use configs for the current driver. + if config.Opaque.Driver != ex.driverName { continue } if err := extractParameters(config.Opaque.Parameters, &env, config.Source == resourceapi.AllocationConfigSourceClass); err != nil { @@ -346,11 +352,11 @@ func (ex *ExamplePlugin) nodePrepareResource(ctx context.Context, claimReq *drap // It also sets a claim__=true env variable. // This can be used to identify which devices where mapped into a container. - claimReqName := "claim_" + claim.Name + "_" + requestName + claimReqName := "claim_" + claim.Name + "_" + baseRequestName claimReqName = regexp.MustCompile(`[^a-zA-Z0-9]`).ReplaceAllString(claimReqName, "_") env[claimReqName] = "true" - deviceName := "claim-" + claimReq.UID + "-" + requestName + deviceName := "claim-" + claimReq.UID + "-" + baseRequestName vendor := ex.driverName class := "test" cdiDeviceID := vendor + "/" + class + "=" + deviceName @@ -385,7 +391,7 @@ func (ex *ExamplePlugin) nodePrepareResource(ctx context.Context, claimReq *drap }, }, } - filePath := ex.getJSONFilePath(claimReq.UID, requestName) + filePath := ex.getJSONFilePath(claimReq.UID, baseRequestName) buffer, err := json.Marshal(spec) if err != nil { return nil, fmt.Errorf("marshal spec: %w", err) @@ -396,7 +402,7 @@ func (ex *ExamplePlugin) nodePrepareResource(ctx context.Context, claimReq *drap device := Device{ PoolName: result.Pool, DeviceName: result.Device, - RequestName: requestName, + RequestName: baseRequestName, CDIDeviceID: cdiDeviceID, } devices = append(devices, device) diff --git a/test/e2e/feature/feature.go b/test/e2e/feature/feature.go index 9df5edabfabb0..6c9b8e1f5b7d2 100644 --- a/test/e2e/feature/feature.go +++ b/test/e2e/feature/feature.go @@ -118,6 +118,19 @@ var ( // OWNER: sig-node // Testing downward API huge pages DownwardAPIHugePages = framework.WithFeature(framework.ValidFeatures.Add("DownwardAPIHugePages")) + + // owning-sig: sig-scheduling + // kep: https://kep.k8s.io/4816 + // test-infra jobs: + // - "ci-kind-dra-all" in https://testgrid.k8s.io/sig-node-dynamic-resource-allocation + // + // This label is used for tests which need: + // - the DynamicResourceAllocation *and* DRAPrioritizedList feature gates + // - the resource.k8s.io API group + // - a container runtime where support for CDI (https://github.com/cncf-tags/container-device-interface) + // is enabled such that passing CDI device IDs through CRI fields is supported + DRAPrioritizedList = framework.WithFeature(framework.ValidFeatures.Add("DRAPrioritizedList")) + // owning-sig: sig-node // kep: https://kep.k8s.io/4381 // test-infra jobs: diff --git a/test/featuregates_linter/test_data/versioned_feature_list.yaml b/test/featuregates_linter/test_data/versioned_feature_list.yaml index deb494360aab3..85b5d9915187a 100644 --- a/test/featuregates_linter/test_data/versioned_feature_list.yaml +++ b/test/featuregates_linter/test_data/versioned_feature_list.yaml @@ -426,6 +426,12 @@ lockToDefault: false preRelease: Alpha version: "1.32" +- name: DRAPrioritizedList + versionedSpecs: + - default: false + lockToDefault: false + preRelease: Alpha + version: "1.33" - name: DRAResourceClaimDeviceStatus versionedSpecs: - default: false diff --git a/test/integration/scheduler_perf/dra.go b/test/integration/scheduler_perf/dra.go index b647ec3871860..0090f41ff43b7 100644 --- a/test/integration/scheduler_perf/dra.go +++ b/test/integration/scheduler_perf/dra.go @@ -321,7 +321,8 @@ claims: } } - allocator, err := structured.NewAllocator(tCtx, utilfeature.DefaultFeatureGate.Enabled(features.DRAAdminAccess), []*resourceapi.ResourceClaim{claim}, allocatedDevices, draManager.DeviceClasses(), slices, celCache) + allocator, err := structured.NewAllocator(tCtx, utilfeature.DefaultFeatureGate.Enabled(features.DRAAdminAccess), utilfeature.DefaultFeatureGate.Enabled(features.DRAPrioritizedList), + []*resourceapi.ResourceClaim{claim}, allocatedDevices, draManager.DeviceClasses(), slices, celCache) tCtx.ExpectNoError(err, "create allocator") rand.Shuffle(len(nodes), func(i, j int) { diff --git a/test/integration/util/util.go b/test/integration/util/util.go index 7eeb288771fba..370b2010ccb02 100644 --- a/test/integration/util/util.go +++ b/test/integration/util/util.go @@ -132,7 +132,11 @@ func CreateResourceClaimController(ctx context.Context, tb ktesting.TB, clientSe podInformer := informerFactory.Core().V1().Pods() claimInformer := informerFactory.Resource().V1beta1().ResourceClaims() claimTemplateInformer := informerFactory.Resource().V1beta1().ResourceClaimTemplates() - claimController, err := resourceclaim.NewController(klog.FromContext(ctx), true /* admin access */, clientSet, podInformer, claimInformer, claimTemplateInformer) + features := resourceclaim.Features{ + AdminAccess: true, + PrioritizedList: true, + } + claimController, err := resourceclaim.NewController(klog.FromContext(ctx), features, clientSet, podInformer, claimInformer, claimTemplateInformer) if err != nil { tb.Fatalf("Error creating claim controller: %v", err) }