[UI] add toggle for connection pooler (#953)

* [UI] add toggle for connection pooler

* remove team service logger

* fix new.tag.pug and change port in Makefile
This commit is contained in:
Felix Kunde 2020-04-30 09:58:07 +02:00 committed by GitHub
parent 865d5b41a7
commit 5af4379118
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 100 additions and 11 deletions

View File

@ -1,7 +1,7 @@
FROM alpine:3.6 FROM alpine:3.6
MAINTAINER team-acid@zalando.de MAINTAINER team-acid@zalando.de
EXPOSE 8080 EXPOSE 8081
RUN \ RUN \
apk add --no-cache \ apk add --no-cache \
@ -29,6 +29,7 @@ RUN \
/var/cache/apk/* /var/cache/apk/*
COPY requirements.txt / COPY requirements.txt /
COPY start_server.sh /
RUN pip3 install -r /requirements.txt RUN pip3 install -r /requirements.txt
COPY operator_ui /operator_ui COPY operator_ui /operator_ui
@ -37,4 +38,4 @@ ARG VERSION=dev
RUN sed -i "s/__version__ = .*/__version__ = '${VERSION}'/" /operator_ui/__init__.py RUN sed -i "s/__version__ = .*/__version__ = '${VERSION}'/" /operator_ui/__init__.py
WORKDIR / WORKDIR /
ENTRYPOINT ["/usr/bin/python3", "-m", "operator_ui"] CMD ["/usr/bin/python3", "-m", "operator_ui"]

View File

@ -36,4 +36,4 @@ push:
docker push "$(IMAGE):$(TAG)$(CDP_TAG)" docker push "$(IMAGE):$(TAG)$(CDP_TAG)"
mock: mock:
docker run -it -p 8080:8080 "$(IMAGE):$(TAG)" --mock docker run -it -p 8081:8081 "$(IMAGE):$(TAG)" --mock

View File

@ -137,6 +137,7 @@ edit
o.spec.numberOfInstances = i.spec.numberOfInstances o.spec.numberOfInstances = i.spec.numberOfInstances
o.spec.enableMasterLoadBalancer = i.spec.enableMasterLoadBalancer || false o.spec.enableMasterLoadBalancer = i.spec.enableMasterLoadBalancer || false
o.spec.enableReplicaLoadBalancer = i.spec.enableReplicaLoadBalancer || false o.spec.enableReplicaLoadBalancer = i.spec.enableReplicaLoadBalancer || false
o.spec.enableConnectionPooler = i.spec.enableConnectionPooler || false
o.spec.volume = { size: i.spec.volume.size } o.spec.volume = { size: i.spec.volume.size }
if ('users' in i.spec && typeof i.spec.users === 'object') { if ('users' in i.spec && typeof i.spec.users === 'object') {

View File

@ -239,6 +239,18 @@ new
| |
| Enable replica ELB | Enable replica ELB
tr
td Enable Connection Pool
td
label
input(
type='checkbox'
value='{ enableConnectionPooler }'
onchange='{ toggleEnableConnectionPooler }'
)
|
| Enable Connection Pool (using PGBouncer)
tr tr
td Volume size td Volume size
td td
@ -493,6 +505,9 @@ new
{{#if enableReplicaLoadBalancer}} {{#if enableReplicaLoadBalancer}}
enableReplicaLoadBalancer: true enableReplicaLoadBalancer: true
{{/if}} {{/if}}
{{#if enableConnectionPooler}}
enableConnectionPooler: true
{{/if}}
volume: volume:
size: "{{ volumeSize }}Gi" size: "{{ volumeSize }}Gi"
{{#if users}} {{#if users}}
@ -516,13 +531,14 @@ new
- {{ odd }}/32 - {{ odd }}/32
{{/if}} {{/if}}
{{#if resourcesVisible}}
resources: resources:
requests: requests:
cpu: {{ cpu.state.request.state }}m cpu: {{ cpu.state.request.state }}m
memory: {{ memory.state.request.state }}Mi memory: {{ memory.state.request.state }}Mi
limits: limits:
cpu: {{ cpu.state.limit.state }}m cpu: {{ cpu.state.limit.state }}m
memory: {{ memory.state.limit.state }}Mi{{#if restoring}} memory: {{ memory.state.limit.state }}Mi{{/if}}{{#if restoring}}
clone: clone:
cluster: "{{ backup.state.name.state }}" cluster: "{{ backup.state.name.state }}"
@ -542,6 +558,7 @@ new
instanceCount: this.instanceCount, instanceCount: this.instanceCount,
enableMasterLoadBalancer: this.enableMasterLoadBalancer, enableMasterLoadBalancer: this.enableMasterLoadBalancer,
enableReplicaLoadBalancer: this.enableReplicaLoadBalancer, enableReplicaLoadBalancer: this.enableReplicaLoadBalancer,
enableConnectionPooler: this.enableConnectionPooler,
volumeSize: this.volumeSize, volumeSize: this.volumeSize,
users: this.users.valids, users: this.users.valids,
databases: this.databases.valids, databases: this.databases.valids,
@ -552,6 +569,7 @@ new
memory: this.memory, memory: this.memory,
backup: this.backup, backup: this.backup,
namespace: this.namespace, namespace: this.namespace,
resourcesVisible: this.config.resources_visible,
restoring: this.backup.state.type.state !== 'empty', restoring: this.backup.state.type.state !== 'empty',
pitr: this.backup.state.type.state === 'pitr', pitr: this.backup.state.type.state === 'pitr',
} }
@ -598,6 +616,10 @@ new
this.enableReplicaLoadBalancer = !this.enableReplicaLoadBalancer this.enableReplicaLoadBalancer = !this.enableReplicaLoadBalancer
} }
this.toggleEnableConnectionPooler = e => {
this.enableConnectionPooler = !this.enableConnectionPooler
}
this.volumeChange = e => { this.volumeChange = e => {
this.volumeSize = +e.target.value this.volumeSize = +e.target.value
} }
@ -892,6 +914,7 @@ new
this.odd = '' this.odd = ''
this.enableMasterLoadBalancer = false this.enableMasterLoadBalancer = false
this.enableReplicaLoadBalancer = false this.enableReplicaLoadBalancer = false
this.enableConnectionPooler = false
this.postgresqlVersion = this.postgresqlVersion = ( this.postgresqlVersion = this.postgresqlVersion = (
this.config.postgresql_versions[0] this.config.postgresql_versions[0]

View File

@ -92,6 +92,8 @@ postgresql
.alert.alert-success(if='{ progress.masterLabel }') PostgreSQL master available, label is attached .alert.alert-success(if='{ progress.masterLabel }') PostgreSQL master available, label is attached
.alert.alert-success(if='{ progress.masterLabel && progress.dnsName }') PostgreSQL ready: <strong>{ progress.dnsName }</strong> .alert.alert-success(if='{ progress.masterLabel && progress.dnsName }') PostgreSQL ready: <strong>{ progress.dnsName }</strong>
.alert.alert-success(if='{ progress.pooler }') Connection pooler deployment created
.col-lg-3 .col-lg-3
help-general(config='{ opts.config }') help-general(config='{ opts.config }')
@ -122,9 +124,11 @@ postgresql
jQuery.get( jQuery.get(
'/postgresqls/' + this.cluster_path, '/postgresqls/' + this.cluster_path,
).done(data => { ).done(data => {
this.progress.pooler = false
this.progress.postgresql = true this.progress.postgresql = true
this.progress.postgresqlManifest = data this.progress.postgresqlManifest = data
this.progress.createdTimestamp = data.metadata.creationTimestamp this.progress.createdTimestamp = data.metadata.creationTimestamp
this.progress.poolerEnabled = data.spec.enableConnectionPooler
this.uid = this.progress.postgresqlManifest.metadata.uid this.uid = this.progress.postgresqlManifest.metadata.uid
this.update() this.update()
@ -160,6 +164,11 @@ postgresql
this.progress.dnsName = data.metadata.name + '.' + data.metadata.namespace this.progress.dnsName = data.metadata.name + '.' + data.metadata.namespace
} }
jQuery.get('/pooler/' + this.cluster_path).done(data => {
this.progress.pooler = {"url": ""}
this.update()
})
this.update() this.update()
}) })
}) })

View File

@ -44,6 +44,8 @@ spec:
value: "http://postgres-operator:8080" value: "http://postgres-operator:8080"
- name: "OPERATOR_CLUSTER_NAME_LABEL" - name: "OPERATOR_CLUSTER_NAME_LABEL"
value: "cluster-name" value: "cluster-name"
- name: "RESOURCES_VISIBLE"
value: "False"
- name: "TARGET_NAMESPACE" - name: "TARGET_NAMESPACE"
value: "default" value: "default"
- name: "TEAMS" - name: "TEAMS"

View File

@ -39,6 +39,7 @@ rules:
- apiGroups: - apiGroups:
- apps - apps
resources: resources:
- deployments
- statefulsets - statefulsets
verbs: verbs:
- get - get

View File

@ -25,7 +25,7 @@ from flask import (
from flask_oauthlib.client import OAuth from flask_oauthlib.client import OAuth
from functools import wraps from functools import wraps
from gevent import sleep, spawn from gevent import sleep, spawn
from gevent.wsgi import WSGIServer from gevent.pywsgi import WSGIServer
from jq import jq from jq import jq
from json import dumps, loads from json import dumps, loads
from logging import DEBUG, ERROR, INFO, basicConfig, exception, getLogger from logging import DEBUG, ERROR, INFO, basicConfig, exception, getLogger
@ -44,6 +44,7 @@ from .spiloutils import (
create_postgresql, create_postgresql,
read_basebackups, read_basebackups,
read_namespaces, read_namespaces,
read_pooler,
read_pods, read_pods,
read_postgresql, read_postgresql,
read_postgresqls, read_postgresqls,
@ -80,6 +81,7 @@ OPERATOR_CLUSTER_NAME_LABEL = getenv('OPERATOR_CLUSTER_NAME_LABEL', 'cluster-nam
OPERATOR_UI_CONFIG = getenv('OPERATOR_UI_CONFIG', '{}') OPERATOR_UI_CONFIG = getenv('OPERATOR_UI_CONFIG', '{}')
OPERATOR_UI_MAINTENANCE_CHECK = getenv('OPERATOR_UI_MAINTENANCE_CHECK', '{}') OPERATOR_UI_MAINTENANCE_CHECK = getenv('OPERATOR_UI_MAINTENANCE_CHECK', '{}')
READ_ONLY_MODE = getenv('READ_ONLY_MODE', False) in [True, 'true'] READ_ONLY_MODE = getenv('READ_ONLY_MODE', False) in [True, 'true']
RESOURCES_VISIBLE = getenv('RESOURCES_VISIBLE', True)
SPILO_S3_BACKUP_PREFIX = getenv('SPILO_S3_BACKUP_PREFIX', 'spilo/') SPILO_S3_BACKUP_PREFIX = getenv('SPILO_S3_BACKUP_PREFIX', 'spilo/')
SUPERUSER_TEAM = getenv('SUPERUSER_TEAM', 'acid') SUPERUSER_TEAM = getenv('SUPERUSER_TEAM', 'acid')
TARGET_NAMESPACE = getenv('TARGET_NAMESPACE') TARGET_NAMESPACE = getenv('TARGET_NAMESPACE')
@ -312,6 +314,7 @@ DEFAULT_UI_CONFIG = {
def get_config(): def get_config():
config = loads(OPERATOR_UI_CONFIG) or DEFAULT_UI_CONFIG config = loads(OPERATOR_UI_CONFIG) or DEFAULT_UI_CONFIG
config['read_only_mode'] = READ_ONLY_MODE config['read_only_mode'] = READ_ONLY_MODE
config['resources_visible'] = RESOURCES_VISIBLE
config['superuser_team'] = SUPERUSER_TEAM config['superuser_team'] = SUPERUSER_TEAM
config['target_namespace'] = TARGET_NAMESPACE config['target_namespace'] = TARGET_NAMESPACE
@ -397,6 +400,22 @@ def get_service(namespace: str, cluster: str):
) )
@app.route('/pooler/<namespace>/<cluster>')
@authorize
def get_list_poolers(namespace: str, cluster: str):
if TARGET_NAMESPACE not in ['', '*', namespace]:
return wrong_namespace()
return respond(
read_pooler(
get_cluster(),
namespace,
"{}-pooler".format(cluster),
),
)
@app.route('/statefulsets/<namespace>/<cluster>') @app.route('/statefulsets/<namespace>/<cluster>')
@authorize @authorize
def get_list_clusters(namespace: str, cluster: str): def get_list_clusters(namespace: str, cluster: str):
@ -587,6 +606,17 @@ def update_postgresql(namespace: str, cluster: str):
spec['volume'] = {'size': size} spec['volume'] = {'size': size}
if 'enableConnectionPooler' in postgresql['spec']:
cp = postgresql['spec']['enableConnectionPooler']
if not cp:
if 'enableConnectionPooler' in o['spec']:
del o['spec']['enableConnectionPooler']
else:
spec['enableConnectionPooler'] = True
else:
if 'enableConnectionPooler' in o['spec']:
del o['spec']['enableConnectionPooler']
if 'enableReplicaLoadBalancer' in postgresql['spec']: if 'enableReplicaLoadBalancer' in postgresql['spec']:
rlb = postgresql['spec']['enableReplicaLoadBalancer'] rlb = postgresql['spec']['enableReplicaLoadBalancer']
if not rlb: if not rlb:
@ -1006,7 +1036,7 @@ def init_cluster():
def main(port, secret_key, debug, clusters: list): def main(port, secret_key, debug, clusters: list):
global TARGET_NAMESPACE global TARGET_NAMESPACE
basicConfig(level=DEBUG if debug else INFO) basicConfig(stream=sys.stdout, level=(DEBUG if debug else INFO), format='%(asctime)s %(levelname)s: %(message)s',)
init_cluster() init_cluster()

View File

@ -1,7 +1,7 @@
from boto3 import client from boto3 import client
from datetime import datetime, timezone from datetime import datetime, timezone
from furl import furl from furl import furl
from json import dumps from json import dumps, loads
from logging import getLogger from logging import getLogger
from os import environ, getenv from os import environ, getenv
from requests import Session from requests import Session
@ -18,6 +18,15 @@ session = Session()
OPERATOR_CLUSTER_NAME_LABEL = getenv('OPERATOR_CLUSTER_NAME_LABEL', 'cluster-name') OPERATOR_CLUSTER_NAME_LABEL = getenv('OPERATOR_CLUSTER_NAME_LABEL', 'cluster-name')
COMMON_CLUSTER_LABEL = getenv('COMMON_CLUSTER_LABEL', '{"application":"spilo"}')
COMMON_POOLER_LABEL = getenv('COMMONG_POOLER_LABEL', '{"application":"db-connection-pooler"}')
logger.info("Common Cluster Label: {}".format(COMMON_CLUSTER_LABEL))
logger.info("Common Pooler Label: {}".format(COMMON_POOLER_LABEL))
COMMON_CLUSTER_LABEL = loads(COMMON_CLUSTER_LABEL)
COMMON_POOLER_LABEL = loads(COMMON_POOLER_LABEL)
def request(cluster, path, **kwargs): def request(cluster, path, **kwargs):
if 'timeout' not in kwargs: if 'timeout' not in kwargs:
@ -85,6 +94,7 @@ def resource_api_version(resource_type):
return { return {
'postgresqls': 'apis/acid.zalan.do/v1', 'postgresqls': 'apis/acid.zalan.do/v1',
'statefulsets': 'apis/apps/v1', 'statefulsets': 'apis/apps/v1',
'deployments': 'apis/apps/v1',
}.get(resource_type, 'api/v1') }.get(resource_type, 'api/v1')
@ -149,7 +159,7 @@ def read_pod(cluster, namespace, resource_name):
resource_type='pods', resource_type='pods',
namespace=namespace, namespace=namespace,
resource_name=resource_name, resource_name=resource_name,
label_selector={'application': 'spilo'}, label_selector=COMMON_CLUSTER_LABEL,
) )
@ -159,7 +169,17 @@ def read_service(cluster, namespace, resource_name):
resource_type='services', resource_type='services',
namespace=namespace, namespace=namespace,
resource_name=resource_name, resource_name=resource_name,
label_selector={'application': 'spilo'}, label_selector=COMMON_CLUSTER_LABEL,
)
def read_pooler(cluster, namespace, resource_name):
return kubernetes_get(
cluster=cluster,
resource_type='deployments',
namespace=namespace,
resource_name=resource_name,
label_selector=COMMON_POOLER_LABEL,
) )
@ -169,7 +189,7 @@ def read_statefulset(cluster, namespace, resource_name):
resource_type='statefulsets', resource_type='statefulsets',
namespace=namespace, namespace=namespace,
resource_name=resource_name, resource_name=resource_name,
label_selector={'application': 'spilo'}, label_selector=COMMON_CLUSTER_LABEL,
) )

View File

@ -1,5 +1,5 @@
Flask-OAuthlib==0.9.5 Flask-OAuthlib==0.9.5
Flask==1.1.1 Flask==1.1.2
backoff==1.8.1 backoff==1.8.1
boto3==1.10.4 boto3==1.10.4
boto==2.49.0 boto==2.49.0

2
ui/start_server.sh Normal file
View File

@ -0,0 +1,2 @@
#!/bin/bash
/usr/bin/python3 -m operator_ui