Various improvements
Add synchronization logic. For now get rid of podTemplate, type fields. Add crd validation & configuration part, put retry on top of lookup function installation.
This commit is contained in:
		
							parent
							
								
									8bd2086cd2
								
							
						
					
					
						commit
						3ff1147bce
					
				| 
						 | 
					@ -106,6 +106,7 @@ rules:
 | 
				
			||||||
  - apps
 | 
					  - apps
 | 
				
			||||||
  resources:
 | 
					  resources:
 | 
				
			||||||
  - statefulsets
 | 
					  - statefulsets
 | 
				
			||||||
 | 
					  - deployments
 | 
				
			||||||
  verbs:
 | 
					  verbs:
 | 
				
			||||||
  - create
 | 
					  - create
 | 
				
			||||||
  - delete
 | 
					  - delete
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -294,6 +294,43 @@ spec:
 | 
				
			||||||
                  pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
 | 
					                  pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
 | 
				
			||||||
                scalyr_server_url:
 | 
					                scalyr_server_url:
 | 
				
			||||||
                  type: string
 | 
					                  type: string
 | 
				
			||||||
 | 
					            connection_pool:
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
 | 
					              properties:
 | 
				
			||||||
 | 
					                connection_pool_schema:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  #default: "pooler"
 | 
				
			||||||
 | 
					                connection_pool_user:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  #default: "pooler"
 | 
				
			||||||
 | 
					                connection_pool_instances_number:
 | 
				
			||||||
 | 
					                  type: integer
 | 
				
			||||||
 | 
					                  #default: 1
 | 
				
			||||||
 | 
					                connection_pool_image:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  #default: "pierone.stups.zalan.do/acid/pgbouncer:0.0.1"
 | 
				
			||||||
 | 
					                connection_pool_mode:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  enum:
 | 
				
			||||||
 | 
					                    - "session"
 | 
				
			||||||
 | 
					                    - "transaction"
 | 
				
			||||||
 | 
					                  #default: "transaction"
 | 
				
			||||||
 | 
					                connection_pool_default_cpu_limit:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
 | 
				
			||||||
 | 
					                  #default: "1"
 | 
				
			||||||
 | 
					                connection_pool_default_cpu_request:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  pattern: '^(\d+m|\d+(\.\d{1,3})?)$'
 | 
				
			||||||
 | 
					                  #default: "1"
 | 
				
			||||||
 | 
					                connection_pool_default_memory_limit:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
 | 
				
			||||||
 | 
					                  #default: "100m"
 | 
				
			||||||
 | 
					                connection_pool_default_memory_request:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$'
 | 
				
			||||||
 | 
					                  #default: "100m"
 | 
				
			||||||
        status:
 | 
					        status:
 | 
				
			||||||
          type: object
 | 
					          type: object
 | 
				
			||||||
          additionalProperties:
 | 
					          additionalProperties:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,8 @@ type PostgresSpec struct {
 | 
				
			||||||
	Patroni         `json:"patroni,omitempty"`
 | 
						Patroni         `json:"patroni,omitempty"`
 | 
				
			||||||
	Resources       `json:"resources,omitempty"`
 | 
						Resources       `json:"resources,omitempty"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ConnectionPool *ConnectionPool `json:"connectionPool,omitempty"`
 | 
						EnableConnectionPool bool            `json:"enable_connection_pool,omitempty"`
 | 
				
			||||||
 | 
						ConnectionPool       *ConnectionPool `json:"connectionPool,omitempty"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	TeamID      string `json:"teamId"`
 | 
						TeamID      string `json:"teamId"`
 | 
				
			||||||
	DockerImage string `json:"dockerImage,omitempty"`
 | 
						DockerImage string `json:"dockerImage,omitempty"`
 | 
				
			||||||
| 
						 | 
					@ -159,12 +160,15 @@ type PostgresStatus struct {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Options for connection pooler
 | 
					// Options for connection pooler
 | 
				
			||||||
type ConnectionPool struct {
 | 
					type ConnectionPool struct {
 | 
				
			||||||
	NumberOfInstances *int32              `json:"instancesNumber,omitempty"`
 | 
						NumberOfInstances *int32 `json:"instancesNumber,omitempty"`
 | 
				
			||||||
	Schema            *string             `json:"schema,omitempty"`
 | 
						Schema            string `json:"schema,omitempty"`
 | 
				
			||||||
	User              *string             `json:"user,omitempty"`
 | 
						User              string `json:"user,omitempty"`
 | 
				
			||||||
	Type              *string             `json:"type,omitempty"`
 | 
						Mode              string `json:"mode,omitempty"`
 | 
				
			||||||
	Mode              *string             `json:"mode,omitempty"`
 | 
						DockerImage       string `json:"dockerImage,omitempty"`
 | 
				
			||||||
	PodTemplate       *v1.PodTemplateSpec `json:"podTemplate,omitempty"`
 | 
						// TODO: prepared snippets of configuration, one can choose via type, e.g.
 | 
				
			||||||
 | 
						// pgbouncer-large (with higher resources) or odyssey-small (with smaller
 | 
				
			||||||
 | 
						// resources)
 | 
				
			||||||
 | 
						// Type              string `json:"type,omitempty"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Resources `json:"resources,omitempty"`
 | 
						Resources `json:"resources,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ import (
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/r3labs/diff"
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
	appsv1 "k8s.io/api/apps/v1"
 | 
						appsv1 "k8s.io/api/apps/v1"
 | 
				
			||||||
	v1 "k8s.io/api/core/v1"
 | 
						v1 "k8s.io/api/core/v1"
 | 
				
			||||||
| 
						 | 
					@ -723,6 +724,17 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// connection pool
 | 
				
			||||||
 | 
						if !reflect.DeepEqual(oldSpec.Spec.ConnectionPool,
 | 
				
			||||||
 | 
							newSpec.Spec.ConnectionPool) {
 | 
				
			||||||
 | 
							c.logger.Debug("syncing connection pool")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := c.syncConnectionPool(oldSpec, newSpec); err != nil {
 | 
				
			||||||
 | 
								c.logger.Errorf("could not sync connection pool: %v", err)
 | 
				
			||||||
 | 
								updateFailed = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -852,13 +864,13 @@ func (c *Cluster) initSystemUsers() {
 | 
				
			||||||
	if c.needConnectionPool() {
 | 
						if c.needConnectionPool() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		username := c.Spec.ConnectionPool.User
 | 
							username := c.Spec.ConnectionPool.User
 | 
				
			||||||
		if username == nil {
 | 
							if username == "" {
 | 
				
			||||||
			username = &c.OpConfig.ConnectionPool.User
 | 
								username = c.OpConfig.ConnectionPool.User
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		c.systemUsers[constants.ConnectionPoolUserKeyName] = spec.PgUser{
 | 
							c.systemUsers[constants.ConnectionPoolUserKeyName] = spec.PgUser{
 | 
				
			||||||
			Origin:   spec.RoleConnectionPool,
 | 
								Origin:   spec.RoleConnectionPool,
 | 
				
			||||||
			Name:     *username,
 | 
								Name:     username,
 | 
				
			||||||
			Password: util.RandomPassword(constants.PasswordLength),
 | 
								Password: util.RandomPassword(constants.PasswordLength),
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -1188,3 +1200,29 @@ func (c *Cluster) deletePatroniClusterConfigMaps() error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return c.deleteClusterObject(get, deleteConfigMapFn, "configmap")
 | 
						return c.deleteClusterObject(get, deleteConfigMapFn, "configmap")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Test if two connection pool configuration needs to be synced. For simplicity
 | 
				
			||||||
 | 
					// compare not the actual K8S objects, but the configuration itself and request
 | 
				
			||||||
 | 
					// sync if there is any difference.
 | 
				
			||||||
 | 
					func (c *Cluster) needSyncConnPoolDeployments(oldSpec, newSpec *acidv1.ConnectionPool) (sync bool, reasons []string) {
 | 
				
			||||||
 | 
						reasons = []string{}
 | 
				
			||||||
 | 
						sync = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						changelog, err := diff.Diff(oldSpec, newSpec)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.logger.Infof("Cannot get diff, do not do anything, %+v", err)
 | 
				
			||||||
 | 
							return false, reasons
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if len(changelog) > 0 {
 | 
				
			||||||
 | 
								sync = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, change := range changelog {
 | 
				
			||||||
 | 
								msg := fmt.Sprintf("%s %+v from %s to %s",
 | 
				
			||||||
 | 
									change.Type, change.Path, change.From, change.To)
 | 
				
			||||||
 | 
								reasons = append(reasons, msg)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sync, reasons
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -300,8 +300,26 @@ func (c *Cluster) installLookupFunction(poolSchema, poolUser string) error {
 | 
				
			||||||
				params, err)
 | 
									params, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if _, err := c.pgDb.Exec(stmtBytes.String()); err != nil {
 | 
							// golang sql will do retries couple of times if pq driver reports
 | 
				
			||||||
			return fmt.Errorf("could not execute sql statement %s: %v",
 | 
							// connections issues (driver.ErrBadConn), but since our query is
 | 
				
			||||||
 | 
							// idempotent, we can retry in a view of other errors (e.g. due to
 | 
				
			||||||
 | 
							// failover a db is temporary in a read-only mode or so) to make sure
 | 
				
			||||||
 | 
							// it was applied.
 | 
				
			||||||
 | 
							execErr := retryutil.Retry(
 | 
				
			||||||
 | 
								constants.PostgresConnectTimeout,
 | 
				
			||||||
 | 
								constants.PostgresConnectRetryTimeout,
 | 
				
			||||||
 | 
								func() (bool, error) {
 | 
				
			||||||
 | 
									if _, err := c.pgDb.Exec(stmtBytes.String()); err != nil {
 | 
				
			||||||
 | 
										msg := fmt.Errorf("could not execute sql statement %s: %v",
 | 
				
			||||||
 | 
											stmtBytes.String(), err)
 | 
				
			||||||
 | 
										return false, msg
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return true, nil
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if execErr != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("could not execute after retries %s: %v",
 | 
				
			||||||
				stmtBytes.String(), err)
 | 
									stmtBytes.String(), err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1734,100 +1734,99 @@ func (c *Cluster) getLogicalBackupJobName() (jobName string) {
 | 
				
			||||||
func (c *Cluster) generateConnPoolPodTemplate(spec *acidv1.PostgresSpec) (
 | 
					func (c *Cluster) generateConnPoolPodTemplate(spec *acidv1.PostgresSpec) (
 | 
				
			||||||
	*v1.PodTemplateSpec, error) {
 | 
						*v1.PodTemplateSpec, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	podTemplate := spec.ConnectionPool.PodTemplate
 | 
						gracePeriod := int64(c.OpConfig.PodTerminateGracePeriod.Seconds())
 | 
				
			||||||
 | 
						resources, err := generateResourceRequirements(
 | 
				
			||||||
 | 
							spec.ConnectionPool.Resources,
 | 
				
			||||||
 | 
							c.makeDefaultConnPoolResources())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if podTemplate == nil {
 | 
						effectiveMode := util.Coalesce(
 | 
				
			||||||
		gracePeriod := int64(c.OpConfig.PodTerminateGracePeriod.Seconds())
 | 
							spec.ConnectionPool.Mode,
 | 
				
			||||||
		resources, err := generateResourceRequirements(
 | 
							c.OpConfig.ConnectionPool.Mode)
 | 
				
			||||||
			spec.ConnectionPool.Resources,
 | 
					 | 
				
			||||||
			c.makeDefaultConnPoolResources())
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		effectiveMode := spec.ConnectionPool.Mode
 | 
						effectiveDockerImage := util.Coalesce(
 | 
				
			||||||
		if effectiveMode == nil {
 | 
							spec.ConnectionPool.DockerImage,
 | 
				
			||||||
			effectiveMode = &c.OpConfig.ConnectionPool.Mode
 | 
							c.OpConfig.ConnectionPool.Image)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("could not generate resource requirements: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						secretSelector := func(key string) *v1.SecretKeySelector {
 | 
				
			||||||
 | 
							return &v1.SecretKeySelector{
 | 
				
			||||||
 | 
								LocalObjectReference: v1.LocalObjectReference{
 | 
				
			||||||
 | 
									Name: c.credentialSecretName(c.OpConfig.SuperUsername),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Key: key,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err != nil {
 | 
						envVars := []v1.EnvVar{
 | 
				
			||||||
			return nil, fmt.Errorf("could not generate resource requirements: %v", err)
 | 
							{
 | 
				
			||||||
		}
 | 
								Name:  "PGHOST",
 | 
				
			||||||
 | 
								Value: c.serviceAddress(Master),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name:  "PGPORT",
 | 
				
			||||||
 | 
								Value: c.servicePort(Master),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name: "PGUSER",
 | 
				
			||||||
 | 
								ValueFrom: &v1.EnvVarSource{
 | 
				
			||||||
 | 
									SecretKeyRef: secretSelector("username"),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// the convention is to use the same schema name as
 | 
				
			||||||
 | 
							// connection pool username
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name: "PGSCHEMA",
 | 
				
			||||||
 | 
								ValueFrom: &v1.EnvVarSource{
 | 
				
			||||||
 | 
									SecretKeyRef: secretSelector("username"),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name: "PGPASSWORD",
 | 
				
			||||||
 | 
								ValueFrom: &v1.EnvVarSource{
 | 
				
			||||||
 | 
									SecretKeyRef: secretSelector("password"),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name:  "CONNECTION_POOL_MODE",
 | 
				
			||||||
 | 
								Value: effectiveMode,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name:  "CONNECTION_POOL_PORT",
 | 
				
			||||||
 | 
								Value: fmt.Sprint(pgPort),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		secretSelector := func(key string) *v1.SecretKeySelector {
 | 
						poolerContainer := v1.Container{
 | 
				
			||||||
			return &v1.SecretKeySelector{
 | 
							Name:            connectionPoolContainer,
 | 
				
			||||||
				LocalObjectReference: v1.LocalObjectReference{
 | 
							Image:           effectiveDockerImage,
 | 
				
			||||||
					Name: c.credentialSecretName(c.OpConfig.SuperUsername),
 | 
							ImagePullPolicy: v1.PullIfNotPresent,
 | 
				
			||||||
				},
 | 
							Resources:       *resources,
 | 
				
			||||||
				Key: key,
 | 
							Ports: []v1.ContainerPort{
 | 
				
			||||||
			}
 | 
								{
 | 
				
			||||||
		}
 | 
									ContainerPort: pgPort,
 | 
				
			||||||
 | 
									Protocol:      v1.ProtocolTCP,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Env: envVars,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		envVars := []v1.EnvVar{
 | 
						podTemplate := &v1.PodTemplateSpec{
 | 
				
			||||||
			{
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
				Name:  "PGHOST",
 | 
								Labels:      c.connPoolLabelsSelector().MatchLabels,
 | 
				
			||||||
				Value: c.serviceAddress(Master),
 | 
								Namespace:   c.Namespace,
 | 
				
			||||||
			},
 | 
								Annotations: c.generatePodAnnotations(spec),
 | 
				
			||||||
			{
 | 
							},
 | 
				
			||||||
				Name:  "PGPORT",
 | 
							Spec: v1.PodSpec{
 | 
				
			||||||
				Value: c.servicePort(Master),
 | 
								ServiceAccountName:            c.OpConfig.PodServiceAccountName,
 | 
				
			||||||
			},
 | 
								TerminationGracePeriodSeconds: &gracePeriod,
 | 
				
			||||||
			{
 | 
								Containers:                    []v1.Container{poolerContainer},
 | 
				
			||||||
				Name: "PGUSER",
 | 
								// TODO: add tolerations to scheduler pooler on the same node
 | 
				
			||||||
				ValueFrom: &v1.EnvVarSource{
 | 
								// as database
 | 
				
			||||||
					SecretKeyRef: secretSelector("username"),
 | 
								//Tolerations:                   *tolerationsSpec,
 | 
				
			||||||
				},
 | 
							},
 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			// the convention is to use the same schema name as
 | 
					 | 
				
			||||||
			// connection pool username
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				Name: "PGSCHEMA",
 | 
					 | 
				
			||||||
				ValueFrom: &v1.EnvVarSource{
 | 
					 | 
				
			||||||
					SecretKeyRef: secretSelector("username"),
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				Name: "PGPASSWORD",
 | 
					 | 
				
			||||||
				ValueFrom: &v1.EnvVarSource{
 | 
					 | 
				
			||||||
					SecretKeyRef: secretSelector("password"),
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				Name:  "CONNECTION_POOL_MODE",
 | 
					 | 
				
			||||||
				Value: *effectiveMode,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				Name:  "CONNECTION_POOL_PORT",
 | 
					 | 
				
			||||||
				Value: fmt.Sprint(pgPort),
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		poolerContainer := v1.Container{
 | 
					 | 
				
			||||||
			Name:            connectionPoolContainer,
 | 
					 | 
				
			||||||
			Image:           c.OpConfig.ConnectionPool.Image,
 | 
					 | 
				
			||||||
			ImagePullPolicy: v1.PullIfNotPresent,
 | 
					 | 
				
			||||||
			Resources:       *resources,
 | 
					 | 
				
			||||||
			Ports: []v1.ContainerPort{
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					ContainerPort: pgPort,
 | 
					 | 
				
			||||||
					Protocol:      v1.ProtocolTCP,
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			Env: envVars,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		podTemplate = &v1.PodTemplateSpec{
 | 
					 | 
				
			||||||
			ObjectMeta: metav1.ObjectMeta{
 | 
					 | 
				
			||||||
				Labels:      c.connPoolLabelsSelector().MatchLabels,
 | 
					 | 
				
			||||||
				Namespace:   c.Namespace,
 | 
					 | 
				
			||||||
				Annotations: c.generatePodAnnotations(spec),
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			Spec: v1.PodSpec{
 | 
					 | 
				
			||||||
				ServiceAccountName:            c.OpConfig.PodServiceAccountName,
 | 
					 | 
				
			||||||
				TerminationGracePeriodSeconds: &gracePeriod,
 | 
					 | 
				
			||||||
				Containers:                    []v1.Container{poolerContainer},
 | 
					 | 
				
			||||||
				// TODO: add tolerations to scheduler pooler on the same node
 | 
					 | 
				
			||||||
				// as database
 | 
					 | 
				
			||||||
				//Tolerations:                   *tolerationsSpec,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return podTemplate, nil
 | 
						return podTemplate, nil
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -613,21 +613,6 @@ func TestConnPoolPodSpec(t *testing.T) {
 | 
				
			||||||
			cluster:  cluster,
 | 
								cluster:  cluster,
 | 
				
			||||||
			check:    testEnvs,
 | 
								check:    testEnvs,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			subTest: "custom pod template",
 | 
					 | 
				
			||||||
			spec: &acidv1.PostgresSpec{
 | 
					 | 
				
			||||||
				ConnectionPool: &acidv1.ConnectionPool{
 | 
					 | 
				
			||||||
					PodTemplate: &v1.PodTemplateSpec{
 | 
					 | 
				
			||||||
						ObjectMeta: metav1.ObjectMeta{
 | 
					 | 
				
			||||||
							Name: "test-pod-template",
 | 
					 | 
				
			||||||
						},
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			expected: nil,
 | 
					 | 
				
			||||||
			cluster:  cluster,
 | 
					 | 
				
			||||||
			check:    testCustomPodTemplate,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, tt := range tests {
 | 
						for _, tt := range tests {
 | 
				
			||||||
		podSpec, err := tt.cluster.generateConnPoolPodTemplate(tt.spec)
 | 
							podSpec, err := tt.cluster.generateConnPoolPodTemplate(tt.spec)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -101,9 +101,17 @@ func (c *Cluster) createConnectionPool(lookup InstallFunction) (*ConnectionPoolR
 | 
				
			||||||
	var msg string
 | 
						var msg string
 | 
				
			||||||
	c.setProcessName("creating connection pool")
 | 
						c.setProcessName("creating connection pool")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err := lookup(
 | 
						schema := c.Spec.ConnectionPool.Schema
 | 
				
			||||||
		c.OpConfig.ConnectionPool.Schema,
 | 
						if schema == "" {
 | 
				
			||||||
		c.OpConfig.ConnectionPool.User)
 | 
							schema = c.OpConfig.ConnectionPool.Schema
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						user := c.Spec.ConnectionPool.User
 | 
				
			||||||
 | 
						if user == "" {
 | 
				
			||||||
 | 
							user = c.OpConfig.ConnectionPool.User
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := lookup(schema, user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		msg = "could not prepare database for connection pool: %v"
 | 
							msg = "could not prepare database for connection pool: %v"
 | 
				
			||||||
| 
						 | 
					@ -116,6 +124,9 @@ func (c *Cluster) createConnectionPool(lookup InstallFunction) (*ConnectionPoolR
 | 
				
			||||||
		return nil, fmt.Errorf(msg, err)
 | 
							return nil, fmt.Errorf(msg, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// client-go does retry 10 times (with NoBackoff by default) when the API
 | 
				
			||||||
 | 
						// believe a request can be retried and returns Retry-After header. This
 | 
				
			||||||
 | 
						// should be good enough to not think about it here.
 | 
				
			||||||
	deployment, err := c.KubeClient.
 | 
						deployment, err := c.KubeClient.
 | 
				
			||||||
		Deployments(deploymentSpec.Namespace).
 | 
							Deployments(deploymentSpec.Namespace).
 | 
				
			||||||
		Create(deploymentSpec)
 | 
							Create(deploymentSpec)
 | 
				
			||||||
| 
						 | 
					@ -154,14 +165,22 @@ func (c *Cluster) deleteConnectionPool() (err error) {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Clean up the deployment object. If deployment resource we've remembered
 | 
				
			||||||
 | 
						// is somehow empty, try to delete based on what would we generate
 | 
				
			||||||
 | 
						deploymentName := c.connPoolName()
 | 
				
			||||||
 | 
						deployment := c.ConnectionPool.Deployment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if deployment != nil {
 | 
				
			||||||
 | 
							deploymentName = deployment.Name
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// set delete propagation policy to foreground, so that replica set will be
 | 
						// set delete propagation policy to foreground, so that replica set will be
 | 
				
			||||||
	// also deleted.
 | 
						// also deleted.
 | 
				
			||||||
	policy := metav1.DeletePropagationForeground
 | 
						policy := metav1.DeletePropagationForeground
 | 
				
			||||||
	options := metav1.DeleteOptions{PropagationPolicy: &policy}
 | 
						options := metav1.DeleteOptions{PropagationPolicy: &policy}
 | 
				
			||||||
	deployment := c.ConnectionPool.Deployment
 | 
					 | 
				
			||||||
	err = c.KubeClient.
 | 
						err = c.KubeClient.
 | 
				
			||||||
		Deployments(deployment.Namespace).
 | 
							Deployments(c.Namespace).
 | 
				
			||||||
		Delete(deployment.Name, &options)
 | 
							Delete(deploymentName, &options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !k8sutil.ResourceNotFound(err) {
 | 
						if !k8sutil.ResourceNotFound(err) {
 | 
				
			||||||
		c.logger.Debugf("Connection pool deployment was already deleted")
 | 
							c.logger.Debugf("Connection pool deployment was already deleted")
 | 
				
			||||||
| 
						 | 
					@ -172,12 +191,19 @@ func (c *Cluster) deleteConnectionPool() (err error) {
 | 
				
			||||||
	c.logger.Infof("Connection pool deployment %q has been deleted",
 | 
						c.logger.Infof("Connection pool deployment %q has been deleted",
 | 
				
			||||||
		util.NameFromMeta(deployment.ObjectMeta))
 | 
							util.NameFromMeta(deployment.ObjectMeta))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Repeat the same for the service object
 | 
				
			||||||
 | 
						service := c.ConnectionPool.Service
 | 
				
			||||||
 | 
						serviceName := c.connPoolName()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if service != nil {
 | 
				
			||||||
 | 
							serviceName = service.Name
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// set delete propagation policy to foreground, so that all the dependant
 | 
						// set delete propagation policy to foreground, so that all the dependant
 | 
				
			||||||
	// will be deleted.
 | 
						// will be deleted.
 | 
				
			||||||
	service := c.ConnectionPool.Service
 | 
					 | 
				
			||||||
	err = c.KubeClient.
 | 
						err = c.KubeClient.
 | 
				
			||||||
		Services(service.Namespace).
 | 
							Services(c.Namespace).
 | 
				
			||||||
		Delete(service.Name, &options)
 | 
							Delete(serviceName, &options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !k8sutil.ResourceNotFound(err) {
 | 
						if !k8sutil.ResourceNotFound(err) {
 | 
				
			||||||
		c.logger.Debugf("Connection pool service was already deleted")
 | 
							c.logger.Debugf("Connection pool service was already deleted")
 | 
				
			||||||
| 
						 | 
					@ -823,3 +849,34 @@ func (c *Cluster) GetStatefulSet() *appsv1.StatefulSet {
 | 
				
			||||||
func (c *Cluster) GetPodDisruptionBudget() *policybeta1.PodDisruptionBudget {
 | 
					func (c *Cluster) GetPodDisruptionBudget() *policybeta1.PodDisruptionBudget {
 | 
				
			||||||
	return c.PodDisruptionBudget
 | 
						return c.PodDisruptionBudget
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Perform actual patching of a connection pool deployment, assuming that all
 | 
				
			||||||
 | 
					// the check were already done before.
 | 
				
			||||||
 | 
					func (c *Cluster) updateConnPoolDeployment(oldDeploymentSpec, newDeployment *appsv1.Deployment) (*appsv1.Deployment, error) {
 | 
				
			||||||
 | 
						c.setProcessName("updating connection pool")
 | 
				
			||||||
 | 
						if c.ConnectionPool == nil || c.ConnectionPool.Deployment == nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("there is no connection pool in the cluster")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						patchData, err := specPatch(newDeployment.Spec)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("could not form patch for the deployment: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// An update probably requires RetryOnConflict, but since only one operator
 | 
				
			||||||
 | 
						// worker at one time will try to update it changes of conflicts are
 | 
				
			||||||
 | 
						// minimal.
 | 
				
			||||||
 | 
						deployment, err := c.KubeClient.
 | 
				
			||||||
 | 
							Deployments(c.ConnectionPool.Deployment.Namespace).
 | 
				
			||||||
 | 
							Patch(
 | 
				
			||||||
 | 
								c.ConnectionPool.Deployment.Name,
 | 
				
			||||||
 | 
								types.MergePatchType,
 | 
				
			||||||
 | 
								patchData, "")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("could not patch deployment: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.ConnectionPool.Deployment = deployment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return deployment, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ package cluster
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	batchv1beta1 "k8s.io/api/batch/v1beta1"
 | 
						batchv1beta1 "k8s.io/api/batch/v1beta1"
 | 
				
			||||||
	v1 "k8s.io/api/core/v1"
 | 
						v1 "k8s.io/api/core/v1"
 | 
				
			||||||
| 
						 | 
					@ -23,6 +24,7 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
 | 
				
			||||||
	c.mu.Lock()
 | 
						c.mu.Lock()
 | 
				
			||||||
	defer c.mu.Unlock()
 | 
						defer c.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						oldSpec := c.Postgresql
 | 
				
			||||||
	c.setSpec(newSpec)
 | 
						c.setSpec(newSpec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	defer func() {
 | 
						defer func() {
 | 
				
			||||||
| 
						 | 
					@ -108,6 +110,20 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// connection pool
 | 
				
			||||||
 | 
						oldPool := oldSpec.Spec.ConnectionPool
 | 
				
			||||||
 | 
						newPool := newSpec.Spec.ConnectionPool
 | 
				
			||||||
 | 
						if c.needConnectionPool() &&
 | 
				
			||||||
 | 
							(c.ConnectionPool == nil || !reflect.DeepEqual(oldPool, newPool)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							c.logger.Debug("syncing connection pool")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := c.syncConnectionPool(&oldSpec, newSpec); err != nil {
 | 
				
			||||||
 | 
								c.logger.Errorf("could not sync connection pool: %v", err)
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -594,3 +610,98 @@ func (c *Cluster) syncLogicalBackupJob() error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Synchronize connection pool resources. Effectively we're interested only in
 | 
				
			||||||
 | 
					// synchronizing the corresponding deployment, but in case of deployment or
 | 
				
			||||||
 | 
					// service is missing, create it. After checking, also remember an object for
 | 
				
			||||||
 | 
					// the future references.
 | 
				
			||||||
 | 
					func (c *Cluster) syncConnectionPool(oldSpec, newSpec *acidv1.Postgresql) error {
 | 
				
			||||||
 | 
						if c.ConnectionPool == nil {
 | 
				
			||||||
 | 
							c.logger.Warning("Connection pool resources are empty")
 | 
				
			||||||
 | 
							c.ConnectionPool = &ConnectionPoolResources{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						deployment, err := c.KubeClient.
 | 
				
			||||||
 | 
							Deployments(c.Namespace).
 | 
				
			||||||
 | 
							Get(c.connPoolName(), metav1.GetOptions{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil && k8sutil.ResourceNotFound(err) {
 | 
				
			||||||
 | 
							msg := "Deployment %s for connection pool synchronization is not found, create it"
 | 
				
			||||||
 | 
							c.logger.Warningf(msg, c.connPoolName())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							deploymentSpec, err := c.generateConnPoolDeployment(&newSpec.Spec)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								msg = "could not generate deployment for connection pool: %v"
 | 
				
			||||||
 | 
								return fmt.Errorf(msg, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							deployment, err := c.KubeClient.
 | 
				
			||||||
 | 
								Deployments(deploymentSpec.Namespace).
 | 
				
			||||||
 | 
								Create(deploymentSpec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							c.ConnectionPool.Deployment = deployment
 | 
				
			||||||
 | 
						} else if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("could not get connection pool deployment to sync: %v", err)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							c.ConnectionPool.Deployment = deployment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// actual synchronization
 | 
				
			||||||
 | 
							oldConnPool := oldSpec.Spec.ConnectionPool
 | 
				
			||||||
 | 
							newConnPool := newSpec.Spec.ConnectionPool
 | 
				
			||||||
 | 
							sync, reason := c.needSyncConnPoolDeployments(oldConnPool, newConnPool)
 | 
				
			||||||
 | 
							if sync {
 | 
				
			||||||
 | 
								c.logger.Infof("Update connection pool deployment %s, reason: %s",
 | 
				
			||||||
 | 
									c.connPoolName(), reason)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								newDeploymentSpec, err := c.generateConnPoolDeployment(&newSpec.Spec)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									msg := "could not generate deployment for connection pool: %v"
 | 
				
			||||||
 | 
									return fmt.Errorf(msg, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								oldDeploymentSpec := c.ConnectionPool.Deployment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								deployment, err := c.updateConnPoolDeployment(
 | 
				
			||||||
 | 
									oldDeploymentSpec,
 | 
				
			||||||
 | 
									newDeploymentSpec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								c.ConnectionPool.Deployment = deployment
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						service, err := c.KubeClient.
 | 
				
			||||||
 | 
							Services(c.Namespace).
 | 
				
			||||||
 | 
							Get(c.connPoolName(), metav1.GetOptions{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil && k8sutil.ResourceNotFound(err) {
 | 
				
			||||||
 | 
							msg := "Service %s for connection pool synchronization is not found, create it"
 | 
				
			||||||
 | 
							c.logger.Warningf(msg, c.connPoolName())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							serviceSpec := c.generateConnPoolService(&newSpec.Spec)
 | 
				
			||||||
 | 
							service, err := c.KubeClient.
 | 
				
			||||||
 | 
								Services(serviceSpec.Namespace).
 | 
				
			||||||
 | 
								Create(serviceSpec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							c.ConnectionPool.Service = service
 | 
				
			||||||
 | 
						} else if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("could not get connection pool service to sync: %v", err)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Service updates are not supported and probably not that useful anyway
 | 
				
			||||||
 | 
							c.ConnectionPool.Service = service
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,125 @@
 | 
				
			||||||
 | 
					package cluster
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
 | 
				
			||||||
 | 
						"github.com/zalando/postgres-operator/pkg/util/config"
 | 
				
			||||||
 | 
						"github.com/zalando/postgres-operator/pkg/util/k8sutil"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						appsv1 "k8s.io/api/apps/v1"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func int32ToPointer(value int32) *int32 {
 | 
				
			||||||
 | 
						return &value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func deploymentUpdated(cluster *Cluster, err error) error {
 | 
				
			||||||
 | 
						if cluster.ConnectionPool.Deployment.Spec.Replicas == nil ||
 | 
				
			||||||
 | 
							*cluster.ConnectionPool.Deployment.Spec.Replicas != 2 {
 | 
				
			||||||
 | 
							return fmt.Errorf("Wrong nubmer of instances")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func objectsAreSaved(cluster *Cluster, err error) error {
 | 
				
			||||||
 | 
						if cluster.ConnectionPool == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Connection pool resources are empty")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if cluster.ConnectionPool.Deployment == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Deployment was not saved")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if cluster.ConnectionPool.Service == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Service was not saved")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestConnPoolSynchronization(t *testing.T) {
 | 
				
			||||||
 | 
						testName := "Test connection pool synchronization"
 | 
				
			||||||
 | 
						var cluster = New(
 | 
				
			||||||
 | 
							Config{
 | 
				
			||||||
 | 
								OpConfig: config.Config{
 | 
				
			||||||
 | 
									ProtectedRoles: []string{"admin"},
 | 
				
			||||||
 | 
									Auth: config.Auth{
 | 
				
			||||||
 | 
										SuperUsername:       superUserName,
 | 
				
			||||||
 | 
										ReplicationUsername: replicationUserName,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									ConnectionPool: config.ConnectionPool{
 | 
				
			||||||
 | 
										ConnPoolDefaultCPURequest:    "100m",
 | 
				
			||||||
 | 
										ConnPoolDefaultCPULimit:      "100m",
 | 
				
			||||||
 | 
										ConnPoolDefaultMemoryRequest: "100M",
 | 
				
			||||||
 | 
										ConnPoolDefaultMemoryLimit:   "100M",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}, k8sutil.KubernetesClient{}, acidv1.Postgresql{}, logger)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cluster.Statefulset = &appsv1.StatefulSet{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name: "test-sts",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clusterMissingObjects := *cluster
 | 
				
			||||||
 | 
						clusterMissingObjects.KubeClient = k8sutil.ClientMissingObjects()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clusterMock := *cluster
 | 
				
			||||||
 | 
						clusterMock.KubeClient = k8sutil.NewMockKubernetesClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							subTest string
 | 
				
			||||||
 | 
							oldSpec *acidv1.Postgresql
 | 
				
			||||||
 | 
							newSpec *acidv1.Postgresql
 | 
				
			||||||
 | 
							cluster *Cluster
 | 
				
			||||||
 | 
							check   func(cluster *Cluster, err error) error
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								subTest: "create if doesn't exist",
 | 
				
			||||||
 | 
								oldSpec: &acidv1.Postgresql{
 | 
				
			||||||
 | 
									Spec: acidv1.PostgresSpec{
 | 
				
			||||||
 | 
										ConnectionPool: &acidv1.ConnectionPool{},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								newSpec: &acidv1.Postgresql{
 | 
				
			||||||
 | 
									Spec: acidv1.PostgresSpec{
 | 
				
			||||||
 | 
										ConnectionPool: &acidv1.ConnectionPool{},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								cluster: &clusterMissingObjects,
 | 
				
			||||||
 | 
								check:   objectsAreSaved,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								subTest: "update deployment",
 | 
				
			||||||
 | 
								oldSpec: &acidv1.Postgresql{
 | 
				
			||||||
 | 
									Spec: acidv1.PostgresSpec{
 | 
				
			||||||
 | 
										ConnectionPool: &acidv1.ConnectionPool{
 | 
				
			||||||
 | 
											NumberOfInstances: int32ToPointer(1),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								newSpec: &acidv1.Postgresql{
 | 
				
			||||||
 | 
									Spec: acidv1.PostgresSpec{
 | 
				
			||||||
 | 
										ConnectionPool: &acidv1.ConnectionPool{
 | 
				
			||||||
 | 
											NumberOfInstances: int32ToPointer(2),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								cluster: &clusterMock,
 | 
				
			||||||
 | 
								check:   deploymentUpdated,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							err := tt.cluster.syncConnectionPool(tt.oldSpec, tt.newSpec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := tt.check(tt.cluster, err); err != nil {
 | 
				
			||||||
 | 
								t.Errorf("%s [%s]: Could not synchronize, %+v",
 | 
				
			||||||
 | 
									testName, tt.subTest, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -497,5 +497,5 @@ func (c *Cluster) patroniUsesKubernetes() bool {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Cluster) needConnectionPool() bool {
 | 
					func (c *Cluster) needConnectionPool() bool {
 | 
				
			||||||
	return c.Spec.ConnectionPool != nil
 | 
						return c.Spec.ConnectionPool != nil || c.Spec.EnableConnectionPool == true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,9 @@ import (
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
 | 
						acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
 | 
				
			||||||
 | 
						"github.com/zalando/postgres-operator/pkg/util"
 | 
				
			||||||
	"github.com/zalando/postgres-operator/pkg/util/config"
 | 
						"github.com/zalando/postgres-operator/pkg/util/config"
 | 
				
			||||||
 | 
						"github.com/zalando/postgres-operator/pkg/util/constants"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -142,17 +144,52 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
 | 
				
			||||||
	result.ScalyrCPULimit = fromCRD.Scalyr.ScalyrCPULimit
 | 
						result.ScalyrCPULimit = fromCRD.Scalyr.ScalyrCPULimit
 | 
				
			||||||
	result.ScalyrMemoryLimit = fromCRD.Scalyr.ScalyrMemoryLimit
 | 
						result.ScalyrMemoryLimit = fromCRD.Scalyr.ScalyrMemoryLimit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// connection pool
 | 
						// Connection pool. Looks like we can't use defaulting in CRD before 1.17,
 | 
				
			||||||
 | 
						// so ensure default values here.
 | 
				
			||||||
	result.ConnectionPool.NumberOfInstances = fromCRD.ConnectionPool.NumberOfInstances
 | 
						result.ConnectionPool.NumberOfInstances = fromCRD.ConnectionPool.NumberOfInstances
 | 
				
			||||||
	result.ConnectionPool.Schema = fromCRD.ConnectionPool.Schema
 | 
						if result.ConnectionPool.NumberOfInstances == nil ||
 | 
				
			||||||
	result.ConnectionPool.User = fromCRD.ConnectionPool.User
 | 
							*result.ConnectionPool.NumberOfInstances < 1 {
 | 
				
			||||||
	result.ConnectionPool.Type = fromCRD.ConnectionPool.Type
 | 
							var value int32
 | 
				
			||||||
	result.ConnectionPool.Image = fromCRD.ConnectionPool.Image
 | 
					
 | 
				
			||||||
	result.ConnectionPool.Mode = fromCRD.ConnectionPool.Mode
 | 
							value = 1
 | 
				
			||||||
	result.ConnectionPool.ConnPoolDefaultCPURequest = fromCRD.ConnectionPool.DefaultCPURequest
 | 
							result.ConnectionPool.NumberOfInstances = &value
 | 
				
			||||||
	result.ConnectionPool.ConnPoolDefaultMemoryRequest = fromCRD.ConnectionPool.DefaultMemoryRequest
 | 
						}
 | 
				
			||||||
	result.ConnectionPool.ConnPoolDefaultCPULimit = fromCRD.ConnectionPool.DefaultCPULimit
 | 
					
 | 
				
			||||||
	result.ConnectionPool.ConnPoolDefaultMemoryLimit = fromCRD.ConnectionPool.DefaultMemoryLimit
 | 
						result.ConnectionPool.Schema = util.Coalesce(
 | 
				
			||||||
 | 
							fromCRD.ConnectionPool.Schema,
 | 
				
			||||||
 | 
							constants.ConnectionPoolSchemaName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result.ConnectionPool.User = util.Coalesce(
 | 
				
			||||||
 | 
							fromCRD.ConnectionPool.User,
 | 
				
			||||||
 | 
							constants.ConnectionPoolUserName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result.ConnectionPool.Type = util.Coalesce(
 | 
				
			||||||
 | 
							fromCRD.ConnectionPool.Type,
 | 
				
			||||||
 | 
							constants.ConnectionPoolDefaultType)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result.ConnectionPool.Image = util.Coalesce(
 | 
				
			||||||
 | 
							fromCRD.ConnectionPool.Image,
 | 
				
			||||||
 | 
							"pgbouncer:0.0.1")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result.ConnectionPool.Mode = util.Coalesce(
 | 
				
			||||||
 | 
							fromCRD.ConnectionPool.Mode,
 | 
				
			||||||
 | 
							constants.ConnectionPoolDefaultMode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result.ConnectionPool.ConnPoolDefaultCPURequest = util.Coalesce(
 | 
				
			||||||
 | 
							fromCRD.ConnectionPool.DefaultCPURequest,
 | 
				
			||||||
 | 
							constants.ConnectionPoolDefaultCpuRequest)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result.ConnectionPool.ConnPoolDefaultMemoryRequest = util.Coalesce(
 | 
				
			||||||
 | 
							fromCRD.ConnectionPool.DefaultMemoryRequest,
 | 
				
			||||||
 | 
							constants.ConnectionPoolDefaultMemoryRequest)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result.ConnectionPool.ConnPoolDefaultCPULimit = util.Coalesce(
 | 
				
			||||||
 | 
							fromCRD.ConnectionPool.DefaultCPULimit,
 | 
				
			||||||
 | 
							constants.ConnectionPoolDefaultCpuLimit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result.ConnectionPool.ConnPoolDefaultMemoryLimit = util.Coalesce(
 | 
				
			||||||
 | 
							fromCRD.ConnectionPool.DefaultMemoryLimit,
 | 
				
			||||||
 | 
							constants.ConnectionPoolDefaultMemoryLimit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return result
 | 
						return result
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					package constants
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Connection pool specific constants
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						ConnectionPoolUserName             = "pooler"
 | 
				
			||||||
 | 
						ConnectionPoolSchemaName           = "pooler"
 | 
				
			||||||
 | 
						ConnectionPoolDefaultType          = "pgbouncer"
 | 
				
			||||||
 | 
						ConnectionPoolDefaultMode          = "transition"
 | 
				
			||||||
 | 
						ConnectionPoolDefaultCpuRequest    = "100m"
 | 
				
			||||||
 | 
						ConnectionPoolDefaultCpuLimit      = "100m"
 | 
				
			||||||
 | 
						ConnectionPoolDefaultMemoryRequest = "100M"
 | 
				
			||||||
 | 
						ConnectionPoolDefaultMemoryLimit   = "100M"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@ import (
 | 
				
			||||||
	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"
 | 
				
			||||||
	apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
						apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/types"
 | 
				
			||||||
	"k8s.io/client-go/kubernetes"
 | 
						"k8s.io/client-go/kubernetes"
 | 
				
			||||||
	appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
 | 
						appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
 | 
				
			||||||
	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
 | 
						corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
 | 
				
			||||||
| 
						 | 
					@ -27,6 +28,10 @@ import (
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func int32ToPointer(value int32) *int32 {
 | 
				
			||||||
 | 
						return &value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// KubernetesClient describes getters for Kubernetes objects
 | 
					// KubernetesClient describes getters for Kubernetes objects
 | 
				
			||||||
type KubernetesClient struct {
 | 
					type KubernetesClient struct {
 | 
				
			||||||
	corev1.SecretsGetter
 | 
						corev1.SecretsGetter
 | 
				
			||||||
| 
						 | 
					@ -61,16 +66,30 @@ type mockDeployment struct {
 | 
				
			||||||
	appsv1.DeploymentInterface
 | 
						appsv1.DeploymentInterface
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type mockDeploymentNotExist struct {
 | 
				
			||||||
 | 
						appsv1.DeploymentInterface
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type MockDeploymentGetter struct {
 | 
					type MockDeploymentGetter struct {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MockDeploymentNotExistGetter struct {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type mockService struct {
 | 
					type mockService struct {
 | 
				
			||||||
	corev1.ServiceInterface
 | 
						corev1.ServiceInterface
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type mockServiceNotExist struct {
 | 
				
			||||||
 | 
						corev1.ServiceInterface
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type MockServiceGetter struct {
 | 
					type MockServiceGetter struct {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MockServiceNotExistGetter struct {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type mockConfigMap struct {
 | 
					type mockConfigMap struct {
 | 
				
			||||||
	corev1.ConfigMapInterface
 | 
						corev1.ConfigMapInterface
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -260,6 +279,10 @@ func (mock *MockDeploymentGetter) Deployments(namespace string) appsv1.Deploymen
 | 
				
			||||||
	return &mockDeployment{}
 | 
						return &mockDeployment{}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (mock *MockDeploymentNotExistGetter) Deployments(namespace string) appsv1.DeploymentInterface {
 | 
				
			||||||
 | 
						return &mockDeploymentNotExist{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (mock *mockDeployment) Create(*apiappsv1.Deployment) (*apiappsv1.Deployment, error) {
 | 
					func (mock *mockDeployment) Create(*apiappsv1.Deployment) (*apiappsv1.Deployment, error) {
 | 
				
			||||||
	return &apiappsv1.Deployment{
 | 
						return &apiappsv1.Deployment{
 | 
				
			||||||
		ObjectMeta: metav1.ObjectMeta{
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
| 
						 | 
					@ -272,10 +295,49 @@ func (mock *mockDeployment) Delete(name string, opts *metav1.DeleteOptions) erro
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (mock *mockDeployment) Get(name string, opts metav1.GetOptions) (*apiappsv1.Deployment, error) {
 | 
				
			||||||
 | 
						return &apiappsv1.Deployment{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name: "test-deployment",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (mock *mockDeployment) Patch(name string, t types.PatchType, data []byte, subres ...string) (*apiappsv1.Deployment, error) {
 | 
				
			||||||
 | 
						return &apiappsv1.Deployment{
 | 
				
			||||||
 | 
							Spec: apiappsv1.DeploymentSpec{
 | 
				
			||||||
 | 
								Replicas: int32ToPointer(2),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name: "test-deployment",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (mock *mockDeploymentNotExist) Get(name string, opts metav1.GetOptions) (*apiappsv1.Deployment, error) {
 | 
				
			||||||
 | 
						return nil, &apierrors.StatusError{
 | 
				
			||||||
 | 
							ErrStatus: metav1.Status{
 | 
				
			||||||
 | 
								Reason: metav1.StatusReasonNotFound,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (mock *mockDeploymentNotExist) Create(*apiappsv1.Deployment) (*apiappsv1.Deployment, error) {
 | 
				
			||||||
 | 
						return &apiappsv1.Deployment{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name: "test-deployment",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (mock *MockServiceGetter) Services(namespace string) corev1.ServiceInterface {
 | 
					func (mock *MockServiceGetter) Services(namespace string) corev1.ServiceInterface {
 | 
				
			||||||
	return &mockService{}
 | 
						return &mockService{}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (mock *MockServiceNotExistGetter) Services(namespace string) corev1.ServiceInterface {
 | 
				
			||||||
 | 
						return &mockServiceNotExist{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (mock *mockService) Create(*v1.Service) (*v1.Service, error) {
 | 
					func (mock *mockService) Create(*v1.Service) (*v1.Service, error) {
 | 
				
			||||||
	return &v1.Service{
 | 
						return &v1.Service{
 | 
				
			||||||
		ObjectMeta: metav1.ObjectMeta{
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
| 
						 | 
					@ -288,6 +350,30 @@ func (mock *mockService) Delete(name string, opts *metav1.DeleteOptions) error {
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (mock *mockService) Get(name string, opts metav1.GetOptions) (*v1.Service, error) {
 | 
				
			||||||
 | 
						return &v1.Service{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name: "test-service",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (mock *mockServiceNotExist) Create(*v1.Service) (*v1.Service, error) {
 | 
				
			||||||
 | 
						return &v1.Service{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name: "test-service",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (mock *mockServiceNotExist) Get(name string, opts metav1.GetOptions) (*v1.Service, error) {
 | 
				
			||||||
 | 
						return nil, &apierrors.StatusError{
 | 
				
			||||||
 | 
							ErrStatus: metav1.Status{
 | 
				
			||||||
 | 
								Reason: metav1.StatusReasonNotFound,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewMockKubernetesClient for other tests
 | 
					// NewMockKubernetesClient for other tests
 | 
				
			||||||
func NewMockKubernetesClient() KubernetesClient {
 | 
					func NewMockKubernetesClient() KubernetesClient {
 | 
				
			||||||
	return KubernetesClient{
 | 
						return KubernetesClient{
 | 
				
			||||||
| 
						 | 
					@ -297,3 +383,10 @@ func NewMockKubernetesClient() KubernetesClient {
 | 
				
			||||||
		ServicesGetter:    &MockServiceGetter{},
 | 
							ServicesGetter:    &MockServiceGetter{},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ClientMissingObjects() KubernetesClient {
 | 
				
			||||||
 | 
						return KubernetesClient{
 | 
				
			||||||
 | 
							DeploymentsGetter: &MockDeploymentNotExistGetter{},
 | 
				
			||||||
 | 
							ServicesGetter:    &MockServiceNotExistGetter{},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue