Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions docs/spec/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,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 @@ -256,6 +271,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 @@ -321,6 +351,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 @@ -335,6 +378,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 @@ -353,6 +409,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 @@ -196,6 +196,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"`

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

const (
Expand Down
137 changes: 133 additions & 4 deletions pkg/apis/serving/v1alpha1/revision_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@ import (
"fmt"
"path/filepath"
"strconv"
"strings"

"github.com/google/go-containerregistry/pkg/name"
"github.com/knative/pkg/apis"
"github.com/knative/pkg/kmp"
networkingv1alpha1 "github.com/knative/serving/pkg/apis/networking/v1alpha1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation"

"github.com/google/go-containerregistry/pkg/name"
"github.com/knative/pkg/apis"
"github.com/knative/pkg/kmp"
networkingv1alpha1 "github.com/knative/serving/pkg/apis/networking/v1alpha1"
)

var (
Expand Down Expand Up @@ -83,6 +85,17 @@ func (rs *RevisionSpec) Validate() *apis.FieldError {
rs.ContainerConcurrency, rs.DeprecatedConcurrencyModel))
}

if err := validateNodeSelector(rs.NodeSelector); err != nil {
return err.ViaField("nodeSelector")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Append to errs instead of returning?

}
for i, toleration := range rs.Tolerations {
if err := validateToleration(toleration); err != nil {
return err.ViaField(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Append to errs instead of returning?

fmt.Sprintf("tolerations[%d]", i),
)
}
}

return errs.Also(validateTimeoutSeconds(rs.TimeoutSeconds))
}

Expand Down Expand Up @@ -389,3 +402,119 @@ 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 errStrs :=
validation.IsQualifiedName(key); len(errStrs) > 0 {
return apis.ErrInvalidKeyName(
key,
key,
strings.Join(errStrs, "; "),
)
}
if errStrs :=
validation.IsValidLabelValue(value); len(errStrs) != 0 {
return &apis.FieldError{
Message: fmt.Sprintf("invalid value %q", value),
Details: strings.Join(errStrs, "; "),
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 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

if toleration.Key != "" {
if errStrs :=
validation.IsQualifiedName(toleration.Key); len(errStrs) > 0 {
return &apis.FieldError{
Message: fmt.Sprintf("invalid value %q", toleration.Key),
Details: strings.Join(errStrs, "; "),
Paths: []string{"key"},
}
}
}
// 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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

// 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