Merge pull request #1337 from brendandburns/Sarsate-pd-support

Taking over PD support from sarsate
This commit is contained in:
erictune
2014-10-09 09:16:04 -07:00
14 changed files with 791 additions and 40 deletions

View File

@@ -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.
@@ -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.
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.

View File

@@ -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.
@@ -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.

View File

@@ -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.
@@ -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].

View File

@@ -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.
@@ -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

View File

@@ -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))
}
@@ -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{}
@@ -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
}

View File

@@ -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)
}
@@ -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},
@@ -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{
@@ -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])
}