diff --git a/mariadb/Chart.yaml b/mariadb/Chart.yaml index 56ca9c2aa..ebe49e9aa 100644 --- a/mariadb/Chart.yaml +++ b/mariadb/Chart.yaml @@ -15,7 +15,7 @@ apiVersion: v1 appVersion: v10.6.7 description: OpenStack-Helm MariaDB name: mariadb -version: 0.2.39 +version: 0.2.40 home: https://mariadb.com/kb/en/ icon: http://badges.mariadb.org/mariadb-badge-180x60.png sources: diff --git a/mariadb/templates/bin/_start.py.tpl b/mariadb/templates/bin/_start.py.tpl index db36168a5..aae1294ca 100644 --- a/mariadb/templates/bin/_start.py.tpl +++ b/mariadb/templates/bin/_start.py.tpl @@ -80,6 +80,10 @@ if check_env_var("STATE_CONFIGMAP"): state_configmap_name = os.environ['STATE_CONFIGMAP'] logger.info("Will use \"{0}\" configmap for cluster state info".format( state_configmap_name)) +if check_env_var("PRIMARY_SERVICE_NAME"): + primary_service_name = os.environ['PRIMARY_SERVICE_NAME'] + logger.info("Will use \"{0}\" service as primary".format( + primary_service_name)) if check_env_var("POD_NAMESPACE"): pod_namespace = os.environ['POD_NAMESPACE'] if check_env_var("DIRECT_SVC_NAME"): @@ -92,6 +96,8 @@ if check_env_var("DISCOVERY_DOMAIN"): discovery_domain = os.environ['DISCOVERY_DOMAIN'] if check_env_var("WSREP_PORT"): wsrep_port = os.environ['WSREP_PORT'] +if check_env_var("MARIADB_PORT"): + mariadb_port = int(os.environ['MARIADB_PORT']) if check_env_var("MYSQL_DBADMIN_USERNAME"): mysql_dbadmin_username = os.environ['MYSQL_DBADMIN_USERNAME'] if check_env_var("MYSQL_DBADMIN_PASSWORD"): @@ -115,7 +121,8 @@ if mysql_dbadmin_username == mysql_dbsst_username: sys.exit(1) # Set some variables for tuneables -cluster_leader_ttl = 120 +if check_env_var("CLUSTER_LEADER_TTL"): + cluster_leader_ttl = int(os.environ['CLUSTER_LEADER_TTL']) state_configmap_update_period = 10 default_sleep = 20 @@ -138,6 +145,25 @@ def ensure_state_configmap(pod_namespace, configmap_name, configmap_body): return False +def ensure_primary_service(pod_namespace, service_name, service_body): + """Ensure the primary service exists. + + Keyword arguments: + pod_namespace -- the namespace to house the service + service_name -- the service name + service_body -- the service body + """ + try: + k8s_api_instance.read_namespaced_service( + name=service_name, namespace=pod_namespace) + return True + except: + k8s_api_instance.create_namespaced_service( + namespace=pod_namespace, body=service_body) + + return False + + def run_cmd_with_logging(popenargs, logger, @@ -388,6 +414,60 @@ def set_configmap_data(key, value): return safe_update_configmap( configmap_dict=configmap_dict, configmap_patch=configmap_patch) +def safe_update_service(service_dict, service_patch): + """Update a service with locking. + + Keyword arguments: + service_dict -- a dict representing the service to be patched + service_patch -- a dict containign the patch + """ + logger.debug("Safe Patching service") + # NOTE(portdirect): Explictly set the resource version we are patching to + # ensure nothing else has modified the service since we read it. + service_patch['metadata']['resourceVersion'] = service_dict[ + 'metadata']['resource_version'] + + # Retry up to 8 times in case of 409 only. Each retry has a ~1 second + # sleep in between so do not want to exceed the roughly 10 second + # write interval per cm update. + for i in range(8): + try: + api_response = k8s_api_instance.patch_namespaced_service( + name=primary_service_name, + namespace=pod_namespace, + body=service_patch) + return True + except kubernetes.client.rest.ApiException as error: + if error.status == 409: + # This status code indicates a collision trying to write to the + # service while another instance is also trying the same. + logger.warning("Collision writing service: {0}".format(error)) + # This often happens when the replicas were started at the same + # time, and tends to be persistent. Sleep with some random + # jitter value briefly to break the synchronization. + naptime = secretsGen.uniform(0.8,1.2) + time.sleep(naptime) + else: + logger.error("Failed to set service: {0}".format(error)) + return error + logger.info("Retry writing service attempt={0} sleep={1}".format( + i+1, naptime)) + return True + +def set_primary_service_spec(key, value): + """Update a service's endpoint via patching. + + Keyword arguments: + key -- the key to be patched + value -- the value to give the key + """ + logger.debug("Setting service spec.selector key={0} to value={1}".format(key, value)) + service_dict = k8s_api_instance.read_namespaced_service( + name=primary_service_name, namespace=pod_namespace).to_dict() + service_patch = {'spec': {'selector': {}}, 'metadata': {}} + service_patch['spec']['selector'][key] = value + return safe_update_service( + service_dict=service_dict, service_patch=service_patch) def get_configmap_value(key, type='data'): """Get a configmap's key's value. @@ -469,6 +549,35 @@ def get_cluster_state(): pod_namespace=pod_namespace, configmap_name=state_configmap_name, configmap_body=initial_configmap_body) + + + initial_primary_service_body = { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": primary_service_name, + }, + "spec": { + "ports": [ + { + "name": "mysql", + "port": mariadb_port + } + ], + "selector": { + "application": "mariadb", + "component": "server", + "statefulset.kubernetes.io/pod-name": leader + } + } + } + if ensure_primary_service( + pod_namespace=pod_namespace, + service_name=primary_service_name, + service_body=initial_primary_service_body): + logger.info("Service {0} already exists".format(primary_service_name)) + else: + logger.info("Service {0} has been successfully created".format(primary_service_name)) return state @@ -480,6 +589,38 @@ def declare_myself_cluster_leader(): leader_expiry = "{0}Z".format(leader_expiry_raw.isoformat("T")) set_configmap_annotation( key='openstackhelm.openstack.org/leader.node', value=local_hostname) + logger.info("Setting primary_service's spec.selector to {0}".format(local_hostname)) + try: + set_primary_service_spec( + key='statefulset.kubernetes.io/pod-name', value=local_hostname) + except: + initial_primary_service_body = { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": primary_service_name, + }, + "spec": { + "ports": [ + { + "name": "mysql", + "port": mariadb_port + } + ], + "selector": { + "application": "mariadb", + "component": "server", + "statefulset.kubernetes.io/pod-name": local_hostname + } + } + } + if ensure_primary_service( + pod_namespace=pod_namespace, + service_name=primary_service_name, + service_body=initial_primary_service_body): + logger.info("Service {0} already exists".format(primary_service_name)) + else: + logger.info("Service {0} has been successfully created".format(primary_service_name)) set_configmap_annotation( key='openstackhelm.openstack.org/leader.expiry', value=leader_expiry) diff --git a/mariadb/templates/statefulset.yaml b/mariadb/templates/statefulset.yaml index b78f69d7c..42521f190 100644 --- a/mariadb/templates/statefulset.yaml +++ b/mariadb/templates/statefulset.yaml @@ -47,6 +47,29 @@ rules: - configmaps verbs: - create + - apiGroups: + - "" + resources: + - services + verbs: + - create + - apiGroups: + - "" + resourceNames: + - {{ tuple "oslo_db" "primary" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }} + resources: + - services + verbs: + - get + - patch + - apiGroups: + - "" + resourceNames: + - {{ tuple "oslo_db" "primary" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }} + resources: + - endpoints + verbs: + - get - apiGroups: - "" resourceNames: @@ -165,6 +188,12 @@ spec: value: {{ tuple "oslo_db" "direct" "wsrep" . | include "helm-toolkit.endpoints.endpoint_port_lookup" | quote }} - name: STATE_CONFIGMAP value: {{ printf "%s-%s" .deployment_name "mariadb-state" | quote }} + - name: PRIMARY_SERVICE_NAME + value: {{ tuple "oslo_db" "primary" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }} + - name: CLUSTER_LEADER_TTL + value: {{ .Values.conf.galera.cluster_leader_ttl | quote }} + - name: MARIADB_PORT + value: {{ tuple "oslo_db" "direct" "mysql" . | include "helm-toolkit.endpoints.endpoint_port_lookup" | quote }} - name: MYSQL_DBADMIN_USERNAME value: {{ .Values.endpoints.oslo_db.auth.admin.username }} - name: MYSQL_DBADMIN_PASSWORD diff --git a/mariadb/values.yaml b/mariadb/values.yaml index d3bc4fb57..9daf08ab3 100644 --- a/mariadb/values.yaml +++ b/mariadb/values.yaml @@ -362,6 +362,8 @@ conf: lock_expire_after: 7200 retry_after: 3600 container_name: throttle-backups-manager + galera: + cluster_leader_ttl: 120 database: mysql_histfile: "/dev/null" my: | @@ -603,6 +605,7 @@ endpoints: direct: mariadb-server discovery: mariadb-discovery error_pages: mariadb-ingress-error-pages + primary: mariadb-server-primary host_fqdn_override: default: null path: null diff --git a/mariadb/values_overrides/primary-service.yaml b/mariadb/values_overrides/primary-service.yaml new file mode 100644 index 000000000..919dcea17 --- /dev/null +++ b/mariadb/values_overrides/primary-service.yaml @@ -0,0 +1,21 @@ +--- +manifests: + deployment_ingress: false + deployment_error: false + service_ingress: false + configmap_ingress_conf: false + configmap_ingress_etc: false + service_error: false +volume: + size: 1Gi + backup: + size: 1Gi +conf: + galera: + cluster_leader_ttl: 10 +endpoints: + oslo_db: + hosts: + default: mariadb + primary: mariadb +... diff --git a/releasenotes/notes/mariadb.yaml b/releasenotes/notes/mariadb.yaml index a045fafc6..6ab298f2f 100644 --- a/releasenotes/notes/mariadb.yaml +++ b/releasenotes/notes/mariadb.yaml @@ -55,4 +55,5 @@ mariadb: - 0.2.37 Backups verification improvements - 0.2.38 Added throttling remote backups - 0.2.39 Template changes for image 1.9 compatibility + - 0.2.40 Start.py allows to create mariadb-service-primary service and endpoint ... diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml index 67f2577e5..876e3dd7b 100644 --- a/zuul.d/jobs.yaml +++ b/zuul.d/jobs.yaml @@ -266,6 +266,34 @@ - ./tools/deployment/openstack-support/120-powerdns.sh - ./tools/deployment/openstack-support/130-cinder.sh +- job: + name: openstack-helm-infra-openstack-support-mariadb-service-primary + parent: openstack-helm-infra-deploy + nodeset: openstack-helm-1node-ubuntu_focal + vars: + osh_params: + openstack_release: "2023.1" + container_distro_name: ubuntu + container_distro_version: focal + feature_gates: "ssl,primary-service" + gate_scripts: + - ./tools/deployment/openstack-support/000-prepare-k8s.sh + - ./tools/deployment/openstack-support/007-namespace-config.sh + - ./tools/deployment/openstack-support/010-ingress.sh + - ./tools/deployment/ceph/ceph.sh + - ./tools/deployment/openstack-support/025-ceph-ns-activate.sh + - ./tools/deployment/openstack-support/030-rabbitmq.sh + - ./tools/deployment/openstack-support/070-mariadb.sh + - ./tools/deployment/openstack-support/040-memcached.sh + - ./tools/deployment/openstack-support/051-libvirt-ssl.sh + - ./tools/deployment/openstack-support/060-openvswitch.sh + - ./tools/deployment/common/setup-client.sh + - ./tools/deployment/openstack-support/090-keystone.sh + - ./tools/deployment/openstack-support/100-ceph-radosgateway.sh + - ./tools/deployment/openstack-support/110-openstack-exporter.sh + - ./tools/deployment/openstack-support/120-powerdns.sh + - ./tools/deployment/openstack-support/130-cinder.sh + - job: name: openstack-helm-infra-mariadb-operator diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml index 0361c2cbf..9d3132b63 100644 --- a/zuul.d/project.yaml +++ b/zuul.d/project.yaml @@ -29,6 +29,7 @@ - openstack-helm-infra-openstack-support-ssl - openstack-helm-infra-metacontroller - openstack-helm-infra-mariadb-operator + - openstack-helm-infra-openstack-support-mariadb-service-primary gate: jobs: - openstack-helm-lint @@ -38,6 +39,7 @@ - openstack-helm-infra-openstack-support - openstack-helm-infra-openstack-support-rook - openstack-helm-infra-openstack-support-ssl + - openstack-helm-infra-openstack-support-mariadb-service-primary post: jobs: - publish-openstack-helm-charts