diff --git a/.goreleaser.yaml b/.goreleaser.yaml index e44a5cfa..cff77a6d 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -35,7 +35,17 @@ nfpms: license: PostgreSQL License formats: - rpm + - deb bindir: /usr/sbin + scripts: + preremove: packaging/preremove.sh + overrides: + rpm: + scripts: + postinstall: packaging/rpm/postinstall.sh + deb: + scripts: + postinstall: packaging/deb/postinstall.sh contents: - src: packaging/pgedge-control-plane.service dst: /usr/lib/systemd/system/pgedge-control-plane.service diff --git a/Makefile b/Makefile index 94a059e9..3425d382 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ CLUSTER_TEST_SKIP_IMAGE_BUILD ?= 0 CLUSTER_TEST_SKIP_CLEANUP ?= 0 CLUSTER_TEST_IMAGE_TAG ?= CLUSTER_TEST_DATA_DIR ?= +DEV_LIMA_OS ?= rocky-9 ci_enabled=$(filter true,$(CI)) docker_swarm_state=$(shell docker info --format '{{.Swarm.LocalNodeState}}') @@ -412,7 +413,7 @@ api-docs: .PHONY: dev-lima-deploy dev-lima-deploy: - $(MAKE) -C lima deploy + $(MAKE) -C lima deploy DEV_LIMA_OS=$(DEV_LIMA_OS) .PHONY: dev-lima-build dev-lima-build: diff --git a/NOTICE.txt b/NOTICE.txt index 19034297..c49cbcff 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -2452,217 +2452,6 @@ SOFTWARE. ``` -## github.com/elastic/gosigar - -* Name: github.com/elastic/gosigar -* Version: v0.14.3 -* License: [Apache-2.0](https://github.com/elastic/gosigar/blob/v0.14.3/LICENSE) - -``` - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -``` - ## github.com/fatih/structs * Name: github.com/fatih/structs diff --git a/changes/unreleased/Added-20260511-090000.yaml b/changes/unreleased/Added-20260511-090000.yaml new file mode 100644 index 00000000..35d14e98 --- /dev/null +++ b/changes/unreleased/Added-20260511-090000.yaml @@ -0,0 +1,3 @@ +kind: Added +body: Added support for Debian-based distributions to the systemd orchestrator. +time: 2026-05-11T09:28:06.065156-04:00 diff --git a/changes/unreleased/Added-20260511-090010.yaml b/changes/unreleased/Added-20260511-090010.yaml new file mode 100644 index 00000000..4704965b --- /dev/null +++ b/changes/unreleased/Added-20260511-090010.yaml @@ -0,0 +1,3 @@ +kind: Added +body: Added deb packages to our releases for Debian-based distributions. +time: 2026-05-11T10:07:07.636452-04:00 diff --git a/changes/unreleased/Added-20260511-090020.yaml b/changes/unreleased/Added-20260511-090020.yaml new file mode 100644 index 00000000..37b27bd9 --- /dev/null +++ b/changes/unreleased/Added-20260511-090020.yaml @@ -0,0 +1,3 @@ +kind: Added +body: Added post-update and pre-remove scriptlets to our RPM and deb packages to automatically restart the Control Plane service during upgrades and to stop and disable the service during uninstallation. +time: 2026-05-11T09:26:22.435191-04:00 diff --git a/changes/unreleased/Added-20260511-100000.yaml b/changes/unreleased/Added-20260511-100000.yaml new file mode 100644 index 00000000..08238066 --- /dev/null +++ b/changes/unreleased/Added-20260511-100000.yaml @@ -0,0 +1,3 @@ +kind: Added +body: Added the ability to configure a separate override for the database owner GID to complement the existing UID override. +time: 2026-05-11T10:09:04.640943-04:00 diff --git a/docs/development/running-locally.md b/docs/development/running-locally.md index aa6d924f..9f048828 100644 --- a/docs/development/running-locally.md +++ b/docs/development/running-locally.md @@ -414,7 +414,17 @@ To run the Control Plane, start by deploying the Lima virtual machines where we' make dev-lima-deploy ``` -Note that this may take a while to create, configure, and install the pre-requisites. Once this command exits, you can build and run the Control Plane servers with: +By default, this creates six-hosts with Rocky Linux 9. Alternatively, you can set the `DEV_LIMA_OS` environment variable to use either Ubuntu 24.04 or Debian 13: + +```sh +make dev-lima-deploy DEV_LIMA_OS=ubuntu-24.04 + +# OR + +make dev-lima-deploy DEV_LIMA_OS=debian-13 +``` + +Note that it may take a while to create, configure, and install the pre-requisites. Once this command exits, you can build and run the Control Plane servers with: ```sh make dev-lima-run diff --git a/docs/installation/configuration.md b/docs/installation/configuration.md index 8bd75cf5..d60f9a0c 100644 --- a/docs/installation/configuration.md +++ b/docs/installation/configuration.md @@ -49,7 +49,8 @@ PGEDGE_CLIENT_ADDRESSES='192.168.1.2,my-host.internal' | `docker_swarm.image_repository_host` | `PGEDGE_DOCKER_SWARM__IMAGE_REPOSITORY_HOST` | string | `ghcr.io/pgedge` | The base URL of pgEdge Docker images. | | | `docker_swarm.database_networks_cidr` | `PGEDGE_DOCKER_SWARM__DATABASE_NETWORKS_CIDR` | string | `10.128.128.0/18` | The CIDR used to allocate per-database networks. | Must not be changed after creating databases. | | `docker_swarm.database_networks_subnet_bits` | `PGEDGE_DOCKER_SWARM__DATABASE_NETWORKS_SUBNET_BITS` | int | `26` | The subnet size for per-database networks. | Must not be changed after creating databases. | -| `database_owner_uid` | `PGEDGE_DATABASE_OWNER_UID` | int | `26` | The UID to use for database configuration and data. | Must match the UID that owns the Postgres server processes. | +| `database_owner_uid` | `PGEDGE_DATABASE_OWNER_UID` | int | Defaults to the `postgres` user's UID | The UID to use for database configuration and data. | Must match the UID that owns the Postgres server processes. | +| `database_owner_gid` | `PGEDGE_DATABASE_OWNER_GID` | int | Defaults to the `postgres` user's GID | The GID to use for database configuration and data. | Must match the GID that owns the Postgres server processes. | | `databases_monitor_interval_seconds` | `PGEDGE_DATABASES_MONITOR_INTERVAL_SECONDS` | uint | `30` | The refresh interval for the 'databases' monitor. This monitor watches for database version changes that happen outside of the Control Plane API, such as through a system package update. | Set to `0` to disable this monitor. | ### Components diff --git a/docs/installation/systemd.md b/docs/installation/systemd.md index c6cfdbdb..64b070b0 100644 --- a/docs/installation/systemd.md +++ b/docs/installation/systemd.md @@ -3,18 +3,19 @@ !!! warning "Preview Feature" System package-based installation is a preview feature that is under active - development. The core database management API is fully functional and tested, - but some features are not yet supported (see [Limitations](#limitations) below). - The installation method and upgrade process between releases may change before - this feature is finalized. We'd love your feedback - please share your - experience in our [GitHub issues](https://github.com/pgedge/control-plane/issues) - or join our [Discord](https://discord.com/invite/pgedge/login). + development. The core database management API is fully functional and + tested, but some features are not yet supported (see + [Limitations](#limitations) below). The installation method and upgrade + process between releases may change before this feature is finalized. We'd + love your feedback - please share your experience in our + [GitHub issues](https://github.com/pgedge/control-plane/issues) or join our + [Discord](https://discord.com/invite/pgedge/login). This guide covers installing the pgEdge Control Plane on Linux hosts that use -the RPM Package Manager (RPM) package format (e.g. Red Hat Enterprise Linux -(RHEL), Rocky Linux, AlmaLinux) using the RPM package attached to each [GitHub -release](https://github.com/pgedge/control-plane/releases). Support for -Debian-based hosts is coming in a future release. +the RPM Package Manager (RPM) package format (e.g., Red Hat Enterprise Linux +(RHEL), Rocky Linux, AlmaLinux) or the Debian (deb) package format (e.g., +Ubuntu, Debian) using the package files attached to each +[GitHub release](https://github.com/pgedge/control-plane/releases). Unlike the Docker Swarm installation method, the system package installation runs the Control Plane directly on the host. The Control Plane uses systemd to @@ -47,18 +48,18 @@ cluster members on each host. - Port `2379` uses TCP for Etcd client communication. - Port `2380` uses TCP for Etcd peer communication. -You can configure alternate ports by modifying the -[configuration file](#configuration) after installing the `pgedge-control-plane` -RPM. +You can configure alternate ports by modifying the [configuration +file](#configuration) after installing the `pgedge-control-plane` package. ### Packages The Control Plane depends on the pgEdge Enterprise Postgres packages. The -Control Plane does not yet install Postgres or its supporting packages -automatically; install the packages on each host before starting the Control -Plane. +Control Plane does not yet automatically install Postgres or its supporting +packages; install the packages on each host before starting the Control Plane. -Run the following commands on each host: +#### RPM Packages + +Run the following commands on each RHEL-like host: ```sh # Install prerequisites for the pgEdge Enterprise Postgres packages @@ -79,11 +80,48 @@ sudo dnf install -y \ pgedge-patroni ``` -## Installing the RPM +#### Deb Packages + +Run the following commands on each Debian-based host: -The pgEdge Control Plane RPM is published with each release on the [GitHub -releases page](https://github.com/pgedge/control-plane/releases) for both -`amd64` and `arm64` architectures. +```sh +# Install prerequisites for the pgEdge Enterprise Postgres packages +sudo apt update +sudo apt install curl gnupg2 lsb-release + +# Install the pgEdge Enterprise Postgres repository +curl -O --output-dir /tmp https://apt.pgedge.com/repodeb/pgedge-release_latest_all.deb +sudo apt install -y /tmp/pgedge-release_latest_all.deb +sudo apt update + +# Install the required packages for your Postgres version. We currently support +# versions 16, 17, and 18. Set POSTGRES_MAJOR_VERSION to your desired version. +POSTGRES_MAJOR_VERSION='<16|17|18>' +sudo apt install -y \ + pgedge-postgresql-${POSTGRES_MAJOR_VERSION} \ + pgedge-postgresql-${POSTGRES_MAJOR_VERSION}-spock50 \ + pgedge-pgbackrest \ + pgedge-patroni + +# The postgresql package will create and start a default database. We recommend +# stopping and disabling that database to avoid confusion or port conflicts. +sudo systemctl disable --now postgresql.service +sudo systemctl disable --now postgresql@${POSTGRES_MAJOR_VERSION}-main.service +``` + +## Installing the Control Plane + +The pgEdge Control Plane packages are published with each release on the +[GitHub releases page](https://github.com/pgedge/control-plane/releases) for +both `amd64` and `arm64` architectures. + +Every package will install the following files: + +- The Control Plane binary is installed at `/usr/sbin/pgedge-control-plane`. +- The systemd service unit is installed at `/usr/lib/systemd/system/pgedge-control-plane.service`. +- The default configuration file is installed at `/etc/pgedge-control-plane/config.json`. + +### RPM Package Use the following commands to download and install the RPM: @@ -101,17 +139,29 @@ curl -LO "https://github.com/pgedge/control-plane/releases/download/${VERSION}/p sudo rpm -i pgedge-control-plane_${VERSION#v}_linux_${ARCH}.rpm ``` -The RPM installs the following files: +### Deb Package -- The Control Plane binary is installed at `/usr/sbin/pgedge-control-plane`. -- The systemd service unit is installed at `/usr/lib/systemd/system/pgedge-control-plane.service`. -- The default configuration file is installed at `/etc/pgedge-control-plane/config.json`. +Use the following commands to download and install the deb package: + +```sh +# Detect architecture +ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') + +# Set the version to install +VERSION="v0.8.0" + +# Download the deb package +curl -LO "https://github.com/pgedge/control-plane/releases/download/${VERSION}/pgedge-control-plane_${VERSION#v}_linux_${ARCH}.deb" + +# Install the deb package +sudo apt install ./pgedge-control-plane_${VERSION#v}_linux_${ARCH}.deb +``` ## Configuration -The Control Plane reads its configuration from a JSON file at -`/etc/pgedge-control-plane/config.json`. The following example shows the -default configuration: +The Control Plane reads its configuration from +`/etc/pgedge-control-plane/config.json`. The following example shows the default +configuration: ```json { @@ -121,11 +171,27 @@ default configuration: ``` The `orchestrator` field must be set to `"systemd"` for this installation -method. The `data_dir` field specifies where the Control Plane stores its -state, including the embedded Etcd data. +method. The `data_dir` field specifies where the Control Plane stores its state, +including the embedded Etcd data. + +By default, the Control Plane follows the operating system's conventions for +Postgres data directories. For RHEL-like distributions, the base directory +defaults to `/var/lib/pgsql`. For Debian-based distributions, the default base +directory is `/var/lib/postgresql`. Add the `systemd.instance_data_dir` to the +configuration file to set a custom base directory: + +```json +{ + "orchestrator": "systemd", + "data_dir": "/data/pgedge-control-plane", + "systemd": { + "instance_data_dir": "/data/postgres" + } +} +``` -The host ID defaults to the machine's short hostname. Add the `host_id` field -to set an explicit host ID: +The host ID defaults to the machine's short hostname. Add the `host_id` field to +set an explicit host ID: ```json { @@ -135,8 +201,8 @@ to set an explicit host ID: } ``` -You can find the full list of configuration settings in the -[Configuration reference](./configuration.md). +You can find the full list of configuration settings in the [Configuration +reference](./configuration.md). ## Starting the Control Plane @@ -161,8 +227,8 @@ sudo journalctl -u pgedge-control-plane.service --follow ## Initializing the Control Plane -Once the service is running on all hosts, initialize and join each host the -same way as a Docker Swarm installation. +Once the service is running on all hosts, initialize and join each host as in a +Docker Swarm installation. Use the following command to initialize the cluster on the first host: @@ -234,8 +300,8 @@ instructions. > [!NOTE] > Unlike with the Swarm orchestrator, `patroni_port` is a required field in -> systemd clusters. As with other port fields, you can specify `0` to -> assign a random port. +> systemd clusters. As with other port fields, you can specify `0` to assign a +> random port. ## Performing Postgres Minor Version Upgrades @@ -243,20 +309,25 @@ Database upgrades are not yet supported via the Control Plane API, but system administrators can perform minor Postgres version upgrades by updating the packages on each machine. Follow these steps on each host in the cluster: -1. Upgrade Postgres and/or other components using `dnf upgrade`. For example: +1. Upgrade Postgres and/or other components using `dnf upgrade` or + `apt install --only-upgrade`. For example, to upgrade Postgres 18: ```sh + # If your system uses dnf, run: sudo dnf upgrade pgedge-postgresql18 + + # If your system uses apt, run: + sudo apt install --only-upgrade pgedge-postgresql-18 ``` -2. Find the systemd unit names for your database instances by listing units that +1. Find the systemd unit names for your database instances by listing units that have the `patroni-*` prefix: ```sh sudo systemctl list-units 'patroni-*' ``` -3. Restart each service: +2. Restart each service: ```sh sudo systemctl try-restart @@ -270,21 +341,23 @@ new versions to be reflected in the database spec in the Control Plane API. ## Updating the Control Plane -Updating the Control Plane requires stopping the service, installing the new -RPM, and restarting the service. Download the new RPM from the [GitHub releases -page](https://github.com/pgedge/control-plane/releases) and run the following -commands: +Updating the Control Plane just involves installing the new package. This will +automatically restart the Control Plane service after the update is complete. +Download the new RPM or deb from the +[GitHub releases page](https://github.com/pgedge/control-plane/releases) and run +one of the following commands: ```sh -sudo systemctl stop pgedge-control-plane.service -sudo rpm -U pgedge-control-plane-..rpm -sudo systemctl start pgedge-control-plane.service +# If you're using the RPM package, run: +sudo rpm -U pgedge-control-plane__linux_.rpm + +# If you're using the deb package, run: +sudo apt install pgedge-control-plane__linux_.deb ``` > [!NOTE] -> The RPM upgrade (`rpm -U`) preserves your existing configuration file at -> `/etc/pgedge-control-plane/config.json` because the RPM marks it as a -> non-replaceable configuration file. +> The package upgrade will preserve any modifications to the configuration file +> at `/etc/pgedge-control-plane/config.json`. ## Uninstalling the Control Plane @@ -312,22 +385,21 @@ Follow these steps to remove the Control Plane after deleting all databases: Deletions are asynchronous; wait for each task to complete before deleting the next database. -2. Stop and disable the Control Plane service with the following command: - - ```sh - sudo systemctl disable --now pgedge-control-plane.service - ``` - -3. Use the following command to uninstall the `pgedge-control-plane` package: +2. Use one of the following commands to uninstall the `pgedge-control-plane` + package: ```sh + # If you installed the RPM package, run: sudo rpm -e pgedge-control-plane + + # If you installed the deb package, run: + sudo apt remove pgedge-control-plane ``` -4. Remove the Control Plane data and configuration directories. +3. Remove the Control Plane data and configuration directories. - The data directory defaults to `/var/lib/pgedge-control-plane`; use the - path configured in `data_dir` if you specified a custom location. Use the + The data directory defaults to `/var/lib/pgedge-control-plane`; use the path + configured in `data_dir` if you specified a custom location. Use the following commands to remove both directories: ```sh @@ -359,14 +431,16 @@ on each host that holds an instance. 2. Delete the instance data directories. - By default, the Control Plane stores instance data at - `/var/lib/pgsql//`; use the path from your - configuration file if you set a custom instance data directory. The - following example command removes the data directory for a Postgres 17 - instance with ID `my-instance`: + By default, the Control Plane follows the OS conventions for Postgres data + directories. On RHEL-like distributions, this defaults to + `/var/lib/pgsql//`. On Debian-based + distributions, this defaults to `/var/lib/postgresql//`. + Use the path from your configuration file if you set a custom instance data + directory. The following example command removes the data directory for a + Postgres 17 instance with ID `my-instance` on an RHEL-like distribution: ```sh sudo rm -rf /var/lib/pgsql/17/my-instance ``` -Repeat these steps on each host that has an instance of the database. +Repeat these steps on each host that has a database instance. diff --git a/e2e/whole_cluster_test.go b/e2e/whole_cluster_test.go index bc30c3e1..4323c6b3 100644 --- a/e2e/whole_cluster_test.go +++ b/e2e/whole_cluster_test.go @@ -9,8 +9,10 @@ import ( "time" "github.com/jackc/pgx/v5" - controlplane "github.com/pgEdge/control-plane/api/apiv1/gen/control_plane" "github.com/stretchr/testify/require" + + controlplane "github.com/pgEdge/control-plane/api/apiv1/gen/control_plane" + "github.com/pgEdge/control-plane/client" ) // TestWholeCluster deploys one instance to each host in the cluster. @@ -81,14 +83,17 @@ func TestWholeCluster(t *testing.T) { continue } - t.Logf("validating table on node %s", read.Name) + primary := db.GetInstance(And(WithNode(read.Name), WithRole(client.RolePrimary))) + require.NotNil(t, primary) - readOpts := ConnectionOptions{ - Matcher: And(WithNode(read.Name), WithRole("primary")), + t.Logf("validating table on node %s, instance %s with role %s", read.Name, primary.ID, *primary.Postgres.Role) + + primaryOpts := ConnectionOptions{ + Instance: primary, Username: username, Password: password, } - db.WithConnection(ctx, readOpts, t, func(conn *pgx.Conn) { + db.WithConnection(ctx, primaryOpts, t, func(conn *pgx.Conn) { t.Log("waiting for replication to finish") var synced bool @@ -105,6 +110,33 @@ func TestWholeCluster(t *testing.T) { require.NoError(t, row.Scan(&actual)) require.Equal(t, "test", actual) }) + + for replica := range db.GetInstances(And(WithNode(read.Name), WithRole(client.RoleReplica))) { + t.Logf("validating table on node %s, instance %s with role %s", read.Name, replica.ID, *replica.Postgres.Role) + + replicaOpts := ConnectionOptions{ + Instance: replica, + Username: username, + Password: password, + } + db.WithConnection(ctx, replicaOpts, t, func(conn *pgx.Conn) { + t.Log("polling until replica syncs to primary") + + deadline := time.Now().Add(5 * time.Second) + var synced bool + for !synced && time.Now().Before(deadline) { + var actual string + row := conn.QueryRow(ctx, fmt.Sprintf(`SELECT data FROM %s WHERE id = 1;`, write.Name)) + err := row.Scan(&actual) + if err != nil || actual != "test" { + time.Sleep(500 * time.Millisecond) + } else { + synced = true + } + } + require.True(t, synced) + }) + } } } } diff --git a/go.mod b/go.mod index 81724abc..c68b3380 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/docker/go-connections v0.5.0 github.com/dustin/go-humanize v1.0.1 github.com/eclipse/paho.golang v0.22.0 - github.com/elastic/gosigar v0.14.3 github.com/goccy/go-yaml v1.18.0 github.com/google/uuid v1.6.0 github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 diff --git a/go.sum b/go.sum index 05515fb4..dfa907b2 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,6 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/eclipse/paho.golang v0.22.0 h1:JhhUngr8TBlyUZDZw/L6WVayPi9qmSmdWeki48i5AVE= github.com/eclipse/paho.golang v0.22.0/go.mod h1:9ZiYJ93iEfGRJri8tErNeStPKLXIGBHiqbHV74t5pqI= -github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/uo= -github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -599,7 +597,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/lima/Makefile b/lima/Makefile index 63ad471e..52d34689 100644 --- a/lima/Makefile +++ b/lima/Makefile @@ -1,5 +1,8 @@ +DEV_LIMA_OS ?= rocky-9 + ansible_playbook=ansible-playbook \ - --extra-vars='@vars.yaml' + --extra-vars='@vars.yaml' \ + --extra-vars='os=$(DEV_LIMA_OS)' .PHONY: quit quit: diff --git a/lima/ansible.cfg b/lima/ansible.cfg index 2bcd975c..b8aa3fa1 100644 --- a/lima/ansible.cfg +++ b/lima/ansible.cfg @@ -3,3 +3,4 @@ localhost_warning = False host_key_checking = False inventory = inventory.yaml interpreter_python = auto_silent +forks = 6 diff --git a/lima/debian-13-template.yaml b/lima/debian-13-template.yaml new file mode 100644 index 00000000..1b96af5c --- /dev/null +++ b/lima/debian-13-template.yaml @@ -0,0 +1,16 @@ +--- +minimumLimaVersion: 1.1.0 + +base: template://debian-13 + +cpus: 4 +memory: 8GiB +disk: 20GiB +containerd: + system: false + user: false +networks: +- lima: user-v2 +mounts: +- location: "~" + writable: true diff --git a/lima/deploy.yaml b/lima/deploy.yaml index 3c6a1201..a5e41466 100644 --- a/lima/deploy.yaml +++ b/lima/deploy.yaml @@ -9,7 +9,10 @@ hosts: all become: true roles: - - role: install_prerequisites + - role: rhel_prerequisites + when: ansible_facts['os_family'] == "RedHat" + - role: deb_prerequisites + when: ansible_facts['os_family'] == "Debian" - name: Write test configs hosts: localhost diff --git a/lima/lima-template.yaml b/lima/rocky-9-template.yaml similarity index 100% rename from lima/lima-template.yaml rename to lima/rocky-9-template.yaml diff --git a/lima/roles/deb_prerequisites/tasks/main.yaml b/lima/roles/deb_prerequisites/tasks/main.yaml new file mode 100644 index 00000000..ef22c89a --- /dev/null +++ b/lima/roles/deb_prerequisites/tasks/main.yaml @@ -0,0 +1,98 @@ +--- +- name: Update apt cache + ansible.builtin.apt: + update_cache: yes +- name: Install pgEdge repository package prerequisites + ansible.builtin.package: + name: '{{ item }}' + state: present + with_items: + - curl + - gnupg2 + - lsb-release +- name: Install pgEdge repository + ansible.builtin.apt: + deb: https://apt.pgedge.com/repodeb/pgedge-release_latest_all.deb + state: present +- name: Template control plane stack yaml + template: + src: pgedge-old.sources.tmpl + dest: /etc/apt/sources.list.d/pgedge-old.sources +- name: Update apt cache again + ansible.builtin.apt: + update_cache: yes + become: true +- name: Install control-plane prerequisites + ansible.builtin.package: + name: '{{ item }}' + state: present + with_items: + - pgedge-postgresql-18 + - pgedge-postgresql-17 + - pgedge-postgresql-16 + - pgedge-postgresql-18-spock50 + - pgedge-postgresql-17-spock50 + - pgedge-postgresql-16-spock50 + - pgedge-postgresql-18-snowflake + - pgedge-postgresql-17-snowflake + - pgedge-postgresql-16-snowflake + - pgedge-postgresql-18-lolor + - pgedge-postgresql-17-lolor + - pgedge-postgresql-16-lolor + - pgedge-patroni + - pgedge-pgbackrest + - which + - wget + - git + - chrony +- name: Disable default postgres units + ansible.builtin.systemd_service: + name: '{{ item }}' + state: stopped + enabled: false + with_items: + - postgresql.service + - postgresql@16-main.service + - postgresql@17-main.service + - postgresql@18-main.service +- name: Install etcdctl and etcdutl + ansible.builtin.unarchive: + src: "{{ etcd_download_url }}" + dest: /usr/bin + remote_src: yes + extra_opts: + - "--strip-components=1" + - "{{ etcd_archive_name }}/etcdctl" + - "{{ etcd_archive_name }}/etcdutl" + creates: /usr/bin/etcdctl +- name: Install Go + ansible.builtin.unarchive: + src: "{{ go_download_url }}" + dest: /usr/local + remote_src: yes + creates: /usr/local/go +- name: Add profile environment + ansible.builtin.copy: + content: | + export PATH=$PATH:/usr/local/go/bin + dest: /etc/profile.d/control-plane-dev.sh + mode: '0755' +- name: Allow large time jumps in chronyd + ansible.builtin.lineinfile: + path: /etc/chrony/chrony.conf + regexp: '^makestep' + line: 'makestep 1 -1' + register: chronycfg +- name: Restart chronyd + ansible.builtin.systemd_service: + name: chrony.service + state: restarted + enabled: true + when: chronycfg.changed +- name: Install delve debugger + ansible.builtin.command: /usr/local/go/bin/go install github.com/go-delve/delve/cmd/dlv@v1.25.2 + args: + creates: /root/go/bin/dlv +- name: Fix clocks + ansible.builtin.command: chronyc -a makestep + changed_when: false diff --git a/lima/roles/deb_prerequisites/templates/pgedge-old.sources.tmpl b/lima/roles/deb_prerequisites/templates/pgedge-old.sources.tmpl new file mode 100644 index 00000000..ab4764a8 --- /dev/null +++ b/lima/roles/deb_prerequisites/templates/pgedge-old.sources.tmpl @@ -0,0 +1,5 @@ +Types: deb +URIs: https://apt.pgedge.com/release_old/{{ansible_facts['distribution_release']}} +Suites: {{ansible_facts['distribution_release']}} +Components: main +Trusted: yes diff --git a/lima/roles/install_prerequisites/vars/main.yaml b/lima/roles/deb_prerequisites/vars/main.yaml similarity index 100% rename from lima/roles/install_prerequisites/vars/main.yaml rename to lima/roles/deb_prerequisites/vars/main.yaml diff --git a/lima/roles/install_prerequisites/tasks/main.yaml b/lima/roles/rhel_prerequisites/tasks/main.yaml similarity index 98% rename from lima/roles/install_prerequisites/tasks/main.yaml rename to lima/roles/rhel_prerequisites/tasks/main.yaml index c6881ee2..8ba95c5e 100644 --- a/lima/roles/install_prerequisites/tasks/main.yaml +++ b/lima/roles/rhel_prerequisites/tasks/main.yaml @@ -92,7 +92,7 @@ state: restarted when: chronycfg.changed - name: Install delve debugger - ansible.builtin.command: /usr/local/go/bin/go install github.com/go-delve/delve/cmd/dlv@latest + ansible.builtin.command: /usr/local/go/bin/go install github.com/go-delve/delve/cmd/dlv@v1.25.2 args: creates: /root/go/bin/dlv - name: Fix clocks diff --git a/lima/roles/rhel_prerequisites/vars/main.yaml b/lima/roles/rhel_prerequisites/vars/main.yaml new file mode 100644 index 00000000..95fb51d9 --- /dev/null +++ b/lima/roles/rhel_prerequisites/vars/main.yaml @@ -0,0 +1,12 @@ +--- +_arch_transform: + amd64: amd64 + x86_64: amd64 + aarch64: arm64 + arm64: arm64 +etcd_version: v3.6.5 +etcd_archive_name: etcd-{{ etcd_version }}-linux-{{ _arch_transform[ansible_facts.architecture] }} +etcd_download_url: https://github.com/etcd-io/etcd/releases/download/{{ etcd_version }}/{{ etcd_archive_name }}.tar.gz +go_version: 1.25.5 +go_archive_name: go{{ go_version }}.linux-{{ _arch_transform[ansible_facts.architecture] }}.tar.gz +go_download_url: https://dl.google.com/go/{{ go_archive_name }} diff --git a/lima/roles/start_vms/tasks/main.yaml b/lima/roles/start_vms/tasks/main.yaml index 344a96ff..fbbeb0f4 100644 --- a/lima/roles/start_vms/tasks/main.yaml +++ b/lima/roles/start_vms/tasks/main.yaml @@ -3,7 +3,7 @@ - name: Create hosts loop: '{{ machines }}' - command: limactl start --yes --name={{ item.name }} lima-template.yaml + command: limactl start --yes --name={{ item.name }} {{ os }}-template.yaml when: item.name not in lima_vms - name: Start stopped hosts diff --git a/lima/ubuntu-24.04-template.yaml b/lima/ubuntu-24.04-template.yaml new file mode 100644 index 00000000..97e21eae --- /dev/null +++ b/lima/ubuntu-24.04-template.yaml @@ -0,0 +1,16 @@ +--- +minimumLimaVersion: 1.1.0 + +base: template://ubuntu-24.04 + +cpus: 4 +memory: 8GiB +disk: 20GiB +containerd: + system: false + user: false +networks: +- lima: user-v2 +mounts: +- location: "~" + writable: true diff --git a/lima/vars.yaml b/lima/vars.yaml index 65349521..c31a0aed 100644 --- a/lima/vars.yaml +++ b/lima/vars.yaml @@ -1,4 +1,5 @@ --- +os: rocky-9 machines: - name: control-plane-dev-1 http_port: 3010 diff --git a/packaging/deb/postinstall.sh b/packaging/deb/postinstall.sh new file mode 100755 index 00000000..9a8dc537 --- /dev/null +++ b/packaging/deb/postinstall.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ "$1" = "configure" ] && [ -n "$2" ]; then + # true during a package upgrade + /bin/systemctl daemon-reload >/dev/null 2>&1 || : + /bin/systemctl try-restart pgedge-control-plane.service >/dev/null 2>&1 || : +fi diff --git a/packaging/preremove.sh b/packaging/preremove.sh new file mode 100755 index 00000000..315bf5e4 --- /dev/null +++ b/packaging/preremove.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +/bin/systemctl stop pgedge-control-plane.service >/dev/null 2>&1 || : +/bin/systemctl --no-reload disable pgedge-control-plane.service >/dev/null 2>&1 || : diff --git a/packaging/rpm/postinstall.sh b/packaging/rpm/postinstall.sh new file mode 100755 index 00000000..387814cd --- /dev/null +++ b/packaging/rpm/postinstall.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ "$1" -ge 1 ]; then + # true during a package upgrade + /bin/systemctl daemon-reload >/dev/null 2>&1 || : + /bin/systemctl try-restart pgedge-control-plane.service >/dev/null 2>&1 || : +fi diff --git a/server/internal/config/config.go b/server/internal/config/config.go index c4059ebe..86ecd18d 100644 --- a/server/internal/config/config.go +++ b/server/internal/config/config.go @@ -266,6 +266,7 @@ type Config struct { DockerSwarm DockerSwarm `koanf:"docker_swarm" json:"docker_swarm,omitzero"` SystemD SystemD `koanf:"systemd" json:"systemd,omitzero"` DatabaseOwnerUID int `koanf:"database_owner_uid" json:"database_owner_uid,omitempty"` + DatabaseOwnerGID int `koanf:"database_owner_gid" json:"database_owner_gid,omitempty"` ProfilingEnabled bool `koanf:"profiling_enabled" json:"profiling_enabled,omitempty"` RandomPorts RandomPorts `koanf:"random_ports" json:"random_ports,omitzero"` DatabasesMonitorIntervalSeconds uint64 `koanf:"databases_monitor_interval_seconds" json:"databases_monitor_interval_seconds,omitempty"` @@ -418,7 +419,6 @@ func DefaultConfig() (Config, error) { EtcdClient: etcdClientDefault, DockerSwarm: defaultDockerSwarm, SystemD: defaultSystemD, - DatabaseOwnerUID: 26, RandomPorts: defaultRandomPorts, DatabasesMonitorIntervalSeconds: 30, }, nil diff --git a/server/internal/orchestrator/common/pgbackrest_config.go b/server/internal/orchestrator/common/pgbackrest_config.go index 665ddad7..11d14159 100644 --- a/server/internal/orchestrator/common/pgbackrest_config.go +++ b/server/internal/orchestrator/common/pgbackrest_config.go @@ -97,6 +97,7 @@ func (c *PgBackRestConfig) Create(ctx context.Context, rc *resource.Context) err Repositories: c.Repositories, DatabaseID: c.DatabaseID, NodeName: c.NodeName, + InstanceID: c.InstanceID, PgDataPath: c.Paths.Instance.PgData(), HostUser: "pgedge", User: "pgedge", diff --git a/server/internal/orchestrator/swarm/orchestrator.go b/server/internal/orchestrator/swarm/orchestrator.go index eb07e007..4195880a 100644 --- a/server/internal/orchestrator/swarm/orchestrator.go +++ b/server/internal/orchestrator/swarm/orchestrator.go @@ -41,7 +41,9 @@ import ( ) const ( - OverlayDriver = "overlay" + OverlayDriver = "overlay" + DefaultDatabaseOwnerUID int = 26 + DefaultDatabaseOwnerGID int = DefaultDatabaseOwnerUID ) type Orchestrator struct { @@ -178,6 +180,7 @@ func (o *Orchestrator) instanceResources(spec *database.InstanceSpec, scripts da } instanceHostname := fmt.Sprintf("postgres-%s", spec.InstanceID) + databaseOwnerUID, databaseOwnerGID := o.databaseOwnerIDs() // If there's more than one instance from the same swarm cluster, each // instance will output this same network. They'll get deduplicated when we @@ -200,24 +203,24 @@ func (o *Orchestrator) instanceResources(spec *database.InstanceSpec, scripts da HostID: spec.HostID, ParentID: instanceDir.ID, Path: "data", - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, } configsDir := &filesystem.DirResource{ ID: spec.InstanceID + "-configs", HostID: spec.HostID, ParentID: instanceDir.ID, Path: "configs", - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, } certificatesDir := &filesystem.DirResource{ ID: spec.InstanceID + "-certificates", HostID: spec.HostID, ParentID: instanceDir.ID, Path: "certificates", - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, } // patroni resources - used to clean up etcd on deletion @@ -239,8 +242,8 @@ func (o *Orchestrator) instanceResources(spec *database.InstanceSpec, scripts da DatabaseID: spec.DatabaseID, NodeName: spec.NodeName, ParentID: certificatesDir.ID, - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, } postgresCerts := &PostgresCerts{ InstanceID: spec.InstanceID, @@ -253,8 +256,8 @@ func (o *Orchestrator) instanceResources(spec *database.InstanceSpec, scripts da }, o.cfg.Addresses(), ), - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, } patroniConfig := &PatroniConfig{ Spec: spec, @@ -263,8 +266,8 @@ func (o *Orchestrator) instanceResources(spec *database.InstanceSpec, scripts da DatabaseNetworkName: databaseNetwork.Name, BridgeNetworkInfo: o.bridgeNetwork, ParentID: configsDir.ID, - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, InstanceHostname: instanceHostname, } @@ -329,8 +332,8 @@ func (o *Orchestrator) instanceResources(spec *database.InstanceSpec, scripts da Repositories: spec.BackupConfig.Repositories, ParentID: configsDir.ID, Type: PgBackRestConfigTypeBackup, - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, }, ) nodeDependents = append(nodeDependents, @@ -362,8 +365,8 @@ func (o *Orchestrator) instanceResources(spec *database.InstanceSpec, scripts da Repositories: []*pgbackrest.Repository{spec.RestoreConfig.Repository}, ParentID: configsDir.ID, Type: PgBackRestConfigTypeRestore, - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, }) } @@ -1177,6 +1180,18 @@ func (o *Orchestrator) validateNetworks(ctx context.Context, nodeName string, ne return err } +func (o *Orchestrator) databaseOwnerIDs() (int, int) { + uid := DefaultDatabaseOwnerUID + gid := DefaultDatabaseOwnerGID + if o.cfg.DatabaseOwnerUID > 0 { + uid = o.cfg.DatabaseOwnerUID + } + if o.cfg.DatabaseOwnerGID > 0 { + gid = o.cfg.DatabaseOwnerGID + } + return uid, gid +} + func validationContainerOpts( image string, cmd []string, diff --git a/server/internal/orchestrator/swarm/pgbackrest_config.go b/server/internal/orchestrator/swarm/pgbackrest_config.go index 72516b8a..69fb4aa0 100644 --- a/server/internal/orchestrator/swarm/pgbackrest_config.go +++ b/server/internal/orchestrator/swarm/pgbackrest_config.go @@ -105,6 +105,7 @@ func (c *PgBackRestConfig) Create(ctx context.Context, rc *resource.Context) err Repositories: c.Repositories, DatabaseID: c.DatabaseID, NodeName: c.NodeName, + InstanceID: c.InstanceID, PgDataPath: "/opt/pgedge/data/pgdata", HostUser: "pgedge", User: "pgedge", diff --git a/server/internal/orchestrator/systemd/apt.go b/server/internal/orchestrator/systemd/apt.go new file mode 100644 index 00000000..439e85e5 --- /dev/null +++ b/server/internal/orchestrator/systemd/apt.go @@ -0,0 +1,63 @@ +package systemd + +import ( + "context" + "fmt" + "path/filepath" + "strings" + "time" +) + +var _ PackageManager = (*Apt)(nil) + +type Apt struct { + ExecCommand ExecCommand +} + +func (d *Apt) InstanceDataBaseDir(pgMajor string) string { + return filepath.Join("/var/lib/postgresql", pgMajor) +} + +func (d *Apt) BinDir(pgMajor string) string { + return filepath.Join("/usr/lib/postgresql", pgMajor, "bin") +} + +func (d *Apt) InstalledPostgresVersions(ctx context.Context) ([]*InstalledPostgres, error) { + execCmd := d.ExecCommand + if execCmd == nil { + execCmd = DefaultExecCommand + } + + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + var stdout, stderr strings.Builder + err := execCmd(ctx, &stdout, &stderr, "dpkg-query", "-f", "${binary:Package} ${Version}\n", "-W") + if err != nil { + return nil, fmt.Errorf("failed to query installed packages: %w, stderr: %s", err, stderr.String()) + } + + return processPackageList( + aptPostgresPackageNames, + aptSpockPackageNames, + stdout.String(), + ) +} + +var aptPostgresPackageNames, aptSpockPackageNames = aptPackageNames() + +func aptPackageNames() (map[string]string, map[string]string) { + postgresPackageNames := make(map[string]string, len(supportedPostgresVersions)) + spockPackageNames := make(map[string]string, len(supportedPostgresVersions)*len(supportedSpockVersions)) + + for _, postgres := range supportedPostgresVersions { + postgresPackageName := fmt.Sprintf("pgedge-postgresql-%s", postgres) + postgresPackageNames[postgresPackageName] = postgres + for _, spock := range supportedSpockVersions { + spockPackageName := fmt.Sprintf("pgedge-postgresql-%s-spock%s", postgres, spock) + spockPackageNames[spockPackageName] = postgres + } + } + + return postgresPackageNames, spockPackageNames +} diff --git a/server/internal/orchestrator/systemd/apt_test.go b/server/internal/orchestrator/systemd/apt_test.go new file mode 100644 index 00000000..830a29c0 --- /dev/null +++ b/server/internal/orchestrator/systemd/apt_test.go @@ -0,0 +1,784 @@ +package systemd_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/pgEdge/control-plane/server/internal/ds" + "github.com/pgEdge/control-plane/server/internal/orchestrator/systemd" +) + +func TestApt(t *testing.T) { + t.Run("installed packages successful", func(t *testing.T) { + apt := systemd.Apt{ + ExecCommand: systemd.MockExecCommand(t, testAptPackageList, "", nil), + } + expected := []*systemd.InstalledPostgres{ + { + Postgres: &systemd.InstalledPackage{ + PostgresMajor: "16", + Version: ds.MustParseVersion("16.13"), + Name: "pgedge-postgresql-16", + }, + Spock: []*systemd.InstalledPackage{ + { + PostgresMajor: "16", + Version: ds.MustParseVersion("5.0.7"), + Name: "pgedge-postgresql-16-spock50", + }, + }, + }, + { + Postgres: &systemd.InstalledPackage{ + PostgresMajor: "17", + Version: ds.MustParseVersion("17.9"), + Name: "pgedge-postgresql-17", + }, + Spock: []*systemd.InstalledPackage{ + { + PostgresMajor: "17", + Version: ds.MustParseVersion("5.0.7"), + Name: "pgedge-postgresql-17-spock50", + }, + }, + }, + { + Postgres: &systemd.InstalledPackage{ + PostgresMajor: "18", + Version: ds.MustParseVersion("18.3"), + Name: "pgedge-postgresql-18", + }, + Spock: []*systemd.InstalledPackage{ + { + PostgresMajor: "18", + Version: ds.MustParseVersion("5.0.7"), + Name: "pgedge-postgresql-18-spock50", + }, + }, + }, + } + installed, err := apt.InstalledPostgresVersions(t.Context()) + require.NoError(t, err) + require.Equal(t, expected, installed) + }) +} + +const testAptPackageList = `adduser 3.137ubuntu1 +apparmor 4.0.1really4.0.1-0ubuntu0.24.04.5 +apport 2.28.1-0ubuntu3.8 +apport-core-dump-handler 2.28.1-0ubuntu3.8 +apport-symptoms 0.25 +appstream 1.0.2-1build6 +apt 2.8.3 +apt-utils 2.8.3 +base-files 13ubuntu10.4 +base-passwd 3.6.3build1 +bash 5.2.21-2ubuntu4 +bash-completion 1:2.11-8 +bc 1.07.1-3ubuntu4 +bcache-tools 1.0.8-5build1 +bind9-dnsutils 1:9.18.39-0ubuntu0.24.04.3 +bind9-host 1:9.18.39-0ubuntu0.24.04.3 +bind9-libs:arm64 1:9.18.39-0ubuntu0.24.04.3 +bolt 0.9.7-1 +bpfcc-tools 0.29.1+ds-1ubuntu7 +bpftrace 0.20.2-1ubuntu4.3 +bsdextrautils 2.39.3-9ubuntu6.5 +bsdutils 1:2.39.3-9ubuntu6.5 +btrfs-progs 6.6.3-1.1build2 +busybox-initramfs 1:1.36.1-6ubuntu3.1 +busybox-static 1:1.36.1-6ubuntu3.1 +byobu 6.11-0ubuntu1 +ca-certificates 20240203 +chrony 4.5-1ubuntu4.2 +cloud-guest-utils 0.33-1 +cloud-init 25.3-0ubuntu1~24.04.1 +cloud-initramfs-copymods 0.49~24.04.1 +cloud-initramfs-dyn-netconf 0.49~24.04.1 +command-not-found 23.04.0 +console-setup 1.226ubuntu1 +console-setup-linux 1.226ubuntu1 +coreutils 9.4-3ubuntu6.2 +cpio 2.15+dfsg-1ubuntu2 +cron 3.0pl1-184ubuntu2 +cron-daemon-common 3.0pl1-184ubuntu2 +cryptsetup 2:2.7.0-1ubuntu4.2 +cryptsetup-bin 2:2.7.0-1ubuntu4.2 +cryptsetup-initramfs 2:2.7.0-1ubuntu4.2 +curl 8.5.0-2ubuntu10.9 +dash 0.5.12-6ubuntu5 +dbus 1.14.10-4ubuntu4.1 +dbus-bin 1.14.10-4ubuntu4.1 +dbus-daemon 1.14.10-4ubuntu4.1 +dbus-session-bus-common 1.14.10-4ubuntu4.1 +dbus-system-bus-common 1.14.10-4ubuntu4.1 +dbus-user-session 1.14.10-4ubuntu4.1 +debconf 1.5.86ubuntu1 +debconf-i18n 1.5.86ubuntu1 +debianutils 5.17build1 +device-tree-compiler 1.7.0-2build1 +devio 1.2-2build1 +dhcpcd-base 1:10.0.6-1ubuntu3.2 +diffutils 1:3.10-1build1 +dirmngr 2.4.4-2ubuntu17.4 +distro-info 1.7build1 +distro-info-data 0.60ubuntu0.5 +dmeventd 2:1.02.185-3ubuntu3.2 +dmidecode 3.5-3ubuntu0.1 +dmsetup 2:1.02.185-3ubuntu3.2 +dosfstools 4.2-1.1build1 +dpkg 1.22.6ubuntu6.5 +dracut-install 060+5-1ubuntu3.3 +e2fsprogs 1.47.0-2.4~exp1ubuntu4.1 +e2fsprogs-l10n 1.47.0-2.4~exp1ubuntu4.1 +eatmydata 131-1ubuntu1 +ed 1.20.1-1 +efibootmgr 18-1build2 +eject 2.39.3-9ubuntu6.5 +ethtool 1:6.7-1build1 +fdisk 2.39.3-9ubuntu6.5 +file 1:5.45-3build1 +finalrd 9build1 +findutils 4.9.0-5build1 +flash-kernel 3.107ubuntu13~24.04.6 +fontconfig-config 2.15.0-1.1ubuntu2 +fonts-dejavu-core 2.37-8 +fonts-dejavu-mono 2.37-8 +fonts-ubuntu-console 0.869+git20240321-0ubuntu1 +friendly-recovery 0.2.42 +ftp 20230507-2build3 +fuse3 3.14.0-5build1 +fwupd 1.9.33-0ubuntu1~24.04.1ubuntu1 +fwupd-signed 1.52+1.4-1 +gawk 1:5.2.1-2build3 +gcc-14-base:arm64 14.2.0-4ubuntu2~24.04.1 +gdisk 1.0.10-1build1 +gettext-base 0.21-14ubuntu2 +gir1.2-girepository-2.0:arm64 1.80.1-1 +gir1.2-glib-2.0:arm64 2.80.0-6ubuntu3.8 +gir1.2-packagekitglib-1.0 1.2.8-2ubuntu1.5 +git 1:2.43.0-1ubuntu7.3 +git-man 1:2.43.0-1ubuntu7.3 +gnu-which 2.21+dfsg-4build1 +gnupg 2.4.4-2ubuntu17.4 +gnupg-l10n 2.4.4-2ubuntu17.4 +gnupg-utils 2.4.4-2ubuntu17.4 +gnupg2 2.4.4-2ubuntu17.4 +gpg 2.4.4-2ubuntu17.4 +gpg-agent 2.4.4-2ubuntu17.4 +gpg-wks-client 2.4.4-2ubuntu17.4 +gpgconf 2.4.4-2ubuntu17.4 +gpgsm 2.4.4-2ubuntu17.4 +gpgv 2.4.4-2ubuntu17.4 +grep 3.11-4build1 +groff-base 1.23.0-3build2 +grub-common 2.12-1ubuntu7.3 +grub-efi-arm64 2.12-1ubuntu7.3 +grub-efi-arm64-bin 2.12-1ubuntu7.3 +grub-efi-arm64-signed 1.202.5+2.12-1ubuntu7.3 +grub2-common 2.12-1ubuntu7.3 +guile-3.0-libs:arm64 3.0.9-1build2 +gzip 1.12-1ubuntu3.1 +hdparm 9.65+ds-1build1 +hostname 3.23+nmu2ubuntu2 +htop 3.3.0-4build1 +hwdata 0.379-1 +ibverbs-providers:arm64 50.0-2ubuntu0.2 +ieee-data 20220827.1 +inetutils-telnet 2:2.5-3ubuntu4.1 +info 7.1-3build2 +init 1.66ubuntu1 +init-system-helpers 1.66ubuntu1 +initramfs-tools 0.142ubuntu25.8 +initramfs-tools-bin 0.142ubuntu25.8 +initramfs-tools-core 0.142ubuntu25.8 +install-info 7.1-3build2 +iproute2 6.1.0-1ubuntu6.2 +iptables 1.8.10-3ubuntu2 +iputils-ping 3:20240117-1ubuntu0.1 +iputils-tracepath 3:20240117-1ubuntu0.1 +iso-codes 4.16.0-1 +jq 1.7.1-3ubuntu0.24.04.2 +kbd 2.6.4-2ubuntu2 +keyboard-configuration 1.226ubuntu1 +keyboxd 2.4.4-2ubuntu17.4 +klibc-utils 2.0.13-4ubuntu0.2 +kmod 31+20240202-2ubuntu7.2 +kpartx 0.9.4-5ubuntu8.1 +krb5-locales 1.20.1-6ubuntu2.6 +landscape-common 24.02-0ubuntu5.7 +less 590-2ubuntu2.1 +libacl1:arm64 2.3.2-1build1.1 +libaio1t64:arm64 0.3.113-6build1.1 +libaom3:arm64 3.8.2-2ubuntu0.1 +libapparmor1:arm64 4.0.1really4.0.1-0ubuntu0.24.04.5 +libappstream5:arm64 1.0.2-1build6 +libapt-pkg6.0t64:arm64 2.8.3 +libarchive13t64:arm64 3.7.2-2ubuntu0.6 +libargon2-1:arm64 0~20190702+dfsg-4build1 +libassuan0:arm64 2.5.6-1build1 +libatasmart4:arm64 0.19-5build3 +libatm1t64:arm64 1:2.5.1-5.1build1 +libattr1:arm64 1:2.5.2-1build1.1 +libaudit-common 1:3.1.2-2.1build1.1 +libaudit1:arm64 1:3.1.2-2.1build1.1 +libblkid1:arm64 2.39.3-9ubuntu6.5 +libblockdev-crypto3:arm64 3.1.1-1ubuntu0.1 +libblockdev-fs3:arm64 3.1.1-1ubuntu0.1 +libblockdev-loop3:arm64 3.1.1-1ubuntu0.1 +libblockdev-mdraid3:arm64 3.1.1-1ubuntu0.1 +libblockdev-nvme3:arm64 3.1.1-1ubuntu0.1 +libblockdev-part3:arm64 3.1.1-1ubuntu0.1 +libblockdev-swap3:arm64 3.1.1-1ubuntu0.1 +libblockdev-utils3:arm64 3.1.1-1ubuntu0.1 +libblockdev3:arm64 3.1.1-1ubuntu0.1 +libbpf1:arm64 1:1.3.0-2build2 +libbpfcc:arm64 0.29.1+ds-1ubuntu7 +libbrotli1:arm64 1.1.0-2build2 +libbsd0:arm64 0.12.1-1build1.1 +libbytesize-common 2.10-1ubuntu2 +libbytesize1:arm64 2.10-1ubuntu2 +libbz2-1.0:arm64 1.0.8-5.1build0.1 +libc-bin 2.39-0ubuntu8.7 +libc-dev-bin 2.39-0ubuntu8.7 +libc-devtools 2.39-0ubuntu8.7 +libc6:arm64 2.39-0ubuntu8.7 +libc6-dev:arm64 2.39-0ubuntu8.7 +libcap-ng0:arm64 0.8.4-2build2 +libcap2:arm64 1:2.66-5ubuntu2.4 +libcap2-bin 1:2.66-5ubuntu2.4 +libcbor0.10:arm64 0.10.2-1.2ubuntu2 +libclang-cpp18 1:18.1.3-1ubuntu1 +libclang1-18 1:18.1.3-1ubuntu1 +libcom-err2:arm64 1.47.0-2.4~exp1ubuntu4.1 +libcommon-sense-perl:arm64 3.75-3build3 +libcrypt-dev:arm64 1:4.4.36-4build1 +libcrypt1:arm64 1:4.4.36-4build1 +libcryptsetup12:arm64 2:2.7.0-1ubuntu4.2 +libcurl3t64-gnutls:arm64 8.5.0-2ubuntu10.9 +libcurl4t64:arm64 8.5.0-2ubuntu10.9 +libdb5.3t64:arm64 5.3.28+dfsg2-7 +libdbus-1-3:arm64 1.14.10-4ubuntu4.1 +libde265-0:arm64 1.0.15-1build3 +libdebconfclient0:arm64 0.271ubuntu3 +libdeflate0:arm64 1.19-1build1.1 +libdevmapper-event1.02.1:arm64 2:1.02.185-3ubuntu3.2 +libdevmapper1.02.1:arm64 2:1.02.185-3ubuntu3.2 +libdrm-common 2.4.125-1ubuntu0.1~24.04.1 +libdrm2:arm64 2.4.125-1ubuntu0.1~24.04.1 +libduktape207:arm64 2.7.0+tests-0ubuntu3 +libdw1t64:arm64 0.190-1.1ubuntu0.1 +libeatmydata1:arm64 131-1ubuntu1 +libedit2:arm64 3.1-20230828-1build1 +libefiboot1t64:arm64 38-3.1build1 +libefivar1t64:arm64 38-3.1build1 +libelf1t64:arm64 0.190-1.1ubuntu0.1 +liberror-perl 0.17029-2 +libestr0:arm64 0.1.11-1build1 +libevdev2:arm64 1.13.1+dfsg-1build1 +libevent-core-2.1-7t64:arm64 2.1.12-stable-9ubuntu2 +libexpat1:arm64 2.6.1-2ubuntu0.4 +libext2fs2t64:arm64 1.47.0-2.4~exp1ubuntu4.1 +libfastjson4:arm64 1.2304.0-1build1 +libfdisk1:arm64 2.39.3-9ubuntu6.5 +libfdt1:arm64 1.7.0-2build1 +libffi8:arm64 3.4.6-1build1 +libfido2-1:arm64 1.14.0-1build3 +libflashrom1:arm64 1.3.0-2.1ubuntu2 +libfontconfig1:arm64 2.15.0-1.1ubuntu2 +libfreetype6:arm64 2.13.2+dfsg-1ubuntu0.1 +libfribidi0:arm64 1.0.13-3build1 +libftdi1-2:arm64 1.5-6build5 +libfuse3-3:arm64 3.14.0-5build1 +libfwupd2:arm64 1.9.33-0ubuntu1~24.04.1ubuntu1 +libgc1:arm64 1:8.2.6-1build1 +libgcc-s1:arm64 14.2.0-4ubuntu2~24.04.1 +libgcrypt20:arm64 1.10.3-2build1 +libgd3:arm64 2.3.3-9ubuntu5 +libgdbm-compat4t64:arm64 1.23-5.1build1 +libgdbm6t64:arm64 1.23-5.1build1 +libgirepository-1.0-1:arm64 1.80.1-1 +libglib2.0-0t64:arm64 2.80.0-6ubuntu3.8 +libglib2.0-bin 2.80.0-6ubuntu3.8 +libglib2.0-data 2.80.0-6ubuntu3.8 +libgmp10:arm64 2:6.3.0+dfsg-2ubuntu6.1 +libgnutls30t64:arm64 3.8.3-1.1ubuntu3.5 +libgpg-error-l10n 1.47-3build2.1 +libgpg-error0:arm64 1.47-3build2.1 +libgpgme11t64:arm64 1.18.0-4.1ubuntu4 +libgpm2:arm64 1.20.7-11 +libgssapi-krb5-2:arm64 1.20.1-6ubuntu2.6 +libgstreamer1.0-0:arm64 1.24.2-1ubuntu0.1 +libgudev-1.0-0:arm64 1:238-5ubuntu1 +libgusb2:arm64 0.4.8-1build2 +libheif-plugin-aomdec:arm64 1.17.6-1ubuntu4.2 +libheif-plugin-aomenc:arm64 1.17.6-1ubuntu4.2 +libheif-plugin-libde265:arm64 1.17.6-1ubuntu4.2 +libheif1:arm64 1.17.6-1ubuntu4.2 +libhogweed6t64:arm64 3.9.1-2.2build1.1 +libibverbs1:arm64 50.0-2ubuntu0.2 +libicu74:arm64 74.2-1ubuntu3.1 +libidn2-0:arm64 2.3.7-2build1.1 +libinih1:arm64 55-1ubuntu2 +libiniparser1:arm64 4.1-7ubuntu0.1 +libintl-perl 1.33-1build3 +libintl-xs-perl 1.33-1build3 +libio-pty-perl 1:1.20-1build2 +libip4tc2:arm64 1.8.10-3ubuntu2 +libip6tc2:arm64 1.8.10-3ubuntu2 +libipc-run-perl 20231003.0-1 +libisns0t64:arm64 0.101-0.3build3 +libjansson4:arm64 2.14-2build2 +libjbig0:arm64 2.1-6.1ubuntu2 +libjcat1:arm64 0.2.0-2build3 +libjpeg-turbo8:arm64 2.1.5-2ubuntu2 +libjpeg8:arm64 8c-2ubuntu11 +libjq1:arm64 1.7.1-3ubuntu0.24.04.2 +libjson-c5:arm64 0.17-1build1 +libjson-glib-1.0-0:arm64 1.8.0-2build2 +libjson-glib-1.0-common 1.8.0-2build2 +libjson-perl 4.10000-1 +libjson-xs-perl 4.040-0ubuntu0.24.04.1 +libk5crypto3:arm64 1.20.1-6ubuntu2.6 +libkeyutils1:arm64 1.6.3-3build1 +libklibc:arm64 2.0.13-4ubuntu0.2 +libkmod2:arm64 31+20240202-2ubuntu7.2 +libkrb5-3:arm64 1.20.1-6ubuntu2.6 +libkrb5support0:arm64 1.20.1-6ubuntu2.6 +libksba8:arm64 1.6.6-1build1 +libldap-common 2.6.10+dfsg-0ubuntu0.24.04.1 +libldap2:arm64 2.6.10+dfsg-0ubuntu0.24.04.1 +liblerc4:arm64 4.0.0+ds-4ubuntu2 +libllvm18:arm64 1:18.1.3-1ubuntu1 +liblmdb0:arm64 0.9.31-1build1 +liblocale-gettext-perl 1.07-6ubuntu5 +liblvm2cmd2.03:arm64 2.03.16-3ubuntu3.2 +liblz4-1:arm64 1.9.4-1build1.1 +liblzma5:arm64 5.6.1+really5.4.5-1ubuntu0.2 +liblzo2-2:arm64 2.10-2build4 +libmagic-mgc 1:5.45-3build1 +libmagic1t64:arm64 1:5.45-3build1 +libmaxminddb0:arm64 1.9.1-1build1 +libmbim-glib4:arm64 1.31.2-0ubuntu3.1 +libmbim-proxy 1.31.2-0ubuntu3.1 +libmbim-utils 1.31.2-0ubuntu3.1 +libmd0:arm64 1.1.0-2build1.1 +libmm-glib0:arm64 1.23.4-0ubuntu2 +libmnl0:arm64 1.0.5-2build1 +libmodule-find-perl 0.16-2 +libmodule-scandeps-perl 1.35-1ubuntu0.24.04.1 +libmount1:arm64 2.39.3-9ubuntu6.5 +libmpfr6:arm64 4.2.1-1build1.1 +libmspack0t64:arm64 0.11-1.1build1 +libncurses6:arm64 6.4+20240113-1ubuntu2 +libncursesw6:arm64 6.4+20240113-1ubuntu2 +libnetfilter-conntrack3:arm64 1.0.9-6build1 +libnetplan1:arm64 1.1.2-8ubuntu1~24.04.1 +libnettle8t64:arm64 3.9.1-2.2build1.1 +libnewt0.52:arm64 0.52.24-2ubuntu2 +libnfnetlink0:arm64 1.0.2-2build1 +libnftables1:arm64 1.0.9-1ubuntu0.1 +libnftnl11:arm64 1.2.6-2build1 +libnghttp2-14:arm64 1.59.0-1ubuntu0.3 +libnl-3-200:arm64 3.7.0-0.3build1.1 +libnl-genl-3-200:arm64 3.7.0-0.3build1.1 +libnl-route-3-200:arm64 3.7.0-0.3build1.1 +libnpth0t64:arm64 1.6-3.1build1 +libnspr4:arm64 2:4.35-1.1build1 +libnss-systemd:arm64 255.4-1ubuntu8.14 +libnss3:arm64 2:3.98-1ubuntu0.1 +libntfs-3g89t64:arm64 1:2022.10.3-1.2ubuntu3.1 +libnuma1:arm64 2.0.18-1ubuntu0.24.04.1 +libnvme1t64 1.8-3ubuntu1 +libonig5:arm64 6.9.9-1build1 +libopeniscsiusr 2.1.9-3ubuntu5.4 +libp11-kit0:arm64 0.25.3-4ubuntu2.1 +libpackagekit-glib2-18:arm64 1.2.8-2ubuntu1.5 +libpam-cap:arm64 1:2.66-5ubuntu2.4 +libpam-modules:arm64 1.5.3-5ubuntu5.5 +libpam-modules-bin 1.5.3-5ubuntu5.5 +libpam-runtime 1.5.3-5ubuntu5.5 +libpam-systemd:arm64 255.4-1ubuntu8.14 +libpam0g:arm64 1.5.3-5ubuntu5.5 +libparted2t64:arm64 3.6-4build1 +libpcap0.8t64:arm64 1.10.4-4.1ubuntu3 +libpci3:arm64 1:3.10.0-2build1 +libpcre2-8-0:arm64 10.42-4ubuntu2.1 +libperl5.38t64:arm64 5.38.2-3.2ubuntu0.2 +libpipeline1:arm64 1.5.7-2 +libplymouth5:arm64 24.004.60-1ubuntu7.1 +libpng16-16t64:arm64 1.6.43-5ubuntu0.5 +libpolkit-agent-1-0:arm64 124-2ubuntu1.24.04.3 +libpolkit-gobject-1-0:arm64 124-2ubuntu1.24.04.3 +libpopt0:arm64 1.19+dfsg-1build1 +libpq5:arm64 18.3-1.noble +libproc-processtable-perl:arm64 0.636-1build3 +libproc2-0:arm64 2:4.0.4-4ubuntu3.2 +libprotobuf-c1:arm64 1.4.1-1ubuntu4 +libpsl5t64:arm64 0.21.2-1.1build1 +libpython3-stdlib:arm64 3.12.3-0ubuntu2.1 +libpython3.12-minimal:arm64 3.12.3-1ubuntu0.13 +libpython3.12-stdlib:arm64 3.12.3-1ubuntu0.13 +libpython3.12t64:arm64 3.12.3-1ubuntu0.13 +libqmi-glib5:arm64 1.35.2-0ubuntu2 +libqmi-proxy 1.35.2-0ubuntu2 +libqmi-utils 1.35.2-0ubuntu2 +libqrtr-glib0:arm64 1.2.2-1ubuntu4 +libreadline8t64:arm64 8.2-4build1 +libreiserfscore0t64 1:3.6.27-7.1build1 +librtmp1:arm64 2.4+20151223.gitfa8646d.1-2build7 +libsasl2-2:arm64 2.1.28+dfsg1-5ubuntu3.1 +libsasl2-modules:arm64 2.1.28+dfsg1-5ubuntu3.1 +libsasl2-modules-db:arm64 2.1.28+dfsg1-5ubuntu3.1 +libseccomp2:arm64 2.5.5-1ubuntu3.1 +libselinux1:arm64 3.5-2ubuntu2.1 +libsemanage-common 3.5-1build5 +libsemanage2:arm64 3.5-1build5 +libsensors-config 1:3.6.0-9build1 +libsensors5:arm64 1:3.6.0-9build1 +libsepol2:arm64 3.5-2build1 +libsgutils2-1.46-2:arm64 1.46-3ubuntu4 +libsharpyuv0:arm64 1.3.2-0.4build3 +libsigsegv2:arm64 2.14-1ubuntu2 +libslang2:arm64 2.3.3-3build2 +libsmartcols1:arm64 2.39.3-9ubuntu6.5 +libsodium23:arm64 1.0.18-1ubuntu0.24.04.1 +libsort-naturally-perl 1.03-4 +libsqlite3-0:arm64 3.45.1-1ubuntu2.5 +libss2:arm64 1.47.0-2.4~exp1ubuntu4.1 +libssh-4:arm64 0.10.6-2ubuntu0.4 +libssh2-1t64:arm64 1.11.0-4.1build2 +libssl3t64:arm64 3.0.13-0ubuntu3.9 +libstdc++6:arm64 14.2.0-4ubuntu2~24.04.1 +libstemmer0d:arm64 2.2.0-4build1 +libsystemd-shared:arm64 255.4-1ubuntu8.14 +libsystemd0:arm64 255.4-1ubuntu8.14 +libtasn1-6:arm64 4.19.0-3ubuntu0.24.04.2 +libtcl8.6:arm64 8.6.14+dfsg-1build1 +libterm-readkey-perl 2.38-2build4 +libtext-charwidth-perl:arm64 0.04-11build3 +libtext-iconv-perl:arm64 1.7-8build3 +libtext-wrapi18n-perl 0.06-10 +libtiff6:arm64 4.5.1+git230720-4ubuntu2.5 +libtinfo6:arm64 6.4+20240113-1ubuntu2 +libtirpc-common 1.3.4+ds-1.1build1 +libtirpc3t64:arm64 1.3.4+ds-1.1build1 +libtraceevent1:arm64 1:1.8.2-1ubuntu2.1 +libtraceevent1-plugin:arm64 1:1.8.2-1ubuntu2.1 +libtracefs1:arm64 1.8.0-1ubuntu1 +libtypes-serialiser-perl 1.01-1 +libubootenv-tool 0.3.5-0.1build1 +libubootenv0.1:arm64 0.3.5-0.1build1 +libuchardet0:arm64 0.0.8-1build1 +libudev1:arm64 255.4-1ubuntu8.14 +libudisks2-0:arm64 2.10.1-6ubuntu1.3 +libunistring5:arm64 1.1-2build1.1 +libunwind8:arm64 1.6.2-3build1.1 +liburcu8t64:arm64 0.14.0-3.1build1 +liburing2:arm64 2.5-1build1 +libusb-1.0-0:arm64 2:1.0.27-1 +libutempter0:arm64 1.2.1-3build1 +libuuid1:arm64 2.39.3-9ubuntu6.5 +libuv1t64:arm64 1.48.0-1.1build1 +libvolume-key1:arm64 0.3.12-7build2 +libwebp7:arm64 1.3.2-0.4build3 +libwrap0:arm64 7.6.q-33 +libx11-6:arm64 2:1.8.7-1build1 +libx11-data 2:1.8.7-1build1 +libxau6:arm64 1:1.0.9-1build6 +libxcb1:arm64 1.15-1ubuntu2 +libxdmcp6:arm64 1:1.1.3-0ubuntu6 +libxext6:arm64 2:1.3.4-1build2 +libxkbcommon0:arm64 1.6.0-1build1 +libxml2:arm64 2.9.14+dfsg-1.3ubuntu3.7 +libxmlb2:arm64 0.3.18-1 +libxmlsec1t64:arm64 1.2.39-5build2 +libxmlsec1t64-openssl:arm64 1.2.39-5build2 +libxmuu1:arm64 2:1.1.3-3build2 +libxpm4:arm64 1:3.5.17-1build2 +libxslt1.1:arm64 1.1.39-0exp1ubuntu0.24.04.3 +libxtables12:arm64 1.8.10-3ubuntu2 +libxxhash0:arm64 0.8.2-2build1 +libyaml-0-2:arm64 0.2.5-1build1 +libzstd1:arm64 1.5.5+dfsg2-2build1.1 +linux-base 4.5ubuntu9+24.04.2 +linux-headers-6.8.0-106 6.8.0-106.106 +linux-headers-6.8.0-106-generic 6.8.0-106.106 +linux-headers-6.8.0-111 6.8.0-111.111 +linux-headers-6.8.0-111-generic 6.8.0-111.111 +linux-headers-generic 6.8.0-111.111 +linux-headers-virtual 6.8.0-111.111 +linux-image-6.8.0-106-generic 6.8.0-106.106 +linux-image-6.8.0-111-generic 6.8.0-111.111 +linux-image-virtual 6.8.0-111.111 +linux-libc-dev:arm64 6.8.0-111.111 +linux-modules-6.8.0-106-generic 6.8.0-106.106 +linux-modules-6.8.0-111-generic 6.8.0-111.111 +linux-tools-6.8.0-106 6.8.0-106.106 +linux-tools-6.8.0-106-generic 6.8.0-106.106 +linux-tools-6.8.0-111 6.8.0-111.111 +linux-tools-6.8.0-111-generic 6.8.0-111.111 +linux-tools-common 6.8.0-111.111 +linux-virtual 6.8.0-111.111 +locales 2.39-0ubuntu8.7 +login 1:4.13+dfsg1-4ubuntu3.2 +logrotate 3.21.0-2build1 +logsave 1.47.0-2.4~exp1ubuntu4.1 +lsb-release 12.0-2 +lshw 02.19.git.2021.06.19.996aaad9c7-2build3 +lsof 4.95.0-1build3 +lvm2 2.03.16-3ubuntu3.2 +lxd-agent-loader 0.7ubuntu0.1 +lxd-installer 4ubuntu0.1 +make-guile 4.3-4.1build2 +man-db 2.12.0-4build2 +manpages 6.7-2 +manpages-dev 6.7-2 +mawk 1.3.4.20240123-1build1 +mdadm 4.3-1ubuntu2.1 +media-types 10.1.0 +modemmanager 1.23.4-0ubuntu2 +mokutil 0.6.0-2build3 +motd-news-config 13ubuntu10.4 +mount 2.39.3-9ubuntu6.5 +mtd-utils 1:2.2.0-1ubuntu2 +mtr-tiny 0.95-1.1ubuntu0.1 +multipath-tools 0.9.4-5ubuntu8.1 +nano 7.2-2ubuntu0.1 +ncurses-base 6.4+20240113-1ubuntu2 +ncurses-bin 6.4+20240113-1ubuntu2 +ncurses-term 6.4+20240113-1ubuntu2 +needrestart 3.6-7ubuntu4.5 +netbase 6.4 +netcat-openbsd 1.226-1ubuntu2 +netplan-generator 1.1.2-8ubuntu1~24.04.1 +netplan.io 1.1.2-8ubuntu1~24.04.1 +networkd-dispatcher 2.2.4-1 +nftables 1.0.9-1ubuntu0.1 +ntfs-3g 1:2022.10.3-1.2ubuntu3.1 +numactl 2.0.18-1ubuntu0.24.04.1 +open-iscsi 2.1.9-3ubuntu5.4 +open-vm-tools 2:12.5.0-1~ubuntu0.24.04.2 +openssh-client 1:9.6p1-3ubuntu13.16 +openssh-server 1:9.6p1-3ubuntu13.16 +openssh-sftp-server 1:9.6p1-3ubuntu13.16 +openssl 3.0.13-0ubuntu3.9 +os-prober 1.81ubuntu4 +overlayroot 0.49~24.04.1 +packagekit 1.2.8-2ubuntu1.5 +packagekit-tools 1.2.8-2ubuntu1.5 +parted 3.6-4build1 +passwd 1:4.13+dfsg1-4ubuntu3.2 +pastebinit 1.6.2-1 +patch 2.7.6-7build3 +pci.ids 0.0~2024.03.31-1ubuntu0.1 +pciutils 1:3.10.0-2build1 +perl 5.38.2-3.2ubuntu0.2 +perl-base 5.38.2-3.2ubuntu0.2 +perl-modules-5.38 5.38.2-3.2ubuntu0.2 +pgedge-patroni 4.1.0-1.noble +pgedge-pgbackrest 2.58.0-1.noble +pgedge-postgresql-16 16.13-1.noble +pgedge-postgresql-16-lolor 1.2.2-1.noble +pgedge-postgresql-16-snowflake 2.4-1.noble +pgedge-postgresql-16-spock50 5.0.7-1.noble +pgedge-postgresql-17 17.9-1.noble +pgedge-postgresql-17-lolor 1.2.2-1.noble +pgedge-postgresql-17-snowflake 2.4-1.noble +pgedge-postgresql-17-spock50 5.0.7-1.noble +pgedge-postgresql-18 18.3-1.noble +pgedge-postgresql-18-jit 18.3-1.noble +pgedge-postgresql-18-lolor 1.2.2-1.noble +pgedge-postgresql-18-snowflake 2.4-1.noble +pgedge-postgresql-18-spock50 5.0.7-1.noble +pgedge-postgresql-client-16 16.13-1.noble +pgedge-postgresql-client-17 17.9-1.noble +pgedge-postgresql-client-18 18.3-1.noble +pgedge-postgresql-client-common 284-1.noble +pgedge-postgresql-common 284-1.noble +pgedge-postgresql-common-dev 284-1.noble +pgedge-python3-etcd 0.4.5-1.noble +pgedge-python3-psycopg2 2.9.10-1.noble +pgedge-python3-ydiff 1.4.2-1.noble +pgedge-release 1.0-2 +pinentry-curses 1.2.1-3ubuntu5 +plymouth 24.004.60-1ubuntu7.1 +plymouth-theme-ubuntu-text 24.004.60-1ubuntu7.1 +polkitd 124-2ubuntu1.24.04.3 +pollinate 4.33-3.1ubuntu1.3 +powermgmt-base 1.37ubuntu0.1 +procps 2:4.0.4-4ubuntu3.2 +psmisc 23.7-1build1 +publicsuffix 20231001.0357-0.1 +python-apt-common 2.7.7ubuntu5.2 +python-babel-localedata 2.10.3-3build1 +python3 3.12.3-0ubuntu2.1 +python3-apport 2.28.1-0ubuntu3.8 +python3-apt 2.7.7ubuntu5.2 +python3-attr 23.2.0-2 +python3-automat 22.10.0-2 +python3-babel 2.10.3-3build1 +python3-bcrypt 3.2.2-1build1 +python3-blinker 1.7.0-1 +python3-boto3 1.34.46+dfsg-1ubuntu1 +python3-botocore 1.34.46+repack-1ubuntu1 +python3-bpfcc 0.29.1+ds-1ubuntu7 +python3-certifi 2023.11.17-1 +python3-cffi-backend:arm64 1.16.0-2build1 +python3-chardet 5.2.0+dfsg-1 +python3-click 8.1.6-2 +python3-colorama 0.4.6-4 +python3-commandnotfound 23.04.0 +python3-configobj 5.0.8-3 +python3-constantly 23.10.4-1 +python3-cryptography 41.0.7-4ubuntu0.4 +python3-dateutil 2.8.2-3ubuntu1 +python3-dbus 1.3.2-5build3 +python3-debconf 1.5.86ubuntu1 +python3-debian 0.1.49ubuntu2 +python3-distro 1.9.0-1 +python3-distro-info 1.7build1 +python3-distupgrade 1:24.04.28 +python3-dnspython 2.6.1-1ubuntu1 +python3-gdbm:arm64 3.12.3-0ubuntu1 +python3-gi 3.48.2-1 +python3-hamcrest 2.1.0-1 +python3-httplib2 0.20.4-3 +python3-hyperlink 21.0.0-5 +python3-idna 3.6-2ubuntu0.1 +python3-incremental 22.10.0-1 +python3-jinja2 3.1.2-1ubuntu1.3 +python3-jmespath 1.0.1-1 +python3-json-pointer 2.0-0ubuntu1 +python3-jsonpatch 1.32-3 +python3-jsonschema 4.10.3-2ubuntu1 +python3-jwt 2.7.0-1ubuntu0.1 +python3-launchpadlib 1.11.0-6 +python3-lazr.restfulclient 0.14.6-1 +python3-lazr.uri 1.0.6-3 +python3-magic 2:0.4.27-3 +python3-markdown-it 3.0.0-2 +python3-markupsafe 2.1.5-1build2 +python3-mdurl 0.1.2-1 +python3-minimal 3.12.3-0ubuntu2.1 +python3-netaddr 0.8.0-2ubuntu1 +python3-netifaces:arm64 0.11.0-2build3 +python3-netplan 1.1.2-8ubuntu1~24.04.1 +python3-newt:arm64 0.52.24-2ubuntu2 +python3-oauthlib 3.2.2-1 +python3-openssl 23.2.0-1ubuntu0.1 +python3-packaging 24.0-1 +python3-pexpect 4.9-2 +python3-pkg-resources 68.1.2-2ubuntu1.2 +python3-prettytable 3.6.0-2 +python3-problem-report 2.28.1-0ubuntu3.8 +python3-psutil 5.9.8-2build2 +python3-ptyprocess 0.7.0-5 +python3-pyasn1 0.4.8-4ubuntu0.2 +python3-pyasn1-modules 0.2.8-1 +python3-pygments 2.17.2+dfsg-1 +python3-pyparsing 3.1.1-1 +python3-pyrsistent:arm64 0.20.0-1build2 +python3-requests 2.31.0+dfsg-1ubuntu1.1 +python3-rich 13.7.1-1 +python3-s3transfer 0.10.1-1ubuntu2 +python3-serial 3.5-2 +python3-service-identity 24.1.0-1 +python3-setuptools 68.1.2-2ubuntu1.2 +python3-six 1.16.0-4 +python3-software-properties 0.99.49.4 +python3-systemd 235-1build4 +python3-twisted 24.3.0-1ubuntu0.1 +python3-typing-extensions 4.10.0-1 +python3-tz 2024.1-2 +python3-update-manager 1:24.04.12 +python3-urllib3 2.0.7-1ubuntu0.6 +python3-wadllib 1.3.6-5 +python3-wcwidth 0.2.5+dfsg1-1.1ubuntu1 +python3-yaml 6.0.1-2build2 +python3-zope.interface 6.1-1build1 +python3.12 3.12.3-1ubuntu0.13 +python3.12-minimal 3.12.3-1ubuntu0.13 +readline-common 8.2-4build1 +rpcsvc-proto 1.4.2-0ubuntu7 +rsync 3.2.7-1ubuntu1.2 +rsyslog 8.2312.0-3ubuntu9.1 +run-one 1.17-0ubuntu2 +sbsigntool 0.9.4-3.1ubuntu7 +screen 4.9.1-1ubuntu1 +secureboot-db 1.9build1 +sed 4.9-2ubuntu0.24.04.1 +sensible-utils 0.0.22 +sg3-utils 1.46-3ubuntu4 +sg3-utils-udev 1.46-3ubuntu4 +sgml-base 1.31 +shared-mime-info 2.4-4 +shim-signed 1.58+15.8-0ubuntu1 +snapd 2.73+ubuntu24.04.2 +software-properties-common 0.99.49.4 +sosreport 4.10.2-0ubuntu0~24.04.1 +squashfs-tools 1:4.6.1-1build1 +ssh-import-id 5.11-0ubuntu2.24.04.1 +ssl-cert 1.1.2ubuntu1 +strace 6.8-0ubuntu2 +sudo 1.9.15p5-3ubuntu5.24.04.2 +sysstat 12.6.1-2 +systemd 255.4-1ubuntu8.14 +systemd-dev 255.4-1ubuntu8.14 +systemd-hwe-hwdb 255.1.7 +systemd-resolved 255.4-1ubuntu8.14 +systemd-sysv 255.4-1ubuntu8.14 +systemd-timesyncd 255.4-1ubuntu8.12 +sysvinit-utils 3.08-6ubuntu3 +tar 1.35+dfsg-3build1 +tcl 8.6.14build1 +tcl8.6 8.6.14+dfsg-1build1 +tcpdump 4.99.4-3ubuntu4.24.04.1 +telnet 0.17+2.5-3ubuntu4.1 +thin-provisioning-tools 0.9.0-2ubuntu5.1 +time 1.9-0.2build1 +tmux 3.4-1ubuntu0.1 +tnftp 20230507-2build3 +trace-cmd 3.2-1ubuntu2 +tzdata 2026a-0ubuntu0.24.04.1 +tzdata-legacy 2026a-0ubuntu0.24.04.1 +u-boot-tools 2025.10-0ubuntu0.24.04.2 +ubuntu-kernel-accessories 1.539.2 +ubuntu-keyring 2023.11.28.1 +ubuntu-minimal 1.539.2 +ubuntu-pro-client 37.1ubuntu0~24.04 +ubuntu-pro-client-l10n 37.1ubuntu0~24.04 +ubuntu-release-upgrader-core 1:24.04.28 +ubuntu-server 1.539.2 +ubuntu-standard 1.539.2 +ucf 3.0043+nmu1 +udev 255.4-1ubuntu8.14 +udisks2 2.10.1-6ubuntu1.3 +ufw 0.36.2-6 +unattended-upgrades 2.9.1+nmu4ubuntu1 +update-manager-core 1:24.04.12 +update-notifier-common 3.192.68.2 +usb-modeswitch 2.6.1-3ubuntu3 +usb-modeswitch-data 20191128-6 +usb.ids 2024.03.18-1 +usbutils 1:017-3build1 +util-linux 2.39.3-9ubuntu6.5 +uuid-runtime 2.39.3-9ubuntu6.5 +vim 2:9.1.0016-1ubuntu7.12 +vim-common 2:9.1.0016-1ubuntu7.12 +vim-runtime 2:9.1.0016-1ubuntu7.12 +vim-tiny 2:9.1.0016-1ubuntu7.12 +wget 1.21.4-1ubuntu4.1 +whiptail 0.52.24-2ubuntu2 +xauth 1:1.1.2-1build1 +xdg-user-dirs 0.18-1build1 +xfsprogs 6.6.0-1ubuntu2.1 +xkb-data 2.41-2ubuntu1.1 +xml-core 0.19 +xxd 2:9.1.0016-1ubuntu7.12 +xz-utils 5.6.1+really5.4.5-1ubuntu0.2 +zerofree 1.1.1-1build5 +zlib1g:arm64 1:1.3.dfsg-3.1ubuntu2.1 +zstd 1.5.5+dfsg2-2build1.1 +` diff --git a/server/internal/orchestrator/systemd/dnf.go b/server/internal/orchestrator/systemd/dnf.go index a4ee5093..deb22201 100644 --- a/server/internal/orchestrator/systemd/dnf.go +++ b/server/internal/orchestrator/systemd/dnf.go @@ -3,20 +3,16 @@ package systemd import ( "context" "fmt" - "maps" - "os/exec" "path/filepath" - "regexp" - "slices" "strings" "time" - - "github.com/pgEdge/control-plane/server/internal/ds" ) var _ PackageManager = (*Dnf)(nil) -type Dnf struct{} +type Dnf struct { + ExecCommand ExecCommand +} func (d *Dnf) InstanceDataBaseDir(pgMajor string) string { return filepath.Join("/var/lib/pgsql", pgMajor) @@ -27,131 +23,42 @@ func (d *Dnf) BinDir(pgMajor string) string { } func (d *Dnf) InstalledPostgresVersions(ctx context.Context) ([]*InstalledPostgres, error) { + execCmd := d.ExecCommand + if execCmd == nil { + execCmd = DefaultExecCommand + } + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - args := append([]string{"list", "--installed"}, supportedDnfPackages()...) - cmd := exec.CommandContext(ctx, "dnf", args...) - - out, err := cmd.CombinedOutput() + var stdout, stderr strings.Builder + err := execCmd(ctx, &stdout, &stderr, "rpm", "--query", "--all", "--qf", "%{NAME} %{VERSION}\n") if err != nil { - if strings.Contains(strings.ToLower(string(out)), "no matching packages to list") { - return nil, nil - } - return nil, fmt.Errorf("failed to execute command: %w, output: %s", err, string(out)) + return nil, fmt.Errorf("failed to query installed packages: %w, stderr: %s", err, stderr.String()) } - installed := map[string]*InstalledPostgres{} - for _, line := range strings.Split(string(out), "\n") { - fields := strings.Fields(line) - - if len(fields) < 2 { - continue - } - - pkg, ver := fields[0], fields[1] - switch { - case strings.HasPrefix(pkg, "pgedge-postgresql"): - inst, err := InstalledPostgresPackage(pkg, ver) - if err != nil { - return nil, err - } - postgres, ok := installed[inst.PostgresMajor] - if !ok { - postgres = &InstalledPostgres{} - installed[inst.PostgresMajor] = postgres - } - postgres.Postgres = inst - case strings.HasPrefix(pkg, "pgedge-spock"): - inst, err := InstalledSpockPackage(pkg, ver) - if err != nil { - return nil, err - } - postgres, ok := installed[inst.PostgresMajor] - if !ok { - postgres = &InstalledPostgres{} - installed[inst.PostgresMajor] = postgres - } - postgres.Spock = append(postgres.Spock, inst) - } - } + return processPackageList( + dnfPostgresPackageNames, + dnfSpockPackageNames, + stdout.String(), + ) +} - ret := slices.Collect(maps.Values(installed)) - for i := range ret { - slices.SortFunc(ret[i].Spock, PackageCmp) - } - slices.SortFunc(ret, InstalledPostgresCmp) +var dnfPostgresPackageNames, dnfSpockPackageNames = dnfPackageNames() - return ret, nil -} +func dnfPackageNames() (map[string]string, map[string]string) { -var supportedPostgresVersions = []string{"16", "17", "18"} -var supportedSpockVersions = []string{"50"} + postgresPackageNames := make(map[string]string, len(supportedPostgresVersions)) + spockPackageNames := make(map[string]string, len(supportedPostgresVersions)*len(supportedSpockVersions)) -func supportedDnfPackages() []string { - var packages []string for _, postgres := range supportedPostgresVersions { - packages = append(packages, fmt.Sprintf("pgedge-postgresql%s", postgres)) - + postgresPackageName := fmt.Sprintf("pgedge-postgresql%s", postgres) + postgresPackageNames[postgresPackageName] = postgres for _, spock := range supportedSpockVersions { - packages = append(packages, fmt.Sprintf("pgedge-spock%s_%s", spock, postgres)) + spockPackageName := fmt.Sprintf("pgedge-spock%s_%s", spock, postgres) + spockPackageNames[spockPackageName] = postgres } } - return packages -} - -var digits = regexp.MustCompile(`\d+`) - -func postgresVersionFromSpockPkg(pkg string) (string, error) { - // pkg should look like pgedge-spock50_18.aarch64, so we want to extract the - // second match. - matches := digits.FindAllString(pkg, 2) - if len(matches) < 2 { - return "", fmt.Errorf("unexpected format for spock package '%s'", pkg) - } - return matches[1], nil -} - -func postgresVersionFromPostgresPkg(pkg string) (string, error) { - // pkg should look like pgedge-postgresql18.aarch64, so we want to extract the - // first match. - matches := digits.FindAllString(pkg, 1) - if len(matches) == 0 { - return "", fmt.Errorf("unexpected format for postgres package '%s'", pkg) - } - return matches[0], nil -} - -func toVersion(ver string) (*ds.Version, error) { - var buf []rune - var components []string -parseLoop: - for _, char := range ver { - switch char { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - buf = append(buf, char) - case '.': - if len(buf) > 0 { - components = append(components, string(buf)) - buf = nil - } - if len(components) == 3 { - // Stop after collecting major, minor, patch - break parseLoop - } - case ':': - // this is the end of the epoch prefix. we want to discard what - // we've captured so far - buf = nil - default: - // this could be the start of the build number, or it could be a - // version format that we don't support. - break parseLoop - } - } - if len(buf) > 0 { - components = append(components, string(buf)) - } - return ds.ParseVersion(strings.Join(components, ".")) + return postgresPackageNames, spockPackageNames } diff --git a/server/internal/orchestrator/systemd/dnf_test.go b/server/internal/orchestrator/systemd/dnf_test.go index 2d674863..28fe9f3b 100644 --- a/server/internal/orchestrator/systemd/dnf_test.go +++ b/server/internal/orchestrator/systemd/dnf_test.go @@ -1,91 +1,447 @@ -package systemd +package systemd_test import ( "testing" - "github.com/stretchr/testify/assert" - "github.com/pgEdge/control-plane/server/internal/ds" + "github.com/pgEdge/control-plane/server/internal/orchestrator/systemd" + "github.com/stretchr/testify/require" ) -func TestToVersion(t *testing.T) { - for _, tc := range []struct { - in string - expected *ds.Version - expectedErr bool - }{ - { - in: "18.3-1.el9", - expected: &ds.Version{Components: []uint64{18, 3}}, - }, - { - in: "5.0.6-1.el9", - expected: &ds.Version{Components: []uint64{5, 0, 6}}, - }, - { - in: "2.6-1.el9", - expected: &ds.Version{Components: []uint64{2, 6}}, - }, - { - in: "1.3.0^20250625git121ab15-1.el9", - expected: &ds.Version{Components: []uint64{1, 3, 0}}, - }, - { - in: "2025.05.04.gita084c80-1.el9", - expected: &ds.Version{Components: []uint64{2025, 5, 4}}, - }, - { - in: "0~git20230917.9b27c3f-1.el9", - expected: &ds.Version{Components: []uint64{0}}, - }, - { - in: "366-12.el9_6", - expected: &ds.Version{Components: []uint64{366}}, - }, - { - in: "1:5.9.1-1.el9", - expected: &ds.Version{Components: []uint64{5, 9, 1}}, - }, - { - in: "20051222-24.el9", - expected: &ds.Version{Components: []uint64{20051222}}, - }, - { - in: "3.1.12-4.el9_3", - expected: &ds.Version{Components: []uint64{3, 1, 12}}, - }, - { - in: "4.0ga14-2.el9", - expected: &ds.Version{Components: []uint64{4, 0}}, - }, - { - in: "final1-3.20210311gitfinal.el9", - expectedErr: true, - }, - { - in: "0.20091126-40.el9", - expected: &ds.Version{Components: []uint64{0, 20091126}}, - }, - { - in: "0.2^1.26e5737-1.el9", - expected: &ds.Version{Components: []uint64{0, 2}}, - }, - { - in: "2:9.4.146.26-1.16.18.1.3.el9", - expected: &ds.Version{Components: []uint64{9, 4, 146}}, - }, - { - in: "1.4.0-4.Final.el9", - expected: &ds.Version{Components: []uint64{1, 4, 0}}, - }, - } { - t.Run(tc.in, func(t *testing.T) { - out, err := toVersion(tc.in) - if tc.expectedErr { - assert.Error(t, err) - } else { - assert.Equal(t, tc.expected, out) - } - }) - } +func TestDnf(t *testing.T) { + t.Run("installed packages successful", func(t *testing.T) { + dnf := systemd.Dnf{ + ExecCommand: systemd.MockExecCommand(t, testDnfPackageList, "", nil), + } + expected := []*systemd.InstalledPostgres{ + { + Postgres: &systemd.InstalledPackage{ + PostgresMajor: "16", + Version: ds.MustParseVersion("16.13"), + Name: "pgedge-postgresql16", + }, + Spock: []*systemd.InstalledPackage{ + { + PostgresMajor: "16", + Version: ds.MustParseVersion("5.0.7"), + Name: "pgedge-spock50_16", + }, + }, + }, + { + Postgres: &systemd.InstalledPackage{ + PostgresMajor: "17", + Version: ds.MustParseVersion("17.9"), + Name: "pgedge-postgresql17", + }, + Spock: []*systemd.InstalledPackage{ + { + PostgresMajor: "17", + Version: ds.MustParseVersion("5.0.7"), + Name: "pgedge-spock50_17", + }, + }, + }, + { + Postgres: &systemd.InstalledPackage{ + PostgresMajor: "18", + Version: ds.MustParseVersion("18.3"), + Name: "pgedge-postgresql18", + }, + Spock: []*systemd.InstalledPackage{ + { + PostgresMajor: "18", + Version: ds.MustParseVersion("5.0.7"), + Name: "pgedge-spock50_18", + }, + }, + }, + } + installed, err := dnf.InstalledPostgresVersions(t.Context()) + require.NoError(t, err) + require.Equal(t, expected, installed) + }) } + +const testDnfPackageList = `gawk-all-langpacks 5.1.0 +setup 2.13.7 +filesystem 3.16 +basesystem 11 +python3-setuptools-wheel 53.0.0 +pcre2-syntax 10.40 +ncurses-base 6.2 +ncurses-libs 6.2 +bash 5.1.8 +libgcc 11.5.0 +zlib 1.2.11 +xz-libs 5.2.5 +bzip2-libs 1.0.8 +libzstd 1.5.5 +libxcrypt 4.4.18 +sqlite-libs 3.34.1 +libgpg-error 1.42 +popt 1.18 +libattr 2.5.1 +libacl 2.3.1 +libffi 3.4.2 +libstdc++ 11.5.0 +lua-libs 5.4.4 +readline 8.1 +crypto-policies 20250905 +libgcrypt 1.10.0 +keyutils-libs 1.6.3 +libcap-ng 0.8.2 +audit-libs 3.1.5 +libcom_err 1.46.5 +libtasn1 4.16.0 +p11-kit 0.25.3 +lz4-libs 1.9.3 +libassuan 2.5.5 +file-libs 5.39 +gdbm-libs 1.23 +gmp 6.2.0 +json-c 0.14 +libsepol 3.6 +libsigsegv 2.13 +libunistring 0.9.10 +pcre 8.44 +grep 3.6 +pcre2 10.40 +libselinux 3.6 +coreutils-single 8.32 +sed 4.8 +gzip 1.12 +cracklib 2.9.6 +cracklib-dicts 2.9.6 +findutils 4.8.0 +libsemanage 3.6 +shadow-utils 4.9 +libutempter 1.2.1 +libidn2 2.3.0 +mpfr 4.1.0 +gawk 5.1.0 +keyutils 1.6.3 +libcomps 0.1.18 +acl 2.3.1 +attr 2.5.1 +libksba 1.5.1 +libxcrypt-compat 4.4.18 +alternatives 1.24 +p11-kit-trust 0.25.3 +ca-certificates 2025.2.80_v9.0.305 +dbus-libs 1.12.20 +kmod-libs 28 +libevent 2.1.12 +python3-pip-wheel 21.3.1 +python3-libcomps 0.1.18 +libdb 5.3.28 +libeconf 0.4.1 +libpwquality 1.4.4 +pam 1.5.1 +libgomp 11.5.0 +libseccomp 2.5.2 +libtool-ltdl 2.4.6 +libverto 0.3.2 +rpm 4.16.1.3 +rpm-libs 4.16.1.3 +libsolv 0.7.24 +rpm-plugin-systemd-inhibit 4.16.1.3 +tpm2-tss 3.2.3 +ima-evm-utils 1.6.2 +openldap 2.6.8 +libyaml 0.2.5 +nettle 3.10.1 +libmodulemd 2.13.0 +npth 1.6 +gpgme 1.15.1 +librepo 1.14.5 +python3-gpg 1.15.1 +rpm-sign-libs 4.16.1.3 +dbus 1.12.20 +dbus-common 1.12.20 +dbus-broker 28 +rpm-build-libs 4.16.1.3 +python3-rpm 4.16.1.3 +libreport-filesystem 2.15.2 +fonts-filesystem 2.0.5 +dejavu-sans-fonts 2.37 +langpacks-core-font-en 3.0 +langpacks-core-en 3.0 +langpacks-en 3.0 +crypto-policies-scripts 20250905 +gdb-gdbserver 16.3 +which 2.21 +rootfiles 8.1 +gpg-pubkey 350d275d +python3-dbus 1.2.18 +dnf-data 4.14.0 +python3-dnf 4.14.0 +dnf 4.14.0 +python3-systemd 234 +python3-six 1.15.0 +python3-dateutil 2.9.0.post0 +python3-dnf-plugins-core 4.3.0 +dnf-plugins-core 4.3.0 +epel-release 9 +yum 4.14.0 +tzdata 2026a +glibc-common 2.34 +glibc-gconv-extra 2.34 +glibc-langpack-en 2.34 +glibc-minimal-langpack 2.34 +glibc 2.34 +libuuid 2.37.4 +libblkid 2.37.4 +libmount 2.37.4 +libsmartcols 2.37.4 +libcap 2.48 +libfdisk 2.37.4 +gnutls 3.8.3 +glib2 2.68.4 +libdnf 0.69.0 +elfutils-libelf 0.193 +expat 2.5.0 +libnghttp2 1.43.0 +libxml2 2.9.13 +openssl-fips-provider 3.5.1 +openssl-libs 3.5.1 +python-unversioned-command 3.9.25 +python3 3.9.25 +python3-libs 3.9.25 +systemd-libs 252 +krb5-libs 1.21.1 +libcurl-minimal 7.76.1 +util-linux-core 2.37.4 +util-linux 2.37.4 +python3-libdnf 0.69.0 +systemd-rpm-macros 252 +systemd-pam 252 +systemd 252 +elfutils-default-yama-scope 0.193 +rocky-gpg-keys 9.7 +rocky-release 9.7 +rocky-repos 9.7 +elfutils-libs 0.193 +python3-hawkey 0.69.0 +curl-minimal 7.76.1 +cyrus-sasl-lib 2.1.27 +libarchive 3.5.3 +openssl 3.5.1 +gnupg2 2.3.3 +tar 1.34 +vim-minimal 8.2.2637 +pgedge-release 1.0 +gpg-pubkey b212ddac +gpg-pubkey 3228467c +nspr 4.36.0 +libjpeg-turbo 2.0.90 +nss-util 3.112.0 +flexiblas 3.0.4 +libquadmath 11.5.0 +libgfortran 11.5.0 +libicu 67.1 +pgedge-libpq5 18.3 +pgedge-geos313 3.13.1 +libpng 1.6.37 +pgedge-postgresql18-libs 18.3 +openjpeg2 2.4.0 +openblas 0.3.29 +openblas-openmp 0.3.29 +libwebp 1.2.0 +numactl-libs 2.0.19 +pgedge-postgresql18 18.3 +libtirpc 1.3.3 +hdf-libs 4.2.15 +libbrotli 1.0.9 +xerces-c 3.2.5 +libaec 1.0.6 +hdf5 1.12.1 +netcdf 4.8.1 +flexiblas-openblas-openmp 3.0.4 +flexiblas-netlib 3.0.4 +openblas-openmp64 0.3.29 +flexiblas-openblas-openmp64 3.0.4 +flexiblas-netlib64 3.0.4 +arpack 3.8.0 +pgedge-librttopo 1.1.0 +blas 3.9.0 +lapack 3.9.0 +nss-softokn-freebl 3.112.0 +nss-softokn 3.112.0 +nss 3.112.0 +nss-sysinit 3.112.0 +libqhull_r 7.2.1 +xml-common 0.6.3 +unixODBC 2.3.9 +poppler-data 0.4.9 +pcre2-utf16 10.40 +mariadb-connector-c-config 3.2.6 +mariadb-connector-c 3.2.6 +llvm-filesystem 20.1.8 +libxslt 1.1.34 +liburing 2.5 +pgedge-postgresql18-server 18.3 +pgedge-pgvector_18 0.8.1 +pgedge-pg_cron_18 1.6.7 +pgedge-pgmq_18 1.8.0 +lcms2 2.12 +jbigkit-libs 2.1 +libtiff 4.4.0 +pgedge-proj96 9.6.2 +pgedge-libgeotiff17 1.7.4 +gmp-c++ 6.2.0 +giflib 5.2.1 +boost-serialization 1.75.0 +pgedge-SFCGAL-libs 2.2.0 +snappy 1.1.8 +protobuf-c 1.3.3 +pkgconf-m4 1.7.3 +ncurses 6.2 +libusbx 1.0.26 +libproxy 0.4.15 +qt5-qtbase-common 5.15.9 +qt5-qtbase 5.15.9 +libpkgconf 1.7.3 +pkgconf 1.7.3 +pkgconf-pkg-config 1.7.3 +libtiff-devel 4.4.0 +pgedge-libgeotiff17-devel 1.7.4 +libedit 3.1 +llvm-libs 20.1.8 +llvm 20.1.8 +jansson 2.14 +groff-base 1.22.4 +perl-Digest 1.19 +perl-Digest-MD5 2.58 +perl-B 1.80 +perl-FileHandle 2.03 +perl-Data-Dumper 2.174 +perl-libnet 3.13 +perl-AutoLoader 5.74 +perl-base 2.27 +perl-URI 5.09 +perl-Mozilla-CA 20200520 +perl-if 0.60.800 +perl-IO-Socket-IP 0.41 +perl-Time-Local 1.300 +perl-File-Path 2.18 +perl-IO-Socket-SSL 2.073 +perl-Net-SSLeay 1.94 +perl-Pod-Escapes 1.07 +perl-Text-Tabs+Wrap 2013.0523 +perl-Class-Struct 0.66 +perl-POSIX 1.94 +perl-Term-ANSIColor 5.01 +perl-IPC-Open3 1.21 +perl-subs 1.03 +perl-File-Temp 0.231.100 +perl-HTTP-Tiny 0.076 +perl-Term-Cap 1.17 +perl-Pod-Simple 3.42 +perl-Socket 2.031 +perl-SelectSaver 1.02 +perl-Symbol 1.08 +perl-File-stat 1.09 +perl-podlators 4.14 +perl-Pod-Perldoc 3.28.01 +perl-Fcntl 1.13 +perl-Text-ParseWords 3.30 +perl-mro 1.23 +perl-IO 1.43 +perl-overloading 0.02 +perl-Pod-Usage 2.01 +perl-Errno 1.30 +perl-File-Basename 2.85 +perl-Getopt-Std 1.12 +perl-MIME-Base64 3.16 +perl-Scalar-List-Utils 1.56 +perl-constant 1.33 +perl-Storable 3.21 +perl-overload 1.31 +perl-parent 0.238 +perl-vars 1.05 +perl-Getopt-Long 2.52 +perl-Carp 1.50 +perl-Exporter 5.74 +perl-NDBM_File 1.15 +perl-PathTools 3.78 +perl-Encode 3.08 +perl-libs 5.32.1 +perl-interpreter 5.32.1 +pgedge-postgresql18-contrib 18.3 +graphite2 1.3.14 +harfbuzz 2.7.4 +freetype 2.10.4 +fontconfig 2.14.0 +poppler 21.01.0 +shapelib 1.5.0 +gpsbabel 1.8.0 +re2 20211101 +libarrow 9.0.0 +minizip 3.0.2 +pgedge-libspatialite50 5.1.0 +pgedge-libspatialite50-devel 5.1.0 +metis 5.1.0 +SuperLU 6.0.1 +armadillo 12.6.6 +libssh2 1.11.1 +libgta 1.2.1 +libdeflate 1.25 +freexl 1.0.6 +cfitsio 4.1.0 +pgedge-gdal311-libs 3.11.5 +pgedge-postgis35_18 3.5.5 +pgedge-pgbackrest 2.58.0 +pgedge-spock50_18 5.0.7 +pgedge-pg-stat-monitor_18 2.3.0 +pgedge-pg-vectorize_18 0.23.0 +pgedge-vectorizer_18 1.0 +pgedge-lolor_18 1.2.2 +pgedge-pg-tokenizer_18 0.1.1 +pgedge-pgaudit_18 18.0 +pgedge-snowflake_18 2.4 +pgedge-system_stats_18 3.2.1 +pgedge-vchord-bm25_18 0.2.2 +pgedge-python3-psycopg2 2.9.10 +pgedge-postgresql16-libs 16.13 +pgedge-postgresql17-libs 17.9 +pgedge-postgresql17 17.9 +pgedge-postgresql17-server 17.9 +pgedge-postgresql16 16.13 +pgedge-postgresql16-server 16.13 +pgedge-python3.12-six 1.17.0 +pgedge-python3.12-dateutil 2.9.0.post0 +python3.12-pip-wheel 23.2.1 +mpdecimal 2.5.1 +libnsl2 2.0.0 +python3.12 3.12.12 +python3.12-libs 3.12.12 +python3.12-idna 3.4 +python3.12-urllib3 1.26.19 +pgedge-python3.12-psutil 6.1.1 +pgedge-python3.12-psycopg2 2.9.10 +python3.12-charset-normalizer 3.3.0 +python3.12-requests 2.28.2 +pgedge-py-consul 1.6.0 +python3.12-ply 3.11 +python3.12-pycparser 2.20 +python3.12-cffi 1.16.0 +python3.12-cryptography 41.0.7 +python3.12-pyyaml 6.0.1 +less 590 +pgedge-python3-ydiff 1.4.2 +pgedge-python3.12-wcwidth 0.2.13 +pgedge-python3.12-prettytable 3.4.0 +pgedge-python3.12-dns 2.6.1 +pgedge-python3-etcd 0.4.5 +pgedge-python3.12-click 8.1.7 +pgedge-patroni 4.1.3 +pgedge-lolor_16 1.2.2 +pgedge-postgresql16-contrib 16.13 +pgedge-snowflake_16 2.4 +pgedge-spock50_16 5.0.7 +pgedge-lolor_17 1.2.2 +pgedge-postgresql17-contrib 17.9 +pgedge-snowflake_17 2.4 +pgedge-spock50_17 5.0.7 +` diff --git a/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/cpu_limit.service b/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/cpu_limit.service index 7bea69b1..907f296a 100644 --- a/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/cpu_limit.service +++ b/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/cpu_limit.service @@ -3,7 +3,8 @@ After=syslog.target network.target [Service] Type=simple -User=postgres +User=26 +Group=27 ExecStart=/usr/bin/patroni /var/lib/pgsql/18/storefront-n1-689qacsi/configs/patroni.yaml ExecReload=/bin/kill -s HUP $MAINPID KillMode=process diff --git a/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/fractional_cpu_limit.service b/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/fractional_cpu_limit.service index 86d40308..3754c87e 100644 --- a/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/fractional_cpu_limit.service +++ b/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/fractional_cpu_limit.service @@ -3,7 +3,8 @@ After=syslog.target network.target [Service] Type=simple -User=postgres +User=26 +Group=27 ExecStart=/usr/bin/patroni /var/lib/pgsql/18/storefront-n1-689qacsi/configs/patroni.yaml ExecReload=/bin/kill -s HUP $MAINPID KillMode=process diff --git a/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/memory_max.service b/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/memory_max.service index ff28515b..7449b639 100644 --- a/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/memory_max.service +++ b/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/memory_max.service @@ -3,7 +3,8 @@ After=syslog.target network.target [Service] Type=simple -User=postgres +User=26 +Group=27 ExecStart=/usr/bin/patroni /var/lib/pgsql/18/storefront-n1-689qacsi/configs/patroni.yaml ExecReload=/bin/kill -s HUP $MAINPID KillMode=process diff --git a/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/minimal.service b/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/minimal.service index f94ab1af..77f9a1e0 100644 --- a/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/minimal.service +++ b/server/internal/orchestrator/systemd/golden_test/TestUnitOptions/PatroniUnitOptions/minimal.service @@ -3,7 +3,8 @@ After=syslog.target network.target [Service] Type=simple -User=postgres +User=26 +Group=27 ExecStart=/usr/bin/patroni /var/lib/pgsql/18/storefront-n1-689qacsi/configs/patroni.yaml ExecReload=/bin/kill -s HUP $MAINPID KillMode=process diff --git a/server/internal/orchestrator/systemd/memory.go b/server/internal/orchestrator/systemd/memory.go new file mode 100644 index 00000000..e29c6520 --- /dev/null +++ b/server/internal/orchestrator/systemd/memory.go @@ -0,0 +1,45 @@ +package systemd + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "strconv" + "strings" +) + +func readTotalMemory() (uint64, error) { + f, err := os.Open("/proc/meminfo") + if err != nil { + return 0, err + } + defer f.Close() + + memTotal, err := extractMemTotal(f) + if err != nil { + return 0, fmt.Errorf("failed to extract MemTotal from /proc/meminfo: %w", err) + } + return memTotal, nil +} + +func extractMemTotal(r io.Reader) (uint64, error) { + scanner := bufio.NewScanner(r) + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) < 3 || fields[0] != "MemTotal:" { + continue + } + kb, err := strconv.ParseUint(fields[1], 10, 64) + if err != nil { + return 0, fmt.Errorf("parse MemTotal: %w", err) + } + return kb * 1024, nil + } + if err := scanner.Err(); err != nil { + return 0, err + } + + return 0, errors.New("MemTotal not found") +} diff --git a/server/internal/orchestrator/systemd/memory_test.go b/server/internal/orchestrator/systemd/memory_test.go new file mode 100644 index 00000000..c6b0b05e --- /dev/null +++ b/server/internal/orchestrator/systemd/memory_test.go @@ -0,0 +1,71 @@ +package systemd + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestExtractMemTotal(t *testing.T) { + const expected uint64 = 8_307_171_328 + out, err := extractMemTotal(strings.NewReader(memInfo)) + require.NoError(t, err) + require.Equal(t, expected, out) +} + +const memInfo = `MemTotal: 8112472 kB +MemFree: 4689284 kB +MemAvailable: 7561956 kB +Buffers: 40324 kB +Cached: 2822440 kB +SwapCached: 0 kB +Active: 408872 kB +Inactive: 2623792 kB +Active(anon): 193908 kB +Inactive(anon): 0 kB +Active(file): 214964 kB +Inactive(file): 2623792 kB +Unevictable: 26096 kB +Mlocked: 26096 kB +SwapTotal: 0 kB +SwapFree: 0 kB +Zswap: 0 kB +Zswapped: 0 kB +Dirty: 0 kB +Writeback: 0 kB +AnonPages: 196032 kB +Mapped: 304360 kB +Shmem: 16564 kB +KReclaimable: 236936 kB +Slab: 319544 kB +SReclaimable: 236936 kB +SUnreclaim: 82608 kB +KernelStack: 2752 kB +ShadowCallStack: 704 kB +PageTables: 4980 kB +SecPageTables: 0 kB +NFS_Unstable: 0 kB +Bounce: 0 kB +WritebackTmp: 0 kB +CommitLimit: 4056236 kB +Committed_AS: 858372 kB +VmallocTotal: 133141626880 kB +VmallocUsed: 12196 kB +VmallocChunk: 0 kB +Percpu: 2608 kB +HardwareCorrupted: 0 kB +AnonHugePages: 0 kB +ShmemHugePages: 0 kB +ShmemPmdMapped: 0 kB +FileHugePages: 0 kB +FilePmdMapped: 0 kB +CmaTotal: 32768 kB +CmaFree: 29696 kB +HugePages_Total: 0 +HugePages_Free: 0 +HugePages_Rsvd: 0 +HugePages_Surp: 0 +Hugepagesize: 2048 kB +Hugetlb: 0 kB +` diff --git a/server/internal/orchestrator/systemd/orchestrator.go b/server/internal/orchestrator/systemd/orchestrator.go index dba7414e..48abb8e4 100644 --- a/server/internal/orchestrator/systemd/orchestrator.go +++ b/server/internal/orchestrator/systemd/orchestrator.go @@ -7,13 +7,14 @@ import ( "io" "net" "os/exec" + "os/user" "path/filepath" "runtime" "slices" + "strconv" "syscall" "github.com/cschleiden/go-workflows/workflow" - "github.com/elastic/gosigar" "github.com/google/uuid" "github.com/rs/zerolog" @@ -49,14 +50,14 @@ func NewOrchestrator( logger := loggerFactory.Logger("systemd_orchestrator") logger.Debug().Msg("initializing orchestrator") - mem := gosigar.Mem{} - if err := mem.Get(); err != nil { - return nil, fmt.Errorf("failed to inspect system memory: %w", err) + mem, err := readTotalMemory() + if err != nil { + return nil, err } cpu := runtime.NumCPU() logger.Debug(). - Uint64("mem", mem.Total). + Uint64("mem", mem). Int("cpu", cpu). Msg("got system stats") @@ -66,7 +67,7 @@ func NewOrchestrator( client: client, packageManager: packageManager, cpus: cpu, - memBytes: mem.Total, + memBytes: mem, }, nil } @@ -151,38 +152,42 @@ func (o *Orchestrator) GenerateInstanceResources(spec *database.InstanceSpec, sc if err != nil { return nil, err } + databaseOwnerUID, databaseOwnerGID, err := o.databaseOwnerIDs() + if err != nil { + return nil, fmt.Errorf("failed to get database owner uid/gid: %w", err) + } // directory resources instanceDir := &filesystem.DirResource{ ID: spec.InstanceID + "-instance", HostID: spec.HostID, Path: paths.Host.BaseDir, - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, } dataDir := &filesystem.DirResource{ ID: spec.InstanceID + "-data", HostID: spec.HostID, ParentID: instanceDir.ID, Path: "data", - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, } configsDir := &filesystem.DirResource{ ID: spec.InstanceID + "-configs", HostID: spec.HostID, ParentID: instanceDir.ID, Path: "configs", - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, } certificatesDir := &filesystem.DirResource{ ID: spec.InstanceID + "-certificates", HostID: spec.HostID, ParentID: instanceDir.ID, Path: "certificates", - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, } // patroni resources - used to clean up etcd on deletion @@ -203,16 +208,16 @@ func (o *Orchestrator) GenerateInstanceResources(spec *database.InstanceSpec, sc DatabaseID: spec.DatabaseID, NodeName: spec.NodeName, ParentID: certificatesDir.ID, - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, } postgresCerts := &common.PostgresCerts{ InstanceID: spec.InstanceID, HostID: spec.HostID, ParentID: certificatesDir.ID, InstanceAddresses: o.cfg.Addresses(), - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, } // These should be caught by `ValidateInstanceSpecs`, but just in case @@ -245,8 +250,8 @@ func (o *Orchestrator) GenerateInstanceResources(spec *database.InstanceSpec, sc Paths: paths, }), ParentID: configsDir.ID, - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, }, } @@ -255,11 +260,13 @@ func (o *Orchestrator) GenerateInstanceResources(spec *database.InstanceSpec, sc return nil, errors.New("got empty postgres version") } + databaseOwnerUser := strconv.Itoa(databaseOwnerUID) + databaseOwnerGroup := strconv.Itoa(databaseOwnerGID) patroniUnit := &UnitResource{ DatabaseID: spec.DatabaseID, HostID: spec.HostID, Name: patroniServiceName(spec.InstanceID), - Options: PatroniUnitOptions(paths, o.packageManager.BinDir(pgMajor), spec.CPUs, spec.MemoryBytes), + Options: PatroniUnitOptions(paths, o.packageManager.BinDir(pgMajor), spec.CPUs, spec.MemoryBytes, databaseOwnerUser, databaseOwnerGroup), ExtraDependencies: []resource.Identifier{ patroniConfig.Identifier(), instanceDir.Identifier(), @@ -297,8 +304,8 @@ func (o *Orchestrator) GenerateInstanceResources(spec *database.InstanceSpec, sc ParentID: configsDir.ID, HostID: spec.HostID, InstanceID: spec.InstanceID, - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, }} var nodeDependents []resource.Resource @@ -312,8 +319,8 @@ func (o *Orchestrator) GenerateInstanceResources(spec *database.InstanceSpec, sc Repositories: spec.BackupConfig.Repositories, ParentID: configsDir.ID, Type: pgbackrest.ConfigTypeBackup, - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, Paths: paths, Port: postgresPort, }, @@ -348,8 +355,8 @@ func (o *Orchestrator) GenerateInstanceResources(spec *database.InstanceSpec, sc Repositories: []*pgbackrest.Repository{spec.RestoreConfig.Repository}, ParentID: configsDir.ID, Type: pgbackrest.ConfigTypeRestore, - OwnerUID: o.cfg.DatabaseOwnerUID, - OwnerGID: o.cfg.DatabaseOwnerUID, + OwnerUID: databaseOwnerUID, + OwnerGID: databaseOwnerGID, Paths: paths, Port: postgresPort, }) @@ -441,11 +448,15 @@ func (o *Orchestrator) ExecuteInstanceCommand(ctx context.Context, w io.Writer, if len(args) == 0 { return errors.New("got empty args") } + databaseOwnerUID, databaseOwnerGID, err := o.databaseOwnerIDs() + if err != nil { + return fmt.Errorf("failed to get database owner uid/gid: %w", err) + } cmd := exec.CommandContext(ctx, args[0], args[1:]...) cmd.SysProcAttr = &syscall.SysProcAttr{ Credential: &syscall.Credential{ - Uid: uint32(o.cfg.DatabaseOwnerUID), - Gid: uint32(o.cfg.DatabaseOwnerUID), + Uid: uint32(databaseOwnerUID), + Gid: uint32(databaseOwnerGID), }, } cmd.Stdout = w @@ -582,6 +593,29 @@ func (o *Orchestrator) InstancePaths(pgVersion *ds.Version, instanceID string) ( }, nil } +func (o *Orchestrator) databaseOwnerIDs() (int, int, error) { + u, err := user.Lookup("postgres") + if err != nil { + return 0, 0, fmt.Errorf("failed to lookup 'postgres' user: %w", err) + } + uid, err := strconv.Atoi(u.Uid) + if err != nil { + return 0, 0, fmt.Errorf("failed to parse 'postgres' uid: %w", err) + } + gid, err := strconv.Atoi(u.Gid) + if err != nil { + return 0, 0, fmt.Errorf("failed to parse 'postgres' gid: %w", err) + } + if o.cfg.DatabaseOwnerUID > 0 { + uid = o.cfg.DatabaseOwnerUID + } + if o.cfg.DatabaseOwnerGID > 0 { + gid = o.cfg.DatabaseOwnerGID + } + + return uid, gid, nil +} + func patroniServiceName(instanceID string) string { return fmt.Sprintf("patroni-%s.service", instanceID) } diff --git a/server/internal/orchestrator/systemd/packages.go b/server/internal/orchestrator/systemd/packages.go index 87362d74..af41940b 100644 --- a/server/internal/orchestrator/systemd/packages.go +++ b/server/internal/orchestrator/systemd/packages.go @@ -2,48 +2,26 @@ package systemd import ( "context" + "fmt" + "io" + "os/exec" + "slices" + "strings" "github.com/pgEdge/control-plane/server/internal/ds" ) +var ( + supportedPostgresVersions = []string{"16", "17", "18"} + supportedSpockVersions = []string{"50"} +) + type InstalledPackage struct { PostgresMajor string Version *ds.Version Name string } -func InstalledPostgresPackage(pkg, ver string) (*InstalledPackage, error) { - version, err := toVersion(ver) - if err != nil { - return nil, err - } - pgMajor, err := postgresVersionFromPostgresPkg(pkg) - if err != nil { - return nil, err - } - return &InstalledPackage{ - PostgresMajor: pgMajor, - Version: version, - Name: pkg, - }, nil -} - -func InstalledSpockPackage(pkg, ver string) (*InstalledPackage, error) { - version, err := toVersion(ver) - if err != nil { - return nil, err - } - pgMajor, err := postgresVersionFromSpockPkg(pkg) - if err != nil { - return nil, err - } - return &InstalledPackage{ - PostgresMajor: pgMajor, - Version: version, - Name: pkg, - }, nil -} - func PackageCmp(a, b *InstalledPackage) int { return a.Version.Compare(b.Version) } @@ -62,3 +40,106 @@ type PackageManager interface { InstanceDataBaseDir(pgMajor string) string BinDir(pgMajor string) string } + +type ExecCommand = func(ctx context.Context, stdout, stderr io.Writer, name string, args ...string) error + +func DefaultExecCommand(ctx context.Context, stdout, stderr io.Writer, name string, args ...string) error { + cmd := exec.CommandContext(ctx, name, args...) + cmd.Stdout = stdout + cmd.Stderr = stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to execute command '%s': %w", cmd.String(), err) + } + return nil +} + +func processPackageList( + postgresPackageNames map[string]string, + spockPackageNames map[string]string, + packageList string, +) ([]*InstalledPostgres, error) { + postgresPackages := map[string]*InstalledPackage{} + spockPackages := map[string][]*InstalledPackage{} + + for line := range strings.SplitSeq(packageList, "\n") { + fields := strings.Fields(line) + if len(fields) < 2 { + continue + } + pkg, ver := fields[0], fields[1] + // Trim architecture qualifier from package name if it exists. + pkg, _, _ = strings.Cut(pkg, ":") + if postgresMajor, ok := postgresPackageNames[pkg]; ok { + installed, err := installedPackage(postgresMajor, pkg, ver) + if err != nil { + return nil, err + } + postgresPackages[postgresMajor] = installed + } + if postgresMajor, ok := spockPackageNames[pkg]; ok { + installed, err := installedPackage(postgresMajor, pkg, ver) + if err != nil { + return nil, err + } + spockPackages[postgresMajor] = append(spockPackages[postgresMajor], installed) + } + } + + installedPostgres := make([]*InstalledPostgres, 0, len(postgresPackages)) + for postgresMajor, postgresPackage := range postgresPackages { + spock := spockPackages[postgresMajor] + slices.SortFunc(spock, PackageCmp) + installedPostgres = append(installedPostgres, &InstalledPostgres{ + Postgres: postgresPackage, + Spock: spock, + }) + } + slices.SortFunc(installedPostgres, InstalledPostgresCmp) + + return installedPostgres, nil +} + +func installedPackage(postgresMajor, pkg, ver string) (*InstalledPackage, error) { + version, err := toVersion(ver) + if err != nil { + return nil, fmt.Errorf("failed to parse version '%s' for package '%s': %w", ver, pkg, err) + } + return &InstalledPackage{ + PostgresMajor: postgresMajor, + Version: version, + Name: pkg, + }, nil +} + +func toVersion(ver string) (*ds.Version, error) { + var buf []rune + var components []string +parseLoop: + for _, char := range ver { + switch char { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + buf = append(buf, char) + case '.': + if len(buf) > 0 { + components = append(components, string(buf)) + buf = nil + } + if len(components) == 3 { + // Stop after collecting major, minor, patch + break parseLoop + } + case ':': + // this is the end of the epoch prefix. we want to discard what + // we've captured so far + buf = nil + default: + // this could be the start of the build number, or it could be a + // version format that we don't support. + break parseLoop + } + } + if len(buf) > 0 { + components = append(components, string(buf)) + } + return ds.ParseVersion(strings.Join(components, ".")) +} diff --git a/server/internal/orchestrator/systemd/packages_test.go b/server/internal/orchestrator/systemd/packages_test.go new file mode 100644 index 00000000..4fd333cd --- /dev/null +++ b/server/internal/orchestrator/systemd/packages_test.go @@ -0,0 +1,122 @@ +package systemd + +import ( + "context" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/pgEdge/control-plane/server/internal/ds" +) + +func TestToVersion(t *testing.T) { + for _, tc := range []struct { + in string + expected *ds.Version + expectedErr bool + }{ + { + in: "18.3-1.el9", + expected: &ds.Version{Components: []uint64{18, 3}}, + }, + { + in: "5.0.6-1.el9", + expected: &ds.Version{Components: []uint64{5, 0, 6}}, + }, + { + in: "2.6-1.el9", + expected: &ds.Version{Components: []uint64{2, 6}}, + }, + { + in: "1.3.0^20250625git121ab15-1.el9", + expected: &ds.Version{Components: []uint64{1, 3, 0}}, + }, + { + in: "2025.05.04.gita084c80-1.el9", + expected: &ds.Version{Components: []uint64{2025, 5, 4}}, + }, + { + in: "0~git20230917.9b27c3f-1.el9", + expected: &ds.Version{Components: []uint64{0}}, + }, + { + in: "366-12.el9_6", + expected: &ds.Version{Components: []uint64{366}}, + }, + { + in: "1:5.9.1-1.el9", + expected: &ds.Version{Components: []uint64{5, 9, 1}}, + }, + { + in: "20051222-24.el9", + expected: &ds.Version{Components: []uint64{20051222}}, + }, + { + in: "3.1.12-4.el9_3", + expected: &ds.Version{Components: []uint64{3, 1, 12}}, + }, + { + in: "4.0ga14-2.el9", + expected: &ds.Version{Components: []uint64{4, 0}}, + }, + { + in: "final1-3.20210311gitfinal.el9", + expectedErr: true, + }, + { + in: "0.20091126-40.el9", + expected: &ds.Version{Components: []uint64{0, 20091126}}, + }, + { + in: "0.2^1.26e5737-1.el9", + expected: &ds.Version{Components: []uint64{0, 2}}, + }, + { + in: "2:9.4.146.26-1.16.18.1.3.el9", + expected: &ds.Version{Components: []uint64{9, 4, 146}}, + }, + { + in: "1.4.0-4.Final.el9", + expected: &ds.Version{Components: []uint64{1, 4, 0}}, + }, + { + in: "2:9.1.0016-1ubuntu7.12", + expected: &ds.Version{Components: []uint64{9, 1, 16}}, + }, + { + in: "3.12.3-1ubuntu0.13", + expected: &ds.Version{Components: []uint64{3, 12, 3}}, + }, + { + in: "17.9-1.noble", + expected: &ds.Version{Components: []uint64{17, 9}}, + }, + } { + t.Run(tc.in, func(t *testing.T) { + out, err := toVersion(tc.in) + if tc.expectedErr { + assert.Error(t, err) + } else { + assert.Equal(t, tc.expected, out) + } + }) + } +} + +func MockExecCommand(t testing.TB, mockStdout, mockStderr string, mockErr error) ExecCommand { + t.Helper() + + return func(_ context.Context, stdout, stderr io.Writer, name string, args ...string) error { + if mockStdout != "" { + _, err := stdout.Write([]byte(mockStdout)) + require.NoError(t, err) + } + if mockStderr != "" { + _, err := stderr.Write([]byte(mockStderr)) + require.NoError(t, err) + } + return mockErr + } +} diff --git a/server/internal/orchestrator/systemd/patroni_unit.go b/server/internal/orchestrator/systemd/patroni_unit.go index 5dbadea2..305b6bf2 100644 --- a/server/internal/orchestrator/systemd/patroni_unit.go +++ b/server/internal/orchestrator/systemd/patroni_unit.go @@ -14,6 +14,8 @@ func PatroniUnitOptions( pgBinPath string, cpus float64, memoryBytes uint64, + databaseOwnerUser string, + databaseOwnerGroup string, ) []*unit.UnitOption { pathEnv := pgBinPath if p := os.Getenv("PATH"); p != "" { @@ -28,7 +30,8 @@ func PatroniUnitOptions( }, Service: ServiceSection{ Type: ServiceTypeSimple, - User: "postgres", + User: databaseOwnerUser, + Group: databaseOwnerGroup, ExecStart: patroniCmd, ExecReload: "/bin/kill -s HUP $MAINPID", KillMode: ServiceKillModeProcess, diff --git a/server/internal/orchestrator/systemd/provide.go b/server/internal/orchestrator/systemd/provide.go index d394bbe7..559c5f28 100644 --- a/server/internal/orchestrator/systemd/provide.go +++ b/server/internal/orchestrator/systemd/provide.go @@ -1,11 +1,24 @@ package systemd import ( + "bufio" + "fmt" + "os" + "strings" + "github.com/pgEdge/control-plane/server/internal/config" "github.com/pgEdge/control-plane/server/internal/logging" "github.com/samber/do" ) +type OSFamily string + +const ( + OSFamilyUnknown OSFamily = "unknown" + OSFamilyRedHat OSFamily = "redhat" + OSFamilyDebian OSFamily = "debian" +) + func Provide(i *do.Injector) { provideClient(i) providePackageManager(i) @@ -25,9 +38,18 @@ func provideClient(i *do.Injector) { func providePackageManager(i *do.Injector) { do.Provide(i, func(i *do.Injector) (PackageManager, error) { - // TODO: add a function to check whether OS is RHEL-like or debian-like - // and return the appropriate package manager implementation. - return &Dnf{}, nil + osFamily, err := getOSFamily() + if err != nil { + return nil, fmt.Errorf("failed to determine os family: %w", err) + } + switch osFamily { + case OSFamilyRedHat: + return &Dnf{}, nil + case OSFamilyDebian: + return &Apt{}, nil + default: + return nil, fmt.Errorf("unrecognized os family '%s'", osFamily) + } }) } @@ -53,3 +75,28 @@ func provideOrchestrator(i *do.Injector) { return NewOrchestrator(cfg, loggerFactory, client, packageManager) }) } + +func getOSFamily() (OSFamily, error) { + file, err := os.Open("/etc/os-release") + if err != nil { + return "", fmt.Errorf("failed to open /etc/os-release: %w", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "ID=") || strings.HasPrefix(line, "ID_LIKE=") { + if strings.Contains(line, "debian") { + return OSFamilyDebian, nil + } + if strings.Contains(line, "rhel") || strings.Contains(line, "fedora") { + return OSFamilyRedHat, nil + } + } + } + if err := scanner.Err(); err != nil { + return "", fmt.Errorf("failed to scan /etc/os-release: %w", err) + } + return OSFamilyUnknown, nil +} diff --git a/server/internal/orchestrator/systemd/unit_options.go b/server/internal/orchestrator/systemd/unit_options.go index aacd8cac..f2b318c4 100644 --- a/server/internal/orchestrator/systemd/unit_options.go +++ b/server/internal/orchestrator/systemd/unit_options.go @@ -85,6 +85,7 @@ func (u UnitSection) Options() []*unit.UnitOption { type ServiceSection struct { Type ServiceType User string + Group string ExecStart string ExecReload string KillMode ServiceKillMode @@ -103,6 +104,9 @@ func (s ServiceSection) Options() []*unit.UnitOption { if s.User != "" { opts = append(opts, ServiceUserOption(s.User)) } + if s.Group != "" { + opts = append(opts, ServiceGroupOption(s.Group)) + } if s.ExecStart != "" { opts = append(opts, ServiceExecStartOption(s.ExecStart)) } @@ -172,6 +176,14 @@ func ServiceUserOption(value string) *unit.UnitOption { } } +func ServiceGroupOption(value string) *unit.UnitOption { + return &unit.UnitOption{ + Section: sectionNameService, + Name: "Group", + Value: value, + } +} + func ServiceExecStartOption(value string) *unit.UnitOption { return &unit.UnitOption{ Section: sectionNameService, diff --git a/server/internal/orchestrator/systemd/unit_options_test.go b/server/internal/orchestrator/systemd/unit_options_test.go index 45872744..da623cff 100644 --- a/server/internal/orchestrator/systemd/unit_options_test.go +++ b/server/internal/orchestrator/systemd/unit_options_test.go @@ -87,7 +87,7 @@ func TestUnitOptions(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Setenv("PATH", "/root/.local/bin:/root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/go/bin") - actual := systemd.PatroniUnitOptions(tc.paths, tc.pgBinPath, tc.cpus, tc.memoryBytes) + actual := systemd.PatroniUnitOptions(tc.paths, tc.pgBinPath, tc.cpus, tc.memoryBytes, "26", "27") golden.Run(t, actual, update) }) } diff --git a/server/internal/pgbackrest/config.go b/server/internal/pgbackrest/config.go index b580ae6a..c178e948 100644 --- a/server/internal/pgbackrest/config.go +++ b/server/internal/pgbackrest/config.go @@ -5,6 +5,7 @@ import ( "io" "maps" "path" + "path/filepath" "regexp" "slices" "strconv" @@ -193,6 +194,7 @@ func (r *Repository) Identifier() string { type ConfigOptions struct { DatabaseID string NodeName string + InstanceID string PgDataPath string HostUser string User string @@ -205,6 +207,7 @@ func WriteConfig(w io.Writer, opts ConfigOptions) error { global := map[string]string{ "start-fast": "y", "log-level-console": "info", + "lock-path": filepath.Join("/", "tmp", "pgbackrest", opts.InstanceID), } for idx, repo := range opts.Repositories { diff --git a/server/internal/pgbackrest/config_test.go b/server/internal/pgbackrest/config_test.go index 01e96bce..7314230d 100644 --- a/server/internal/pgbackrest/config_test.go +++ b/server/internal/pgbackrest/config_test.go @@ -20,6 +20,7 @@ func TestWriteConfig(t *testing.T) { opts: pgbackrest.ConfigOptions{ DatabaseID: "706fd161-8df5-4ddd-b25a-f85d8b7bb033", NodeName: "n1", + InstanceID: "706fd161-8df5-4ddd-b25a-f85d8b7bb033-n1-n5fe2mcy", PgDataPath: "/opt/pgedge/data", HostUser: "pgedge-db", User: "pgedge", @@ -34,6 +35,7 @@ func TestWriteConfig(t *testing.T) { }, expectedLines: []string{ "[global]", + "lock-path = /tmp/pgbackrest/706fd161-8df5-4ddd-b25a-f85d8b7bb033-n1-n5fe2mcy", "log-level-console = info", "repo1-cipher-type = none", "repo1-path = /databases/706fd161-8df5-4ddd-b25a-f85d8b7bb033/0c28ad6e-233b-4ca7-ae60-4214e3a5356d/n1", @@ -58,6 +60,7 @@ func TestWriteConfig(t *testing.T) { opts: pgbackrest.ConfigOptions{ DatabaseID: "706fd161-8df5-4ddd-b25a-f85d8b7bb033", NodeName: "n1", + InstanceID: "706fd161-8df5-4ddd-b25a-f85d8b7bb033-n1-n5fe2mcy", PgDataPath: "/opt/pgedge/data", HostUser: "pgedge-db", User: "pgedge", @@ -77,6 +80,7 @@ func TestWriteConfig(t *testing.T) { }, expectedLines: []string{ "[global]", + "lock-path = /tmp/pgbackrest/706fd161-8df5-4ddd-b25a-f85d8b7bb033-n1-n5fe2mcy", "log-level-console = info", "repo1-cipher-type = none", "repo1-path = /backups/databases/706fd161-8df5-4ddd-b25a-f85d8b7bb033/0c28ad6e-233b-4ca7-ae60-4214e3a5356d/n1", @@ -102,6 +106,7 @@ func TestWriteConfig(t *testing.T) { opts: pgbackrest.ConfigOptions{ DatabaseID: "706fd161-8df5-4ddd-b25a-f85d8b7bb033", NodeName: "n1", + InstanceID: "706fd161-8df5-4ddd-b25a-f85d8b7bb033-n1-n5fe2mcy", PgDataPath: "/opt/pgedge/data", HostUser: "pgedge-db", User: "pgedge", @@ -121,6 +126,7 @@ func TestWriteConfig(t *testing.T) { }, expectedLines: []string{ "[global]", + "lock-path = /tmp/pgbackrest/706fd161-8df5-4ddd-b25a-f85d8b7bb033-n1-n5fe2mcy", "log-level-console = info", "repo1-cipher-pass = zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPvTGirhPygu4jOKOXf9LO4vjfO", "repo1-cipher-type = aes-256-cbc", @@ -146,6 +152,7 @@ func TestWriteConfig(t *testing.T) { opts: pgbackrest.ConfigOptions{ DatabaseID: "706fd161-8df5-4ddd-b25a-f85d8b7bb033", NodeName: "n1", + InstanceID: "706fd161-8df5-4ddd-b25a-f85d8b7bb033-n1-n5fe2mcy", PgDataPath: "/opt/pgedge/data", HostUser: "pgedge-db", User: "pgedge", @@ -168,6 +175,7 @@ func TestWriteConfig(t *testing.T) { }, expectedLines: []string{ "[global]", + "lock-path = /tmp/pgbackrest/706fd161-8df5-4ddd-b25a-f85d8b7bb033-n1-n5fe2mcy", "log-level-console = info", "repo1-cipher-type = none", "repo1-path = /databases/706fd161-8df5-4ddd-b25a-f85d8b7bb033/time-retention/n1", @@ -201,6 +209,7 @@ func TestWriteConfig(t *testing.T) { opts: pgbackrest.ConfigOptions{ DatabaseID: "706fd161-8df5-4ddd-b25a-f85d8b7bb033", NodeName: "n1", + InstanceID: "706fd161-8df5-4ddd-b25a-f85d8b7bb033-n1-n5fe2mcy", PgDataPath: "/opt/pgedge/data", HostUser: "pgedge-db", User: "pgedge", @@ -214,6 +223,7 @@ func TestWriteConfig(t *testing.T) { }, expectedLines: []string{ "[global]", + "lock-path = /tmp/pgbackrest/706fd161-8df5-4ddd-b25a-f85d8b7bb033-n1-n5fe2mcy", "log-level-console = info", "repo1-cipher-type = none", "repo1-gcs-bucket = backups", @@ -237,6 +247,7 @@ func TestWriteConfig(t *testing.T) { opts: pgbackrest.ConfigOptions{ DatabaseID: "706fd161-8df5-4ddd-b25a-f85d8b7bb033", NodeName: "n1", + InstanceID: "706fd161-8df5-4ddd-b25a-f85d8b7bb033-n1-n5fe2mcy", PgDataPath: "/opt/pgedge/data", HostUser: "pgedge-db", User: "pgedge", @@ -251,6 +262,7 @@ func TestWriteConfig(t *testing.T) { }, expectedLines: []string{ "[global]", + "lock-path = /tmp/pgbackrest/706fd161-8df5-4ddd-b25a-f85d8b7bb033-n1-n5fe2mcy", "log-level-console = info", "repo1-cipher-type = none", "repo1-gcs-bucket = backups", @@ -314,6 +326,7 @@ func TestWriteConfig(t *testing.T) { opts: pgbackrest.ConfigOptions{ DatabaseID: "706fd161-8df5-4ddd-b25a-f85d8b7bb033", NodeName: "n1", + InstanceID: "706fd161-8df5-4ddd-b25a-f85d8b7bb033-n1-n5fe2mcy", PgDataPath: "/opt/pgedge/data", HostUser: "pgedge-db", User: "pgedge", @@ -329,6 +342,7 @@ func TestWriteConfig(t *testing.T) { }, expectedLines: []string{ "[global]", + "lock-path = /tmp/pgbackrest/706fd161-8df5-4ddd-b25a-f85d8b7bb033-n1-n5fe2mcy", "log-level-console = info", "repo1-azure-account = backups-account", "repo1-azure-container = backups",