diff --git a/pkg/api/helpers.go b/pkg/api/helpers.go index 6d60773a93b..b905726b7d1 100644 --- a/pkg/api/helpers.go +++ b/pkg/api/helpers.go @@ -71,7 +71,8 @@ var standardResources = util.NewStringSet( string(ResourcePods), string(ResourceQuotas), string(ResourceServices), - string(ResourceReplicationControllers)) + string(ResourceReplicationControllers), + string(ResourceStorage)) func IsStandardResourceName(str string) bool { return standardResources.Has(str) diff --git a/pkg/api/latest/latest.go b/pkg/api/latest/latest.go index 92f88de9149..219678e0abe 100644 --- a/pkg/api/latest/latest.go +++ b/pkg/api/latest/latest.go @@ -118,9 +118,10 @@ func init() { // the list of kinds that are scoped at the root of the api hierarchy // if a kind is not enumerated here, it is assumed to have a namespace scope kindToRootScope := map[string]bool{ - "Node": true, - "Minion": true, - "Namespace": true, + "Node": true, + "Minion": true, + "Namespace": true, + "PersistentVolume": true, } // these kinds should be excluded from the list of resources diff --git a/pkg/api/register.go b/pkg/api/register.go index 81d01572076..bb984859c7f 100644 --- a/pkg/api/register.go +++ b/pkg/api/register.go @@ -53,6 +53,10 @@ func init() { &Secret{}, &SecretList{}, &DeleteOptions{}, + &PersistentVolume{}, + &PersistentVolumeList{}, + &PersistentVolumeClaim{}, + &PersistentVolumeClaimList{}, ) // Legacy names are supported Scheme.AddKnownTypeWithName("", "Minion", &Node{}) @@ -87,3 +91,7 @@ func (*NamespaceList) IsAnAPIObject() {} func (*Secret) IsAnAPIObject() {} func (*SecretList) IsAnAPIObject() {} func (*DeleteOptions) IsAnAPIObject() {} +func (*PersistentVolume) IsAnAPIObject() {} +func (*PersistentVolumeList) IsAnAPIObject() {} +func (*PersistentVolumeClaim) IsAnAPIObject() {} +func (*PersistentVolumeClaimList) IsAnAPIObject() {} diff --git a/pkg/api/rest/create.go b/pkg/api/rest/create.go index 348d1f8a739..27f0d34c397 100644 --- a/pkg/api/rest/create.go +++ b/pkg/api/rest/create.go @@ -68,7 +68,7 @@ func BeforeCreate(strategy RESTCreateStrategy, ctx api.Context, obj runtime.Obje return nil } -// CheckGeneratedNameError checks whether an error that occured creating a resource is due +// CheckGeneratedNameError checks whether an error that occurred creating a resource is due // to generation being unable to pick a valid name. func CheckGeneratedNameError(strategy RESTCreateStrategy, err error, obj runtime.Object) error { if !errors.IsAlreadyExists(err) { diff --git a/pkg/api/types.go b/pkg/api/types.go index f7d3d0df06f..3a4d73f36cb 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -195,7 +195,88 @@ type VolumeSource struct { NFS *NFSVolumeSource `json:"nfs"` } -// used by VolumeSources to describe their mounting/access modes +// Similar to VolumeSource but meant for the administrator who creates PVs. +// Exactly one of its members must be set. +type PersistentVolumeSource struct { + // GCEPersistentDisk represents a GCE Disk resource that is attached to a + // kubelet's host machine and then exposed to the pod. + GCEPersistentDisk *GCEPersistentDiskVolumeSource `json:"gcePersistentDisk"` + // HostPath represents a directory on the host. + // This is useful for development and testing only. + // on-host storage is not supported in any way + HostPath *HostPathVolumeSource `json:"hostPath"` +} + +type PersistentVolume struct { + TypeMeta `json:",inline"` + ObjectMeta `json:"metadata,omitempty"` + + //Spec defines a persistent volume owned by the cluster + Spec PersistentVolumeSpec `json:"spec,omitempty"` + + // Status represents the current information about persistent volume. + Status PersistentVolumeStatus `json:"status,omitempty"` +} + +type PersistentVolumeSpec struct { + // Resources represents the actual resources of the volume + Capacity ResourceList `json:"capacity` + // Source represents the location and type of a volume to mount. + // AccessModeTypes are inferred from the Source. + PersistentVolumeSource `json:",inline"` + // holds the binding reference to a PersistentVolumeClaim + ClaimRef *ObjectReference `json:"claimRef,omitempty"` +} + +type PersistentVolumeStatus struct { + // Phase indicates if a volume is available, bound to a claim, or released by a claim + Phase PersistentVolumePhase `json:"phase,omitempty"` +} + +type PersistentVolumeList struct { + TypeMeta `json:",inline"` + ListMeta `json:"metadata,omitempty"` + Items []PersistentVolume `json:"items,omitempty"` +} + +// PersistentVolumeClaim is a user's request for and claim to a persistent volume +type PersistentVolumeClaim struct { + TypeMeta `json:",inline"` + ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the volume requested by a pod author + Spec PersistentVolumeClaimSpec `json:"spec,omitempty"` + + // Status represents the current information about a claim + Status PersistentVolumeClaimStatus `json:"status,omitempty"` +} + +type PersistentVolumeClaimList struct { + TypeMeta `json:",inline"` + ListMeta `json:"metadata,omitempty"` + Items []PersistentVolumeClaim `json:"items,omitempty"` +} + +// PersistentVolumeClaimSpec describes the common attributes of storage devices +// and allows a Source for provider-specific attributes +type PersistentVolumeClaimSpec struct { + // Contains the types of access modes required + AccessModes []AccessModeType `json:"accessModes,omitempty"` + // Resources represents the minimum resources required + Resources ResourceRequirements `json:"resources,omitempty"` +} + +type PersistentVolumeClaimStatus struct { + // Phase represents the current phase of PersistentVolumeClaim + Phase PersistentVolumeClaimPhase `json:"phase,omitempty"` + // AccessModes contains all ways the volume backing the PVC can be mounted + AccessModes []AccessModeType `json:"accessModes,omitempty` + // Represents the actual resources of the underlying volume + Capacity ResourceList `json:"capacity,omitempty"` + // VolumeRef is a reference to the PersistentVolume bound to the PersistentVolumeClaim + VolumeRef *ObjectReference `json:"volumeRef,omitempty"` +} + type AccessModeType string const ( @@ -207,6 +288,27 @@ const ( ReadWriteMany AccessModeType = "ReadWriteMany" ) +type PersistentVolumePhase string + +const ( + // used for PersistentVolumes that are not yet bound + VolumeAvailable PersistentVolumePhase = "Available" + // used for PersistentVolumes that are bound + VolumeBound PersistentVolumePhase = "Bound" + // used for PersistentVolumes where the bound PersistentVolumeClaim was deleted + // released volumes must be recycled before becoming available again + VolumeReleased PersistentVolumePhase = "Released" +) + +type PersistentVolumeClaimPhase string + +const ( + // used for PersistentVolumeClaims that are not yet bound + ClaimPending PersistentVolumeClaimPhase = "Pending" + // used for PersistentVolumeClaims that are bound + ClaimBound PersistentVolumeClaimPhase = "Bound" +) + // HostPathVolumeSource represents a host directory mapped into a pod. type HostPathVolumeSource struct { Path string `json:"path"` @@ -390,6 +492,8 @@ type Capabilities struct { type ResourceRequirements struct { // Limits describes the maximum amount of compute resources required. Limits ResourceList `json:"limits,omitempty"` + // Requests describes the minimum amount of compute resources required. + Requests ResourceList `json:"requests,omitempty"` } // Container represents a single container that is expected to be run on the host. @@ -957,6 +1061,8 @@ const ( ResourceCPU ResourceName = "cpu" // Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) ResourceMemory ResourceName = "memory" + // Volume size, in bytes (e,g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024) + ResourceStorage ResourceName = "storage" ) // ResourceList is a set of (resource name, quantity) pairs. diff --git a/pkg/api/v1beta1/register.go b/pkg/api/v1beta1/register.go index 5765cbf7554..456a634a240 100644 --- a/pkg/api/v1beta1/register.go +++ b/pkg/api/v1beta1/register.go @@ -60,6 +60,10 @@ func init() { &Secret{}, &SecretList{}, &DeleteOptions{}, + &PersistentVolume{}, + &PersistentVolumeList{}, + &PersistentVolumeClaim{}, + &PersistentVolumeClaimList{}, ) // Future names are supported api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{}) @@ -94,3 +98,7 @@ func (*NamespaceList) IsAnAPIObject() {} func (*Secret) IsAnAPIObject() {} func (*SecretList) IsAnAPIObject() {} func (*DeleteOptions) IsAnAPIObject() {} +func (*PersistentVolume) IsAnAPIObject() {} +func (*PersistentVolumeList) IsAnAPIObject() {} +func (*PersistentVolumeClaim) IsAnAPIObject() {} +func (*PersistentVolumeClaimList) IsAnAPIObject() {} diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 2b78cf6a592..50e95fcfa82 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -91,7 +91,7 @@ type Volume struct { Source VolumeSource `json:"source,omitempty" description:"location and type of volume to mount; at most one of HostDir, EmptyDir, GCEPersistentDisk, or GitRepo; default is EmptyDir"` } -// VolumeSource represents the source location of a valume to mount. +// VolumeSource represents the source location of a volume to mount. // Only one of its members may be specified. type VolumeSource struct { // HostDir represents a pre-existing directory on the host machine that is directly @@ -113,7 +113,84 @@ type VolumeSource struct { NFS *NFSVolumeSource `json:"nfs" description:"NFS volume that will be mounted in the host machine "` } -// used by VolumeSources to describe their mounting/access modes +// Similar to VolumeSource but meant for the administrator who creates PVs. +// Exactly one of its members must be set. +type PersistentVolumeSource struct { + // GCEPersistentDisk represents a GCE Disk resource that is attached to a + // kubelet's host machine and then exposed to the pod. + GCEPersistentDisk *GCEPersistentDiskVolumeSource `json:"persistentDisk" description:"GCE disk resource provisioned by an admin"` + // HostPath represents a directory on the host. + // This is useful for development and testing only. + // on-host storage is not supported in any way. + HostPath *HostPathVolumeSource `json:"hostPath" description:"a HostPath provisioned by a developer or tester; for develment use only"` +} + +type PersistentVolume struct { + TypeMeta `json:",inline"` + + //Spec defines a persistent volume owned by the cluster + Spec PersistentVolumeSpec `json:"spec,omitempty" description:"specification of a persistent volume as provisioned by an administrator"` + + // Status represents the current information about persistent volume. + Status PersistentVolumeStatus `json:"status,omitempty" description:"current status of a persistent volume; populated by the system, read-only"` +} + +type PersistentVolumeSpec struct { + // Resources represents the actual resources of the volume + Capacity ResourceList `json:"capacity,omitempty" description:"a description of the persistent volume's resources and capacity"` + // Source represents the location and type of a volume to mount. + // AccessModeTypes are inferred from the Source. + PersistentVolumeSource `json:",inline" description:"the actual volume backing the persistent volume"` + // holds the binding reference to a PersistentVolumeClaim + ClaimRef *ObjectReference `json:"claimRef,omitempty" description:"the binding reference to a persistent volume claim"` +} + +type PersistentVolumeStatus struct { + // Phase indicates if a volume is available, bound to a claim, or released by a claim + Phase PersistentVolumePhase `json:"phase,omitempty" description:"the current phase of a persistent volume"` +} + +type PersistentVolumeList struct { + TypeMeta `json:",inline"` + Items []PersistentVolume `json:"items,omitempty" description:"list of persistent volumes"` +} + +// PersistentVolumeClaim is a user's request for and claim to a persistent volume +type PersistentVolumeClaim struct { + TypeMeta `json:",inline"` + + // Spec defines the volume requested by a pod author + Spec PersistentVolumeClaimSpec `json:"spec,omitempty" description: "the desired characteristics of a volume"` + + // Status represents the current information about a claim + Status PersistentVolumeClaimStatus `json:"status,omitempty" description:"the current status of a persistent volume claim; read-only"` +} + +type PersistentVolumeClaimList struct { + TypeMeta `json:",inline"` + Items []PersistentVolumeClaim `json:"items,omitempty" description: "a list of persistent volume claims"` +} + +// PersistentVolumeClaimSpec describes the common attributes of storage devices +// and allows a Source for provider-specific attributes +type PersistentVolumeClaimSpec struct { + // Contains the types of access modes required + AccessModes []AccessModeType `json:"accessModes,omitempty" description:"the desired access modes the volume should have"` + // Resources represents the minimum resources required + Resources ResourceRequirements `json:"resources,omitempty" description:"the desired resources the volume should have"` +} + +type PersistentVolumeClaimStatus struct { + // Phase represents the current phase of PersistentVolumeClaim + Phase PersistentVolumeClaimPhase `json:"phase,omitempty" description:"the current phase of the claim"` + // AccessModes contains all ways the volume backing the PVC can be mounted + AccessModes []AccessModeType `json:"accessModes,omitempty" description:"the actual access modes the volume has"` + // Represents the actual resources of the underlying volume + Capacity ResourceList `json:"capacity,omitempty" description:"the actual resources the volume has"` + // VolumeRef is a reference to the PersistentVolume bound to the PersistentVolumeClaim + VolumeRef *ObjectReference `json:"volumeRef,omitempty" description:"a reference to the backing persistent volume, when bound"` +} + type AccessModeType string const ( @@ -125,6 +202,27 @@ const ( ReadWriteMany AccessModeType = "ReadWriteMany" ) +type PersistentVolumePhase string + +const ( + // used for PersistentVolumes that are not yet bound + VolumeAvailable PersistentVolumePhase = "Available" + // used for PersistentVolumes that are bound + VolumeBound PersistentVolumePhase = "Bound" + // used for PersistentVolumes where the bound PersistentVolumeClaim was deleted + // released volumes must be recycled before becoming available again + VolumeReleased PersistentVolumePhase = "Released" +) + +type PersistentVolumeClaimPhase string + +const ( + // used for PersistentVolumeClaims that are not yet bound + ClaimPending PersistentVolumeClaimPhase = "Pending" + // used for PersistentVolumeClaims that are bound + ClaimBound PersistentVolumeClaimPhase = "Bound" +) + // HostPathVolumeSource represents bare host directory volume. type HostPathVolumeSource struct { Path string `json:"path" description:"path of the directory on the host"` @@ -302,6 +400,8 @@ type Capabilities struct { type ResourceRequirements struct { // Limits describes the maximum amount of compute resources required. Limits ResourceList `json:"limits,omitempty" description:"Maximum amount of compute resources allowed"` + // Requests describes the minimum amount of compute resources required. + Requests ResourceList `json:"requests,omitempty" description:"Minimum amount of resources requested"` } // Container represents a single container that is expected to be run on the host. @@ -764,10 +864,12 @@ type NodeResources struct { type ResourceName string const ( - // CPU, in cores. (floating point w/ 3 decimal places) + // CPU, in cores. (500m = .5 cores) ResourceCPU ResourceName = "cpu" - // Memory, in bytes. + // Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) ResourceMemory ResourceName = "memory" + // Volume size, in bytes (e,g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024) + ResourceStorage ResourceName = "storage" ) type ResourceList map[ResourceName]util.IntOrString diff --git a/pkg/api/v1beta2/register.go b/pkg/api/v1beta2/register.go index caf553af06a..21be3ff2c33 100644 --- a/pkg/api/v1beta2/register.go +++ b/pkg/api/v1beta2/register.go @@ -60,6 +60,10 @@ func init() { &Secret{}, &SecretList{}, &DeleteOptions{}, + &PersistentVolume{}, + &PersistentVolumeList{}, + &PersistentVolumeClaim{}, + &PersistentVolumeClaimList{}, ) // Future names are supported api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{}) @@ -93,4 +97,8 @@ func (*Namespace) IsAnAPIObject() {} func (*NamespaceList) IsAnAPIObject() {} func (*Secret) IsAnAPIObject() {} func (*SecretList) IsAnAPIObject() {} +func (*PersistentVolume) IsAnAPIObject() {} +func (*PersistentVolumeList) IsAnAPIObject() {} +func (*PersistentVolumeClaim) IsAnAPIObject() {} +func (*PersistentVolumeClaimList) IsAnAPIObject() {} func (*DeleteOptions) IsAnAPIObject() {} diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index 32f42221746..8b177282c07 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -58,7 +58,7 @@ type Volume struct { Source VolumeSource `json:"source,omitempty" description:"location and type of volume to mount; at most one of HostDir, EmptyDir, GCEPersistentDisk, or GitRepo; default is EmptyDir"` } -// VolumeSource represents the source location of a valume to mount. +// VolumeSource represents the source location of a volume to mount. // Only one of its members may be specified. // // https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/volumes.md#types-of-volumes @@ -82,7 +82,84 @@ type VolumeSource struct { NFS *NFSVolumeSource `json:"nfs" description:"NFS volume that will be mounted in the host machine"` } -// used by VolumeSources to describe their mounting/access modes +// Similar to VolumeSource but meant for the administrator who creates PVs. +// Exactly one of its members must be set. +type PersistentVolumeSource struct { + // GCEPersistentDisk represents a GCE Disk resource that is attached to a + // kubelet's host machine and then exposed to the pod. + GCEPersistentDisk *GCEPersistentDiskVolumeSource `json:"persistentDisk" description:"GCE disk resource provisioned by an admin"` + // HostPath represents a directory on the host. + // This is useful for development and testing only. + // on-host storage is not supported in any way. + HostPath *HostPathVolumeSource `json:"hostPath" description:"a HostPath provisioned by a developer or tester; for develment use only"` +} + +type PersistentVolume struct { + TypeMeta `json:",inline"` + + //Spec defines a persistent volume owned by the cluster + Spec PersistentVolumeSpec `json:"spec,omitempty" description:"specification of a persistent volume as provisioned by an administrator"` + + // Status represents the current information about persistent volume. + Status PersistentVolumeStatus `json:"status,omitempty" description:"current status of a persistent volume; populated by the system, read-only"` +} + +type PersistentVolumeSpec struct { + // Resources represents the actual resources of the volume + Capacity ResourceList `json:"capacity,omitempty" description:"a description of the persistent volume's resources and capacity"` + // Source represents the location and type of a volume to mount. + // AccessModeTypes are inferred from the Source. + PersistentVolumeSource `json:",inline" description:"the actual volume backing the persistent volume"` + // holds the binding reference to a PersistentVolumeClaim + ClaimRef *ObjectReference `json:"claimRef,omitempty" description:"the binding reference to a persistent volume claim"` +} + +type PersistentVolumeStatus struct { + // Phase indicates if a volume is available, bound to a claim, or released by a claim + Phase PersistentVolumePhase `json:"phase,omitempty" description:"the current phase of a persistent volume"` +} + +type PersistentVolumeList struct { + TypeMeta `json:",inline"` + Items []PersistentVolume `json:"items,omitempty" description:"list of persistent volumes"` +} + +// PersistentVolumeClaim is a user's request for and claim to a persistent volume +type PersistentVolumeClaim struct { + TypeMeta `json:",inline"` + + // Spec defines the volume requested by a pod author + Spec PersistentVolumeClaimSpec `json:"spec,omitempty" description: "the desired characteristics of a volume"` + + // Status represents the current information about a claim + Status PersistentVolumeClaimStatus `json:"status,omitempty" description:"the current status of a persistent volume claim; read-only"` +} + +type PersistentVolumeClaimList struct { + TypeMeta `json:",inline"` + Items []PersistentVolumeClaim `json:"items,omitempty" description: "a list of persistent volume claims"` +} + +// PersistentVolumeClaimSpec describes the common attributes of storage devices +// and allows a Source for provider-specific attributes +type PersistentVolumeClaimSpec struct { + // Contains the types of access modes required + AccessModes []AccessModeType `json:"accessModes,omitempty" description:"the desired access modes the volume should have"` + // Resources represents the minimum resources required + Resources ResourceRequirements `json:"resources,omitempty" description:"the desired resources the volume should have"` +} + +type PersistentVolumeClaimStatus struct { + // Phase represents the current phase of PersistentVolumeClaim + Phase PersistentVolumeClaimPhase `json:"phase,omitempty" description:"the current phase of the claim"` + // AccessModes contains all ways the volume backing the PVC can be mounted + AccessModes []AccessModeType `json:"accessModes,omitempty" description:"the actual access modes the volume has"` + // Represents the actual resources of the underlying volume + Capacity ResourceList `json:"capacity,omitempty" description:"the actual resources the volume has"` + // VolumeRef is a reference to the PersistentVolume bound to the PersistentVolumeClaim + VolumeRef *ObjectReference `json:"volumeRef,omitempty" description:"a reference to the backing persistent volume, when bound"` +} + type AccessModeType string const ( @@ -94,6 +171,27 @@ const ( ReadWriteMany AccessModeType = "ReadWriteMany" ) +type PersistentVolumePhase string + +const ( + // used for PersistentVolumes that are not yet bound + VolumeAvailable PersistentVolumePhase = "Available" + // used for PersistentVolumes that are bound + VolumeBound PersistentVolumePhase = "Bound" + // used for PersistentVolumes where the bound PersistentVolumeClaim was deleted + // released volumes must be recycled before becoming available again + VolumeReleased PersistentVolumePhase = "Released" +) + +type PersistentVolumeClaimPhase string + +const ( + // used for PersistentVolumeClaims that are not yet bound + ClaimPending PersistentVolumeClaimPhase = "Pending" + // used for PersistentVolumeClaims that are bound + ClaimBound PersistentVolumeClaimPhase = "Bound" +) + // HostPathVolumeSource represents bare host directory volume. // // https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/volumes.md#hostdir @@ -282,6 +380,8 @@ type Capabilities struct { type ResourceRequirements struct { // Limits describes the maximum amount of compute resources required. Limits ResourceList `json:"limits,omitempty" description:"Maximum amount of compute resources allowed"` + // Requests describes the minimum amount of compute resources required. + Requests ResourceList `json:"requests,omitempty" description:"Minimum amount of resources requested"` } // Container represents a single container that is expected to be run on the host. @@ -781,6 +881,8 @@ const ( ResourceCPU ResourceName = "cpu" // Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) ResourceMemory ResourceName = "memory" + // Volume size, in bytes (e,g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024) + ResourceStorage ResourceName = "storage" ) type ResourceList map[ResourceName]util.IntOrString diff --git a/pkg/api/v1beta3/register.go b/pkg/api/v1beta3/register.go index ef0ad07c5d4..ae74d2f8edf 100644 --- a/pkg/api/v1beta3/register.go +++ b/pkg/api/v1beta3/register.go @@ -54,6 +54,10 @@ func init() { &Secret{}, &SecretList{}, &DeleteOptions{}, + &PersistentVolume{}, + &PersistentVolumeList{}, + &PersistentVolumeClaim{}, + &PersistentVolumeClaimList{}, ) // Legacy names are supported api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{}) @@ -87,4 +91,8 @@ func (*Namespace) IsAnAPIObject() {} func (*NamespaceList) IsAnAPIObject() {} func (*Secret) IsAnAPIObject() {} func (*SecretList) IsAnAPIObject() {} +func (*PersistentVolume) IsAnAPIObject() {} +func (*PersistentVolumeList) IsAnAPIObject() {} +func (*PersistentVolumeClaim) IsAnAPIObject() {} +func (*PersistentVolumeClaimList) IsAnAPIObject() {} func (*DeleteOptions) IsAnAPIObject() {} diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index 2bc7927da74..0e338677ec3 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -214,7 +214,88 @@ type VolumeSource struct { NFS *NFSVolumeSource `json:"nfs" description:"NFS volume that will be mounted in the host machine"` } -// used by VolumeSources to describe their mounting/access modes +// Similar to VolumeSource but meant for the administrator who creates PVs. +// Exactly one of its members must be set. +type PersistentVolumeSource struct { + // GCEPersistentDisk represents a GCE Disk resource that is attached to a + // kubelet's host machine and then exposed to the pod. + GCEPersistentDisk *GCEPersistentDiskVolumeSource `json:"gcePersistentDisk" description:"GCE disk resource provisioned by an admin"` + // HostPath represents a directory on the host. + // This is useful for development and testing only. + // on-host storage is not supported in any way. + HostPath *HostPathVolumeSource `json:"hostPath" description:"a HostPath provisioned by a developer or tester; for develment use only"` +} + +type PersistentVolume struct { + TypeMeta `json:",inline"` + ObjectMeta `json:"metadata,omitempty"` + + //Spec defines a persistent volume owned by the cluster + Spec PersistentVolumeSpec `json:"spec,omitempty" description:"specification of a persistent volume as provisioned by an administrator"` + + // Status represents the current information about persistent volume. + Status PersistentVolumeStatus `json:"status,omitempty" description:"current status of a persistent volume; populated by the system, read-only"` +} + +type PersistentVolumeSpec struct { + // Resources represents the actual resources of the volume + Capacity ResourceList `json:"capacity,omitempty" description:"a description of the persistent volume's resources and capacity"` + // Source represents the location and type of a volume to mount. + // AccessModeTypes are inferred from the Source. + PersistentVolumeSource `json:",inline" description:"the actual volume backing the persistent volume"` + // holds the binding reference to a PersistentVolumeClaim + ClaimRef *ObjectReference `json:"claimRef,omitempty" description:"the binding reference to a persistent volume claim"` +} + +type PersistentVolumeStatus struct { + // Phase indicates if a volume is available, bound to a claim, or released by a claim + Phase PersistentVolumePhase `json:"phase,omitempty" description:"the current phase of a persistent volume"` +} + +type PersistentVolumeList struct { + TypeMeta `json:",inline"` + ListMeta `json:"metadata,omitempty"` + Items []PersistentVolume `json:"items,omitempty" description:"list of persistent volumes"` +} + +// PersistentVolumeClaim is a user's request for and claim to a persistent volume +type PersistentVolumeClaim struct { + TypeMeta `json:",inline"` + ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the volume requested by a pod author + Spec PersistentVolumeClaimSpec `json:"spec,omitempty" description: "the desired characteristics of a volume"` + + // Status represents the current information about a claim + Status PersistentVolumeClaimStatus `json:"status,omitempty" description:"the current status of a persistent volume claim; read-only"` +} + +type PersistentVolumeClaimList struct { + TypeMeta `json:",inline"` + ListMeta `json:"metadata,omitempty"` + Items []PersistentVolumeClaim `json:"items,omitempty" description: "a list of persistent volume claims"` +} + +// PersistentVolumeClaimSpec describes the common attributes of storage devices +// and allows a Source for provider-specific attributes +type PersistentVolumeClaimSpec struct { + // Contains the types of access modes required + AccessModes []AccessModeType `json:"accessModes,omitempty" description:"the desired access modes the volume should have"` + // Resources represents the minimum resources required + Resources ResourceRequirements `json:"resources,omitempty" description:"the desired resources the volume should have"` +} + +type PersistentVolumeClaimStatus struct { + // Phase represents the current phase of PersistentVolumeClaim + Phase PersistentVolumeClaimPhase `json:"phase,omitempty" description:"the current phase of the claim"` + // AccessModes contains all ways the volume backing the PVC can be mounted + AccessModes []AccessModeType `json:"accessModes,omitempty" description:"the actual access modes the volume has"` + // Represents the actual resources of the underlying volume + Capacity ResourceList `json:"capacity,omitempty" description:"the actual resources the volume has"` + // VolumeRef is a reference to the PersistentVolume bound to the PersistentVolumeClaim + VolumeRef *ObjectReference `json:"volumeRef,omitempty" description:"a reference to the backing persistent volume, when bound"` +} + type AccessModeType string const ( @@ -226,6 +307,27 @@ const ( ReadWriteMany AccessModeType = "ReadWriteMany" ) +type PersistentVolumePhase string + +const ( + // used for PersistentVolumes that are not yet bound + VolumeAvailable PersistentVolumePhase = "Available" + // used for PersistentVolumes that are bound + VolumeBound PersistentVolumePhase = "Bound" + // used for PersistentVolumes where the bound PersistentVolumeClaim was deleted + // released volumes must be recycled before becoming available again + VolumeReleased PersistentVolumePhase = "Released" +) + +type PersistentVolumeClaimPhase string + +const ( + // used for PersistentVolumeClaims that are not yet bound + ClaimPending PersistentVolumeClaimPhase = "Pending" + // used for PersistentVolumeClaims that are bound + ClaimBound PersistentVolumeClaimPhase = "Bound" +) + // HostPathVolumeSource represents bare host directory volume. type HostPathVolumeSource struct { Path string `json:"path" description:"path of the directory on the host"` @@ -402,6 +504,8 @@ type Capabilities struct { type ResourceRequirements struct { // Limits describes the maximum amount of compute resources required. Limits ResourceList `json:"limits,omitempty" description:"Maximum amount of compute resources allowed"` + // Requests describes the minimum amount of compute resources required. + Requests ResourceList `json:"requests,omitempty" description:"Minimum amount of resources requested"` } const ( @@ -945,6 +1049,8 @@ const ( ResourceCPU ResourceName = "cpu" // Memory, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) ResourceMemory ResourceName = "memory" + // Volume size, in bytes (e,g. 5Gi = 5GiB = 5 * 1024 * 1024 * 1024) + ResourceStorage ResourceName = "storage" ) // ResourceList is a set of (resource name, quantity) pairs. diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 94127672a55..fe640900fa9 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -351,6 +351,47 @@ func validateNFS(nfs *api.NFSVolumeSource) errs.ValidationErrorList { return allErrs } +func ValidatePersistentVolumeName(name string, prefix bool) (bool, string) { + return util.IsDNS1123Label(name), name +} + +func ValidatePersistentVolume(pv *api.PersistentVolume) errs.ValidationErrorList { + allErrs := ValidateObjectMeta(&pv.ObjectMeta, false, ValidatePersistentVolumeName) + + if len(pv.Spec.Capacity) == 0 { + allErrs = append(allErrs, errs.NewFieldRequired("persistentVolume.Capacity")) + } + + if _, ok := pv.Spec.Capacity[api.ResourceStorage]; !ok || len(pv.Spec.Capacity) > 1 { + allErrs = append(allErrs, errs.NewFieldInvalid("", pv.Spec.Capacity, fmt.Sprintf("only %s is expected", api.ResourceStorage))) + } + + numVolumes := 0 + if pv.Spec.HostPath != nil { + numVolumes++ + allErrs = append(allErrs, validateHostPathVolumeSource(pv.Spec.HostPath).Prefix("hostPath")...) + } + if pv.Spec.GCEPersistentDisk != nil { + numVolumes++ + allErrs = append(allErrs, validateGCEPersistentDiskVolumeSource(pv.Spec.GCEPersistentDisk).Prefix("persistentDisk")...) + } + if numVolumes != 1 { + allErrs = append(allErrs, errs.NewFieldInvalid("", pv.Spec.PersistentVolumeSource, "exactly 1 volume type is required")) + } + return allErrs +} + +func ValidatePersistentVolumeClaim(pvc *api.PersistentVolumeClaim) errs.ValidationErrorList { + allErrs := ValidateObjectMeta(&pvc.ObjectMeta, true, ValidatePersistentVolumeName) + if len(pvc.Spec.AccessModes) == 0 { + allErrs = append(allErrs, errs.NewFieldInvalid("persistentVolumeClaim.Spec.AccessModes", pvc.Spec.AccessModes, "at least 1 AccessModeType is required")) + } + if len(pvc.Spec.Resources.Requests) == 0 { + allErrs = append(allErrs, errs.NewFieldInvalid("persistentVolumeClaim.Spec.Resources.Requests", pvc.Spec.AccessModes, "No Resource.Requests specified")) + } + return allErrs +} + var supportedPortProtocols = util.NewStringSet(string(api.ProtocolTCP), string(api.ProtocolUDP)) func validatePorts(ports []api.ContainerPort) errs.ValidationErrorList { diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index a1afcfa529f..2465029f911 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -203,6 +203,169 @@ func TestValidateAnnotations(t *testing.T) { } } +func testVolume(name string, namespace string, spec api.PersistentVolumeSpec) *api.PersistentVolume { + + objMeta := api.ObjectMeta{Name: name} + if namespace != "" { + objMeta.Namespace = namespace + } + + return &api.PersistentVolume{ + ObjectMeta: objMeta, + Spec: spec, + } +} + +func TestValidatePersistentVolumes(t *testing.T) { + + scenarios := map[string]struct { + isExpectedFailure bool + volume *api.PersistentVolume + }{ + "good-volume": { + isExpectedFailure: false, + volume: testVolume("foo", "", api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{Path: "/foo"}, + }, + }), + }, + "unexpected-namespace": { + isExpectedFailure: true, + volume: testVolume("foo", "unexpected-namespace", api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{Path: "/foo"}, + }, + }), + }, + "bad-name": { + isExpectedFailure: true, + volume: testVolume("123*Bad(Name", "unexpected-namespace", api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{Path: "/foo"}, + }, + }, + ), + }, + "missing-name": { + isExpectedFailure: true, + volume: testVolume("", "", api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), + }, + }), + }, + "missing-capacity": { + isExpectedFailure: true, + volume: testVolume("foo", "", api.PersistentVolumeSpec{}), + }, + "too-many-sources": { + isExpectedFailure: true, + volume: testVolume("", "", api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("5G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{Path: "/foo"}, + GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "foo", FSType: "ext4"}, + }, + }), + }, + } + + for name, scenario := range scenarios { + errs := ValidatePersistentVolume(scenario.volume) + if len(errs) == 0 && scenario.isExpectedFailure { + t.Errorf("Unexpected success for scenario: %s", name) + } + if len(errs) > 0 && !scenario.isExpectedFailure { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) + } + } + +} + +func testVolumeClaim(name string, namespace string, spec api.PersistentVolumeClaimSpec) *api.PersistentVolumeClaim { + return &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace}, + Spec: spec, + } +} + +func TestValidatePersistentVolumeClaim(t *testing.T) { + + scenarios := map[string]struct { + isExpectedFailure bool + claim *api.PersistentVolumeClaim + }{ + "good-claim": { + isExpectedFailure: false, + claim: testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ + AccessModes: []api.AccessModeType{ + api.ReadWriteOnce, + api.ReadOnlyMany, + }, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), + }, + }, + }), + }, + "missing-namespace": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "", api.PersistentVolumeClaimSpec{ + AccessModes: []api.AccessModeType{ + api.ReadWriteOnce, + api.ReadOnlyMany, + }, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), + }, + }, + }), + }, + "no-access-modes": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), + }, + }, + }), + }, + "no-resource-requests": { + isExpectedFailure: true, + claim: testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{ + AccessModes: []api.AccessModeType{ + api.ReadWriteOnce, + }, + }), + }, + } + + for name, scenario := range scenarios { + errs := ValidatePersistentVolumeClaim(scenario.claim) + if len(errs) == 0 && scenario.isExpectedFailure { + t.Errorf("Unexpected success for scenario: %s", name) + } + if len(errs) > 0 && !scenario.isExpectedFailure { + t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) + } + } +} + func TestValidateVolumes(t *testing.T) { successCase := []api.Volume{ {Name: "abc", VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{"/mnt/path1"}}},