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
b40ea2c426
commit
6c3752068b
|
|
@ -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"
|
||||||
|
)
|
||||||
|
|
@ -16,6 +16,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"
|
||||||
|
|
@ -28,6 +29,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
|
||||||
|
|
@ -62,16 +67,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
|
||||||
}
|
}
|
||||||
|
|
@ -245,6 +264,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{
|
||||||
|
|
@ -257,10 +280,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{
|
||||||
|
|
@ -273,6 +335,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{
|
||||||
|
|
@ -282,3 +368,10 @@ func NewMockKubernetesClient() KubernetesClient {
|
||||||
ServicesGetter: &MockServiceGetter{},
|
ServicesGetter: &MockServiceGetter{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ClientMissingObjects() KubernetesClient {
|
||||||
|
return KubernetesClient{
|
||||||
|
DeploymentsGetter: &MockDeploymentNotExistGetter{},
|
||||||
|
ServicesGetter: &MockServiceNotExistGetter{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue