use sync methods while updating the cluster

This commit is contained in:
Murat Kabilov 2017-11-03 12:00:43 +01:00 committed by GitHub
parent 47dd766fa7
commit 86803406db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 456 additions and 382 deletions

View File

@ -28,7 +28,6 @@ import (
"github.com/zalando-incubator/postgres-operator/pkg/util/patroni" "github.com/zalando-incubator/postgres-operator/pkg/util/patroni"
"github.com/zalando-incubator/postgres-operator/pkg/util/teams" "github.com/zalando-incubator/postgres-operator/pkg/util/teams"
"github.com/zalando-incubator/postgres-operator/pkg/util/users" "github.com/zalando-incubator/postgres-operator/pkg/util/users"
"github.com/zalando-incubator/postgres-operator/pkg/util/volumes"
) )
var ( var (
@ -46,7 +45,7 @@ type Config struct {
type kubeResources struct { type kubeResources struct {
Services map[PostgresRole]*v1.Service Services map[PostgresRole]*v1.Service
Endpoint *v1.Endpoints Endpoints map[PostgresRole]*v1.Endpoints
Secrets map[types.UID]*v1.Secret Secrets map[types.UID]*v1.Secret
Statefulset *v1beta1.StatefulSet Statefulset *v1beta1.StatefulSet
PodDisruptionBudget *policybeta1.PodDisruptionBudget PodDisruptionBudget *policybeta1.PodDisruptionBudget
@ -99,12 +98,15 @@ func New(cfg Config, kubeClient k8sutil.KubernetesClient, pgSpec spec.Postgresql
}) })
cluster := &Cluster{ cluster := &Cluster{
Config: cfg, Config: cfg,
Postgresql: pgSpec, Postgresql: pgSpec,
pgUsers: make(map[string]spec.PgUser), pgUsers: make(map[string]spec.PgUser),
systemUsers: make(map[string]spec.PgUser), systemUsers: make(map[string]spec.PgUser),
podSubscribers: make(map[spec.NamespacedName]chan spec.PodEvent), podSubscribers: make(map[spec.NamespacedName]chan spec.PodEvent),
kubeResources: kubeResources{Secrets: make(map[types.UID]*v1.Secret), Services: make(map[PostgresRole]*v1.Service)}, kubeResources: kubeResources{
Secrets: make(map[types.UID]*v1.Secret),
Services: make(map[PostgresRole]*v1.Service),
Endpoints: make(map[PostgresRole]*v1.Endpoints)},
masterLess: false, masterLess: false,
userSyncStrategy: users.DefaultUserSyncStrategy{}, userSyncStrategy: users.DefaultUserSyncStrategy{},
deleteOptions: &metav1.DeleteOptions{OrphanDependents: &orphanDependents}, deleteOptions: &metav1.DeleteOptions{OrphanDependents: &orphanDependents},
@ -203,17 +205,16 @@ func (c *Cluster) Create() error {
c.setStatus(spec.ClusterStatusCreating) c.setStatus(spec.ClusterStatusCreating)
//service will create endpoint implicitly
ep, err = c.createEndpoint()
if err != nil {
return fmt.Errorf("could not create endpoint: %v", err)
}
c.logger.Infof("endpoint %q has been successfully created", util.NameFromMeta(ep.ObjectMeta))
for _, role := range []PostgresRole{Master, Replica} { for _, role := range []PostgresRole{Master, Replica} {
if role == Replica && !c.Spec.ReplicaLoadBalancer { if role == Replica && !c.Spec.ReplicaLoadBalancer {
continue continue
} }
ep, err = c.createEndpoint(role)
if err != nil {
return fmt.Errorf("could not create %s endpoint: %v", role, err)
}
c.logger.Infof("endpoint %q has been successfully created", util.NameFromMeta(ep.ObjectMeta))
service, err = c.createService(role) service, err = c.createService(role)
if err != nil { if err != nil {
return fmt.Errorf("could not create %s service: %v", role, err) return fmt.Errorf("could not create %s service: %v", role, err)
@ -226,7 +227,7 @@ func (c *Cluster) Create() error {
} }
c.logger.Infof("users have been initialized") c.logger.Infof("users have been initialized")
if err = c.applySecrets(); err != nil { if err = c.syncSecrets(); err != nil {
return fmt.Errorf("could not create secrets: %v", err) return fmt.Errorf("could not create secrets: %v", err)
} }
c.logger.Infof("secrets have been successfully created") c.logger.Infof("secrets have been successfully created")
@ -257,8 +258,8 @@ func (c *Cluster) Create() error {
} }
c.logger.Infof("users have been successfully created") c.logger.Infof("users have been successfully created")
if err = c.createDatabases(); err != nil { if err = c.syncDatabases(); err != nil {
return fmt.Errorf("could not create databases: %v", err) return fmt.Errorf("could not sync databases: %v", err)
} }
c.logger.Infof("databases have been successfully created") c.logger.Infof("databases have been successfully created")
} else { } else {
@ -267,48 +268,13 @@ func (c *Cluster) Create() error {
} }
} }
err = c.listResources() if err := c.listResources(); err != nil {
if err != nil {
c.logger.Errorf("could not list resources: %v", err) c.logger.Errorf("could not list resources: %v", err)
} }
return nil return nil
} }
func (c *Cluster) sameServiceWith(role PostgresRole, service *v1.Service) (match bool, reason string) {
//TODO: improve comparison
if c.Services[role].Spec.Type != service.Spec.Type {
return false, fmt.Sprintf("new %s service's type %q doesn't match the current one %q",
role, service.Spec.Type, c.Services[role].Spec.Type)
}
oldSourceRanges := c.Services[role].Spec.LoadBalancerSourceRanges
newSourceRanges := service.Spec.LoadBalancerSourceRanges
/* work around Kubernetes 1.6 serializing [] as nil. See https://github.com/kubernetes/kubernetes/issues/43203 */
if (len(oldSourceRanges) == 0) && (len(newSourceRanges) == 0) {
return true, ""
}
if !reflect.DeepEqual(oldSourceRanges, newSourceRanges) {
return false, fmt.Sprintf("new %s service's LoadBalancerSourceRange doesn't match the current one", role)
}
oldDNSAnnotation := c.Services[role].Annotations[constants.ZalandoDNSNameAnnotation]
newDNSAnnotation := service.Annotations[constants.ZalandoDNSNameAnnotation]
if oldDNSAnnotation != newDNSAnnotation {
return false, fmt.Sprintf("new %s service's %q annotation doesn't match the current one", role, constants.ZalandoDNSNameAnnotation)
}
return true, ""
}
func (c *Cluster) sameVolumeWith(volume spec.Volume) (match bool, reason string) {
if !reflect.DeepEqual(c.Spec.Volume, volume) {
reason = "new volume's specification doesn't match the current one"
} else {
match = true
}
return
}
func (c *Cluster) compareStatefulSetWith(statefulSet *v1beta1.StatefulSet) *compareStatefulsetResult { func (c *Cluster) compareStatefulSetWith(statefulSet *v1beta1.StatefulSet) *compareStatefulsetResult {
reasons := make([]string, 0) reasons := make([]string, 0)
var match, needsRollUpdate, needsReplace bool var match, needsRollUpdate, needsReplace bool
@ -406,6 +372,7 @@ func (c *Cluster) compareStatefulSetWith(statefulSet *v1beta1.StatefulSet) *comp
if needsRollUpdate || needsReplace { if needsRollUpdate || needsReplace {
match = false match = false
} }
return &compareStatefulsetResult{match: match, reasons: reasons, rollingUpdate: needsRollUpdate, replace: needsReplace} return &compareStatefulsetResult{match: match, reasons: reasons, rollingUpdate: needsRollUpdate, replace: needsReplace}
} }
@ -417,12 +384,13 @@ func compareResources(a *v1.ResourceRequirements, b *v1.ResourceRequirements) (e
if equal && (b != nil) { if equal && (b != nil) {
equal = compareResoucesAssumeFirstNotNil(b, a) equal = compareResoucesAssumeFirstNotNil(b, a)
} }
return return
} }
func compareResoucesAssumeFirstNotNil(a *v1.ResourceRequirements, b *v1.ResourceRequirements) bool { func compareResoucesAssumeFirstNotNil(a *v1.ResourceRequirements, b *v1.ResourceRequirements) bool {
if b == nil || (len(b.Requests) == 0) { if b == nil || (len(b.Requests) == 0) {
return (len(a.Requests) == 0) return len(a.Requests) == 0
} }
for k, v := range a.Requests { for k, v := range a.Requests {
if (&v).Cmp(b.Requests[k]) != 0 { if (&v).Cmp(b.Requests[k]) != 0 {
@ -440,108 +408,108 @@ func compareResoucesAssumeFirstNotNil(a *v1.ResourceRequirements, b *v1.Resource
// Update changes Kubernetes objects according to the new specification. Unlike the sync case, the missing object. // Update changes Kubernetes objects according to the new specification. Unlike the sync case, the missing object.
// (i.e. service) is treated as an error. // (i.e. service) is treated as an error.
func (c *Cluster) Update(newSpec *spec.Postgresql) error { func (c *Cluster) Update(oldSpec, newSpec *spec.Postgresql) error {
updateFailed := false
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
c.setStatus(spec.ClusterStatusUpdating) c.setStatus(spec.ClusterStatusUpdating)
c.Postgresql = *newSpec
/* Make sure we update when this function exits */
defer func() { defer func() {
c.Postgresql = *newSpec if updateFailed {
c.setStatus(spec.ClusterStatusUpdateFailed)
} else if c.Status != spec.ClusterStatusRunning {
c.setStatus(spec.ClusterStatusRunning)
}
}() }()
for _, role := range []PostgresRole{Master, Replica} { if oldSpec.Spec.PgVersion != newSpec.Spec.PgVersion { // PG versions comparison
if role == Replica { c.logger.Warningf("postgresql version change(%q -> %q) has no effect", oldSpec.Spec.PgVersion, newSpec.Spec.PgVersion)
if !newSpec.Spec.ReplicaLoadBalancer { //we need that hack to generate statefulset with the old version
// old spec had a load balancer, but the new one doesn't newSpec.Spec.PgVersion = oldSpec.Spec.PgVersion
if c.Spec.ReplicaLoadBalancer { }
err := c.deleteService(role)
if err != nil { // Service
return fmt.Errorf("could not delete obsolete %s service: %v", role, err) if !reflect.DeepEqual(c.generateService(Master, &oldSpec.Spec), c.generateService(Master, &newSpec.Spec)) ||
} !reflect.DeepEqual(c.generateService(Replica, &oldSpec.Spec), c.generateService(Replica, &newSpec.Spec)) ||
c.logger.Infof("deleted obsolete %s service", role) oldSpec.Spec.ReplicaLoadBalancer != newSpec.Spec.ReplicaLoadBalancer {
} c.logger.Debugf("syncing services")
} else { if err := c.syncServices(); err != nil {
if !c.Spec.ReplicaLoadBalancer { c.logger.Errorf("could not sync services: %v", err)
// old spec didn't have a load balancer, but the one does updateFailed = true
service, err := c.createService(role)
if err != nil {
return fmt.Errorf("could not create new %s service: %v", role, err)
}
c.logger.Infof("%s service %q has been created", role, util.NameFromMeta(service.ObjectMeta))
}
}
// only proceed further if both old and new load balancer were present
if !(newSpec.Spec.ReplicaLoadBalancer && c.Spec.ReplicaLoadBalancer) {
continue
}
}
newService := c.generateService(role, &newSpec.Spec)
if match, reason := c.sameServiceWith(role, newService); !match {
c.logServiceChanges(role, c.Services[role], newService, true, reason)
if err := c.updateService(role, newService); err != nil {
c.setStatus(spec.ClusterStatusUpdateFailed)
return fmt.Errorf("could not update %s service: %v", role, err)
}
c.logger.Infof("%s service %q has been updated", role, util.NameFromMeta(c.Services[role].ObjectMeta))
} }
} }
newStatefulSet, err := c.generateStatefulSet(newSpec.Spec) if !reflect.DeepEqual(oldSpec.Spec.Users, newSpec.Spec.Users) {
if err != nil { c.logger.Debugf("syncing secrets")
return fmt.Errorf("could not generate statefulset: %v", err) if err := c.initUsers(); err != nil {
} c.logger.Errorf("could not init users: %v", err)
cmp := c.compareStatefulSetWith(newStatefulSet) updateFailed = true
}
if !cmp.match { c.logger.Debugf("syncing secrets")
c.logStatefulSetChanges(c.Statefulset, newStatefulSet, true, cmp.reasons)
//TODO: mind the case of updating allowedSourceRanges //TODO: mind the secrets of the deleted/new users
if !cmp.replace { if err := c.syncSecrets(); err != nil {
if err := c.updateStatefulSet(newStatefulSet); err != nil { c.logger.Errorf("could not sync secrets: %v", err)
c.setStatus(spec.ClusterStatusUpdateFailed) updateFailed = true
return fmt.Errorf("could not upate statefulset: %v", err) }
}
} else { if !c.databaseAccessDisabled() {
if err := c.replaceStatefulSet(newStatefulSet); err != nil { c.logger.Debugf("syncing roles")
c.setStatus(spec.ClusterStatusUpdateFailed) if err := c.syncRoles(true); err != nil {
return fmt.Errorf("could not replace statefulset: %v", err) c.logger.Errorf("could not sync roles: %v", err)
updateFailed = true
} }
} }
//TODO: if there is a change in numberOfInstances, make sure Pods have been created/deleted
c.logger.Infof("statefulset %q has been updated", util.NameFromMeta(c.Statefulset.ObjectMeta))
} }
if c.Spec.PgVersion != newSpec.Spec.PgVersion { // PG versions comparison // Volume
c.logger.Warningf("postgresql version change(%q -> %q) is not allowed", if oldSpec.Spec.Size != newSpec.Spec.Size {
c.Spec.PgVersion, newSpec.Spec.PgVersion) c.logger.Debugf("syncing persistent volumes")
//TODO: rewrite pg version in tpr spec c.logVolumeChanges(oldSpec.Spec.Volume, newSpec.Spec.Volume)
}
if cmp.rollingUpdate { if err := c.syncVolumes(); err != nil {
c.logger.Infof("rolling update is needed") c.logger.Errorf("could not sync persistent volumes: %v", err)
// TODO: wait for actual streaming to the replica updateFailed = true
if err := c.recreatePods(); err != nil {
c.setStatus(spec.ClusterStatusUpdateFailed)
return fmt.Errorf("could not recreate pods: %v", err)
} }
c.logger.Infof("rolling update has been finished")
} }
if match, reason := c.sameVolumeWith(newSpec.Spec.Volume); !match { // Statefulset
c.logVolumeChanges(c.Spec.Volume, newSpec.Spec.Volume, reason) func() {
if err := c.resizeVolumes(newSpec.Spec.Volume, []volumes.VolumeResizer{&volumes.EBSVolumeResizer{}}); err != nil { oldSs, err := c.generateStatefulSet(&oldSpec.Spec)
return fmt.Errorf("could not update volumes: %v", err) if err != nil {
c.logger.Errorf("could not generate old statefulset spec")
updateFailed = true
return
} }
c.logger.Infof("volumes have been updated successfully")
}
if err := c.syncPodDisruptionBudget(true); err != nil { newSs, err := c.generateStatefulSet(&newSpec.Spec)
c.setStatus(spec.ClusterStatusUpdateFailed) if err != nil {
return fmt.Errorf("could not update pod disruption budget: %v", err) c.logger.Errorf("could not generate new statefulset spec")
} updateFailed = true
return
}
c.setStatus(spec.ClusterStatusRunning) if !reflect.DeepEqual(oldSs, newSs) {
c.logger.Debugf("syncing statefulsets")
if err := c.syncStatefulSet(); err != nil {
c.logger.Errorf("could not sync statefulsets: %v", err)
updateFailed = true
}
}
}()
// Databases
if !reflect.DeepEqual(oldSpec.Spec.Databases, newSpec.Spec.Databases) {
c.logger.Infof("syncing databases")
if err := c.syncDatabases(); err != nil {
c.logger.Errorf("could not sync databases: %v", err)
updateFailed = true
}
}
return nil return nil
} }
@ -551,14 +519,15 @@ func (c *Cluster) Delete() error {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
if err := c.deleteEndpoint(); err != nil {
return fmt.Errorf("could not delete endpoint: %v", err)
}
for _, role := range []PostgresRole{Master, Replica} { for _, role := range []PostgresRole{Master, Replica} {
if role == Replica && !c.Spec.ReplicaLoadBalancer { if role == Replica && !c.Spec.ReplicaLoadBalancer {
continue continue
} }
if err := c.deleteEndpoint(role); err != nil {
return fmt.Errorf("could not delete %s endpoint: %v", role, err)
}
if err := c.deleteService(role); err != nil { if err := c.deleteService(role); err != nil {
return fmt.Errorf("could not delete %s service: %v", role, err) return fmt.Errorf("could not delete %s service: %v", role, err)
} }
@ -715,7 +684,8 @@ func (c *Cluster) GetStatus() *spec.ClusterStatus {
MasterService: c.GetServiceMaster(), MasterService: c.GetServiceMaster(),
ReplicaService: c.GetServiceReplica(), ReplicaService: c.GetServiceReplica(),
Endpoint: c.GetEndpoint(), MasterEndpoint: c.GetEndpointMaster(),
ReplicaEndpoint: c.GetEndpointReplica(),
StatefulSet: c.GetStatefulSet(), StatefulSet: c.GetStatefulSet(),
PodDisruptionBudget: c.GetPodDisruptionBudget(), PodDisruptionBudget: c.GetPodDisruptionBudget(),
CurrentProcess: c.GetCurrentProcess(), CurrentProcess: c.GetCurrentProcess(),

View File

@ -55,8 +55,13 @@ func (c *Cluster) statefulSetName() string {
return c.Name return c.Name
} }
func (c *Cluster) endpointName() string { func (c *Cluster) endpointName(role PostgresRole) string {
return c.Name name := c.Name
if role == Replica {
name = name + "-repl"
}
return name
} }
func (c *Cluster) serviceName(role PostgresRole) string { func (c *Cluster) serviceName(role PostgresRole) string {
@ -149,7 +154,7 @@ func (c *Cluster) generateSpiloJSONConfiguration(pg *spec.PostgresqlParam, patro
// maps and normal string items in the array of initdb options. We need // maps and normal string items in the array of initdb options. We need
// both to convert the initial key-value to strings when necessary, and // both to convert the initial key-value to strings when necessary, and
// to de-duplicate the options supplied. // to de-duplicate the options supplied.
PATRONI_INITDB_PARAMS: PatroniInitDBParams:
for _, k := range initdbOptionNames { for _, k := range initdbOptionNames {
v := patroni.InitDB[k] v := patroni.InitDB[k]
for i, defaultParam := range config.Bootstrap.Initdb { for i, defaultParam := range config.Bootstrap.Initdb {
@ -159,7 +164,7 @@ PATRONI_INITDB_PARAMS:
for k1 := range defaultParam.(map[string]string) { for k1 := range defaultParam.(map[string]string) {
if k1 == k { if k1 == k {
(config.Bootstrap.Initdb[i]).(map[string]string)[k] = v (config.Bootstrap.Initdb[i]).(map[string]string)[k] = v
continue PATRONI_INITDB_PARAMS continue PatroniInitDBParams
} }
} }
} }
@ -167,12 +172,12 @@ PATRONI_INITDB_PARAMS:
{ {
/* if the option already occurs in the list */ /* if the option already occurs in the list */
if defaultParam.(string) == v { if defaultParam.(string) == v {
continue PATRONI_INITDB_PARAMS continue PatroniInitDBParams
} }
} }
default: default:
c.logger.Warningf("unsupported type for initdb configuration item %s: %T", defaultParam, defaultParam) c.logger.Warningf("unsupported type for initdb configuration item %s: %T", defaultParam, defaultParam)
continue PATRONI_INITDB_PARAMS continue PatroniInitDBParams
} }
} }
// The following options are known to have no parameters // The following options are known to have no parameters
@ -356,7 +361,7 @@ func (c *Cluster) generatePodTemplate(resourceRequirements *v1.ResourceRequireme
if cloneDescription.ClusterName != "" { if cloneDescription.ClusterName != "" {
envVars = append(envVars, c.generateCloneEnvironment(cloneDescription)...) envVars = append(envVars, c.generateCloneEnvironment(cloneDescription)...)
} }
privilegedMode := bool(true) privilegedMode := true
container := v1.Container{ container := v1.Container{
Name: c.containerName(), Name: c.containerName(),
Image: c.OpConfig.DockerImage, Image: c.OpConfig.DockerImage,
@ -411,7 +416,7 @@ func (c *Cluster) generatePodTemplate(resourceRequirements *v1.ResourceRequireme
return &template return &template
} }
func (c *Cluster) generateStatefulSet(spec spec.PostgresSpec) (*v1beta1.StatefulSet, error) { func (c *Cluster) generateStatefulSet(spec *spec.PostgresSpec) (*v1beta1.StatefulSet, error) {
resourceRequirements, err := c.resourceRequirements(spec.Resources) resourceRequirements, err := c.resourceRequirements(spec.Resources)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not generate resource requirements: %v", err) return nil, fmt.Errorf("could not generate resource requirements: %v", err)
@ -513,7 +518,7 @@ func (c *Cluster) generateSingleUserSecret(namespace string, pgUser spec.PgUser)
return &secret return &secret
} }
func (c *Cluster) generateService(role PostgresRole, newSpec *spec.PostgresSpec) *v1.Service { func (c *Cluster) generateService(role PostgresRole, spec *spec.PostgresSpec) *v1.Service {
var dnsName string var dnsName string
if role == Master { if role == Master {
@ -534,12 +539,12 @@ func (c *Cluster) generateService(role PostgresRole, newSpec *spec.PostgresSpec)
var annotations map[string]string var annotations map[string]string
// Examine the per-cluster load balancer setting, if it is not defined - check the operator configuration. // Examine the per-cluster load balancer setting, if it is not defined - check the operator configuration.
if (newSpec.UseLoadBalancer != nil && *newSpec.UseLoadBalancer) || if (spec.UseLoadBalancer != nil && *spec.UseLoadBalancer) ||
(newSpec.UseLoadBalancer == nil && c.OpConfig.EnableLoadBalancer) { (spec.UseLoadBalancer == nil && c.OpConfig.EnableLoadBalancer) {
// safe default value: lock load balancer to only local address unless overridden explicitly. // safe default value: lock load balancer to only local address unless overridden explicitly.
sourceRanges := []string{localHost} sourceRanges := []string{localHost}
allowedSourceRanges := newSpec.AllowedSourceRanges allowedSourceRanges := spec.AllowedSourceRanges
if len(allowedSourceRanges) >= 0 { if len(allowedSourceRanges) >= 0 {
sourceRanges = allowedSourceRanges sourceRanges = allowedSourceRanges
} }
@ -566,12 +571,12 @@ func (c *Cluster) generateService(role PostgresRole, newSpec *spec.PostgresSpec)
return service return service
} }
func (c *Cluster) generateMasterEndpoints(subsets []v1.EndpointSubset) *v1.Endpoints { func (c *Cluster) generateEndpoint(role PostgresRole, subsets []v1.EndpointSubset) *v1.Endpoints {
endpoints := &v1.Endpoints{ endpoints := &v1.Endpoints{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: c.endpointName(), Name: c.endpointName(role),
Namespace: c.Namespace, Namespace: c.Namespace,
Labels: c.roleLabelsSet(Master), Labels: c.roleLabelsSet(role),
}, },
} }
if len(subsets) > 0 { if len(subsets) > 0 {

View File

@ -11,67 +11,12 @@ import (
"k8s.io/client-go/pkg/apis/apps/v1beta1" "k8s.io/client-go/pkg/apis/apps/v1beta1"
policybeta1 "k8s.io/client-go/pkg/apis/policy/v1beta1" policybeta1 "k8s.io/client-go/pkg/apis/policy/v1beta1"
"github.com/zalando-incubator/postgres-operator/pkg/spec"
"github.com/zalando-incubator/postgres-operator/pkg/util" "github.com/zalando-incubator/postgres-operator/pkg/util"
"github.com/zalando-incubator/postgres-operator/pkg/util/constants" "github.com/zalando-incubator/postgres-operator/pkg/util/constants"
"github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil" "github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil"
"github.com/zalando-incubator/postgres-operator/pkg/util/retryutil" "github.com/zalando-incubator/postgres-operator/pkg/util/retryutil"
) )
func (c *Cluster) loadResources() error {
var err error
ns := c.Namespace
masterService, err := c.KubeClient.Services(ns).Get(c.serviceName(Master), metav1.GetOptions{})
if err == nil {
c.Services[Master] = masterService
} else if !k8sutil.ResourceNotFound(err) {
c.logger.Errorf("could not get master service: %v", err)
}
replicaService, err := c.KubeClient.Services(ns).Get(c.serviceName(Replica), metav1.GetOptions{})
if err == nil {
c.Services[Replica] = replicaService
} else if !k8sutil.ResourceNotFound(err) {
c.logger.Errorf("could not get replica service: %v", err)
}
ep, err := c.KubeClient.Endpoints(ns).Get(c.endpointName(), metav1.GetOptions{})
if err == nil {
c.Endpoint = ep
} else if !k8sutil.ResourceNotFound(err) {
c.logger.Errorf("could not get endpoint: %v", err)
}
secrets, err := c.KubeClient.Secrets(ns).List(metav1.ListOptions{LabelSelector: c.labelsSet().String()})
if err != nil {
c.logger.Errorf("could not get list of secrets: %v", err)
}
for i, secret := range secrets.Items {
if _, ok := c.Secrets[secret.UID]; ok {
continue
}
c.Secrets[secret.UID] = &secrets.Items[i]
c.logger.Debugf("secret loaded, uid: %q", secret.UID)
}
ss, err := c.KubeClient.StatefulSets(ns).Get(c.statefulSetName(), metav1.GetOptions{})
if err == nil {
c.Statefulset = ss
} else if !k8sutil.ResourceNotFound(err) {
c.logger.Errorf("could not get statefulset: %v", err)
}
pdb, err := c.KubeClient.PodDisruptionBudgets(ns).Get(c.podDisruptionBudgetName(), metav1.GetOptions{})
if err == nil {
c.PodDisruptionBudget = pdb
} else if !k8sutil.ResourceNotFound(err) {
c.logger.Errorf("could not get pod disruption budget: %v", err)
}
return nil
}
func (c *Cluster) listResources() error { func (c *Cluster) listResources() error {
if c.PodDisruptionBudget != nil { if c.PodDisruptionBudget != nil {
c.logger.Infof("found pod disruption budget: %q (uid: %q)", util.NameFromMeta(c.PodDisruptionBudget.ObjectMeta), c.PodDisruptionBudget.UID) c.logger.Infof("found pod disruption budget: %q (uid: %q)", util.NameFromMeta(c.PodDisruptionBudget.ObjectMeta), c.PodDisruptionBudget.UID)
@ -85,8 +30,8 @@ func (c *Cluster) listResources() error {
c.logger.Infof("found secret: %q (uid: %q)", util.NameFromMeta(obj.ObjectMeta), obj.UID) c.logger.Infof("found secret: %q (uid: %q)", util.NameFromMeta(obj.ObjectMeta), obj.UID)
} }
if c.Endpoint != nil { for role, endpoint := range c.Endpoints {
c.logger.Infof("found endpoint: %q (uid: %q)", util.NameFromMeta(c.Endpoint.ObjectMeta), c.Endpoint.UID) c.logger.Infof("found %s endpoint: %q (uid: %q)", role, util.NameFromMeta(endpoint.ObjectMeta), endpoint.UID)
} }
for role, service := range c.Services { for role, service := range c.Services {
@ -119,7 +64,7 @@ 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, err := c.generateStatefulSet(c.Spec) statefulSetSpec, err := c.generateStatefulSet(&c.Spec)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not generate statefulset: %v", err) return nil, fmt.Errorf("could not generate statefulset: %v", err)
} }
@ -307,11 +252,13 @@ func (c *Cluster) createService(role PostgresRole) (*v1.Service, error) {
func (c *Cluster) updateService(role PostgresRole, newService *v1.Service) error { func (c *Cluster) updateService(role PostgresRole, newService *v1.Service) error {
c.setProcessName("updating %v service", role) c.setProcessName("updating %v service", role)
if c.Services[role] == nil { if c.Services[role] == nil {
return fmt.Errorf("there is no service in the cluster") return fmt.Errorf("there is no service in the cluster")
} }
serviceName := util.NameFromMeta(c.Services[role].ObjectMeta) serviceName := util.NameFromMeta(c.Services[role].ObjectMeta)
endpointName := util.NameFromMeta(c.Endpoint.ObjectMeta) endpointName := util.NameFromMeta(c.Endpoints[role].ObjectMeta)
// TODO: check if it possible to change the service type with a patch in future versions of Kubernetes // TODO: check if it possible to change the service type with a patch in future versions of Kubernetes
if newService.Spec.Type != c.Services[role].Spec.Type { if newService.Spec.Type != c.Services[role].Spec.Type {
// service type has changed, need to replace the service completely. // service type has changed, need to replace the service completely.
@ -324,38 +271,42 @@ func (c *Cluster) updateService(role PostgresRole, newService *v1.Service) error
if role == Master { if role == Master {
// for the master service we need to re-create the endpoint as well. Get the up-to-date version of // for the master service we need to re-create the endpoint as well. Get the up-to-date version of
// the addresses stored in it before the service is deleted (deletion of the service removes the endpooint) // the addresses stored in it before the service is deleted (deletion of the service removes the endpooint)
currentEndpoint, err = c.KubeClient.Endpoints(c.Services[role].Namespace).Get(c.Services[role].Name, metav1.GetOptions{}) currentEndpoint, err = c.KubeClient.Endpoints(c.Namespace).Get(c.endpointName(role), metav1.GetOptions{})
if err != nil { if err != nil {
return fmt.Errorf("could not get current cluster endpoints: %v", err) return fmt.Errorf("could not get current cluster %s endpoints: %v", role, err)
} }
} }
err = c.KubeClient.Services(c.Services[role].Namespace).Delete(c.Services[role].Name, c.deleteOptions) err = c.KubeClient.Services(serviceName.Namespace).Delete(serviceName.Name, c.deleteOptions)
if err != nil { if err != nil {
return fmt.Errorf("could not delete service %q: %v", serviceName, err) return fmt.Errorf("could not delete service %q: %v", serviceName, err)
} }
c.Endpoint = nil
svc, err := c.KubeClient.Services(newService.Namespace).Create(newService) c.Endpoints[role] = nil
svc, err := c.KubeClient.Services(serviceName.Namespace).Create(newService)
if err != nil { if err != nil {
return fmt.Errorf("could not create service %q: %v", serviceName, err) return fmt.Errorf("could not create service %q: %v", serviceName, err)
} }
c.Services[role] = svc c.Services[role] = svc
if role == Master { if role == Master {
// create the new endpoint using the addresses obtained from the previous one // create the new endpoint using the addresses obtained from the previous one
endpointSpec := c.generateMasterEndpoints(currentEndpoint.Subsets) endpointSpec := c.generateEndpoint(role, currentEndpoint.Subsets)
ep, err := c.KubeClient.Endpoints(c.Services[role].Namespace).Create(endpointSpec) ep, err := c.KubeClient.Endpoints(endpointSpec.Namespace).Create(endpointSpec)
if err != nil { if err != nil {
return fmt.Errorf("could not create endpoint %q: %v", endpointName, err) return fmt.Errorf("could not create endpoint %q: %v", endpointName, err)
} }
c.Endpoint = ep
c.Endpoints[role] = ep
} }
return nil return nil
} }
if len(newService.ObjectMeta.Annotations) > 0 { if len(newService.ObjectMeta.Annotations) > 0 {
annotationsPatchData := metadataAnnotationsPatch(newService.ObjectMeta.Annotations) annotationsPatchData := metadataAnnotationsPatch(newService.ObjectMeta.Annotations)
_, err := c.KubeClient.Services(c.Services[role].Namespace).Patch( _, err := c.KubeClient.Services(serviceName.Namespace).Patch(
c.Services[role].Name, serviceName.Name,
types.StrategicMergePatchType, types.StrategicMergePatchType,
[]byte(annotationsPatchData), "") []byte(annotationsPatchData), "")
@ -369,8 +320,8 @@ func (c *Cluster) updateService(role PostgresRole, newService *v1.Service) error
return fmt.Errorf("could not form patch for the service %q: %v", serviceName, err) return fmt.Errorf("could not form patch for the service %q: %v", serviceName, err)
} }
svc, err := c.KubeClient.Services(c.Services[role].Namespace).Patch( svc, err := c.KubeClient.Services(serviceName.Namespace).Patch(
c.Services[role].Name, serviceName.Name,
types.MergePatchType, types.MergePatchType,
patchData, "") patchData, "")
if err != nil { if err != nil {
@ -383,31 +334,36 @@ func (c *Cluster) updateService(role PostgresRole, newService *v1.Service) error
func (c *Cluster) deleteService(role PostgresRole) error { func (c *Cluster) deleteService(role PostgresRole) error {
c.logger.Debugf("deleting service %s", role) c.logger.Debugf("deleting service %s", role)
if c.Services[role] == nil {
return fmt.Errorf("there is no %s service in the cluster", role)
}
service := c.Services[role] service := c.Services[role]
err := c.KubeClient.Services(service.Namespace).Delete(service.Name, c.deleteOptions)
if err != nil { if err := c.KubeClient.Services(service.Namespace).Delete(service.Name, c.deleteOptions); err != nil {
return err return err
} }
c.logger.Infof("%s service %q has been deleted", role, util.NameFromMeta(service.ObjectMeta)) c.logger.Infof("%s service %q has been deleted", role, util.NameFromMeta(service.ObjectMeta))
c.Services[role] = nil c.Services[role] = nil
return nil return nil
} }
func (c *Cluster) createEndpoint() (*v1.Endpoints, error) { func (c *Cluster) createEndpoint(role PostgresRole) (*v1.Endpoints, error) {
c.setProcessName("creating endpoint") c.setProcessName("creating endpoint")
if c.Endpoint != nil { if c.Endpoints[role] != nil {
return nil, fmt.Errorf("endpoint already exists in the cluster") return nil, fmt.Errorf("%s endpoint already exists in the cluster", role)
} }
endpointsSpec := c.generateMasterEndpoints(nil) subsets := make([]v1.EndpointSubset, 0)
if role == Master {
//TODO: set subsets to the master
}
endpointsSpec := c.generateEndpoint(role, subsets)
endpoints, err := c.KubeClient.Endpoints(endpointsSpec.Namespace).Create(endpointsSpec) endpoints, err := c.KubeClient.Endpoints(endpointsSpec.Namespace).Create(endpointsSpec)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("could not create %s endpoint: %v", role, err)
} }
c.Endpoint = endpoints
c.Endpoints[role] = endpoints
return endpoints, nil return endpoints, nil
} }
@ -430,13 +386,19 @@ func (c *Cluster) createPodDisruptionBudget() (*policybeta1.PodDisruptionBudget,
} }
func (c *Cluster) updatePodDisruptionBudget(pdb *policybeta1.PodDisruptionBudget) error { func (c *Cluster) updatePodDisruptionBudget(pdb *policybeta1.PodDisruptionBudget) error {
if c.podEventsQueue == nil { if c.PodDisruptionBudget == nil {
return fmt.Errorf("there is no pod disruption budget in the cluster") return fmt.Errorf("there is no pod disruption budget in the cluster")
} }
newPdb, err := c.KubeClient.PodDisruptionBudgets(pdb.Namespace).Update(pdb) if err := c.deletePodDisruptionBudget(); err != nil {
return fmt.Errorf("could not delete pod disruption budget: %v", err)
}
newPdb, err := c.KubeClient.
PodDisruptionBudgets(pdb.Namespace).
Create(pdb)
if err != nil { if err != nil {
return fmt.Errorf("could not update pod disruption budget: %v", err) return fmt.Errorf("could not create pod disruption budget: %v", err)
} }
c.PodDisruptionBudget = newPdb c.PodDisruptionBudget = newPdb
@ -448,69 +410,50 @@ func (c *Cluster) deletePodDisruptionBudget() error {
if c.PodDisruptionBudget == nil { if c.PodDisruptionBudget == nil {
return fmt.Errorf("there is no pod disruption budget in the cluster") return fmt.Errorf("there is no pod disruption budget in the cluster")
} }
pdbName := util.NameFromMeta(c.PodDisruptionBudget.ObjectMeta)
err := c.KubeClient. err := c.KubeClient.
PodDisruptionBudgets(c.PodDisruptionBudget.Namespace). PodDisruptionBudgets(c.PodDisruptionBudget.Namespace).
Delete(c.PodDisruptionBudget.Namespace, c.deleteOptions) Delete(c.PodDisruptionBudget.Name, c.deleteOptions)
if err != nil { if err != nil {
return fmt.Errorf("could not delete pod disruption budget: %v", err) return fmt.Errorf("could not delete pod disruption budget: %v", err)
} }
c.logger.Infof("pod disruption budget %q has been deleted", util.NameFromMeta(c.PodDisruptionBudget.ObjectMeta)) c.logger.Infof("pod disruption budget %q has been deleted", util.NameFromMeta(c.PodDisruptionBudget.ObjectMeta))
c.PodDisruptionBudget = nil c.PodDisruptionBudget = nil
err = retryutil.Retry(c.OpConfig.ResourceCheckInterval, c.OpConfig.ResourceCheckTimeout,
func() (bool, error) {
_, err2 := c.KubeClient.PodDisruptionBudgets(pdbName.Namespace).Get(pdbName.Name, metav1.GetOptions{})
if err2 == nil {
return false, nil
}
if k8sutil.ResourceNotFound(err2) {
return true, nil
} else {
return false, err2
}
})
if err != nil {
return fmt.Errorf("could not delete pod disruption budget: %v", err)
}
return nil return nil
} }
func (c *Cluster) deleteEndpoint() error { func (c *Cluster) deleteEndpoint(role PostgresRole) error {
c.setProcessName("deleting endpoint") c.setProcessName("deleting endpoint")
c.logger.Debugln("deleting endpoint") c.logger.Debugln("deleting endpoint")
if c.Endpoint == nil { if c.Endpoints[role] == nil {
return fmt.Errorf("there is no endpoint in the cluster") return fmt.Errorf("there is no %s endpoint in the cluster", role)
} }
err := c.KubeClient.Endpoints(c.Endpoint.Namespace).Delete(c.Endpoint.Name, c.deleteOptions)
if err != nil { if err := c.KubeClient.Endpoints(c.Endpoints[role].Namespace).Delete(c.Endpoints[role].Name, c.deleteOptions); err != nil {
return fmt.Errorf("could not delete endpoint: %v", err) return fmt.Errorf("could not delete endpoint: %v", err)
} }
c.logger.Infof("endpoint %q has been deleted", util.NameFromMeta(c.Endpoint.ObjectMeta))
c.Endpoint = nil
return nil c.logger.Infof("endpoint %q has been deleted", util.NameFromMeta(c.Endpoints[role].ObjectMeta))
}
func (c *Cluster) applySecrets() error { c.Endpoints[role] = nil
c.setProcessName("applying secrets")
secrets := c.generateUserSecrets()
for secretUsername, secretSpec := range secrets {
secret, err := c.KubeClient.Secrets(secretSpec.Namespace).Create(secretSpec)
if k8sutil.ResourceAlreadyExists(err) {
var userMap map[string]spec.PgUser
curSecret, err2 := c.KubeClient.Secrets(secretSpec.Namespace).Get(secretSpec.Name, metav1.GetOptions{})
if err2 != nil {
return fmt.Errorf("could not get current secret: %v", err2)
}
c.logger.Debugf("secret %q already exists, fetching it's password", util.NameFromMeta(curSecret.ObjectMeta))
if secretUsername == c.systemUsers[constants.SuperuserKeyName].Name {
secretUsername = constants.SuperuserKeyName
userMap = c.systemUsers
} else if secretUsername == c.systemUsers[constants.ReplicationUserKeyName].Name {
secretUsername = constants.ReplicationUserKeyName
userMap = c.systemUsers
} else {
userMap = c.pgUsers
}
pwdUser := userMap[secretUsername]
pwdUser.Password = string(curSecret.Data["password"])
userMap[secretUsername] = pwdUser
continue
} else {
if err != nil {
return fmt.Errorf("could not create secret for user %q: %v", secretUsername, err)
}
c.Secrets[secret.UID] = secret
c.logger.Debugf("created new secret %q, uid: %q", util.NameFromMeta(secret.ObjectMeta), secret.UID)
}
}
return nil return nil
} }
@ -543,9 +486,14 @@ func (c *Cluster) GetServiceReplica() *v1.Service {
return c.Services[Replica] return c.Services[Replica]
} }
// GetEndpoint returns cluster's kubernetes Endpoint // GetEndpointMaster returns cluster's kubernetes master Endpoint
func (c *Cluster) GetEndpoint() *v1.Endpoints { func (c *Cluster) GetEndpointMaster() *v1.Endpoints {
return c.Endpoint return c.Endpoints[Master]
}
// GetEndpointReplica returns cluster's kubernetes master Endpoint
func (c *Cluster) GetEndpointReplica() *v1.Endpoints {
return c.Endpoints[Replica]
} }
// GetStatefulSet returns cluster's kubernetes StatefulSet // GetStatefulSet returns cluster's kubernetes StatefulSet

View File

@ -4,10 +4,12 @@ import (
"fmt" "fmt"
"reflect" "reflect"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
policybeta1 "k8s.io/client-go/pkg/apis/policy/v1beta1" policybeta1 "k8s.io/client-go/pkg/apis/policy/v1beta1"
"github.com/zalando-incubator/postgres-operator/pkg/spec" "github.com/zalando-incubator/postgres-operator/pkg/spec"
"github.com/zalando-incubator/postgres-operator/pkg/util" "github.com/zalando-incubator/postgres-operator/pkg/util"
"github.com/zalando-incubator/postgres-operator/pkg/util/constants"
"github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil" "github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil"
"github.com/zalando-incubator/postgres-operator/pkg/util/volumes" "github.com/zalando-incubator/postgres-operator/pkg/util/volumes"
) )
@ -20,11 +22,6 @@ func (c *Cluster) Sync(newSpec *spec.Postgresql) (err error) {
c.Postgresql = *newSpec c.Postgresql = *newSpec
err = c.loadResources()
if err != nil {
c.logger.Errorf("could not load resources: %v", err)
}
defer func() { defer func() {
if err != nil { if err != nil {
c.setStatus(spec.ClusterStatusSyncFailed) c.setStatus(spec.ClusterStatusSyncFailed)
@ -41,39 +38,15 @@ func (c *Cluster) Sync(newSpec *spec.Postgresql) (err error) {
c.logger.Debugf("syncing secrets") c.logger.Debugf("syncing secrets")
//TODO: mind the secrets of the deleted/new users //TODO: mind the secrets of the deleted/new users
if err = c.applySecrets(); err != nil { if err = c.syncSecrets(); err != nil {
if !k8sutil.ResourceAlreadyExists(err) { err = fmt.Errorf("could not sync secrets: %v", err)
err = fmt.Errorf("could not sync secrets: %v", err) return
return
}
}
c.logger.Debugf("syncing endpoints")
if err = c.syncEndpoint(); err != nil {
if !k8sutil.ResourceAlreadyExists(err) {
err = fmt.Errorf("could not sync endpoints: %v", err)
return
}
} }
c.logger.Debugf("syncing services") c.logger.Debugf("syncing services")
for _, role := range []PostgresRole{Master, Replica} { if err = c.syncServices(); err != nil {
if role == Replica && !c.Spec.ReplicaLoadBalancer { err = fmt.Errorf("could not sync services: %v", err)
if c.Services[role] != nil { return
// delete the left over replica service
if err = c.deleteService(role); err != nil {
err = fmt.Errorf("could not delete obsolete %s service: %v", role, err)
return
}
}
continue
}
if err = c.syncService(role); err != nil {
if !k8sutil.ResourceAlreadyExists(err) {
err = fmt.Errorf("coud not sync %s service: %v", role, err)
return
}
}
} }
c.logger.Debugf("syncing statefulsets") c.logger.Debugf("syncing statefulsets")
@ -112,73 +85,170 @@ func (c *Cluster) Sync(newSpec *spec.Postgresql) (err error) {
return return
} }
func (c *Cluster) syncService(role PostgresRole) error { func (c *Cluster) syncServices() error {
cSpec := c.Spec for _, role := range []PostgresRole{Master, Replica} {
if c.Services[role] == nil { c.logger.Debugf("syncing %s service", role)
c.logger.Infof("could not find the cluster's %s service", role)
svc, err := c.createService(role) if err := c.syncEndpoint(role); err != nil {
if err != nil { return fmt.Errorf("could not sync %s endpont: %v", role, err)
return fmt.Errorf("could not create missing %s service: %v", role, err)
} }
c.logger.Infof("created missing %s service %q", role, util.NameFromMeta(svc.ObjectMeta))
return nil if err := c.syncService(role); err != nil {
return fmt.Errorf("could not sync %s service: %v", role, err)
}
} }
desiredSvc := c.generateService(role, &cSpec)
match, reason := c.sameServiceWith(role, desiredSvc)
if match {
return nil
}
c.logServiceChanges(role, c.Services[role], desiredSvc, false, reason)
if err := c.updateService(role, desiredSvc); err != nil {
return fmt.Errorf("could not update %s service to match desired state: %v", role, err)
}
c.logger.Infof("%s service %q is in the desired state now", role, util.NameFromMeta(desiredSvc.ObjectMeta))
return nil return nil
} }
func (c *Cluster) syncEndpoint() error { func (c *Cluster) syncService(role PostgresRole) error {
if c.Endpoint == nil { c.setProcessName("syncing %s service", role)
c.logger.Infof("could not find the cluster's endpoint")
ep, err := c.createEndpoint() svc, err := c.KubeClient.Services(c.Namespace).Get(c.serviceName(role), metav1.GetOptions{})
if err != nil { if err == nil {
return fmt.Errorf("could not create missing endpoint: %v", err) if role == Replica && !c.Spec.ReplicaLoadBalancer {
if err := c.deleteService(role); err != nil {
return fmt.Errorf("could not delete %s service", role)
}
} }
c.logger.Infof("created missing endpoint %q", util.NameFromMeta(ep.ObjectMeta))
desiredSvc := c.generateService(role, &c.Spec)
match, reason := k8sutil.SameService(svc, desiredSvc)
if match {
c.Services[role] = svc
return nil
}
c.logServiceChanges(role, svc, desiredSvc, false, reason)
if err := c.updateService(role, desiredSvc); err != nil {
return fmt.Errorf("could not update %s service to match desired state: %v", role, err)
}
c.logger.Infof("%s service %q is in the desired state now", role, util.NameFromMeta(desiredSvc.ObjectMeta))
return nil return nil
} else if !k8sutil.ResourceNotFound(err) {
return fmt.Errorf("could not get %s service: %v", role, err)
}
c.Services[role] = nil
// Service does not exist
if role == Replica && !c.Spec.ReplicaLoadBalancer {
return nil
}
c.logger.Infof("could not find the cluster's %s service", role)
if svc, err := c.createService(role); err != nil {
if k8sutil.ResourceAlreadyExists(err) {
c.logger.Infof("%s service %q already exists", role, util.NameFromMeta(svc.ObjectMeta))
svc, err := c.KubeClient.Services(c.Namespace).Get(c.serviceName(role), metav1.GetOptions{})
if err == nil {
c.Services[role] = svc
} else {
c.logger.Infof("could not fetch existing %s service: %v", role, err)
}
} else {
return fmt.Errorf("could not create missing %s service: %v", role, err)
}
} else {
c.logger.Infof("created missing %s service %q", role, util.NameFromMeta(svc.ObjectMeta))
c.Services[role] = svc
}
return nil
}
func (c *Cluster) syncEndpoint(role PostgresRole) error {
c.setProcessName("syncing %s endpoint", role)
ep, err := c.KubeClient.Endpoints(c.Namespace).Get(c.endpointName(role), metav1.GetOptions{})
if err == nil {
if role == Replica && !c.Spec.ReplicaLoadBalancer {
if err := c.deleteEndpoint(role); err != nil {
return fmt.Errorf("could not delete %s endpoint", role)
}
}
c.Endpoints[role] = ep
return nil
} else if !k8sutil.ResourceNotFound(err) {
return fmt.Errorf("could not get %s endpoint: %v", role, err)
}
c.Endpoints[role] = nil
if role == Replica && !c.Spec.ReplicaLoadBalancer {
return nil
}
c.logger.Infof("could not find the cluster's %s endpoint", role)
if ep, err := c.createEndpoint(role); err != nil {
if k8sutil.ResourceAlreadyExists(err) {
c.logger.Infof("%s endpoint %q already exists", role, util.NameFromMeta(ep.ObjectMeta))
ep, err := c.KubeClient.Endpoints(c.Namespace).Get(c.endpointName(role), metav1.GetOptions{})
if err == nil {
c.Endpoints[role] = ep
} else {
c.logger.Infof("could not fetch existing %s endpoint: %v", role, err)
}
} else {
return fmt.Errorf("could not create missing %s endpoint: %v", role, err)
}
} else {
c.logger.Infof("created missing %s endpoint %q", role, util.NameFromMeta(ep.ObjectMeta))
c.Endpoints[role] = ep
} }
return nil return nil
} }
func (c *Cluster) syncPodDisruptionBudget(isUpdate bool) error { func (c *Cluster) syncPodDisruptionBudget(isUpdate bool) error {
if c.PodDisruptionBudget == nil { pdb, err := c.KubeClient.PodDisruptionBudgets(c.Namespace).Get(c.podDisruptionBudgetName(), metav1.GetOptions{})
c.logger.Infof("could not find the cluster's pod disruption budget") if err == nil {
pdb, err := c.createPodDisruptionBudget() c.PodDisruptionBudget = pdb
if err != nil {
return fmt.Errorf("could not create pod disruption budget: %v", err)
}
c.logger.Infof("created missing pod disruption budget %q", util.NameFromMeta(pdb.ObjectMeta))
return nil
} else {
newPDB := c.generatePodDisruptionBudget() newPDB := c.generatePodDisruptionBudget()
if match, reason := c.samePDBWith(newPDB); !match { if match, reason := k8sutil.SamePDB(pdb, newPDB); !match {
c.logPDBChanges(c.PodDisruptionBudget, newPDB, isUpdate, reason) c.logPDBChanges(pdb, newPDB, isUpdate, reason)
if err := c.updatePodDisruptionBudget(newPDB); err != nil { if err := c.updatePodDisruptionBudget(newPDB); err != nil {
return err return err
} }
} else {
c.PodDisruptionBudget = pdb
} }
return nil
} else if !k8sutil.ResourceNotFound(err) {
return fmt.Errorf("could not get pod disruption budget: %v", err)
} }
c.PodDisruptionBudget = nil
c.logger.Infof("could not find the cluster's pod disruption budget")
if pdb, err = c.createPodDisruptionBudget(); err != nil {
if k8sutil.ResourceAlreadyExists(err) {
c.logger.Infof("pod disruption budget %q already exists", util.NameFromMeta(pdb.ObjectMeta))
} else {
return fmt.Errorf("could not create pod disruption budget: %v", err)
}
} else {
c.logger.Infof("created missing pod disruption budget %q", util.NameFromMeta(pdb.ObjectMeta))
c.PodDisruptionBudget = pdb
}
return nil return nil
} }
func (c *Cluster) syncStatefulSet() error { func (c *Cluster) syncStatefulSet() error {
cSpec := c.Spec var (
var rollUpdate bool err error
if c.Statefulset == nil { rollUpdate bool
)
c.Statefulset, err = c.KubeClient.StatefulSets(c.Namespace).Get(c.statefulSetName(), metav1.GetOptions{})
if err != nil && !k8sutil.ResourceNotFound(err) {
return fmt.Errorf("could not get statefulset: %v", err)
}
if err != nil && k8sutil.ResourceNotFound(err) {
c.logger.Infof("could not find the cluster's statefulset") c.logger.Infof("could not find the cluster's statefulset")
pods, err := c.listPods() pods, err := c.listPods()
if err != nil { if err != nil {
@ -189,22 +259,25 @@ func (c *Cluster) syncStatefulSet() error {
c.logger.Infof("found pods without the statefulset: trigger rolling update") c.logger.Infof("found pods without the statefulset: trigger rolling update")
rollUpdate = true rollUpdate = true
} }
ss, err := c.createStatefulSet() ss, err := c.createStatefulSet()
if err != nil { if err != nil {
return fmt.Errorf("could not create missing statefulset: %v", err) return fmt.Errorf("could not create missing statefulset: %v", err)
} }
err = c.waitStatefulsetPodsReady()
if err != nil { if err = c.waitStatefulsetPodsReady(); err != nil {
return fmt.Errorf("cluster is not ready: %v", err) return fmt.Errorf("cluster is not ready: %v", err)
} }
c.logger.Infof("created missing statefulset %q", util.NameFromMeta(ss.ObjectMeta)) c.logger.Infof("created missing statefulset %q", util.NameFromMeta(ss.ObjectMeta))
if !rollUpdate { if !rollUpdate {
return nil return nil
} }
} }
/* TODO: should check that we need to replace the statefulset */ /* TODO: should check that we need to replace the statefulset */
if !rollUpdate { if !rollUpdate {
desiredSS, err := c.generateStatefulSet(cSpec) desiredSS, err := c.generateStatefulSet(&c.Spec)
if err != nil { if err != nil {
return fmt.Errorf("could not generate statefulset: %v", err) return fmt.Errorf("could not generate statefulset: %v", err)
} }
@ -239,6 +312,45 @@ func (c *Cluster) syncStatefulSet() error {
return nil return nil
} }
func (c *Cluster) syncSecrets() error {
c.setProcessName("syncing secrets")
secrets := c.generateUserSecrets()
for secretUsername, secretSpec := range secrets {
secret, err := c.KubeClient.Secrets(secretSpec.Namespace).Create(secretSpec)
if k8sutil.ResourceAlreadyExists(err) {
var userMap map[string]spec.PgUser
curSecret, err2 := c.KubeClient.Secrets(secretSpec.Namespace).Get(secretSpec.Name, metav1.GetOptions{})
if err2 != nil {
return fmt.Errorf("could not get current secret: %v", err2)
}
c.logger.Debugf("secret %q already exists, fetching it's password", util.NameFromMeta(curSecret.ObjectMeta))
if secretUsername == c.systemUsers[constants.SuperuserKeyName].Name {
secretUsername = constants.SuperuserKeyName
userMap = c.systemUsers
} else if secretUsername == c.systemUsers[constants.ReplicationUserKeyName].Name {
secretUsername = constants.ReplicationUserKeyName
userMap = c.systemUsers
} else {
userMap = c.pgUsers
}
pwdUser := userMap[secretUsername]
pwdUser.Password = string(curSecret.Data["password"])
userMap[secretUsername] = pwdUser
continue
} else {
if err != nil {
return fmt.Errorf("could not create secret for user %q: %v", secretUsername, err)
}
c.Secrets[secret.UID] = secret
c.logger.Debugf("created new secret %q, uid: %q", util.NameFromMeta(secret.ObjectMeta), secret.UID)
}
}
return nil
}
func (c *Cluster) syncRoles(readFromDatabase bool) error { func (c *Cluster) syncRoles(readFromDatabase bool) error {
c.setProcessName("syncing roles") c.setProcessName("syncing roles")
@ -268,11 +380,14 @@ func (c *Cluster) syncRoles(readFromDatabase bool) error {
if err = c.userSyncStrategy.ExecuteSyncRequests(pgSyncRequests, c.pgDb); err != nil { if err = c.userSyncStrategy.ExecuteSyncRequests(pgSyncRequests, c.pgDb); err != nil {
return fmt.Errorf("error executing sync statements: %v", err) return fmt.Errorf("error executing sync statements: %v", err)
} }
return nil return nil
} }
// syncVolumes reads all persistent volumes and checks that their size matches the one declared in the statefulset. // syncVolumes reads all persistent volumes and checks that their size matches the one declared in the statefulset.
func (c *Cluster) syncVolumes() error { func (c *Cluster) syncVolumes() error {
c.setProcessName("syncing volumes")
act, err := c.volumesNeedResizing(c.Spec.Volume) act, err := c.volumesNeedResizing(c.Spec.Volume)
if err != nil { if err != nil {
return fmt.Errorf("could not compare size of the volumes: %v", err) return fmt.Errorf("could not compare size of the volumes: %v", err)

View File

@ -144,12 +144,9 @@ func (c *Cluster) logServiceChanges(role PostgresRole, old, new *v1.Service, isU
} }
} }
func (c *Cluster) logVolumeChanges(old, new spec.Volume, reason string) { func (c *Cluster) logVolumeChanges(old, new spec.Volume) {
c.logger.Infof("volume specification has been changed") c.logger.Infof("volume specification has been changed")
c.logger.Debugf("diff\n%s\n", util.PrettyDiff(old, new)) c.logger.Debugf("diff\n%s\n", util.PrettyDiff(old, new))
if reason != "" {
c.logger.Infof("reason: %s", reason)
}
} }
func (c *Cluster) getOAuthToken() (string, error) { func (c *Cluster) getOAuthToken() (string, error) {

View File

@ -146,11 +146,11 @@ func (c *Cluster) resizeVolumes(newVolume spec.Volume, resizers []volumes.Volume
} }
func (c *Cluster) volumesNeedResizing(newVolume spec.Volume) (bool, error) { func (c *Cluster) volumesNeedResizing(newVolume spec.Volume) (bool, error) {
volumes, manifestSize, err := c.listVolumesWithManifestSize(newVolume) vols, manifestSize, err := c.listVolumesWithManifestSize(newVolume)
if err != nil { if err != nil {
return false, err return false, err
} }
for _, pv := range volumes { for _, pv := range vols {
currentSize := quantityToGigabyte(pv.Spec.Capacity[v1.ResourceStorage]) currentSize := quantityToGigabyte(pv.Spec.Capacity[v1.ResourceStorage])
if currentSize != manifestSize { if currentSize != manifestSize {
return true, nil return true, nil
@ -165,11 +165,11 @@ func (c *Cluster) listVolumesWithManifestSize(newVolume spec.Volume) ([]*v1.Pers
return nil, 0, fmt.Errorf("could not parse volume size from the manifest: %v", err) return nil, 0, fmt.Errorf("could not parse volume size from the manifest: %v", err)
} }
manifestSize := quantityToGigabyte(newSize) manifestSize := quantityToGigabyte(newSize)
volumes, err := c.listPersistentVolumes() vols, err := c.listPersistentVolumes()
if err != nil { if err != nil {
return nil, 0, fmt.Errorf("could not list persistent volumes: %v", err) return nil, 0, fmt.Errorf("could not list persistent volumes: %v", err)
} }
return volumes, manifestSize, nil return vols, manifestSize, nil
} }
// getPodNameFromPersistentVolume returns a pod name that it extracts from the volume claim ref. // getPodNameFromPersistentVolume returns a pod name that it extracts from the volume claim ref.

View File

@ -193,7 +193,7 @@ func (c *Controller) processEvent(event spec.ClusterEvent) {
return return
} }
c.curWorkerCluster.Store(event.WorkerID, cl) c.curWorkerCluster.Store(event.WorkerID, cl)
if err := cl.Update(event.NewSpec); err != nil { if err := cl.Update(event.OldSpec, event.NewSpec); err != nil {
cl.Error = fmt.Errorf("could not update cluster: %v", err) cl.Error = fmt.Errorf("could not update cluster: %v", err)
lg.Error(cl.Error) lg.Error(cl.Error)
@ -374,9 +374,6 @@ func (c *Controller) postgresqlUpdate(prev, cur interface{}) {
if !ok { if !ok {
c.logger.Errorf("could not cast to postgresql spec") c.logger.Errorf("could not cast to postgresql spec")
} }
if pgOld.ResourceVersion == pgNew.ResourceVersion {
return
}
if reflect.DeepEqual(pgOld.Spec, pgNew.Spec) { if reflect.DeepEqual(pgOld.Spec, pgNew.Spec) {
return return
} }

View File

@ -99,7 +99,8 @@ type ClusterStatus struct {
Cluster string Cluster string
MasterService *v1.Service MasterService *v1.Service
ReplicaService *v1.Service ReplicaService *v1.Service
Endpoint *v1.Endpoints MasterEndpoint *v1.Endpoints
ReplicaEndpoint *v1.Endpoints
StatefulSet *v1beta1.StatefulSet StatefulSet *v1beta1.StatefulSet
PodDisruptionBudget *policyv1beta1.PodDisruptionBudget PodDisruptionBudget *policyv1beta1.PodDisruptionBudget

View File

@ -2,6 +2,7 @@ package k8sutil
import ( import (
"fmt" "fmt"
"reflect"
apiextclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" apiextclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
apiextbeta1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1" apiextbeta1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
@ -9,10 +10,12 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
v1beta1 "k8s.io/client-go/kubernetes/typed/apps/v1beta1" "k8s.io/client-go/kubernetes/typed/apps/v1beta1"
v1core "k8s.io/client-go/kubernetes/typed/core/v1" v1core "k8s.io/client-go/kubernetes/typed/core/v1"
policyv1beta1 "k8s.io/client-go/kubernetes/typed/policy/v1beta1" policyv1beta1 "k8s.io/client-go/kubernetes/typed/policy/v1beta1"
"k8s.io/client-go/pkg/api" "k8s.io/client-go/pkg/api"
"k8s.io/client-go/pkg/api/v1"
policybeta1 "k8s.io/client-go/pkg/apis/policy/v1beta1"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
@ -99,5 +102,43 @@ func NewFromConfig(cfg *rest.Config) (KubernetesClient, error) {
kubeClient.CustomResourceDefinitionsGetter = apiextClient.ApiextensionsV1beta1() kubeClient.CustomResourceDefinitionsGetter = apiextClient.ApiextensionsV1beta1()
return kubeClient, nil return kubeClient, nil
}
// SameService compares the Services
func SameService(cur, new *v1.Service) (match bool, reason string) {
//TODO: improve comparison
if cur.Spec.Type != new.Spec.Type {
return false, fmt.Sprintf("new service's type %q doesn't match the current one %q",
new.Spec.Type, cur.Spec.Type)
}
oldSourceRanges := cur.Spec.LoadBalancerSourceRanges
newSourceRanges := new.Spec.LoadBalancerSourceRanges
/* work around Kubernetes 1.6 serializing [] as nil. See https://github.com/kubernetes/kubernetes/issues/43203 */
if (len(oldSourceRanges) == 0) && (len(newSourceRanges) == 0) {
return true, ""
}
if !reflect.DeepEqual(oldSourceRanges, newSourceRanges) {
return false, "new service's LoadBalancerSourceRange doesn't match the current one"
}
oldDNSAnnotation := cur.Annotations[constants.ZalandoDNSNameAnnotation]
newDNSAnnotation := new.Annotations[constants.ZalandoDNSNameAnnotation]
if oldDNSAnnotation != newDNSAnnotation {
return false, fmt.Sprintf("new service's %q annotation doesn't match the current one", constants.ZalandoDNSNameAnnotation)
}
return true, ""
}
// SamePDB compares the PodDisruptionBudgets
func SamePDB(cur, new *policybeta1.PodDisruptionBudget) (match bool, reason string) {
//TODO: improve comparison
match = reflect.DeepEqual(new.Spec, cur.Spec)
if !match {
reason = "new service spec doesn't match the current one"
}
return
} }