mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-04 01:40:07 +00:00
Merge pull request #4151 from brendandburns/fixer
Adjust replication controller validation to be more flexible. Fix docs.
This commit is contained in:
commit
e89a4ef33f
@ -49,9 +49,10 @@ A Volume with a GCEPersistentDisk property allows access to files on a Google Co
|
|||||||
There are some restrictions when using a GCEPersistentDisk:
|
There are some restrictions when using a GCEPersistentDisk:
|
||||||
- the nodes (what the kubelet runs on) need to be GCE VMs
|
- the nodes (what the kubelet runs on) need to be GCE VMs
|
||||||
- those VMs need to be in the same GCE project and zone as the PD
|
- those VMs need to be in the same GCE project and zone as the PD
|
||||||
- avoid creating multiple pods that use the same Volume
|
- avoid creating multiple pods that use the same Volume if any mount it read/write.
|
||||||
- if multiple pods refer to the same Volume and both are scheduled on the same machine, regardless of whether they are read-only or read-write, then the second pod scheduled will fail.
|
- if a pod P already mounts a volume read/write, and a second pod Q attempts to use the volume, regardless of if it tries to use it read-only or read/write, Q will fail.
|
||||||
- Replication controllers can only be created for pods that use read-only mounts.
|
- if a pod P already mounts a volume read-only, and a second pod Q attempts to use the volume read/write, Q will fail.
|
||||||
|
- replication controllers with replicas > 1 can only be created for pods that use read-only mounts.
|
||||||
|
|
||||||
#### Creating a PD
|
#### Creating a PD
|
||||||
Before you can use a GCE PD with a pod, you need to create it and format it.
|
Before you can use a GCE PD with a pod, you need to create it and format it.
|
||||||
|
@ -623,7 +623,6 @@ func ValidateReplicationControllerUpdate(oldController, controller *api.Replicat
|
|||||||
allErrs := errs.ValidationErrorList{}
|
allErrs := errs.ValidationErrorList{}
|
||||||
allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldController.ObjectMeta, &controller.ObjectMeta).Prefix("metadata")...)
|
allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldController.ObjectMeta, &controller.ObjectMeta).Prefix("metadata")...)
|
||||||
allErrs = append(allErrs, ValidateReplicationControllerSpec(&controller.Spec).Prefix("spec")...)
|
allErrs = append(allErrs, ValidateReplicationControllerSpec(&controller.Spec).Prefix("spec")...)
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -646,7 +645,7 @@ func ValidateReplicationControllerSpec(spec *api.ReplicationControllerSpec) errs
|
|||||||
if !selector.Matches(labels) {
|
if !selector.Matches(labels) {
|
||||||
allErrs = append(allErrs, errs.NewFieldInvalid("template.labels", spec.Template.Labels, "selector does not match template"))
|
allErrs = append(allErrs, errs.NewFieldInvalid("template.labels", spec.Template.Labels, "selector does not match template"))
|
||||||
}
|
}
|
||||||
allErrs = append(allErrs, ValidatePodTemplateSpec(spec.Template).Prefix("template")...)
|
allErrs = append(allErrs, ValidatePodTemplateSpec(spec.Template, spec.Replicas).Prefix("template")...)
|
||||||
// RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec().
|
// RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec().
|
||||||
if spec.Template.Spec.RestartPolicy.Always == nil {
|
if spec.Template.Spec.RestartPolicy.Always == nil {
|
||||||
// TODO: should probably be Unsupported
|
// TODO: should probably be Unsupported
|
||||||
@ -658,12 +657,14 @@ func ValidateReplicationControllerSpec(spec *api.ReplicationControllerSpec) errs
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidatePodTemplateSpec validates the spec of a pod template
|
// ValidatePodTemplateSpec validates the spec of a pod template
|
||||||
func ValidatePodTemplateSpec(spec *api.PodTemplateSpec) errs.ValidationErrorList {
|
func ValidatePodTemplateSpec(spec *api.PodTemplateSpec, replicas int) errs.ValidationErrorList {
|
||||||
allErrs := errs.ValidationErrorList{}
|
allErrs := errs.ValidationErrorList{}
|
||||||
allErrs = append(allErrs, ValidateLabels(spec.Labels, "labels")...)
|
allErrs = append(allErrs, ValidateLabels(spec.Labels, "labels")...)
|
||||||
allErrs = append(allErrs, ValidateAnnotations(spec.Annotations, "annotations")...)
|
allErrs = append(allErrs, ValidateAnnotations(spec.Annotations, "annotations")...)
|
||||||
allErrs = append(allErrs, ValidatePodSpec(&spec.Spec).Prefix("spec")...)
|
allErrs = append(allErrs, ValidatePodSpec(&spec.Spec).Prefix("spec")...)
|
||||||
|
if replicas > 1 {
|
||||||
allErrs = append(allErrs, ValidateReadOnlyPersistentDisks(spec.Spec.Volumes).Prefix("spec.volumes")...)
|
allErrs = append(allErrs, ValidateReadOnlyPersistentDisks(spec.Spec.Volumes).Prefix("spec.volumes")...)
|
||||||
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -672,7 +673,7 @@ func ValidateReadOnlyPersistentDisks(volumes []api.Volume) errs.ValidationErrorL
|
|||||||
for _, vol := range volumes {
|
for _, vol := range volumes {
|
||||||
if vol.Source.GCEPersistentDisk != nil {
|
if vol.Source.GCEPersistentDisk != nil {
|
||||||
if vol.Source.GCEPersistentDisk.ReadOnly == false {
|
if vol.Source.GCEPersistentDisk.ReadOnly == false {
|
||||||
allErrs = append(allErrs, errs.NewFieldInvalid("GCEPersistentDisk.ReadOnly", false, "must be true"))
|
allErrs = append(allErrs, errs.NewFieldInvalid("GCEPersistentDisk.ReadOnly", false, "ReadOnly must be true for replicated pods > 1, as GCE PD can only be mounted on multiple machines if it is read-only."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1400,6 +1400,166 @@ func TestValidateService(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateReplicationControllerUpdate(t *testing.T) {
|
||||||
|
validSelector := map[string]string{"a": "b"}
|
||||||
|
validPodTemplate := api.PodTemplate{
|
||||||
|
Spec: api.PodTemplateSpec{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Labels: validSelector,
|
||||||
|
},
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
|
||||||
|
DNSPolicy: api.DNSClusterFirst,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
readWriteVolumePodTemplate := api.PodTemplate{
|
||||||
|
Spec: api.PodTemplateSpec{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Labels: validSelector,
|
||||||
|
},
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
|
||||||
|
DNSPolicy: api.DNSClusterFirst,
|
||||||
|
Volumes: []api.Volume{{Name: "gcepd", Source: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDisk{"my-PD", "ext4", 1, false}}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
|
||||||
|
invalidPodTemplate := api.PodTemplate{
|
||||||
|
Spec: api.PodTemplateSpec{
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
|
||||||
|
DNSPolicy: api.DNSClusterFirst,
|
||||||
|
},
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Labels: invalidSelector,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
type rcUpdateTest struct {
|
||||||
|
old api.ReplicationController
|
||||||
|
update api.ReplicationController
|
||||||
|
}
|
||||||
|
successCases := []rcUpdateTest{
|
||||||
|
{
|
||||||
|
old: api.ReplicationController{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
|
||||||
|
Spec: api.ReplicationControllerSpec{
|
||||||
|
Selector: validSelector,
|
||||||
|
Template: &validPodTemplate.Spec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: api.ReplicationController{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
|
||||||
|
Spec: api.ReplicationControllerSpec{
|
||||||
|
Replicas: 3,
|
||||||
|
Selector: validSelector,
|
||||||
|
Template: &validPodTemplate.Spec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
old: api.ReplicationController{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
|
||||||
|
Spec: api.ReplicationControllerSpec{
|
||||||
|
Selector: validSelector,
|
||||||
|
Template: &validPodTemplate.Spec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: api.ReplicationController{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
|
||||||
|
Spec: api.ReplicationControllerSpec{
|
||||||
|
Replicas: 1,
|
||||||
|
Selector: validSelector,
|
||||||
|
Template: &readWriteVolumePodTemplate.Spec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, successCase := range successCases {
|
||||||
|
if errs := ValidateReplicationControllerUpdate(&successCase.old, &successCase.update); len(errs) != 0 {
|
||||||
|
t.Errorf("expected success: %v", errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errorCases := map[string]rcUpdateTest{
|
||||||
|
"more than one read/write": {
|
||||||
|
old: api.ReplicationController{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
|
||||||
|
Spec: api.ReplicationControllerSpec{
|
||||||
|
Selector: validSelector,
|
||||||
|
Template: &validPodTemplate.Spec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: api.ReplicationController{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
|
||||||
|
Spec: api.ReplicationControllerSpec{
|
||||||
|
Replicas: 2,
|
||||||
|
Selector: validSelector,
|
||||||
|
Template: &readWriteVolumePodTemplate.Spec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"invalid selector": {
|
||||||
|
old: api.ReplicationController{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
|
||||||
|
Spec: api.ReplicationControllerSpec{
|
||||||
|
Selector: validSelector,
|
||||||
|
Template: &validPodTemplate.Spec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: api.ReplicationController{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
|
||||||
|
Spec: api.ReplicationControllerSpec{
|
||||||
|
Replicas: 2,
|
||||||
|
Selector: invalidSelector,
|
||||||
|
Template: &validPodTemplate.Spec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"invalid pod": {
|
||||||
|
old: api.ReplicationController{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
|
||||||
|
Spec: api.ReplicationControllerSpec{
|
||||||
|
Selector: validSelector,
|
||||||
|
Template: &validPodTemplate.Spec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: api.ReplicationController{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
|
||||||
|
Spec: api.ReplicationControllerSpec{
|
||||||
|
Replicas: 2,
|
||||||
|
Selector: validSelector,
|
||||||
|
Template: &invalidPodTemplate.Spec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"negative replicas": {
|
||||||
|
old: api.ReplicationController{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
|
||||||
|
Spec: api.ReplicationControllerSpec{
|
||||||
|
Selector: validSelector,
|
||||||
|
Template: &validPodTemplate.Spec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: api.ReplicationController{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
|
||||||
|
Spec: api.ReplicationControllerSpec{
|
||||||
|
Replicas: -1,
|
||||||
|
Selector: validSelector,
|
||||||
|
Template: &validPodTemplate.Spec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for testName, errorCase := range errorCases {
|
||||||
|
if errs := ValidateReplicationControllerUpdate(&errorCase.old, &errorCase.update); len(errs) == 0 {
|
||||||
|
t.Errorf("expected failure: %s", testName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidateReplicationController(t *testing.T) {
|
func TestValidateReplicationController(t *testing.T) {
|
||||||
validSelector := map[string]string{"a": "b"}
|
validSelector := map[string]string{"a": "b"}
|
||||||
validPodTemplate := api.PodTemplate{
|
validPodTemplate := api.PodTemplate{
|
||||||
@ -1413,8 +1573,11 @@ func TestValidateReplicationController(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
invalidVolumePodTemplate := api.PodTemplate{
|
readWriteVolumePodTemplate := api.PodTemplate{
|
||||||
Spec: api.PodTemplateSpec{
|
Spec: api.PodTemplateSpec{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Labels: validSelector,
|
||||||
|
},
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
Volumes: []api.Volume{{Name: "gcepd", Source: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDisk{"my-PD", "ext4", 1, false}}}},
|
Volumes: []api.Volume{{Name: "gcepd", Source: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDisk{"my-PD", "ext4", 1, false}}}},
|
||||||
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
|
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
|
||||||
@ -1449,6 +1612,14 @@ func TestValidateReplicationController(t *testing.T) {
|
|||||||
Template: &validPodTemplate.Spec,
|
Template: &validPodTemplate.Spec,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "abc-123", Namespace: api.NamespaceDefault},
|
||||||
|
Spec: api.ReplicationControllerSpec{
|
||||||
|
Replicas: 1,
|
||||||
|
Selector: validSelector,
|
||||||
|
Template: &readWriteVolumePodTemplate.Spec,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, successCase := range successCases {
|
for _, successCase := range successCases {
|
||||||
if errs := ValidateReplicationController(&successCase); len(errs) != 0 {
|
if errs := ValidateReplicationController(&successCase); len(errs) != 0 {
|
||||||
@ -1490,11 +1661,12 @@ func TestValidateReplicationController(t *testing.T) {
|
|||||||
Selector: validSelector,
|
Selector: validSelector,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"read-write persistent disk": {
|
"read-write persistent disk with > 1 pod": {
|
||||||
ObjectMeta: api.ObjectMeta{Name: "abc"},
|
ObjectMeta: api.ObjectMeta{Name: "abc"},
|
||||||
Spec: api.ReplicationControllerSpec{
|
Spec: api.ReplicationControllerSpec{
|
||||||
|
Replicas: 2,
|
||||||
Selector: validSelector,
|
Selector: validSelector,
|
||||||
Template: &invalidVolumePodTemplate.Spec,
|
Template: &readWriteVolumePodTemplate.Spec,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"negative_replicas": {
|
"negative_replicas": {
|
||||||
|
Loading…
Reference in New Issue
Block a user