Skip to content
69 changes: 69 additions & 0 deletions docs/spec/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,21 @@ spec:
timeoutSeconds: ...
serviceAccountName: ... # Name of the service account the code should run as.

# +optional. Influence scheduling by requiring nodes with certain attributes
# See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
nodeSelector:
key: value
...

# +optional. Influence scheduling by tolerating specific node "smells"
# See: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
tolerations:
- key: ...
operator: Equals | Exists
value: ...
effect: NoSchedule | PreferNoSchedule | NoExecute
- ...

status:
# the latest created and ready to serve. Watched by Route
latestReadyRevisionName: abc
Expand Down Expand Up @@ -239,6 +254,21 @@ spec:
# Many higher-level systems impose a per-request response deadline.
timeoutSeconds: ...

# +optional. Influence scheduling by requiring nodes with certain attributes
# See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
nodeSelector:
key: value
...

# +optional. Influence scheduling by tolerating specific node "smells"
# See: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
tolerations:
- key: ...
operator: Equals | Exists
value: ...
effect: NoSchedule | PreferNoSchedule | NoExecute
- ...

status:
# This is a copy of metadata from the container image or grafeas,
# indicating the provenance of the revision. This is based on the
Expand Down Expand Up @@ -305,6 +335,19 @@ spec: # One of "runLatest", "release", "pinned" (DEPRECATED), or "manual"
containerConcurrency: ... # Optional
timeoutSeconds: ... # Optional
serviceAccountName: ... # Name of the service account the code should run as
# +optional. Influence scheduling by requiring nodes with certain attributes
# See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
nodeSelector:
key: value
...
# +optional. Influence scheduling by tolerating specific node "smells"
# See: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
tolerations:
- key: ...
operator: Equals | Exists
value: ...
effect: NoSchedule | PreferNoSchedule | NoExecute
- ...

# Example, only one of "runLatest", "release", "pinned" (DEPRECATED), or "manual" can be set in practice.
pinned:
Expand All @@ -318,6 +361,19 @@ spec: # One of "runLatest", "release", "pinned" (DEPRECATED), or "manual"
containerConcurrency: ... # Optional
timeoutSeconds: ... # Optional
serviceAccountName: ... # Name of the service account the code should run as
# +optional. Influence scheduling by requiring nodes with certain attributes
# See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
nodeSelector:
key: value
...
# +optional. Influence scheduling by tolerating specific node "smells"
# See: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
tolerations:
- key: ...
operator: Equals | Exists
value: ...
effect: NoSchedule | PreferNoSchedule | NoExecute
- ...

# Example, only one of "runLatest", "release", "pinned" (DEPRECATED), or "manual" can be set in practice.
release:
Expand All @@ -334,6 +390,19 @@ spec: # One of "runLatest", "release", "pinned" (DEPRECATED), or "manual"
containerConcurrency: ... # Optional
timeoutSeconds: ... # Optional
serviceAccountName: ... # Name of the service account the code should run as
# +optional. Influence scheduling by requiring nodes with certain attributes
# See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
nodeSelector:
key: value
...
# +optional. Influence scheduling by tolerating specific node "smells"
# See: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
tolerations:
- key: ...
operator: Equals | Exists
value: ...
effect: NoSchedule | PreferNoSchedule | NoExecute
- ...

# Example, only one of "runLatest", "release", "pinned" (DEPRECATED), or "manual" can be set in practice.
# Manual has no fields. It enables direct access to modify a previously created
Expand Down
12 changes: 12 additions & 0 deletions pkg/apis/serving/v1alpha1/revision_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,18 @@ type RevisionSpec struct {
// TimeoutSeconds holds the max duration the instance is allowed for responding to a request.
// +optional
TimeoutSeconds int64 `json:"timeoutSeconds,omitempty"`

// NodeSelector is a selector which must be true for the revision's pod(s) to
// fit on a node.
// Selector which must match a node's labels for the pod to be scheduled on
// that node.
// More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
// +optional
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This api change (and the Tolerations below it) should include a corresponding change to the spec at https://github.com/knative/serving/blob/master/docs/spec/spec.md and the conformance tests at https://github.com/knative/serving/tree/master/test/conformance, if applicable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bbrowning I updated docs just now.

I'm really not sure whether the conformance tests are applicable here, and if so, I'm not sure where to begin.


// If specified, the revision's tolerations.
// +optional
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
}

const (
Expand Down
118 changes: 118 additions & 0 deletions pkg/apis/serving/v1alpha1/revision_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package v1alpha1
import (
"fmt"
"strconv"
"strings"

"github.com/google/go-containerregistry/pkg/name"
"github.com/knative/pkg/apis"
Expand Down Expand Up @@ -58,6 +59,17 @@ func (rs *RevisionSpec) Validate() *apis.FieldError {
if err := validateTimeoutSeconds(rs.TimeoutSeconds); err != nil {
errs = errs.Also(err)
}

if err := validateNodeSelector(rs.NodeSelector); err != nil {
errs = errs.Also(err.ViaField("nodeSelector"))
}

for i, toleration := range rs.Tolerations {
if err := validateToleration(toleration); err != nil {
errs = errs.Also(err.ViaField(fmt.Sprintf("tolerations[%d]", i)))
}
}

return errs
}

Expand Down Expand Up @@ -303,3 +315,109 @@ func (current *Revision) CheckImmutableFields(og apis.Immutable) *apis.FieldErro

return nil
}

func validateNodeSelector(nodeSelector map[string]string) *apis.FieldError {
for key, value := range nodeSelector {
if verrs := validation.IsQualifiedName(key); len(verrs) > 0 {
return apis.ErrInvalidKeyName(key, key, verrs...)
}
if verrs :=
validation.IsValidLabelValue(value); len(verrs) != 0 {
return &apis.FieldError{
Message: fmt.Sprintf("invalid value %q", value),
Details: strings.Join(verrs, ", "),
Paths: []string{key},
}
}
}
return nil
}

// validateToleration duplicates and adapts logic from
// k8s.io/kubernetes/pkg/apis/core/validation. Although relevant functions from
// that package are exported, they're not usable here because they are for
// unversioned resources.
func validateToleration(toleration corev1.Toleration) *apis.FieldError {
if toleration.Key != "" {
if verrs := validation.IsQualifiedName(toleration.Key); len(verrs) > 0 {
return apis.ErrInvalidKeyName(toleration.Key, "key", verrs...)
}
}
// empty toleration key with Exists operator means match all taints
if toleration.Key == "" &&
toleration.Operator != corev1.TolerationOpExists {
return &apis.FieldError{
Message: fmt.Sprintf("invalid value %q", toleration.Operator),
Details: "operator must be Exists when `key` is empty, which means " +
`"match all values and all keys"`,
Paths: []string{"operator"},
}
}
if toleration.TolerationSeconds != nil &&
toleration.Effect != corev1.TaintEffectNoExecute {
return &apis.FieldError{
Message: fmt.Sprintf("invalid value %q", toleration.Effect),
Details: "effect must be 'NoExecute' when `tolerationSeconds` is set",
Paths: []string{"effect"},
}
}
// validate toleration operator and value
switch toleration.Operator {
// empty operator means Equal
case corev1.TolerationOpEqual, "":
if errStrs :=
validation.IsValidLabelValue(toleration.Value); len(errStrs) != 0 {
return &apis.FieldError{
Message: fmt.Sprintf("invalid value %q", toleration.Value),
Details: strings.Join(errStrs, "; "),
Paths: []string{"value"},
}
}
case corev1.TolerationOpExists:
if toleration.Value != "" {
return &apis.FieldError{
Message: fmt.Sprintf("invalid value %q", toleration.Value),
Details: "value must be empty when `operator` is 'Exists'",
Paths: []string{"value"},
}
}
default:
validValues := []string{
string(corev1.TolerationOpEqual),
string(corev1.TolerationOpExists),
}
return &apis.FieldError{
Message: fmt.Sprintf("invalid value %q", toleration.Operator),
Details: fmt.Sprintf(
"allowed values are: %s",
strings.Join(validValues, ", "),
),
Paths: []string{"operator"},
}
}
// validate toleration effect, empty toleration effect means match all taint
// effects
switch toleration.Effect {
// TODO: Uncomment TaintEffectNoScheduleNoAdmit once it is implemented.
case "", corev1.TaintEffectNoSchedule, corev1.TaintEffectPreferNoSchedule,
corev1.TaintEffectNoExecute: // corev1.TaintEffectNoScheduleNoAdmit
return nil
default:
validValues := []string{
string(corev1.TaintEffectNoSchedule),
string(corev1.TaintEffectPreferNoSchedule),
string(corev1.TaintEffectNoExecute),
// TODO: Uncomment next line when TaintEffectNoScheduleNoAdmit is
// implemented.
// string(corev1.TaintEffectNoScheduleNoAdmit),
}
return &apis.FieldError{
Message: fmt.Sprintf("invalid value %q", toleration.Effect),
Details: fmt.Sprintf(
"allowed values are: %s",
strings.Join(validValues, ", "),
),
Paths: []string{"effect"},
}
}
}
Loading