Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Taking over PD support from sarsate #1337

Merged
merged 1 commit into from
Oct 9, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion cluster/gce/config-default.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ MASTER_TAG="${INSTANCE_PREFIX}-master"
MINION_TAG="${INSTANCE_PREFIX}-minion"
MINION_NAMES=($(eval echo ${INSTANCE_PREFIX}-minion-{1..${NUM_MINIONS}}))
MINION_IP_RANGES=($(eval echo "10.244.{1..${NUM_MINIONS}}.0/24"))
MINION_SCOPES=""
MINION_SCOPES="compute-rw"
# Increase the sleep interval value if concerned about API rate limits. 3, in seconds, is the default.
POLL_SLEEP_INTERVAL=3
26 changes: 25 additions & 1 deletion pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ type VolumeSource struct {
HostDir *HostDir `yaml:"hostDir" json:"hostDir"`
// EmptyDir represents a temporary directory that shares a pod's lifetime.
EmptyDir *EmptyDir `yaml:"emptyDir" json:"emptyDir"`
// GCEPersistentDisk represents a GCE Disk resource that is attached to a
// kubelet's host machine and then exposed to the pod.
GCEPersistentDisk *GCEPersistentDisk `yaml:"persistentDisk" json:"persistentDisk"`
}

// HostDir represents bare host directory volume.
Expand All @@ -111,7 +114,28 @@ const (
ProtocolUDP Protocol = "UDP"
)

// Port represents a network port in a single container.
// GCEPersistent Disk resource.
// A GCE PD must exist and be formatted before mounting to a container.
// The disk must also be in the same GCE project and zone as the kubelet.
// A GCE PD can only be mounted as read/write once.
Copy link
Member

Choose a reason for hiding this comment

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

How should a user debug when they have violated this rule?

Copy link
Member

Choose a reason for hiding this comment

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

Okay. I see the validation in the replication controller.

type GCEPersistentDisk struct {
// Unique name of the PD resource. Used to identify the disk in GCE
PDName string `yaml:"pdName" json:"pdName"`
// Required: Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs"
// TODO: how do we prevent errors in the filesystem from compromising the machine
FSType string `yaml:"fsType,omitempty" json:"fsType,omitempty"`
// Optional: Partition on the disk to mount.
// If omitted, kubelet will attempt to mount the device name.
// Ex. For /dev/sda1, this field is "1", for /dev/sda, this field is 0 or empty.
Partition int `yaml:"partition,omitempty" json:"partition,omitempty"`
// Optional: Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
ReadOnly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"`
}

// Port represents a network port in a single container
type Port struct {
// Optional: If specified, this must be a DNS_LABEL. Each named port
// in a pod must have a unique name.
Expand Down
26 changes: 25 additions & 1 deletion pkg/api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ type VolumeSource struct {
HostDir *HostDir `yaml:"hostDir" json:"hostDir"`
// EmptyDir represents a temporary directory that shares a pod's lifetime.
EmptyDir *EmptyDir `yaml:"emptyDir" json:"emptyDir"`
// GCEPersistentDisk represents a GCE Disk resource that is attached to a
// kubelet's host machine and then exposed to the pod.
GCEPersistentDisk *GCEPersistentDisk `yaml:"persistentDisk" json:"persistentDisk"`
}

// HostDir represents bare host directory volume.
Expand All @@ -109,7 +112,28 @@ const (
ProtocolUDP Protocol = "UDP"
)

// Port represents a network port in a single container.
// GCEPersistent Disk resource.
// A GCE PD must exist before mounting to a container. The disk must
// also be in the same GCE project and zone as the kubelet.
// A GCE PD can only be mounted as read/write once.
type GCEPersistentDisk struct {
// Unique name of the PD resource. Used to identify the disk in GCE
PDName string `yaml:"pdName" json:"pdName"`
// Required: Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs"
// TODO: how do we prevent errors in the filesystem from compromising the machine
FSType string `yaml:"fsType,omitempty" json:"fsType,omitempty"`
// Optional: Partition on the disk to mount.
// If omitted, kubelet will attempt to mount the device name.
// Ex. For /dev/sda1, this field is "1", for /dev/sda, this field 0 or empty.
Partition int `yaml:"partition,omitempty" json:"partition,omitempty"`
// Optional: Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
ReadOnly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"`
}

// Port represents a network port in a single container
type Port struct {
// Optional: If specified, this must be a DNS_LABEL. Each named port
// in a pod must have a unique name.
Expand Down
24 changes: 24 additions & 0 deletions pkg/api/v1beta2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ type VolumeSource struct {
HostDir *HostDir `yaml:"hostDir" json:"hostDir"`
// EmptyDir represents a temporary directory that shares a pod's lifetime.
EmptyDir *EmptyDir `yaml:"emptyDir" json:"emptyDir"`
// A persistent disk that is mounted to the
// kubelet's host machine and then exposed to the pod.
GCEPersistentDisk *GCEPersistentDisk `yaml:"persistentDisk" json:"persistentDisk"`
}

// HostDir represents bare host directory volume.
Expand Down Expand Up @@ -124,6 +127,27 @@ type Port struct {
HostIP string `yaml:"hostIP,omitempty" json:"hostIP,omitempty"`
}

// GCEPersistent Disk resource.
// A GCE PD must exist before mounting to a container. The disk must
// also be in the same GCE project and zone as the kubelet.
// A GCE PD can only be mounted as read/write once.
type GCEPersistentDisk struct {
// Unique name of the PD resource. Used to identify the disk in GCE
PDName string `yaml:"pdName" json:"pdName"`
// Required: Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs"
// TODO: how do we prevent errors in the filesystem from compromising the machine
FSType string `yaml:"fsType,omitempty" json:"fsType,omitempty"`
// Optional: Partition on the disk to mount.
// If omitted, kubelet will attempt to mount the device name.
// Ex. For /dev/sda1, this field is "1", for /dev/sda, this field 0 or empty.
Partition int `yaml:"partition,omitempty" json:"partition,omitempty"`
// Optional: Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
ReadOnly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"`
}

// VolumeMount describes a mounting of a Volume within a container.
type VolumeMount struct {
// Required: This must match the Name of a Volume [above].
Expand Down
24 changes: 24 additions & 0 deletions pkg/api/v1beta3/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ type VolumeSource struct {
HostDir *HostDir `json:"hostDir" yaml:"hostDir"`
// EmptyDir represents a temporary directory that shares a pod's lifetime.
EmptyDir *EmptyDir `json:"emptyDir" yaml:"emptyDir"`
// GCEPersistentDisk represents a GCE Disk resource that is attached to a
// kubelet's host machine and then exposed to the pod.
GCEPersistentDisk *GCEPersistentDisk `yaml:"persistentDisk" json:"persistentDisk"`
}

// HostDir represents bare host directory volume.
Expand All @@ -194,6 +197,27 @@ const (
ProtocolUDP Protocol = "UDP"
)

// GCEPersistent Disk resource.
// A GCE PD must exist and be formatted before mounting to a container.
// The disk must also be in the same GCE project and zone as the kubelet.
// A GCE PD can only be mounted as read/write once.
type GCEPersistentDisk struct {
// Unique name of the PD resource. Used to identify the disk in GCE
PDName string `yaml:"pdName" json:"pdName"`
// Required: Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs"
// TODO: how do we prevent errors in the filesystem from compromising the machine
FSType string `yaml:"fsType,omitempty" json:"fsType,omitempty"`
// Optional: Partition on the disk to mount.
// If omitted, kubelet will attempt to mount the device name.
// Ex. For /dev/sda1, this field is "1", for /dev/sda, this field is 0 or empty.
Partition int `yaml:"partition,omitempty" json:"partition,omitempty"`
// Optional: Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
ReadOnly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"`
}

// Port represents a network port in a single container.
type Port struct {
// Optional: If specified, this must be a DNS_LABEL. Each named port
Expand Down
30 changes: 30 additions & 0 deletions pkg/api/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ func validateSource(source *api.VolumeSource) errs.ErrorList {
numVolumes++
//EmptyDirs have nothing to validate
}
if source.GCEPersistentDisk != nil {
numVolumes++
allErrs = append(allErrs, validateGCEPersistentDisk(source.GCEPersistentDisk)...)
}
if numVolumes != 1 {
allErrs = append(allErrs, errs.NewFieldInvalid("", source))
}
Expand All @@ -80,6 +84,20 @@ func validateHostDir(hostDir *api.HostDir) errs.ErrorList {

var supportedPortProtocols = util.NewStringSet(string(api.ProtocolTCP), string(api.ProtocolUDP))

func validateGCEPersistentDisk(PD *api.GCEPersistentDisk) errs.ErrorList {
allErrs := errs.ErrorList{}
if PD.PDName == "" {
allErrs = append(allErrs, errs.NewFieldInvalid("PD.PDName", PD.PDName))
}
if PD.FSType == "" {
allErrs = append(allErrs, errs.NewFieldInvalid("PD.FSType", PD.FSType))
}
if PD.Partition < 0 || PD.Partition > 255 {
allErrs = append(allErrs, errs.NewFieldInvalid("PD.Partition", PD.Partition))
}
return allErrs
}

func validatePorts(ports []api.Port) errs.ErrorList {
allErrs := errs.ErrorList{}

Expand Down Expand Up @@ -373,5 +391,17 @@ func ValidateReplicationControllerState(state *api.ReplicationControllerState) e
allErrs = append(allErrs, errs.NewFieldInvalid("replicas", state.Replicas))
}
allErrs = append(allErrs, ValidateManifest(&state.PodTemplate.DesiredState.Manifest).Prefix("podTemplate.desiredState.manifest")...)
allErrs = append(allErrs, ValidateReadOnlyPersistentDisks(state.PodTemplate.DesiredState.Manifest.Volumes).Prefix("podTemplate.desiredState.manifest")...)
return allErrs
}
func ValidateReadOnlyPersistentDisks(volumes []api.Volume) errs.ErrorList {
allErrs := errs.ErrorList{}
for _, vol := range volumes {
if vol.Source.GCEPersistentDisk != nil {
if vol.Source.GCEPersistentDisk.ReadOnly == false {
allErrs = append(allErrs, errs.NewFieldInvalid("GCEPersistentDisk.ReadOnly", false))
}
}
}
return allErrs
}
20 changes: 18 additions & 2 deletions pkg/api/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ func TestValidateVolumes(t *testing.T) {
{Name: "123", Source: &api.VolumeSource{HostDir: &api.HostDir{"/mnt/path2"}}},
{Name: "abc-123", Source: &api.VolumeSource{HostDir: &api.HostDir{"/mnt/path3"}}},
{Name: "empty", Source: &api.VolumeSource{EmptyDir: &api.EmptyDir{}}},
{Name: "gcepd", Source: &api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDisk{"my-PD", "ext4", 1, false}}},
}
names, errs := validateVolumes(successCase)
if len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
if len(names) != 4 || !names.HasAll("abc", "123", "abc-123", "empty") {
if len(names) != 5 || !names.HasAll("abc", "123", "abc-123", "empty", "gcepd") {
t.Errorf("wrong names result: %v", names)
}

Expand Down Expand Up @@ -552,7 +553,14 @@ func TestValidateReplicationController(t *testing.T) {
},
Labels: validSelector,
}

invalidVolumePodTemplate := api.PodTemplate{
DesiredState: api.PodState{
Manifest: api.ContainerManifest{
Version: "v1beta1",
Volumes: []api.Volume{{Name: "gcepd", Source: &api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDisk{"my-PD", "ext4", 1, false}}}},
},
},
}
successCases := []api.ReplicationController{
{
TypeMeta: api.TypeMeta{ID: "abc", Namespace: api.NamespaceDefault},
Expand Down Expand Up @@ -609,6 +617,13 @@ func TestValidateReplicationController(t *testing.T) {
ReplicaSelector: validSelector,
},
},
"read-write presistent disk": {
TypeMeta: api.TypeMeta{ID: "abc"},
DesiredState: api.ReplicationControllerState{
ReplicaSelector: validSelector,
PodTemplate: invalidVolumePodTemplate,
},
},
"negative_replicas": {
TypeMeta: api.TypeMeta{ID: "abc", Namespace: api.NamespaceDefault},
DesiredState: api.ReplicationControllerState{
Expand All @@ -628,6 +643,7 @@ func TestValidateReplicationController(t *testing.T) {
field != "id" &&
field != "namespace" &&
field != "desiredState.replicaSelector" &&
field != "GCEPersistentDisk.ReadOnly" &&
field != "desiredState.replicas" {
t.Errorf("%s: missing prefix for: %v", k, errs[i])
}
Expand Down