skip clusters with invalid spec

This commit is contained in:
Murat Kabilov 2017-05-16 16:46:37 +02:00 committed by GitHub
parent 1d2fb0091f
commit 356be8f0f1
6 changed files with 130 additions and 44 deletions

View File

@ -369,7 +369,10 @@ func (c *Cluster) Update(newSpec *spec.Postgresql) error {
//TODO: update PVC //TODO: update PVC
} }
newStatefulSet := c.genStatefulSet(newSpec.Spec) newStatefulSet, err := c.genStatefulSet(newSpec.Spec)
if err != nil {
return fmt.Errorf("Can't generate StatefulSet: %s", err)
}
sameSS, rollingUpdate, reason := c.compareStatefulSetWith(newStatefulSet) sameSS, rollingUpdate, reason := c.compareStatefulSetWith(newStatefulSet)
if !sameSS { if !sameSS {

View File

@ -43,7 +43,9 @@ type spiloConfiguration struct {
Bootstrap pgBootstrap `json:"bootstrap"` Bootstrap pgBootstrap `json:"bootstrap"`
} }
func (c *Cluster) resourceRequirements(resources spec.Resources) *v1.ResourceRequirements { func (c *Cluster) resourceRequirements(resources spec.Resources) (*v1.ResourceRequirements, error) {
var err error
specRequests := resources.ResourceRequest specRequests := resources.ResourceRequest
specLimits := resources.ResourceLimits specLimits := resources.ResourceLimits
@ -54,26 +56,47 @@ func (c *Cluster) resourceRequirements(resources spec.Resources) *v1.ResourceReq
result := v1.ResourceRequirements{} result := v1.ResourceRequirements{}
result.Requests = fillResourceList(specRequests, defaultRequests) result.Requests, err = fillResourceList(specRequests, defaultRequests)
result.Limits = fillResourceList(specLimits, defaultLimits) if err != nil {
return nil, fmt.Errorf("Can't fill resource requests: %s", err)
}
return &result result.Limits, err = fillResourceList(specLimits, defaultLimits)
if err != nil {
return nil, fmt.Errorf("Can't fill resource limits: %s", err)
}
return &result, nil
} }
func fillResourceList(spec spec.ResourceDescription, defaults spec.ResourceDescription) v1.ResourceList { func fillResourceList(spec spec.ResourceDescription, defaults spec.ResourceDescription) (v1.ResourceList, error) {
var err error
requests := v1.ResourceList{} requests := v1.ResourceList{}
if spec.Cpu != "" { if spec.Cpu != "" {
requests[v1.ResourceCPU] = resource.MustParse(spec.Cpu) requests[v1.ResourceCPU], err = resource.ParseQuantity(spec.Cpu)
if err != nil {
return nil, fmt.Errorf("Can't parse CPU quantity: %s", err)
}
} else { } else {
requests[v1.ResourceCPU] = resource.MustParse(defaults.Cpu) requests[v1.ResourceCPU], err = resource.ParseQuantity(defaults.Cpu)
if err != nil {
return nil, fmt.Errorf("Can't parse default CPU quantity: %s", err)
}
} }
if spec.Memory != "" { if spec.Memory != "" {
requests[v1.ResourceMemory] = resource.MustParse(spec.Memory) requests[v1.ResourceMemory], err = resource.ParseQuantity(spec.Memory)
if err != nil {
return nil, fmt.Errorf("Can't parse memory quantity: %s", err)
}
} else { } else {
requests[v1.ResourceMemory] = resource.MustParse(defaults.Memory) requests[v1.ResourceMemory], err = resource.ParseQuantity(defaults.Memory)
if err != nil {
return nil, fmt.Errorf("Can't parse default memory quantity: %s", err)
}
} }
return requests
return requests, nil
} }
func (c *Cluster) generateSpiloJSONConfiguration(pg *spec.PostgresqlParam, patroni *spec.Patroni) string { func (c *Cluster) generateSpiloJSONConfiguration(pg *spec.PostgresqlParam, patroni *spec.Patroni) string {
@ -170,7 +193,6 @@ PATRONI_INITDB_PARAMS:
} }
func (c *Cluster) genPodTemplate(resourceRequirements *v1.ResourceRequirements, pgParameters *spec.PostgresqlParam, patroniParameters *spec.Patroni) *v1.PodTemplateSpec { func (c *Cluster) genPodTemplate(resourceRequirements *v1.ResourceRequirements, pgParameters *spec.PostgresqlParam, patroniParameters *spec.Patroni) *v1.PodTemplateSpec {
spiloConfiguration := c.generateSpiloJSONConfiguration(pgParameters, patroniParameters) spiloConfiguration := c.generateSpiloJSONConfiguration(pgParameters, patroniParameters)
envVars := []v1.EnvVar{ envVars := []v1.EnvVar{
@ -290,10 +312,17 @@ func (c *Cluster) genPodTemplate(resourceRequirements *v1.ResourceRequirements,
return &template return &template
} }
func (c *Cluster) genStatefulSet(spec spec.PostgresSpec) *v1beta1.StatefulSet { func (c *Cluster) genStatefulSet(spec spec.PostgresSpec) (*v1beta1.StatefulSet, error) {
resourceRequirements := c.resourceRequirements(spec.Resources) resourceRequirements, err := c.resourceRequirements(spec.Resources)
if err != nil {
return nil, err
}
podTemplate := c.genPodTemplate(resourceRequirements, &spec.PostgresqlParam, &spec.Patroni) podTemplate := c.genPodTemplate(resourceRequirements, &spec.PostgresqlParam, &spec.Patroni)
volumeClaimTemplate := persistentVolumeClaimTemplate(spec.Volume.Size, spec.Volume.StorageClass) volumeClaimTemplate, err := persistentVolumeClaimTemplate(spec.Volume.Size, spec.Volume.StorageClass)
if err != nil {
return nil, err
}
statefulSet := &v1beta1.StatefulSet{ statefulSet := &v1beta1.StatefulSet{
ObjectMeta: v1.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
@ -309,10 +338,10 @@ func (c *Cluster) genStatefulSet(spec spec.PostgresSpec) *v1beta1.StatefulSet {
}, },
} }
return statefulSet return statefulSet, nil
} }
func persistentVolumeClaimTemplate(volumeSize, volumeStorageClass string) *v1.PersistentVolumeClaim { func persistentVolumeClaimTemplate(volumeSize, volumeStorageClass string) (*v1.PersistentVolumeClaim, error) {
metadata := v1.ObjectMeta{ metadata := v1.ObjectMeta{
Name: constants.DataVolumeName, Name: constants.DataVolumeName,
} }
@ -323,18 +352,24 @@ func persistentVolumeClaimTemplate(volumeSize, volumeStorageClass string) *v1.Pe
metadata.Annotations = map[string]string{"volume.alpha.kubernetes.io/storage-class": "default"} metadata.Annotations = map[string]string{"volume.alpha.kubernetes.io/storage-class": "default"}
} }
quantity, err := resource.ParseQuantity(volumeSize)
if err != nil {
return nil, fmt.Errorf("Can't parse volume size: %s", err)
}
volumeClaim := &v1.PersistentVolumeClaim{ volumeClaim := &v1.PersistentVolumeClaim{
ObjectMeta: metadata, ObjectMeta: metadata,
Spec: v1.PersistentVolumeClaimSpec{ Spec: v1.PersistentVolumeClaimSpec{
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
Resources: v1.ResourceRequirements{ Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{ Requests: v1.ResourceList{
v1.ResourceStorage: resource.MustParse(volumeSize), v1.ResourceStorage: quantity,
}, },
}, },
}, },
} }
return volumeClaim
return volumeClaim, nil
} }
func (c *Cluster) genUserSecrets() (secrets map[string]*v1.Secret) { func (c *Cluster) genUserSecrets() (secrets map[string]*v1.Secret) {

View File

@ -106,7 +106,10 @@ func (c *Cluster) createStatefulSet() (*v1beta1.StatefulSet, error) {
if c.Statefulset != nil { if c.Statefulset != nil {
return nil, fmt.Errorf("StatefulSet already exists in the cluster") return nil, fmt.Errorf("StatefulSet already exists in the cluster")
} }
statefulSetSpec := c.genStatefulSet(c.Spec) statefulSetSpec, err := c.genStatefulSet(c.Spec)
if err != nil {
return nil, fmt.Errorf("Can't generate StatefulSet: %s", err)
}
statefulSet, err := c.KubeClient.StatefulSets(statefulSetSpec.Namespace).Create(statefulSetSpec) statefulSet, err := c.KubeClient.StatefulSets(statefulSetSpec.Namespace).Create(statefulSetSpec)
if k8sutil.ResourceAlreadyExists(err) { if k8sutil.ResourceAlreadyExists(err) {
return nil, fmt.Errorf("StatefulSet '%s' already exists", util.NameFromMeta(statefulSetSpec.ObjectMeta)) return nil, fmt.Errorf("StatefulSet '%s' already exists", util.NameFromMeta(statefulSetSpec.ObjectMeta))

View File

@ -137,7 +137,11 @@ func (c *Cluster) syncStatefulSet() error {
match bool match bool
reason string reason string
) )
desiredSS := c.genStatefulSet(cSpec) desiredSS, err := c.genStatefulSet(cSpec)
if err != nil {
return fmt.Errorf("Can't generate StatefulSet: %s", err)
}
match, rollUpdate, reason = c.compareStatefulSetWith(desiredSS) match, rollUpdate, reason = c.compareStatefulSetWith(desiredSS)
if match { if match {
return nil return nil

View File

@ -37,17 +37,30 @@ func (c *Controller) clusterListFunc(options api.ListOptions) (runtime.Object, e
return nil, fmt.Errorf("Can't extract list of postgresql objects: %s", err) return nil, fmt.Errorf("Can't extract list of postgresql objects: %s", err)
} }
var activeClustersCnt, failedClustersCnt int
for _, obj := range objList { for _, obj := range objList {
pg, ok := obj.(*spec.Postgresql) pg, ok := obj.(*spec.Postgresql)
if !ok { if !ok {
return nil, fmt.Errorf("Can't cast object to postgresql") return nil, fmt.Errorf("Can't cast object to postgresql")
} }
if pg.Error != nil {
failedClustersCnt++
continue
}
c.queueClusterEvent(nil, pg, spec.EventSync) c.queueClusterEvent(nil, pg, spec.EventSync)
c.logger.Debugf("Sync of the '%s' cluster has been queued", util.NameFromMeta(pg.Metadata)) c.logger.Debugf("Sync of the '%s' cluster has been queued", util.NameFromMeta(pg.Metadata))
activeClustersCnt++
} }
if len(objList) > 0 { if len(objList) > 0 {
c.logger.Infof("There are %d clusters currently running", len(objList)) if failedClustersCnt > 0 && activeClustersCnt == 0 {
c.logger.Infof("There are no clusters running. %d are in the failed state", failedClustersCnt)
} else if failedClustersCnt == 0 && activeClustersCnt > 0 {
c.logger.Infof("There are %d clusters running", activeClustersCnt)
} else {
c.logger.Infof("There are %d clusters running and %d are in the failed state", activeClustersCnt, failedClustersCnt)
}
} else { } else {
c.logger.Infof("No clusters running") c.logger.Infof("No clusters running")
} }
@ -168,17 +181,31 @@ func (c *Controller) processClusterEventsQueue(idx int) {
func (c *Controller) queueClusterEvent(old, new *spec.Postgresql, eventType spec.EventType) { func (c *Controller) queueClusterEvent(old, new *spec.Postgresql, eventType spec.EventType) {
var ( var (
uid types.UID uid types.UID
clusterName spec.NamespacedName clusterName spec.NamespacedName
clusterError error
) )
if old != nil { if old != nil { //update, delete
uid = old.Metadata.GetUID() uid = old.Metadata.GetUID()
clusterName = util.NameFromMeta(old.Metadata) clusterName = util.NameFromMeta(old.Metadata)
} else { if eventType == spec.EventUpdate && new.Error == nil && old != nil {
eventType = spec.EventAdd
clusterError = new.Error
} else {
clusterError = old.Error
}
} else { //add, sync
uid = new.Metadata.GetUID() uid = new.Metadata.GetUID()
clusterName = util.NameFromMeta(new.Metadata) clusterName = util.NameFromMeta(new.Metadata)
clusterError = new.Error
} }
if clusterError != nil && eventType != spec.EventDelete {
c.logger.Debugf("Skipping %s event for invalid cluster %s (reason: %s)", eventType, clusterName, clusterError)
return
}
workerId := c.clusterWorkerId(clusterName) workerId := c.clusterWorkerId(clusterName)
clusterEvent := spec.ClusterEvent{ clusterEvent := spec.ClusterEvent{
EventType: eventType, EventType: eventType,

View File

@ -38,8 +38,8 @@ type ResourceDescription struct {
} }
type Resources struct { type Resources struct {
ResourceRequest ResourceDescription `json:"requests,omitempty""` ResourceRequest ResourceDescription `json:"requests,omitempty"`
ResourceLimits ResourceDescription `json:"limits,omitempty""` ResourceLimits ResourceDescription `json:"limits,omitempty"`
} }
type Patroni struct { type Patroni struct {
@ -62,6 +62,7 @@ const (
ClusterStatusUpdateFailed PostgresStatus = "UpdateFailed" ClusterStatusUpdateFailed PostgresStatus = "UpdateFailed"
ClusterStatusAddFailed PostgresStatus = "CreateFailed" ClusterStatusAddFailed PostgresStatus = "CreateFailed"
ClusterStatusRunning PostgresStatus = "Running" ClusterStatusRunning PostgresStatus = "Running"
ClusterStatusInvalid PostgresStatus = "Invalid"
) )
// PostgreSQL Third Party (resource) Object // PostgreSQL Third Party (resource) Object
@ -71,6 +72,7 @@ type Postgresql struct {
Spec PostgresSpec `json:"spec"` Spec PostgresSpec `json:"spec"`
Status PostgresStatus `json:"status"` Status PostgresStatus `json:"status"`
Error error `json:"-"`
} }
type PostgresSpec struct { type PostgresSpec struct {
@ -189,6 +191,18 @@ func (pl *PostgresqlList) GetListMeta() unversioned.List {
return &pl.Metadata return &pl.Metadata
} }
func clusterName(clusterName string, teamName string) (string, error) {
teamNameLen := len(teamName)
if len(clusterName) < teamNameLen+2 {
return "", fmt.Errorf("Name is too short")
}
if strings.ToLower(clusterName[:teamNameLen+1]) != strings.ToLower(teamName)+"-" {
return "", fmt.Errorf("Name must match {TEAM}-{NAME} format")
}
return clusterName[teamNameLen+1:], nil
}
// The code below is used only to work around a known problem with third-party // The code below is used only to work around a known problem with third-party
// resources and ugorji. If/when these issues are resolved, the code below // resources and ugorji. If/when these issues are resolved, the code below
// should no longer be required. // should no longer be required.
@ -196,31 +210,31 @@ func (pl *PostgresqlList) GetListMeta() unversioned.List {
type PostgresqlListCopy PostgresqlList type PostgresqlListCopy PostgresqlList
type PostgresqlCopy Postgresql type PostgresqlCopy Postgresql
func clusterName(clusterName string, teamName string) (string, error) {
teamNameLen := len(teamName)
if len(clusterName) < teamNameLen+2 {
return "", fmt.Errorf("Name is too short")
}
if strings.ToLower(clusterName[:teamNameLen+1]) != strings.ToLower(teamName)+"-" {
return "", fmt.Errorf("Name must start with the team name and dash")
}
return clusterName[teamNameLen+1:], nil
}
func (p *Postgresql) UnmarshalJSON(data []byte) error { func (p *Postgresql) UnmarshalJSON(data []byte) error {
tmp := PostgresqlCopy{} tmp := PostgresqlCopy{}
err := json.Unmarshal(data, &tmp) err := json.Unmarshal(data, &tmp)
if err != nil { if err != nil {
return err metaErr := json.Unmarshal(data, &tmp.Metadata)
if metaErr != nil {
return err
}
tmp.Error = err
tmp.Status = ClusterStatusInvalid
*p = Postgresql(tmp)
return nil
} }
tmp2 := Postgresql(tmp) tmp2 := Postgresql(tmp)
clusterName, err := clusterName(tmp2.Metadata.Name, tmp2.Spec.TeamId) clusterName, err := clusterName(tmp2.Metadata.Name, tmp2.Spec.TeamId)
if err != nil { if err == nil {
return err tmp2.Spec.ClusterName = clusterName
} else {
tmp2.Error = err
tmp2.Status = ClusterStatusInvalid
} }
tmp2.Spec.ClusterName = clusterName
*p = tmp2 *p = tmp2
return nil return nil