diff --git a/.github/workflows/auto-release.yaml b/.github/workflows/auto-release.yaml index 19d4b460..44ca8ca6 100644 --- a/.github/workflows/auto-release.yaml +++ b/.github/workflows/auto-release.yaml @@ -20,6 +20,14 @@ jobs: pull-requests: read steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.COZYSTACK_CI_APP_ID }} + private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }} + owner: cozystack + - name: Checkout code uses: actions/checkout@v4 with: @@ -28,27 +36,27 @@ jobs: - name: Configure git env: - GH_PAT: ${{ secrets.GH_PAT }} + APP_TOKEN: ${{ steps.app-token.outputs.token }} run: | - git config user.name "cozystack-bot" - git config user.email "217169706+cozystack-bot@users.noreply.github.com" - git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY} + git config user.name "cozystack-ci[bot]" + git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com" + git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY} git config --unset-all http.https://github.com/.extraheader || true - name: Process release branches uses: actions/github-script@v7 env: - GH_PAT: ${{ secrets.GH_PAT }} + APP_TOKEN: ${{ steps.app-token.outputs.token }} with: - github-token: ${{ secrets.GH_PAT }} + github-token: ${{ steps.app-token.outputs.token }} script: | const { execSync } = require('child_process'); - // Configure git to use PAT for authentication - execSync('git config user.name "cozystack-bot"', { encoding: 'utf8' }); - execSync('git config user.email "217169706+cozystack-bot@users.noreply.github.com"', { encoding: 'utf8' }); - execSync(`git remote set-url origin https://cozystack-bot:${process.env.GH_PAT}@github.com/${process.env.GITHUB_REPOSITORY}`, { encoding: 'utf8' }); - // Remove GITHUB_TOKEN extraheader to ensure PAT is used (needed to trigger other workflows) + // Configure git to use GitHub App token for authentication + execSync('git config user.name "cozystack-ci[bot]"', { encoding: 'utf8' }); + execSync('git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"', { encoding: 'utf8' }); + execSync(`git remote set-url origin https://x-access-token:${process.env.APP_TOKEN}@github.com/${process.env.GITHUB_REPOSITORY}`, { encoding: 'utf8' }); + // Remove GITHUB_TOKEN extraheader to ensure App token is used (needed to trigger other workflows) execSync('git config --unset-all http.https://github.com/.extraheader || true', { encoding: 'utf8' }); // Get all release-X.Y branches diff --git a/.github/workflows/pull-requests-release.yaml b/.github/workflows/pull-requests-release.yaml index 72f31b54..bb7f5da5 100644 --- a/.github/workflows/pull-requests-release.yaml +++ b/.github/workflows/pull-requests-release.yaml @@ -23,6 +23,14 @@ jobs: contains(github.event.pull_request.labels.*.name, 'release') steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.COZYSTACK_CI_APP_ID }} + private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }} + owner: cozystack + # Extract tag from branch name (branch = release-X.Y.Z*) - name: Extract tag from branch name id: get_tag @@ -47,11 +55,11 @@ jobs: - name: Create tag on merge commit env: - GH_PAT: ${{ secrets.GH_PAT }} + APP_TOKEN: ${{ steps.app-token.outputs.token }} run: | - git config user.name "cozystack-bot" - git config user.email "217169706+cozystack-bot@users.noreply.github.com" - git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY} + git config user.name "cozystack-ci[bot]" + git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com" + git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY} git tag -f ${{ steps.get_tag.outputs.tag }} ${{ github.sha }} git push -f origin ${{ steps.get_tag.outputs.tag }} @@ -59,7 +67,7 @@ jobs: - name: Ensure maintenance branch release-X.Y uses: actions/github-script@v7 with: - github-token: ${{ secrets.GH_PAT }} + github-token: ${{ steps.app-token.outputs.token }} script: | const tag = '${{ steps.get_tag.outputs.tag }}'; // e.g. v0.1.3 or v0.1.3-rc3 const match = tag.match(/^v(\d+)\.(\d+)\.\d+(?:[-\w\.]+)?$/); diff --git a/.github/workflows/pull-requests.yaml b/.github/workflows/pull-requests.yaml index 76dbfc17..d9898616 100644 --- a/.github/workflows/pull-requests.yaml +++ b/.github/workflows/pull-requests.yaml @@ -99,6 +99,14 @@ jobs: disk_id: ${{ steps.fetch_assets.outputs.disk_id }} steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.COZYSTACK_CI_APP_ID }} + private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }} + owner: cozystack + - name: Checkout code if: contains(github.event.pull_request.labels.*.name, 'release') uses: actions/checkout@v4 @@ -125,7 +133,7 @@ jobs: id: fetch_assets uses: actions/github-script@v7 with: - github-token: ${{ secrets.GH_PAT }} + github-token: ${{ steps.app-token.outputs.token }} script: | const tag = '${{ steps.get_tag.outputs.tag }}'; const releases = await github.rest.repos.listReleases({ @@ -159,6 +167,15 @@ jobs: if: ${{ always() && (needs.build.result == 'success' || needs.resolve_assets.result == 'success') }} steps: + - name: Generate GitHub App token + if: contains(github.event.pull_request.labels.*.name, 'release') + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.COZYSTACK_CI_APP_ID }} + private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }} + owner: cozystack + # ▸ Checkout and prepare the codebase - name: Checkout code uses: actions/checkout@v4 @@ -188,11 +205,11 @@ jobs: if: contains(github.event.pull_request.labels.*.name, 'release') run: | mkdir -p _out/assets - curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \ + curl -sSL -H "Authorization: token ${APP_TOKEN}" -H "Accept: application/octet-stream" \ -o _out/assets/nocloud-amd64.raw.xz \ "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.disk_id }}" env: - GH_PAT: ${{ secrets.GH_PAT }} + APP_TOKEN: ${{ steps.app-token.outputs.token }} - name: Set sandbox ID run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV diff --git a/.github/workflows/tags.yaml b/.github/workflows/tags.yaml index f26ef632..d698333e 100644 --- a/.github/workflows/tags.yaml +++ b/.github/workflows/tags.yaml @@ -25,6 +25,14 @@ jobs: actions: write steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.COZYSTACK_CI_APP_ID }} + private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }} + owner: cozystack + # Check if a non-draft release with this tag already exists - name: Check if release already exists id: check_release @@ -115,34 +123,29 @@ jobs: - name: Commit release artifacts if: steps.check_release.outputs.skip == 'false' env: - GH_PAT: ${{ secrets.GH_PAT }} + APP_TOKEN: ${{ steps.app-token.outputs.token }} run: | - git config user.name "cozystack-bot" - git config user.email "217169706+cozystack-bot@users.noreply.github.com" - git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY} + git config user.name "cozystack-ci[bot]" + git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com" + git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY} git config --unset-all http.https://github.com/.extraheader || true git add . git commit -m "Prepare release ${GITHUB_REF#refs/tags/}" -s || echo "No changes to commit" - git push origin HEAD || true # Tag the api/apps/v1alpha1 submodule for pkg.go.dev - name: Tag API submodule if: steps.check_release.outputs.skip == 'false' env: - GH_PAT: ${{ secrets.GH_PAT }} + APP_TOKEN: ${{ steps.app-token.outputs.token }} run: | VTAG="${{ steps.tag.outputs.tag }}" SUBTAG="api/apps/v1alpha1/${VTAG}" - git config user.name "cozystack-bot" - git config user.email "217169706+cozystack-bot@users.noreply.github.com" - git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY} + git config user.name "cozystack-ci[bot]" + git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com" + git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY} TARGET="$(git rev-parse "${VTAG}^{}")" - if git rev-parse -q --verify "refs/tags/${SUBTAG}" >/dev/null; then - test "$(git rev-list -n1 "${SUBTAG}")" = "$TARGET" - else - git tag "${SUBTAG}" "$TARGET" - fi - git push origin "${SUBTAG}" + git tag -f "${SUBTAG}" "$TARGET" + git push -f origin "refs/tags/${SUBTAG}" # Create or reuse draft release - name: Create / reuse draft release @@ -188,11 +191,11 @@ jobs: - name: Create release branch if: steps.check_release.outputs.skip == 'false' env: - GH_PAT: ${{ secrets.GH_PAT }} + APP_TOKEN: ${{ steps.app-token.outputs.token }} run: | - git config user.name "cozystack-bot" - git config user.email "217169706+cozystack-bot@users.noreply.github.com" - git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY} + git config user.name "cozystack-ci[bot]" + git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com" + git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY} BRANCH="release-${GITHUB_REF#refs/tags/v}" git branch -f "$BRANCH" git push -f origin "$BRANCH" @@ -202,7 +205,7 @@ jobs: if: steps.check_release.outputs.skip == 'false' uses: actions/github-script@v7 with: - github-token: ${{ secrets.GH_PAT }} + github-token: ${{ steps.app-token.outputs.token }} script: | const version = context.ref.replace('refs/tags/v', ''); const base = '${{ steps.get_base.outputs.branch }}'; @@ -244,6 +247,14 @@ jobs: pull-requests: write if: needs.prepare-release.result == 'success' steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.COZYSTACK_CI_APP_ID }} + private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }} + owner: cozystack + - name: Parse tag id: tag uses: actions/github-script@v7 @@ -266,7 +277,7 @@ jobs: ref: main fetch-depth: 0 fetch-tags: true - token: ${{ secrets.GH_PAT }} + token: ${{ steps.app-token.outputs.token }} - name: Check if changelog already exists id: check_changelog @@ -294,7 +305,7 @@ jobs: if: steps.check_changelog.outputs.exists == 'false' env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - GH_TOKEN: ${{ secrets.GH_PAT }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | copilot --prompt "prepare changelog file for tagged release v${{ steps.tag.outputs.version }}, use @docs/agents/changelog.md for it. Create the changelog file at docs/changelogs/v${{ steps.tag.outputs.version }}.md" \ --allow-all-tools --allow-all-paths < /dev/null @@ -302,11 +313,11 @@ jobs: - name: Create changelog branch and commit if: steps.check_changelog.outputs.exists == 'false' env: - GH_PAT: ${{ secrets.GH_PAT }} + APP_TOKEN: ${{ steps.app-token.outputs.token }} run: | - git config user.name "cozystack-bot" - git config user.email "217169706+cozystack-bot@users.noreply.github.com" - git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY} + git config user.name "cozystack-ci[bot]" + git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com" + git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY} CHANGELOG_FILE="docs/changelogs/v${{ steps.tag.outputs.version }}.md" CHANGELOG_BRANCH="changelog-v${{ steps.tag.outputs.version }}" @@ -341,7 +352,7 @@ jobs: if: steps.check_changelog.outputs.exists == 'false' uses: actions/github-script@v7 with: - github-token: ${{ secrets.GH_PAT }} + github-token: ${{ steps.app-token.outputs.token }} script: | const version = '${{ steps.tag.outputs.version }}'; const changelogBranch = `changelog-v${version}`; @@ -400,6 +411,14 @@ jobs: permissions: contents: read steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.COZYSTACK_CI_APP_ID }} + private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }} + owner: cozystack + - name: Parse tag id: tag uses: actions/github-script@v7 @@ -419,19 +438,19 @@ jobs: uses: actions/checkout@v4 with: repository: cozystack/website - token: ${{ secrets.GH_PAT }} + token: ${{ steps.app-token.outputs.token }} ref: main - name: Update docs from release branch env: - GH_TOKEN: ${{ secrets.GH_PAT }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: make update-all BRANCH=release-${{ steps.tag.outputs.version }} RELEASE_TAG=${{ steps.tag.outputs.tag }} - name: Commit and push id: commit run: | - git config user.name "cozystack-bot" - git config user.email "217169706+cozystack-bot@users.noreply.github.com" + git config user.name "cozystack-ci[bot]" + git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com" git add content if git diff --cached --quiet; then echo "No changes to commit" @@ -448,7 +467,7 @@ jobs: - name: Open pull request if: steps.commit.outputs.changed == 'true' env: - GH_TOKEN: ${{ secrets.GH_PAT }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | BRANCH="update-docs-v${{ steps.tag.outputs.version }}" pr_state=$(gh pr view "$BRANCH" --repo cozystack/website --json state --jq .state 2>/dev/null || echo "") diff --git a/Makefile b/Makefile index ce6eb7b3..59a55bfb 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ build: build-deps make -C packages/system/dashboard image make -C packages/system/metallb image make -C packages/system/kamaji image + make -C packages/system/multus image make -C packages/system/bucket image make -C packages/system/objectstorage-controller image make -C packages/system/grafana-operator image diff --git a/hack/common-envs.mk b/hack/common-envs.mk index 56eb5478..9b8db5db 100644 --- a/hack/common-envs.mk +++ b/hack/common-envs.mk @@ -6,13 +6,13 @@ else endif REGISTRY ?= ghcr.io/cozystack/cozystack -TAG = $(shell git describe --tags --exact-match 2>/dev/null || echo latest) +TAG = $(shell git describe --tags --exact-match --match 'v*' 2>/dev/null || echo latest) PUSH := 1 LOAD := 0 BUILDER ?= PLATFORM ?= BUILDX_EXTRA_ARGS ?= -COZYSTACK_VERSION = $(patsubst v%,%,$(shell git describe --tags)) +COZYSTACK_VERSION = $(patsubst v%,%,$(shell git describe --tags --match 'v*')) BUILDX_ARGS := --provenance=false --push=$(PUSH) --load=$(LOAD) \ --label org.opencontainers.image.source=https://github.com/cozystack/cozystack \ @@ -28,6 +28,6 @@ endef ifeq ($(COZYSTACK_VERSION),) $(shell git remote add upstream https://github.com/cozystack/cozystack.git || true) $(shell git fetch upstream --tags) - COZYSTACK_VERSION = $(patsubst v%,%,$(shell git describe --tags)) + COZYSTACK_VERSION = $(patsubst v%,%,$(shell git describe --tags --match 'v*')) endif diff --git a/packages/apps/harbor/templates/ingress.yaml b/packages/apps/harbor/templates/ingress.yaml index bb6ef07c..70933f5f 100644 --- a/packages/apps/harbor/templates/ingress.yaml +++ b/packages/apps/harbor/templates/ingress.yaml @@ -15,7 +15,7 @@ metadata: nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/backend-protocol: "HTTP" {{- if eq $solver "http01" }} - acme.cert-manager.io/http01-ingress-class: {{ $ingress }} + acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }} {{- end }} cert-manager.io/cluster-issuer: {{ $clusterIssuer }} spec: diff --git a/packages/apps/http-cache/images/nginx-cache.tag b/packages/apps/http-cache/images/nginx-cache.tag index 1da929ec..b08a374e 100644 --- a/packages/apps/http-cache/images/nginx-cache.tag +++ b/packages/apps/http-cache/images/nginx-cache.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:2f987017ff95d3c782e16ef0d99928831f520daf154d08a2fa38c2d68363e036 +ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:d397781152ab9123b11b8191d92eba7a0d2faa376aa2c15ddeb67842a9b59bab diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag index e623b778..60d536ce 100644 --- a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:5b209248acba3d8cf0d999edd8a915915a2e565a1c8ac1f5dde0dffec20fa02e +ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:0e70f4391b344c71a9567e1ed22ca24910116a619cb681381059b8c39f382219 diff --git a/packages/apps/kubernetes/images/ubuntu-container-disk-v1.30.tag b/packages/apps/kubernetes/images/ubuntu-container-disk-v1.30.tag index 2d1e4d52..f49fffe6 100644 --- a/packages/apps/kubernetes/images/ubuntu-container-disk-v1.30.tag +++ b/packages/apps/kubernetes/images/ubuntu-container-disk-v1.30.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.30@sha256:51e272db23d64eb20bc44e38cdbb70199fd00a49a958b76c2a89d4a46fec9256 +ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.30@sha256:f460ed1fd5a721aed423e6c3b8be0105d7f693cd86ad992d9c15fd4b27e58cec diff --git a/packages/apps/kubernetes/images/ubuntu-container-disk-v1.31.tag b/packages/apps/kubernetes/images/ubuntu-container-disk-v1.31.tag index 9698df5c..b5bdc79a 100644 --- a/packages/apps/kubernetes/images/ubuntu-container-disk-v1.31.tag +++ b/packages/apps/kubernetes/images/ubuntu-container-disk-v1.31.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.31@sha256:31c3290305159fe484caf6c5780960cf071dc3939528295336da4abfa731a81b +ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.31@sha256:aaa2dfa8ee53ae26295f44d2491330f51412457bbe4aa6ba256297cc0bd8a0da diff --git a/packages/apps/kubernetes/images/ubuntu-container-disk-v1.32.tag b/packages/apps/kubernetes/images/ubuntu-container-disk-v1.32.tag index 90545820..ca285644 100644 --- a/packages/apps/kubernetes/images/ubuntu-container-disk-v1.32.tag +++ b/packages/apps/kubernetes/images/ubuntu-container-disk-v1.32.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.32@sha256:9efb1fe18cffb045530b43ac4b39910f1802d3c16798d733165dcd1c67de9237 +ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.32@sha256:7acb4728ef6b9c0ff3344bf486bb00f2a083f2098452344a362242235013db01 diff --git a/packages/apps/kubernetes/images/ubuntu-container-disk-v1.33.tag b/packages/apps/kubernetes/images/ubuntu-container-disk-v1.33.tag index 64345fab..bb58690a 100644 --- a/packages/apps/kubernetes/images/ubuntu-container-disk-v1.33.tag +++ b/packages/apps/kubernetes/images/ubuntu-container-disk-v1.33.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.33@sha256:a0afca1d6f720cfff764a1bc912bfe404b5e97ff731f6c126a7abdb51ed23f85 +ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.33@sha256:a2024339ab9edb980a96b43ad2744b7aa31afaef0983ddbc6a546068b4cfa87a diff --git a/packages/apps/kubernetes/images/ubuntu-container-disk-v1.34.tag b/packages/apps/kubernetes/images/ubuntu-container-disk-v1.34.tag index f6916755..36eed7a7 100644 --- a/packages/apps/kubernetes/images/ubuntu-container-disk-v1.34.tag +++ b/packages/apps/kubernetes/images/ubuntu-container-disk-v1.34.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.34@sha256:51dfe5ce3e2f8765fde9422ae7e9fba677016ec5e2be41559c5f11a56958b0e6 +ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.34@sha256:97bcf946a5687889c6a421d14e5adece11f0978151b2165dc87b28f77f7607db diff --git a/packages/apps/kubernetes/images/ubuntu-container-disk-v1.35.tag b/packages/apps/kubernetes/images/ubuntu-container-disk-v1.35.tag index b80ce821..faae83d2 100644 --- a/packages/apps/kubernetes/images/ubuntu-container-disk-v1.35.tag +++ b/packages/apps/kubernetes/images/ubuntu-container-disk-v1.35.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.35@sha256:c4ae418b2a2c139794cd9630c3b2c2171870cc7748ac200344d2c4ed4283cf0b +ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.35@sha256:364c6d454891f1eb1a598fddb69cf328a14dbc451a8ac65812b038a7756da60a diff --git a/packages/apps/kubernetes/templates/cluster.yaml b/packages/apps/kubernetes/templates/cluster.yaml index 7d71858b..10d6fd80 100644 --- a/packages/apps/kubernetes/templates/cluster.yaml +++ b/packages/apps/kubernetes/templates/cluster.yaml @@ -70,6 +70,8 @@ spec: memory: guest: {{ .group.resources.memory }} {{- end }} + resources: + {{- include "cozy-lib.resources.sanitize" (list (dict "ephemeral-storage" .group.ephemeralStorage) $) | nindent 14 }} evictionStrategy: External volumes: - name: system diff --git a/packages/apps/mariadb/images/mariadb-backup.tag b/packages/apps/mariadb/images/mariadb-backup.tag index 1e381661..6c830892 100644 --- a/packages/apps/mariadb/images/mariadb-backup.tag +++ b/packages/apps/mariadb/images/mariadb-backup.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/mariadb-backup:0.0.0@sha256:0ddbbec0568dcb9fbc317cd9cc654e826dbe88ba3f184fa9b6b58aacb93b4570 +ghcr.io/cozystack/cozystack/mariadb-backup:0.0.0@sha256:3841eb171416711977dea0cf8cd45d32344caac9727af760c37d5e1dd41ee4bb diff --git a/packages/apps/vm-instance/templates/service.yaml b/packages/apps/vm-instance/templates/service.yaml index b12f7612..b7e3a420 100644 --- a/packages/apps/vm-instance/templates/service.yaml +++ b/packages/apps/vm-instance/templates/service.yaml @@ -7,6 +7,7 @@ metadata: apps.cozystack.io/user-service: "true" {{- include "virtual-machine.labels" . | nindent 4 }} {{- if .Values.external }} + service.kubernetes.io/service-proxy-name: "cozy-proxy" annotations: networking.cozystack.io/wholeIP: "true" {{- end }} diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 13c8fd4e..94db9e26 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,9 +1,9 @@ cozystackOperator: # Deployment variant: talos, generic, hosted variant: talos - image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.2.0@sha256:dbc0cbd99dbc2405769bed0d33d5e5d3f53e79e0269ad9ddaa26d9eebbcc6da2 + image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.2.4@sha256:1cd36a4643ecf4f06782f050ff9544b316ea52af67d738bf593f40e60c4acbec platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages' - platformSourceRef: 'digest=sha256:c659905000e8279d32965169cde0817e16a5b46f81d305a3606defcd5446abae' + platformSourceRef: 'digest=sha256:98d595ded030ee0b71ff6570419b81ae6cd1bcc8fb7a31c5e29556fbdfa5ec4a' # Generic variant configuration (only used when cozystackOperator.variant=generic) cozystack: # Kubernetes API server host (IP only, no protocol/port) diff --git a/packages/core/platform/images/migrations/migrations/37 b/packages/core/platform/images/migrations/migrations/37 new file mode 100755 index 00000000..856da262 --- /dev/null +++ b/packages/core/platform/images/migrations/migrations/37 @@ -0,0 +1,78 @@ +#!/bin/bash +# Migration 37 --> 38 +# Pin PostgreSQL image to 17.7-standard-trixie for system databases. +# +# This migration updates the imageName for all CNPG clusters belonging to +# system components (keycloak-db, grafana-db, alerta-db, seaweedfs-db, and +# harbor's dynamically-named DB): +# - If imageName is not set: pins to 17.7-standard-trixie +# - If imageName has any PG 17 tag: forces 17.7-standard-trixie +# - If imageName has a bare version tag (e.g. :18.1): appends -standard-trixie +# +# NOTE: This migration ONLY updates system CNPG Cluster resources (clusters.postgresql.cnpg.io), +# NOT user-created Postgres applications. + +set -euo pipefail + +TARGET_IMAGE="ghcr.io/cloudnative-pg/postgresql:17.7-standard-trixie" + +# Static system database names +STATIC_DB_NAMES="keycloak-db grafana-db alerta-db seaweedfs-db" + +echo "=== Updating PostgreSQL images for system databases ===" +echo "Target image: $TARGET_IMAGE" + +# Fetch all CNPG clusters with their name, namespace, imageName, and Helm release annotation in one call +ALL_CLUSTERS=$(kubectl get clusters.postgresql.cnpg.io -A \ + -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.imageName}{"\t"}{.metadata.annotations.meta\.helm\.sh/release-name}{"\n"}{end}' 2>/dev/null || true) + +while IFS=$'\t' read -r NAMESPACE CLUSTER_NAME CURRENT_IMAGE HELM_RELEASE; do + [ -z "$NAMESPACE" ] && continue + + # Check if cluster name matches one of the static system databases + MATCH=false + for db_name in $STATIC_DB_NAMES; do + if [ "$CLUSTER_NAME" = "$db_name" ]; then + MATCH=true + break + fi + done + + # For harbor: match clusters ending with -db whose Helm release ends with -system + if [ "$MATCH" = false ] && [[ "$CLUSTER_NAME" == *-db ]] && [[ "$HELM_RELEASE" == *-system ]]; then + MATCH=true + fi + + if [ "$MATCH" = false ]; then + continue + fi + + PATCH_IMAGE="" + + if [ -z "$CURRENT_IMAGE" ]; then + # imageName not set — pin to target + PATCH_IMAGE="$TARGET_IMAGE" + echo "PATCH $NAMESPACE/$CLUSTER_NAME: imageName not set, setting to $PATCH_IMAGE" + elif [[ "$CURRENT_IMAGE" =~ :17\. ]]; then + # Any PG 17 image — force to the pinned 17.7-standard-trixie + PATCH_IMAGE="$TARGET_IMAGE" + echo "PATCH $NAMESPACE/$CLUSTER_NAME: PG 17 detected, $CURRENT_IMAGE -> $PATCH_IMAGE" + elif [[ "$CURRENT_IMAGE" =~ :[0-9]+\.[0-9]+$ ]]; then + # Bare version tag for other majors (e.g. :18.1) — append -standard-trixie suffix + PATCH_IMAGE="${CURRENT_IMAGE}-standard-trixie" + echo "PATCH $NAMESPACE/$CLUSTER_NAME: bare tag detected, $CURRENT_IMAGE -> $PATCH_IMAGE" + else + echo "SKIP $NAMESPACE/$CLUSTER_NAME: imageName already set to $CURRENT_IMAGE" + continue + fi + + kubectl patch clusters.postgresql.cnpg.io -n "$NAMESPACE" "$CLUSTER_NAME" \ + --type=merge \ + --patch "{\"spec\":{\"imageName\":\"${PATCH_IMAGE}\"}}" +done <<< "$ALL_CLUSTERS" + +echo "=== PostgreSQL image update completed ===" + +# Stamp version +kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=38 --dry-run=client -o yaml | kubectl apply -f- diff --git a/packages/core/platform/sources/networking.yaml b/packages/core/platform/sources/networking.yaml index 8e8485b3..bece76f8 100644 --- a/packages/core/platform/sources/networking.yaml +++ b/packages/core/platform/sources/networking.yaml @@ -66,6 +66,7 @@ spec: path: system/cilium valuesFiles: - values.yaml + - values-apparmor.yaml install: privileged: true namespace: cozy-cilium @@ -117,6 +118,7 @@ spec: path: system/cilium valuesFiles: - values.yaml + - values-apparmor.yaml - values-kubeovn.yaml install: privileged: true diff --git a/packages/core/platform/templates/_helpers.tpl b/packages/core/platform/templates/_helpers.tpl index 684ee812..4bc02429 100644 --- a/packages/core/platform/templates/_helpers.tpl +++ b/packages/core/platform/templates/_helpers.tpl @@ -13,6 +13,8 @@ apiVersion: cozystack.io/v1alpha1 kind: Package metadata: name: {{ $name }} + annotations: + helm.sh/resource-policy: keep spec: variant: {{ $variant }} {{- if $components }} @@ -40,6 +42,8 @@ apiVersion: cozystack.io/v1alpha1 kind: Package metadata: name: {{ $name }} + annotations: + helm.sh/resource-policy: keep spec: variant: {{ $variant }} {{- end }} diff --git a/packages/core/platform/templates/apps.yaml b/packages/core/platform/templates/apps.yaml index 16ef3c35..7ee0de52 100644 --- a/packages/core/platform/templates/apps.yaml +++ b/packages/core/platform/templates/apps.yaml @@ -36,6 +36,9 @@ stringData: branding: {{- . | toYaml | nindent 8 }} {{- end }} + cpu-allocation-ratio: {{ .Values.resources.cpuAllocationRatio | quote }} + memory-allocation-ratio: {{ .Values.resources.memoryAllocationRatio | quote }} + ephemeral-storage-allocation-ratio: {{ .Values.resources.ephemeralStorageAllocationRatio | quote }} {{- with .Values.scheduling }} scheduling: {{- . | toYaml | nindent 8 }} diff --git a/packages/core/platform/templates/bundles/iaas.yaml b/packages/core/platform/templates/bundles/iaas.yaml index 41dc5181..bd002c16 100644 --- a/packages/core/platform/templates/bundles/iaas.yaml +++ b/packages/core/platform/templates/bundles/iaas.yaml @@ -2,7 +2,11 @@ {{- fail "bundles.iaas.enabled can only be true when bundles.system.variant is 'isp-full' or 'isp-full-generic'" }} {{- end }} {{- if and .Values.bundles.iaas.enabled (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-full-generic")) }} -{{include "cozystack.platform.package.default" (list "cozystack.kubevirt" $) }} +{{- $kubevirtComponents := dict -}} +{{- if .Values.resources.cpuAllocationRatio -}} +{{- $_ := set $kubevirtComponents "kubevirt" (dict "values" (dict "cpuAllocationRatio" (.Values.resources.cpuAllocationRatio | int))) -}} +{{- end -}} +{{include "cozystack.platform.package" (list "cozystack.kubevirt" "default" $ $kubevirtComponents) }} {{include "cozystack.platform.package.default" (list "cozystack.kubevirt-cdi" $) }} {{include "cozystack.platform.package.optional.default" (list "cozystack.gpu-operator" $) }} {{include "cozystack.platform.package.default" (list "cozystack.kamaji" $) }} diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 8baa6845..9a7dc681 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -5,8 +5,8 @@ sourceRef: path: / migrations: enabled: false - image: ghcr.io/cozystack/cozystack/platform-migrations:v1.2.0@sha256:3a3d8cfa4323d8023b7c7800d8b552b250bc2de01ca7550fd024a10b12324f6d - targetVersion: 36 + image: ghcr.io/cozystack/cozystack/platform-migrations:v1.2.4@sha256:72af209576eb42793dbf241c024d713fd23cc1435673dc2f9a81f70c237df124 + targetVersion: 38 # Bundle deployment configuration bundles: system: diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index 8a1887bd..1e4cebd1 100644 --- a/packages/core/testing/values.yaml +++ b/packages/core/testing/values.yaml @@ -1,2 +1,2 @@ e2e: - image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.2.0@sha256:7964a3e4b11053887be201d62f94138c3a7836c861036c26fb833aa1fcb934e1 + image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.2.4@sha256:0367a03b981df2a3ea13f411d4cb7869c2bf2c89c07d3d5c8971b9a28921ccef diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index 67beab8c..30b47e5b 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v1.2.0@sha256:313f36db2e8b9dea0cd1a80d73f27a0694e996631d430bbd1b8391fbb9859020 +ghcr.io/cozystack/cozystack/matchbox:v1.2.4@sha256:a7bfd50d2b18eaf644c5b84e95bb115b07479feed8fe2664ecf2810766dc322b diff --git a/packages/extra/bootbox/templates/matchbox/ingress.yaml b/packages/extra/bootbox/templates/matchbox/ingress.yaml index 4262cd10..5eef3489 100644 --- a/packages/extra/bootbox/templates/matchbox/ingress.yaml +++ b/packages/extra/bootbox/templates/matchbox/ingress.yaml @@ -10,7 +10,7 @@ metadata: app: bootbox annotations: {{- if eq $solver "http01" }} - acme.cert-manager.io/http01-ingress-class: {{ $ingress }} + acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }} {{- end }} cert-manager.io/cluster-issuer: {{ $clusterIssuer }} {{- if .Values.whitelistHTTP }} diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index b4262b02..80c02ada 100644 --- a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag +++ b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.2.0@sha256:b508ca451c6051b64e71cedd6209c80ac0e0bea8aa6bb47e838ec9d99bf4796a +ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.2.4@sha256:cc1f60ef2cb40ab61c4787e234f83e57a7b8aca0ffe7b17f4c4fa6e3de1669f4 diff --git a/packages/extra/seaweedfs/templates/seaweedfs.yaml b/packages/extra/seaweedfs/templates/seaweedfs.yaml index 42441b26..2f5720ca 100644 --- a/packages/extra/seaweedfs/templates/seaweedfs.yaml +++ b/packages/extra/seaweedfs/templates/seaweedfs.yaml @@ -243,7 +243,7 @@ spec: nginx.ingress.kubernetes.io/proxy-body-size: "0" nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" {{- if eq $solver "http01" }} - acme.cert-manager.io/http01-ingress-class: {{ $ingress }} + acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }} {{- end }} cert-manager.io/cluster-issuer: {{ $clusterIssuer }} tls: diff --git a/packages/system/backup-controller/values.yaml b/packages/system/backup-controller/values.yaml index a868fba0..58c23f97 100644 --- a/packages/system/backup-controller/values.yaml +++ b/packages/system/backup-controller/values.yaml @@ -1,5 +1,5 @@ backupController: - image: "ghcr.io/cozystack/cozystack/backup-controller:v1.2.0@sha256:07bcd89bd725951801870d819f0fc536705069608f0fe5c20617607af6f8a61a" + image: "ghcr.io/cozystack/cozystack/backup-controller:v1.2.4@sha256:318bd6986190142b1a46de2df63975931d6669c138831c9da51a324439097689" replicas: 2 debug: false metrics: diff --git a/packages/system/backupstrategy-controller/values.yaml b/packages/system/backupstrategy-controller/values.yaml index 39466430..0ad78804 100644 --- a/packages/system/backupstrategy-controller/values.yaml +++ b/packages/system/backupstrategy-controller/values.yaml @@ -1,5 +1,5 @@ backupStrategyController: - image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.2.0@sha256:0039ec58f531faf5a5ba3f70993c2c71d74dae4cc8e8e83bd21d2cc2adf10d8a" + image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.2.4@sha256:e620387a891d7834d77d09d7106f7ab7d4c0dc9f288cafb484b6bfe1a7c47f28" replicas: 2 debug: false metrics: diff --git a/packages/system/bucket/images/s3manager.tag b/packages/system/bucket/images/s3manager.tag index 6c3d1449..1423ee5c 100644 --- a/packages/system/bucket/images/s3manager.tag +++ b/packages/system/bucket/images/s3manager.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:73382d7911274e6528cc2272ca9bfdb095c59e78845a3cdffd39d6c8ac29d920 +ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:be364c6ebc27b8dc51f41965ed56523a6a03bb66e95d25cc5b6aaaf4e203bde4 diff --git a/packages/system/bucket/templates/ingress.yaml b/packages/system/bucket/templates/ingress.yaml index 6e5028fc..b7ffaf8b 100644 --- a/packages/system/bucket/templates/ingress.yaml +++ b/packages/system/bucket/templates/ingress.yaml @@ -12,7 +12,7 @@ metadata: nginx.ingress.kubernetes.io/proxy-read-timeout: "99999" nginx.ingress.kubernetes.io/proxy-send-timeout: "99999" {{- if eq $solver "http01" }} - acme.cert-manager.io/http01-ingress-class: {{ $ingress }} + acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }} {{- end }} cert-manager.io/cluster-issuer: {{ $clusterIssuer }} spec: diff --git a/packages/system/cert-manager-issuers/templates/cluster-issuers.yaml b/packages/system/cert-manager-issuers/templates/cluster-issuers.yaml index 442f1fb3..3b582082 100644 --- a/packages/system/cert-manager-issuers/templates/cluster-issuers.yaml +++ b/packages/system/cert-manager-issuers/templates/cluster-issuers.yaml @@ -1,6 +1,7 @@ {{- $solver := (index .Values._cluster "solver") | default "http01" }} +{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} -apiVersion: cert-manager.io/v1 +apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod @@ -17,9 +18,9 @@ spec: name: cloudflare-api-token-secret key: api-token {{- else }} - http01: - ingress: - class: nginx + http01: + ingress: + ingressClassName: {{ $exposeIngress }} {{- end }} --- @@ -41,9 +42,9 @@ spec: name: cloudflare-api-token-secret key: api-token {{- else }} - http01: - ingress: - class: nginx + http01: + ingress: + ingressClassName: {{ $exposeIngress }} {{- end }} --- diff --git a/packages/system/cilium/Makefile b/packages/system/cilium/Makefile index 07d4ca97..71b47a77 100644 --- a/packages/system/cilium/Makefile +++ b/packages/system/cilium/Makefile @@ -12,6 +12,15 @@ update: helm repo update cilium helm pull cilium/cilium --untar --untardir charts --version 1.19 $(SED_INPLACE) -e '/Used in iptables/d' -e '/SYS_MODULE/d' charts/cilium/values.yaml + # Drop the whole k8s<1.30 AppArmor annotations block from the cilium-agent + # DaemonSet. Cozystack manages these annotations through cilium.podAnnotations + # in values.yaml on every k8s version, so keeping the upstream block would + # produce duplicate mapping keys on k8s<1.30. + perl -i -0pe 's|\n \{\{- if not \.Values\.securityContext\.privileged \}\}\n \{\{- if semverCompare "<1\.30\.0".*?\n \{\{- end \}\}\n \{\{- end \}\}\n \{\{- end \}\}||s' \ + charts/cilium/templates/cilium-agent/daemonset.yaml + @! grep -q 'container\.apparmor\.security\.beta\.kubernetes\.io' \ + charts/cilium/templates/cilium-agent/daemonset.yaml || \ + { echo 'ERROR: perl patch did not remove the upstream AppArmor block from cilium-agent/daemonset.yaml (upstream template format may have changed)' >&2; exit 1; } version=$$(awk '$$1 == "version:" {print $$2}' charts/cilium/Chart.yaml) && \ $(SED_INPLACE) "s/ARG VERSION=.*/ARG VERSION=v$${version}/" images/cilium/Dockerfile diff --git a/packages/system/cilium/charts/cilium/templates/cilium-agent/daemonset.yaml b/packages/system/cilium/charts/cilium/templates/cilium-agent/daemonset.yaml index f9a4cec7..fa3afc14 100644 --- a/packages/system/cilium/charts/cilium/templates/cilium-agent/daemonset.yaml +++ b/packages/system/cilium/charts/cilium/templates/cilium-agent/daemonset.yaml @@ -57,19 +57,6 @@ spec: # ensure pods roll when configmap updates cilium.io/cilium-configmap-checksum: {{ include (print $.Template.BasePath "/cilium-configmap.yaml") . | sha256sum | quote }} {{- end }} - {{- if not .Values.securityContext.privileged }} - {{- if semverCompare "<1.30.0" (printf "%d.%d.0" (semver .Capabilities.KubeVersion.Version).Major (semver .Capabilities.KubeVersion.Version).Minor) }} - # Set app AppArmor's profile to "unconfined". The value of this annotation - # can be modified as long users know which profiles they have available - # in AppArmor. - container.apparmor.security.beta.kubernetes.io/cilium-agent: "unconfined" - container.apparmor.security.beta.kubernetes.io/clean-cilium-state: "unconfined" - {{- if .Values.cgroup.autoMount.enabled }} - container.apparmor.security.beta.kubernetes.io/mount-cgroup: "unconfined" - container.apparmor.security.beta.kubernetes.io/apply-sysctl-overwrites: "unconfined" - {{- end }} - {{- end }} - {{- end }} {{- with .Values.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/packages/system/cilium/values-apparmor.yaml b/packages/system/cilium/values-apparmor.yaml new file mode 100644 index 00000000..0712836c --- /dev/null +++ b/packages/system/cilium/values-apparmor.yaml @@ -0,0 +1,20 @@ +cilium: + # Opt out of the cri-containerd.apparmor.d profile for containers that invoke + # nsenter to join the host cgroup/mount namespace. The kernel reports the + # denial as a blocked "ptrace" operation. Required on Ubuntu 22.04+ and + # other distros that load this AppArmor profile by default, where the + # denial otherwise puts cilium-agent into Init:CrashLoopBackOff. + # The deprecated annotations are used because k8s >= 1.30 pod-level + # appArmorProfile: Unconfined is not honoured by containerd CRI today + # (kubernetes/kubernetes#125069). + # Only applied on non-Talos cilium variants where cgroup.autoMount.enabled is + # true — on Talos mount-cgroup is not rendered and the kube-apiserver would + # reject an annotation referencing a non-existent container. + # mount-bpf-fs is intentionally excluded: it renders with its own + # securityContext.privileged=true, and the kernel does not apply AppArmor + # profiles to privileged containers. + podAnnotations: + container.apparmor.security.beta.kubernetes.io/cilium-agent: unconfined + container.apparmor.security.beta.kubernetes.io/clean-cilium-state: unconfined + container.apparmor.security.beta.kubernetes.io/mount-cgroup: unconfined + container.apparmor.security.beta.kubernetes.io/apply-sysctl-overwrites: unconfined diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index cafd81c9..543244ba 100644 --- a/packages/system/cozystack-api/values.yaml +++ b/packages/system/cozystack-api/values.yaml @@ -1,3 +1,3 @@ cozystackAPI: - image: ghcr.io/cozystack/cozystack/cozystack-api:v1.2.0@sha256:b40ba5c87bb625d646dd69075d21dcc1aca18775b3de8e63d04466d60d42cee5 + image: ghcr.io/cozystack/cozystack/cozystack-api:v1.2.4@sha256:daf48b09d79df112953a43d6cfdb10a4440dd63779ecbfa62436805076dcf977 replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index ab64e34d..1f212987 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -1,4 +1,4 @@ cozystackController: - image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.2.0@sha256:f56c8222411fed890331502939c8d4c5700489e697da39e8e46896cf674cce71 + image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.2.4@sha256:e2472fd4f12efd3a7763f76b276e5a5f465031fa8bb113df585ce4b8ebede717 debug: false disableTelemetry: false diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index 69541202..f02ee812 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig := .Values._cluster.branding | default dict }} -{{- $tenantText := "v1.2.0" }} +{{- $tenantText := "v1.2.4" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/templates/ingress.yaml b/packages/system/dashboard/templates/ingress.yaml index a10797fb..6cf01490 100644 --- a/packages/system/dashboard/templates/ingress.yaml +++ b/packages/system/dashboard/templates/ingress.yaml @@ -11,7 +11,7 @@ metadata: annotations: cert-manager.io/cluster-issuer: {{ $clusterIssuer }} {{- if eq $solver "http01" }} - acme.cert-manager.io/http01-ingress-class: {{ $exposeIngress }} + acme.cert-manager.io/http01-ingress-ingressclassname: {{ $exposeIngress }} {{- end }} nginx.ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/client-max-body-size: 100m diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index 5246eb1c..70d337f3 100644 --- a/packages/system/dashboard/values.yaml +++ b/packages/system/dashboard/values.yaml @@ -1,6 +1,6 @@ openapiUI: - image: ghcr.io/cozystack/cozystack/openapi-ui:v1.2.0@sha256:bf68676a809be8b37ae3ab7416b2ae3185a4efa216b8b09358d916df081d0a3e + image: ghcr.io/cozystack/cozystack/openapi-ui:v1.2.4@sha256:ee273aaacc6fcb58c180cbdc58c047fd85068724c6478ea0cdfe7b8396877078 openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.2.0@sha256:89b5c62df0ed9a1984023ba909765e4b60af3f9a61e42ebdd67437d71ebbb645 + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.2.4@sha256:7dd82ebe1d0c1925bbc440bc65a86852f3dd2bc0f6f12856904df847f15913d3 tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v1.2.0@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc + image: ghcr.io/cozystack/cozystack/token-proxy:v1.2.4@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc diff --git a/packages/system/grafana-operator/images/grafana-dashboards.tag b/packages/system/grafana-operator/images/grafana-dashboards.tag index 60839fd4..b2b5211d 100644 --- a/packages/system/grafana-operator/images/grafana-dashboards.tag +++ b/packages/system/grafana-operator/images/grafana-dashboards.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/grafana-dashboards:v1.2.0@sha256:2c9aa0b48e2bf6167db198f4d15882bfe51700108edf2e9f6d0942940a2c1204 +ghcr.io/cozystack/cozystack/grafana-dashboards:v1.2.4@sha256:2c9aa0b48e2bf6167db198f4d15882bfe51700108edf2e9f6d0942940a2c1204 diff --git a/packages/system/harbor/templates/database.yaml b/packages/system/harbor/templates/database.yaml index b1221e1c..9a948601 100644 --- a/packages/system/harbor/templates/database.yaml +++ b/packages/system/harbor/templates/database.yaml @@ -5,6 +5,9 @@ metadata: name: {{ .Values.harbor.fullnameOverride }}-db spec: instances: {{ .Values.db.replicas }} + {{- $existingCluster := lookup "postgresql.cnpg.io/v1" "Cluster" .Release.Namespace (printf "%s-db" .Values.harbor.fullnameOverride) }} + {{- $image := dig "spec" "imageName" "ghcr.io/cloudnative-pg/postgresql:17.7-standard-trixie" $existingCluster }} + imageName: {{ if regexMatch ":17\\." $image }}ghcr.io/cloudnative-pg/postgresql:17.7-standard-trixie{{ else if regexMatch ":[0-9]+\\.[0-9]+$" $image }}{{ printf "%s-standard-trixie" $image }}{{ else }}{{ $image }}{{ end }} storage: size: {{ .Values.db.size }} {{- with .Values.db.storageClass }} diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index 63970e8f..e9f50984 100644 --- a/packages/system/kamaji/values.yaml +++ b/packages/system/kamaji/values.yaml @@ -3,7 +3,7 @@ kamaji: deploy: false image: pullPolicy: IfNotPresent - tag: v1.2.0@sha256:071f5f893f16918e6f03c3cf42315a3bce1c7624f10c155f3b3e6dfcd4d60c2a + tag: v1.2.4@sha256:441ead7178dd429a16cebe506a9df489a3cd38c8fb8255437f6927b2dd0a7a04 repository: ghcr.io/cozystack/cozystack/kamaji resources: limits: @@ -13,4 +13,4 @@ kamaji: cpu: 100m memory: 100Mi extraArgs: - - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v1.2.0@sha256:071f5f893f16918e6f03c3cf42315a3bce1c7624f10c155f3b3e6dfcd4d60c2a + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v1.2.4@sha256:441ead7178dd429a16cebe506a9df489a3cd38c8fb8255437f6927b2dd0a7a04 diff --git a/packages/system/keycloak/templates/db.yaml b/packages/system/keycloak/templates/db.yaml index 1cd98ffc..6a57e93a 100644 --- a/packages/system/keycloak/templates/db.yaml +++ b/packages/system/keycloak/templates/db.yaml @@ -4,6 +4,9 @@ metadata: name: keycloak-db spec: instances: 2 + {{- $existingCluster := lookup "postgresql.cnpg.io/v1" "Cluster" .Release.Namespace "keycloak-db" }} + {{- $image := dig "spec" "imageName" "ghcr.io/cloudnative-pg/postgresql:17.7-standard-trixie" $existingCluster }} + imageName: {{ if regexMatch ":17\\." $image }}ghcr.io/cloudnative-pg/postgresql:17.7-standard-trixie{{ else if regexMatch ":[0-9]+\\.[0-9]+$" $image }}{{ printf "%s-standard-trixie" $image }}{{ else }}{{ $image }}{{ end }} storage: size: 20Gi {{- if .Values._cluster.scheduling }} diff --git a/packages/system/keycloak/templates/ingress.yaml b/packages/system/keycloak/templates/ingress.yaml index aa282f71..7f9fd476 100644 --- a/packages/system/keycloak/templates/ingress.yaml +++ b/packages/system/keycloak/templates/ingress.yaml @@ -11,7 +11,7 @@ metadata: {{- with .Values.ingress.annotations }} annotations: {{- if eq $solver "http01" }} - acme.cert-manager.io/http01-ingress-class: {{ $exposeIngress }} + acme.cert-manager.io/http01-ingress-ingressclassname: {{ $exposeIngress }} {{- end }} cert-manager.io/cluster-issuer: {{ $clusterIssuer }} {{- toYaml . | nindent 4 }} diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index c52e699b..d545396f 100644 --- a/packages/system/kubeovn-plunger/values.yaml +++ b/packages/system/kubeovn-plunger/values.yaml @@ -1,4 +1,4 @@ portSecurity: true routes: "" -image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.2.0@sha256:39f9f0e6ec42afdd91082f7c025699425096244bb10f4e2f7a1d17155ac0cebb +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.2.4@sha256:174768084b30b070aaa411fd55f461439449a4e5fe0e31298986066c4fb2e104 ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index 8db1d9af..c0a406cc 100644 --- a/packages/system/kubeovn-webhook/values.yaml +++ b/packages/system/kubeovn-webhook/values.yaml @@ -1,3 +1,3 @@ portSecurity: true routes: "" -image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.2.0@sha256:e6334c29d3aaf0dea766c88e3e05b53ad623d1bb497b3c836e6f76adade45b29 +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.2.4@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a diff --git a/packages/system/kubevirt-csi-node/values.yaml b/packages/system/kubevirt-csi-node/values.yaml index ad7e6f01..8e38b7e2 100644 --- a/packages/system/kubevirt-csi-node/values.yaml +++ b/packages/system/kubevirt-csi-node/values.yaml @@ -1,3 +1,3 @@ storageClass: replicated csiDriver: - image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:5b209248acba3d8cf0d999edd8a915915a2e565a1c8ac1f5dde0dffec20fa02e + image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:0e70f4391b344c71a9567e1ed22ca24910116a619cb681381059b8c39f382219 diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index 0776861c..4390ce0b 100644 --- a/packages/system/lineage-controller-webhook/values.yaml +++ b/packages/system/lineage-controller-webhook/values.yaml @@ -1,5 +1,5 @@ lineageControllerWebhook: - image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.2.0@sha256:fdbd4ed09c2e825880e84b8668d8bb6e0795da5c51d7c20b9d5eb4abe31d3ae0 + image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.2.4@sha256:b7bd8300a06c87ba4f5815e240e6f9843d80baaddd1f549004ddbfe9858e9310 debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/linstor/Makefile b/packages/system/linstor/Makefile index b211baec..2ad4def9 100644 --- a/packages/system/linstor/Makefile +++ b/packages/system/linstor/Makefile @@ -4,7 +4,7 @@ export NAMESPACE=cozy-$(NAME) include ../../../hack/common-envs.mk include ../../../hack/package.mk -LINSTOR_VERSION ?= 1.32.3 +LINSTOR_VERSION ?= 1.33.2 LINSTOR_CSI_VERSION ?= v1.10.5 image: image-piraeus-server image-linstor-csi diff --git a/packages/system/linstor/images/piraeus-server/Dockerfile b/packages/system/linstor/images/piraeus-server/Dockerfile index 049a3f47..f93e494f 100644 --- a/packages/system/linstor/images/piraeus-server/Dockerfile +++ b/packages/system/linstor/images/piraeus-server/Dockerfile @@ -61,6 +61,8 @@ RUN test -d .gradlehome && echo ".gradlehome found in tarball" || (echo ".gradle # Build DEB packages from tarball # Override GRADLE_FLAGS to remove --offline flag, allowing Gradle to download missing dependencies RUN sed -i 's/GRADLE_FLAGS = --offline/GRADLE_FLAGS =/' debian/rules || true +# Skip dh_strip_nondeterminism to avoid failures on some JAR files (logback-core) +RUN printf '\noverride_dh_strip_nondeterminism:\n\ttrue\n' >> debian/rules RUN LD_LIBRARY_PATH='' dpkg-buildpackage -rfakeroot -b -uc # Copy built .deb packages to a location accessible from final image diff --git a/packages/system/linstor/images/piraeus-server/patches/README.md b/packages/system/linstor/images/piraeus-server/patches/README.md index 5ee29318..dfe75c2e 100644 --- a/packages/system/linstor/images/piraeus-server/patches/README.md +++ b/packages/system/linstor/images/piraeus-server/patches/README.md @@ -1,14 +1,17 @@ # LINSTOR Server Patches -Custom patches for piraeus-server (linstor-server) v1.32.3. +Custom patches for piraeus-server (linstor-server) v1.33.2. -- **adjust-on-resfile-change.diff** — Use actual device path in res file during toggle-disk; fix LUKS data offset - - Upstream: [#473](https://github.com/LINBIT/linstor-server/pull/473), [#472](https://github.com/LINBIT/linstor-server/pull/472) -- **allow-toggle-disk-retry.diff** — Allow retry and cancellation of failed toggle-disk operations - - Upstream: [#475](https://github.com/LINBIT/linstor-server/pull/475) -- **force-metadata-check-on-disk-add.diff** — Create metadata during toggle-disk from diskless to diskful - - Upstream: [#474](https://github.com/LINBIT/linstor-server/pull/474) -- **fix-duplicate-tcp-ports.diff** — Prevent duplicate TCP ports after toggle-disk operations - - Upstream: [#476](https://github.com/LINBIT/linstor-server/pull/476) -- **skip-adjust-when-device-inaccessible.diff** — Fix resources stuck in StandAlone after reboot, Unknown state race condition, and encrypted resource deletion - - Upstream: [#477](https://github.com/LINBIT/linstor-server/pull/477) +- **allow-toggle-disk-retry.diff** — Backport maintainer implementation of toggle-disk retry/abort + - Source PR/comment: [#475](https://github.com/LINBIT/linstor-server/pull/475), [maintainer note](https://github.com/LINBIT/linstor-server/pull/475#issuecomment-3949630419) + - Backported from upstream commit: [`3d97f71c9`](https://github.com/LINBIT/linstor-server/commit/3d97f71c95a493588d3d521c63eac4d846935fb3) +- **fix-duplicate-tcp-ports.diff** — Preserve DRBD TCP ports during toggle-disk and avoid redundant `ensureStackDataExists()` + - Source PR/review: [#476](https://github.com/LINBIT/linstor-server/pull/476), [review suggestion](https://github.com/LINBIT/linstor-server/pull/476#discussion_r3007725079) + - Backported from commits: [`79d6375c5`](https://github.com/kvaps/linstor-server/commit/79d6375c55d6181b35a7b7f0fe8dbdfb86e126cd), [`bcc89902f`](https://github.com/kvaps/linstor-server/commit/bcc89902f4f61ac1589dd07ebb7f5aae1935370d) +- **fix-luks-header-size.diff** — Account for LUKS2 `--offset`, metadata/keyslots sizing, and device `optimal_io_size` + - Source PR/comment: [#472](https://github.com/LINBIT/linstor-server/pull/472), [maintainer note](https://github.com/LINBIT/linstor-server/pull/472#issuecomment-3949687603) + - Backported from commits: [`ccc85fbd2`](https://github.com/LINBIT/linstor-server/commit/ccc85fbd2c65f0b97c52403fa80f1efdb886ec4e), [`71b601554`](https://github.com/LINBIT/linstor-server/commit/71b601554f41bcb50cd5bd06989c5b0d3a814acd) + - Note: upstream commit [`3d0402a0c`](https://github.com/LINBIT/linstor-server/commit/3d0402a0c25f0a4b57b380321f10e89982f26e7a) is already included in `v1.33.1` +- **retry-adjust-after-stale-bitmap.diff** — Retry `drbdadm adjust` after detaching a stale local bitmap state + - Source PR: [#491](https://github.com/LINBIT/linstor-server/pull/491) + - Backported from commit: [`51ae50a84`](https://github.com/kvaps/linstor-server/commit/51ae50a84dcb98093f543b819652c750a94d96c9) diff --git a/packages/system/linstor/images/piraeus-server/patches/adjust-on-resfile-change.diff b/packages/system/linstor/images/piraeus-server/patches/adjust-on-resfile-change.diff deleted file mode 100644 index 6788efaf..00000000 --- a/packages/system/linstor/images/piraeus-server/patches/adjust-on-resfile-change.diff +++ /dev/null @@ -1,48 +0,0 @@ -diff --git a/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/ConfFileBuilder.java b/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/ConfFileBuilder.java -index 36c52ccf8..c0bb7b967 100644 ---- a/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/ConfFileBuilder.java -+++ b/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/ConfFileBuilder.java -@@ -894,12 +894,16 @@ public class ConfFileBuilder - if (((Volume) vlmData.getVolume()).getFlags().isUnset(localAccCtx, Volume.Flags.DELETE)) - { - final String disk; -+ // Check if we're in toggle-disk operation (adding disk to diskless resource) -+ boolean isDiskAdding = vlmData.getVolume().getAbsResource().getStateFlags().isSomeSet( -+ localAccCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING); - if ((!isPeerRsc && vlmData.getDataDevice() == null) || - (isPeerRsc && - // FIXME: vlmData.getRscLayerObject().getFlags should be used here - vlmData.getVolume().getAbsResource().disklessForDrbdPeers(accCtx) - ) || -- (!isPeerRsc && -+ // For toggle-disk: if dataDevice is set and we're adding disk, use the actual device -+ (!isPeerRsc && !isDiskAdding && - // FIXME: vlmData.getRscLayerObject().getFlags should be used here - vlmData.getVolume().getAbsResource().isDrbdDiskless(accCtx) - ) -diff --git a/satellite/src/main/java/com/linbit/linstor/layer/luks/CryptSetupCommands.java b/satellite/src/main/java/com/linbit/linstor/layer/luks/CryptSetupCommands.java -index 54dd5c19f..018de58cf 100644 ---- a/satellite/src/main/java/com/linbit/linstor/layer/luks/CryptSetupCommands.java -+++ b/satellite/src/main/java/com/linbit/linstor/layer/luks/CryptSetupCommands.java -@@ -34,6 +34,9 @@ public class CryptSetupCommands implements Luks - private static final Version V2_1_0 = new Version(2, 1, 0); - private static final Version V2_0_0 = new Version(2, 0, 0); - private static final String PBDKF_MAX_MEMORY_KIB = "262144"; // 256 MiB -+ // Fixed LUKS2 data offset in 512-byte sectors (16 MiB = 32768 sectors) -+ // This ensures consistent LUKS header size across all nodes regardless of system defaults -+ private static final String LUKS2_DATA_OFFSET_SECTORS = "32768"; - - @SuppressWarnings("unused") - private final ErrorReporter errorReporter; -@@ -78,6 +81,11 @@ public class CryptSetupCommands implements Luks - command.add(CRYPTSETUP); - command.add("-q"); - command.add("luksFormat"); -+ // Always specify explicit offset to ensure consistent LUKS header size across all nodes -+ // Without this, different systems may create LUKS with different header sizes (16MiB vs 32MiB) -+ // which causes "Low.dev. smaller than requested DRBD-dev. size" errors during toggle-disk -+ command.add("--offset"); -+ command.add(LUKS2_DATA_OFFSET_SECTORS); - if (version.greaterOrEqual(V2_0_0)) - { - command.add("--pbkdf-memory"); diff --git a/packages/system/linstor/images/piraeus-server/patches/allow-toggle-disk-retry.diff b/packages/system/linstor/images/piraeus-server/patches/allow-toggle-disk-retry.diff index 264e0221..952ba16c 100644 --- a/packages/system/linstor/images/piraeus-server/patches/allow-toggle-disk-retry.diff +++ b/packages/system/linstor/images/piraeus-server/patches/allow-toggle-disk-retry.diff @@ -1,168 +1,232 @@ diff --git a/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java b/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java -index d93a18014..cc8ce4f04 100644 +index d93a18014..a944cb809 100644 --- a/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java +++ b/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java -@@ -57,7 +57,9 @@ import com.linbit.linstor.stateflags.StateFlags; - import com.linbit.linstor.storage.StorageException; - import com.linbit.linstor.storage.data.adapter.drbd.DrbdRscData; - import com.linbit.linstor.storage.interfaces.categories.resource.AbsRscLayerObject; -+import com.linbit.linstor.storage.interfaces.categories.resource.VlmProviderObject; - import com.linbit.linstor.storage.kinds.DeviceLayerKind; -+import com.linbit.linstor.storage.kinds.DeviceProviderKind; - import com.linbit.linstor.storage.utils.LayerUtils; - import com.linbit.linstor.tasks.AutoDiskfulTask; - import com.linbit.linstor.utils.layer.LayerRscUtils; -@@ -387,21 +389,84 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL +@@ -111,6 +111,14 @@ import reactor.util.function.Tuple2; + @Singleton + public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionListener + { ++ private enum ToggleDiskAction ++ { ++ NOOP, ++ NORMAL, ++ ABORT, ++ RETRY ++ } ++ + private final AccessContext apiCtx; + private final ScopeRunner scopeRunner; + private final BackgroundRunner backgroundRunner; +@@ -386,69 +394,33 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL + "Toggle Disk on %s/%s %s", nodeNameStr, rscNameStr, removeDisk ? "removing disk" : "adding disk"); Resource rsc = ctrlApiDataLoader.loadRsc(nodeName, rscName, true); ++ ResourceDefinition rscDfn = rsc.getResourceDefinition(); -+ // Allow retry of the same operation if the previous attempt failed -+ // (the requested flag remains set for retry on reconnection, but we should also allow manual retry) -+ // Also allow cancellation of a failed operation by requesting the opposite operation - if (hasDiskAddRequested(rsc)) - { +- if (hasDiskAddRequested(rsc)) +- { - throw new ApiRcException(ApiCallRcImpl.simpleEntry( - ApiConsts.FAIL_RSC_BUSY, - "Addition of disk to resource already requested", - true - )); -+ if (removeDisk) -+ { -+ // User wants to cancel the failed add-disk operation and go back to diskless -+ // Use the existing disk removal flow to properly cleanup storage on satellite -+ errorReporter.logInfo( -+ "Toggle Disk cancel on %s/%s - cancelling failed DISK_ADD_REQUESTED, reverting to diskless", -+ nodeNameStr, rscNameStr); -+ unmarkDiskAddRequested(rsc); -+ // Also clear DISK_ADDING if it was set -+ unmarkDiskAdding(rsc); -+ -+ // Set storage pool to diskless pool (overwrite the diskful pool that was set) -+ Props rscProps = ctrlPropsHelper.getProps(rsc); -+ rscProps.map().put(ApiConsts.KEY_STOR_POOL_NAME, LinStor.DISKLESS_STOR_POOL_NAME); -+ -+ // Set DISK_REMOVE_REQUESTED to use the existing disk removal flow -+ // This will: -+ // 1. updateAndAdjustDisk sets DISK_REMOVING flag -+ // 2. Satellite sees DISK_REMOVING and deletes LUKS/storage devices -+ // 3. finishOperation rebuilds layer stack as diskless -+ // We keep the existing layer data so satellite can properly cleanup -+ markDiskRemoveRequested(rsc); -+ -+ ctrlTransactionHelper.commit(); -+ -+ // Use existing disk removal flow - this will properly cleanup storage on satellite -+ return Flux -+ .just(ApiCallRcImpl.singleApiCallRc( -+ ApiConsts.MODIFIED, -+ "Cancelling disk addition, reverting to diskless" -+ )) -+ .concatWith(updateAndAdjustDisk(nodeName, rscName, true, toggleIntoTiebreakerRef, context)) -+ .concatWith(ctrlRscDfnApiCallHandler.get().updateProps(rsc.getResourceDefinition())); -+ } -+ // If adding disk and DISK_ADD_REQUESTED is already set, treat as retry -+ // Simply retry the operation with existing layer data - satellite will handle it idempotently -+ // NOTE: We don't remove/recreate layer data here because removeLayerData() only deletes -+ // from controller DB without calling drbdadm down on satellite, leaving orphaned DRBD devices -+ errorReporter.logInfo( -+ "Toggle Disk retry on %s/%s - DISK_ADD_REQUESTED already set, retrying operation", -+ nodeNameStr, rscNameStr); -+ ctrlTransactionHelper.commit(); -+ return Flux -+ .just(new ApiCallRcImpl()) -+ .concatWith(updateAndAdjustDisk(nodeName, rscName, false, toggleIntoTiebreakerRef, context)) -+ .concatWith(ctrlRscDfnApiCallHandler.get().updateProps(rsc.getResourceDefinition())); - } - if (hasDiskRemoveRequested(rsc)) - { +- } +- if (hasDiskRemoveRequested(rsc)) +- { - throw new ApiRcException(ApiCallRcImpl.simpleEntry( - ApiConsts.FAIL_RSC_BUSY, - "Removal of disk from resource already requested", - true - )); -+ if (!removeDisk) -+ { -+ // User wants to cancel the failed remove-disk operation +- } ++ ToggleDiskAction action = determineToggleDiskAction(rsc, removeDisk); ++ errorReporter.logDebug("Toggle Disk action: %s", action); + +- if (!removeDisk && !ctrlVlmCrtApiHelper.isDiskless(rsc)) ++ switch (action) + { +- throw new ApiRcException(ApiCallRcImpl.simpleEntry( +- ApiConsts.WARN_RSC_ALREADY_HAS_DISK, +- "Resource already has disk", +- true +- )); +- } +- if (removeDisk && ctrlVlmCrtApiHelper.isDiskless(rsc)) +- { +- throw new ApiRcException(ApiCallRcImpl.simpleEntry( +- ApiConsts.WARN_RSC_ALREADY_DISKLESS, +- "Resource already diskless", +- true +- )); ++ case NOOP: ++ return handleNoopAction(rsc, removeDisk); ++ case RETRY: ++ return handleRetryAction(rsc, removeDisk, toggleIntoTiebreakerRef, context); ++ case ABORT: ++ clearToggleDiskFlags(rsc); + errorReporter.logInfo( -+ "Toggle Disk cancel on %s/%s - cancelling failed DISK_REMOVE_REQUESTED", -+ nodeNameStr, rscNameStr); -+ unmarkDiskRemoveRequested(rsc); -+ ctrlTransactionHelper.commit(); -+ return Flux.just( -+ ApiCallRcImpl.singleApiCallRc( -+ ApiConsts.MODIFIED, -+ "Cancelled disk removal request" -+ ) ++ "Aborting previous toggle disk transition, starting new transition to %s", ++ removeDisk ? "diskless" : "diskful" + ); -+ } -+ // If removing disk and DISK_REMOVE_REQUESTED is already set, treat as retry -+ errorReporter.logInfo( -+ "Toggle Disk retry on %s/%s - DISK_REMOVE_REQUESTED already set, continuing operation", -+ nodeNameStr, rscNameStr); -+ ctrlTransactionHelper.commit(); -+ return Flux -+ .just(new ApiCallRcImpl()) -+ .concatWith(updateAndAdjustDisk(nodeName, rscName, true, toggleIntoTiebreakerRef, context)) -+ .concatWith(ctrlRscDfnApiCallHandler.get().updateProps(rsc.getResourceDefinition())); ++ break; ++ case NORMAL: ++ break; ++ default: ++ throw new ImplementationError("Unhandled case: " + action); } - if (!removeDisk && !ctrlVlmCrtApiHelper.isDiskless(rsc)) -@@ -412,17 +477,43 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL - true - )); - } -+ ResourceDefinition rscDfn = rsc.getResourceDefinition(); -+ AccessContext peerCtx = peerAccCtx.get(); -+ - if (removeDisk && ctrlVlmCrtApiHelper.isDiskless(rsc)) - { -+ // Resource is marked as diskless - check if it has orphaned storage layers that need cleanup -+ AbsRscLayerObject layerData = getLayerData(peerCtx, rsc); -+ if (layerData != null && (LayerUtils.hasLayer(layerData, DeviceLayerKind.LUKS) || -+ hasNonDisklessStorageLayer(layerData))) -+ { -+ // Resource is marked as diskless but has orphaned storage layers - need cleanup -+ // Use the existing disk removal flow to properly cleanup storage on satellite -+ errorReporter.logInfo( -+ "Toggle Disk cleanup on %s/%s - resource is diskless but has orphaned storage layers, cleaning up", -+ nodeNameStr, rscNameStr); -+ -+ // Set DISK_REMOVE_REQUESTED to use the existing disk removal flow -+ // This will trigger proper satellite cleanup via DISK_REMOVING flag -+ markDiskRemoveRequested(rsc); -+ -+ ctrlTransactionHelper.commit(); -+ -+ // Use existing disk removal flow - this will properly cleanup storage on satellite -+ return Flux -+ .just(ApiCallRcImpl.singleApiCallRc( -+ ApiConsts.MODIFIED, -+ "Cleaning up orphaned storage layers" -+ )) -+ .concatWith(updateAndAdjustDisk(nodeName, rscName, true, toggleIntoTiebreakerRef, context)) -+ .concatWith(ctrlRscDfnApiCallHandler.get().updateProps(rsc.getResourceDefinition())); -+ } - throw new ApiRcException(ApiCallRcImpl.simpleEntry( - ApiConsts.WARN_RSC_ALREADY_DISKLESS, - "Resource already diskless", - true - )); - } -- - ResourceDefinition rscDfn = rsc.getResourceDefinition(); - AccessContext peerCtx = peerAccCtx.get(); - if (removeDisk) - { - // Prevent removal of the last disk -@@ -1446,6 +1537,30 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL +- if (removeDisk) +- { +- // Prevent removal of the last disk +- int haveDiskCount = countDisksAndIsOnline(rscDfn); +- if (haveDiskCount <= 1) +- { +- throw new ApiRcException(ApiCallRcImpl.simpleEntry( +- ApiConsts.FAIL_INSUFFICIENT_REPLICA_COUNT, +- "Cannot remove the disk from the only online resource with a disk", +- true +- )); +- } ++ validateToggleDiskPreconditions(rsc, removeDisk); + +- if (!LayerUtils.hasLayer(getLayerData(peerCtx, rsc), DeviceLayerKind.DRBD)) +- { +- throw new ApiRcException(ApiCallRcImpl.simpleEntry( +- ApiConsts.FAIL_INVLD_LAYER_STACK, +- "Toggle disk is only supported in combination with DRBD", +- true +- )); +- } +- } +- else +- { +- ensureAllPeersHavePeerSlotLeft(rscDfn); +- } ++ AccessContext peerCtx = peerAccCtx.get(); + + // Save the requested storage pool in the resource properties. + // This does not cause the storage pool to be used automatically. +@@ -628,10 +600,10 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL + + ctrlTransactionHelper.commit(); + +- String action = removeDisk ? "Removal of disk from" : "Addition of disk to"; ++ String actionStr = removeDisk ? "Removal of disk from" : "Addition of disk to"; + responses.addEntry(ApiCallRcImpl.simpleEntry( + ApiConsts.MODIFIED, +- action + " resource '" + rscDfn.getName().displayValue + "' " + ++ actionStr + " resource '" + rscDfn.getName().displayValue + "' " + + "on node '" + rsc.getNode().getName().displayValue + "' registered" + )); + +@@ -651,6 +623,40 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL + ); + } + ++ private Flux handleNoopAction(Resource rscRef, boolean removeDiskRef) ++ { ++ String state = removeDiskRef ? "diskless" : "diskful"; ++ ApiCallRcImpl responses = new ApiCallRcImpl(); ++ responses.addEntry(ApiCallRcImpl.simpleEntry( ++ ApiConsts.INFO_NOOP, ++ "Resource '" + rscRef.getResourceDefinition().getName().displayValue + "' on node '" + ++ rscRef.getNode().getName().displayValue + "' is already " + state ++ )); ++ return Flux.just(responses); ++ } ++ ++ private Flux handleRetryAction( ++ Resource rscRef, ++ boolean removeDisk, ++ boolean toggleIntoTiebreakerRef, ++ ResponseContext context ++ ) ++ { ++ String direction = removeDisk ? "diskless" : "diskful"; ++ ApiCallRcImpl responses = new ApiCallRcImpl(); ++ NodeName nodeName = rscRef.getNode().getName(); ++ ResourceName rscName = rscRef.getResourceDefinition().getName(); ++ responses.addEntry(ApiCallRcImpl.simpleEntry( ++ ApiConsts.INFO_NOOP, ++ "Retrying toggle disk to " + direction + " for resource '" + rscName.displayValue + ++ "' on node '" + nodeName.displayValue + "'" ++ )); ++ ++ return Flux ++ .just(responses) ++ .concatWith(updateAndAdjustDisk(nodeName, rscName, removeDisk, toggleIntoTiebreakerRef, context)); ++ } ++ + private long getVlmDfnSizePrivileged(VolumeDefinition vlmDfnRef) + { + try +@@ -781,6 +787,96 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL } } -+ private void unmarkDiskAddRequested(Resource rsc) ++ private ToggleDiskAction determineToggleDiskAction(Resource rsc, boolean removeDisk) ++ { ++ boolean isDiskless = ctrlVlmCrtApiHelper.isDiskless(rsc); ++ boolean diskAddRequested = hasDiskAddRequested(rsc); ++ boolean diskRemoveRequested = hasDiskRemoveRequested(rsc); ++ ++ ToggleDiskAction action; ++ if (isDiskless) ++ { ++ if (diskAddRequested) ++ { ++ action = removeDisk ? ToggleDiskAction.ABORT : ToggleDiskAction.RETRY; ++ } ++ else if (diskRemoveRequested) ++ { ++ action = removeDisk ? ToggleDiskAction.RETRY : ToggleDiskAction.ABORT; ++ } ++ else ++ { ++ action = removeDisk ? ToggleDiskAction.NOOP : ToggleDiskAction.NORMAL; ++ } ++ } ++ else ++ { ++ if (diskRemoveRequested) ++ { ++ action = removeDisk ? ToggleDiskAction.RETRY : ToggleDiskAction.ABORT; ++ } ++ else ++ { ++ action = removeDisk ? ToggleDiskAction.NORMAL : ToggleDiskAction.NOOP; ++ } ++ } ++ ++ return action; ++ } ++ ++ private void validateToggleDiskPreconditions(Resource rsc, boolean removeDisk) ++ { ++ ResourceDefinition rscDfn = rsc.getResourceDefinition(); ++ if (removeDisk) ++ { ++ validateDiskRemovalAllowed(rsc, rscDfn); ++ } ++ else ++ { ++ ensureAllPeersHavePeerSlotLeft(rscDfn); ++ } ++ } ++ ++ private void clearToggleDiskFlags(Resource rsc) + { + try + { -+ rsc.getStateFlags().disableFlags(apiCtx, Resource.Flags.DISK_ADD_REQUESTED); ++ rsc.getStateFlags().disableFlags( ++ apiCtx, ++ Resource.Flags.DISK_ADD_REQUESTED, ++ Resource.Flags.DISK_ADDING, ++ Resource.Flags.DISK_REMOVE_REQUESTED, ++ Resource.Flags.DISK_REMOVING ++ ); + } + catch (AccessDeniedException | DatabaseException exc) + { @@ -170,60 +234,28 @@ index d93a18014..cc8ce4f04 100644 + } + } + -+ private void unmarkDiskRemoveRequested(Resource rsc) ++ private void validateDiskRemovalAllowed(Resource rsc, ResourceDefinition rscDfn) + { -+ try ++ int haveDiskCount = countDisksAndIsOnline(rscDfn); ++ if (haveDiskCount <= 1) + { -+ rsc.getStateFlags().disableFlags(apiCtx, Resource.Flags.DISK_REMOVE_REQUESTED); ++ throw new ApiRcException(ApiCallRcImpl.simpleEntry( ++ ApiConsts.FAIL_INSUFFICIENT_REPLICA_COUNT, ++ "Cannot remove the disk from the only online resource with a disk", ++ true ++ )); + } -+ catch (AccessDeniedException | DatabaseException exc) ++ ++ if (!LayerUtils.hasLayer(getLayerData(peerAccCtx.get(), rsc), DeviceLayerKind.DRBD)) + { -+ throw new ImplementationError(exc); ++ throw new ApiRcException(ApiCallRcImpl.simpleEntry( ++ ApiConsts.FAIL_INVLD_LAYER_STACK, ++ "Toggle disk is only supported in combination with DRBD", ++ true ++ )); + } + } + - private void markDiskAdded(Resource rscData) - { - try -@@ -1511,6 +1626,41 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL - return layerData; - } - -+ /** -+ * Check if the layer stack has a non-diskless STORAGE layer. -+ * This is used to detect orphaned storage layers that need cleanup. -+ */ -+ private boolean hasNonDisklessStorageLayer(AbsRscLayerObject layerDataRef) -+ { -+ boolean hasNonDiskless = false; -+ if (layerDataRef != null) -+ { -+ if (layerDataRef.getLayerKind() == DeviceLayerKind.STORAGE) -+ { -+ for (VlmProviderObject vlmData : layerDataRef.getVlmLayerObjects().values()) -+ { -+ if (vlmData.getProviderKind() != DeviceProviderKind.DISKLESS) -+ { -+ hasNonDiskless = true; -+ break; -+ } -+ } -+ } -+ if (!hasNonDiskless) -+ { -+ for (AbsRscLayerObject child : layerDataRef.getChildren()) -+ { -+ if (hasNonDisklessStorageLayer(child)) -+ { -+ hasNonDiskless = true; -+ break; -+ } -+ } -+ } -+ } -+ return hasNonDiskless; -+ } -+ - private LockGuard createLockGuard() - { - return lockGuardFactory.buildDeferred(LockType.WRITE, LockObj.NODES_MAP, LockObj.RSC_DFN_MAP); + /** + * Although we need to rebuild the layerData as the layerList might have changed, if we do not + * deactivate (i.e. down) the current resource, we need to make sure that deleting DrbdRscData diff --git a/packages/system/linstor/images/piraeus-server/patches/fix-duplicate-tcp-ports.diff b/packages/system/linstor/images/piraeus-server/patches/fix-duplicate-tcp-ports.diff index 07cd9eac..ed106f98 100644 --- a/packages/system/linstor/images/piraeus-server/patches/fix-duplicate-tcp-ports.diff +++ b/packages/system/linstor/images/piraeus-server/patches/fix-duplicate-tcp-ports.diff @@ -1,87 +1,137 @@ -From 1250abe99d64a0501795e37d3b6af62410002239 Mon Sep 17 00:00:00 2001 -From: Andrei Kvapil -Date: Mon, 12 Jan 2026 13:44:46 +0100 -Subject: [PATCH] fix(drbd): prevent duplicate TCP ports after toggle-disk - operations - -Remove redundant ensureStackDataExists() call with empty payload from -resetStoragePools() method that was causing TCP port conflicts after -toggle-disk operations. - -Root Cause: ------------ -The resetStoragePools() method, introduced in 2019 (commit 95cc17d0b8), -calls ensureStackDataExists() with an empty LayerPayload. This worked -correctly when TCP ports were stored at RscDfn level. - -After the TCP port migration to per-node level (commit f754943463, May -2025), this empty payload results in DrbdRscData being created without -TCP ports assigned. The controller then sends a Pojo with an empty port -Set to satellites. - -On satellites, when DrbdRscData is initialized with an empty port list, -initPorts() uses preferredNewPortsRef from peer resources. Since -SatelliteDynamicNumberPool.tryAllocate() always returns true (no-op), -any port from preferredNewPortsRef is accepted without conflict checking, -leading to duplicate TCP port assignments. - -Impact: -------- -This regression affects toggle-disk operations, particularly: -- Snapshot creation/restore operations -- Manual toggle-disk operations -- Any operation calling resetStoragePools() - -Symptoms include: -- DRBD resources failing to adjust with "port is also used" errors -- Resources stuck in StandAlone or Connecting states -- Multiple resources on the same node using identical TCP ports - -Solution: ---------- -Remove the ensureStackDataExists() call from resetStoragePools() as it -is redundant. The calling code (e.g., CtrlRscToggleDiskApiCallHandler -line 1071) already invokes ensureStackDataExists() with the correct -payload immediately after resetStoragePools(). - -This fix ensures: -1. resetStoragePools() only resets storage pool assignments -2. Layer data creation with proper TCP ports happens via the caller's - ensureStackDataExists() with correct payload -3. No DrbdRscData objects are created without TCP port assignments - -Related Issues: ---------------- -Fixes #454 - Duplicate TCP ports after backup/restore operations -Related to user reports of resources stuck in StandAlone after node -reboots when toggle-disk or backup operations were in progress. - -Testing: --------- -Verified that: -- Toggle-disk operations no longer create resources without TCP ports -- Backup/restore operations complete without TCP port conflicts -- Resources maintain unique TCP ports across toggle-disk cycles - -Co-Authored-By: Claude -Signed-off-by: Andrei Kvapil ---- - .../linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java | 2 -- - 1 file changed, 2 deletions(-) - +diff --git a/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java b/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java +index d93a18014..01cfbbacf 100644 +--- a/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java ++++ b/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java +@@ -37,6 +37,7 @@ import com.linbit.linstor.core.objects.StorPool; + import com.linbit.linstor.core.objects.Volume; + import com.linbit.linstor.core.objects.VolumeDefinition; + import com.linbit.linstor.core.objects.utils.MixedStorPoolHelper; ++import com.linbit.linstor.core.types.TcpPortNumber; + import com.linbit.linstor.dbdrivers.DatabaseException; + import com.linbit.linstor.event.EventWaiter; + import com.linbit.linstor.event.ObjectIdentifier; +@@ -85,6 +86,7 @@ import java.util.List; + import java.util.Map; + import java.util.Map.Entry; + import java.util.Set; ++import java.util.TreeSet; + + import org.reactivestreams.Publisher; + import reactor.core.publisher.Flux; +@@ -575,12 +577,13 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL + + /* + * We also have to remove the currently diskless DrbdRscData and free up the node-id as now we must +- * use the shared resource's node-id ++ * use the shared resource's node-id. We still need to preserve TCP ports though. + */ ++ copyDrbdTcpPortsIfExists(rsc, payload); + } + else + { +- copyDrbdNodeIdIfExists(rsc, payload); ++ copyDrbdSettings(rsc, payload); + } + /* + * rebuilds the layerdata in case we just removed it.. +@@ -782,10 +785,20 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL + } + + /** +- * Although we need to rebuild the layerData as the layerList might have changed, if we do not +- * deactivate (i.e. down) the current resource, we need to make sure that deleting DrbdRscData +- * and recreating a new DrbdRscData ends up with the same node-id as before. ++ * Copies DRBD settings (node-id and TCP ports) from the existing DrbdRscData into the payload ++ * before removeLayerData() deletes them. This ensures that recreated DrbdRscData ends up with ++ * the same node-id and TCP ports as before. ++ * ++ * TCP ports must be preserved because if the satellite misses the update (e.g. due to controller ++ * restart or connectivity issues), it will keep the old ports while peers receive the new ones, ++ * causing DRBD connections to fail with StandAlone state. + */ ++ private void copyDrbdSettings(Resource rsc, LayerPayload payload) throws ImplementationError ++ { ++ copyDrbdNodeIdIfExists(rsc, payload); ++ copyDrbdTcpPortsIfExists(rsc, payload); ++ } ++ + private void copyDrbdNodeIdIfExists(Resource rsc, LayerPayload payload) throws ImplementationError + { + Set> drbdRscDataSet = LayerRscUtils.getRscDataByLayer( +@@ -804,6 +817,28 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL + } + } + ++ private void copyDrbdTcpPortsIfExists(Resource rsc, LayerPayload payload) throws ImplementationError ++ { ++ Set> drbdRscDataSet = LayerRscUtils.getRscDataByLayer( ++ getLayerData(apiCtx, rsc), ++ DeviceLayerKind.DRBD ++ ); ++ if (!drbdRscDataSet.isEmpty()) ++ { ++ DrbdRscData drbdRscData = (DrbdRscData) drbdRscDataSet.iterator().next(); ++ Collection tcpPorts = drbdRscData.getTcpPortList(); ++ if (tcpPorts != null && !tcpPorts.isEmpty()) ++ { ++ Set portInts = new TreeSet<>(); ++ for (TcpPortNumber port : tcpPorts) ++ { ++ portInts.add(port.value); ++ } ++ payload.drbdRsc.tcpPorts = portInts; ++ } ++ } ++ } ++ + private List removeLayerData(Resource rscRef) + { + List layerList; +@@ -1058,15 +1093,15 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL + /* + * We also have to remove the possible meta-children of previous StorageRscData. + * LayerData will be recreated with ensureStackDataExists. +- * However, we still need to remember our node-id if we had / have DRBD in the list ++ * However, we still need to remember our DRBD settings if we had / have DRBD in the list + */ +- copyDrbdNodeIdIfExists(rsc, payload); ++ copyDrbdSettings(rsc, payload); + layerList = removeLayerData(rsc); + } + else + { + markDiskAdded(rsc); +- ctrlLayerStackHelper.resetStoragePools(rsc); ++ ctrlLayerStackHelper.resetStoragePools(rsc, false); + } + ctrlLayerStackHelper.ensureStackDataExists(rsc, layerList, payload); + diff --git a/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java b/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java -index 3538b380c..4f589145e 100644 +index 3538b380c..f9733b6f1 100644 --- a/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java +++ b/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java -@@ -276,8 +276,6 @@ public class CtrlRscLayerDataFactory +@@ -263,6 +263,11 @@ public class CtrlRscLayerDataFactory + } + + public void resetStoragePools(Resource rscRef) ++ { ++ resetStoragePools(rscRef, true); ++ } ++ ++ public void resetStoragePools(Resource rscRef, boolean callEnsureStackDataExistsRef) + { + try + { +@@ -276,8 +281,10 @@ public class CtrlRscLayerDataFactory rscDataToProcess.addAll(rscData.getChildren()); } - - ensureStackDataExists(rscRef, null, new LayerPayload()); ++ if (callEnsureStackDataExistsRef) ++ { ++ ensureStackDataExists(rscRef, null, new LayerPayload()); ++ } } catch (AccessDeniedException exc) { --- -2.39.5 (Apple Git-154) - diff --git a/packages/system/linstor/images/piraeus-server/patches/fix-luks-header-size.diff b/packages/system/linstor/images/piraeus-server/patches/fix-luks-header-size.diff new file mode 100644 index 00000000..8611825b --- /dev/null +++ b/packages/system/linstor/images/piraeus-server/patches/fix-luks-header-size.diff @@ -0,0 +1,570 @@ +diff --git a/satellite/src/main/java/com/linbit/linstor/layer/storage/AbsStorageProvider.java b/satellite/src/main/java/com/linbit/linstor/layer/storage/AbsStorageProvider.java +--- a/satellite/src/main/java/com/linbit/linstor/layer/storage/AbsStorageProvider.java ++++ b/satellite/src/main/java/com/linbit/linstor/layer/storage/AbsStorageProvider.java +@@ -2081,7 +2081,6 @@ public abstract class AbsStorageProvider< + ) + throws IOException, AccessDeniedException + { +- final StorPoolName storPoolObjName = storPoolObj.getName(); + if (storDevicePath != null) + { + errorReporter.logDebug("updateMinIoSize: Have storDevicePath \"%s\"", storDevicePath); +@@ -2094,35 +2093,18 @@ public abstract class AbsStorageProvider< + "updateMinIoSize: Block device path is \"%s\"", + blockDevicePath.toString() + ); +- final long minIoSize = BlockSizeInfo.getBlockSize(blockDevicePath); +- +- boolean updateValue = true; +- +- final String propKey = StorageConstants.NAMESPACE_INTERNAL + '/' + +- StorageConstants.BLK_DEV_MIN_IO_SIZE; +- final Props storPoolProps = storPoolObj.getProps(storDriverAccCtx); +- final @Nullable String currentPropValue = storPoolProps.getProp(propKey); +- if (currentPropValue != null) +- { +- try +- { +- final long currentPropMinIoSize = Long.parseLong(currentPropValue); +- updateValue = currentPropMinIoSize != minIoSize; +- } +- catch (NumberFormatException ignored) +- { +- } +- } +- +- if (updateValue) +- { +- final String propValue = Long.toString(minIoSize); +- errorReporter.logDebug( +- "Storage pool \"%s\": Set property \"%s\" = \"%s\"", +- storPoolObjName.displayValue, propKey, propValue +- ); +- propsChange.changeStorPoolProp(storPoolObj, propKey, propValue); +- } ++ updatePropIfNeeded( ++ propsChange, ++ storPoolObj, ++ StorageConstants.NAMESPACE_INTERNAL + '/' + StorageConstants.BLK_DEV_MIN_IO_SIZE, ++ Long.toString(BlockSizeInfo.getBlockSize(blockDevicePath)) ++ ); ++ updatePropIfNeeded( ++ propsChange, ++ storPoolObj, ++ StorageConstants.NAMESPACE_INTERNAL + '/' + StorageConstants.BLK_DEV_OPT_IO_SIZE, ++ Long.toString(BlockSizeInfo.getOptimalIoSize(blockDevicePath)) ++ ); + } + else + { +@@ -2130,6 +2112,28 @@ public abstract class AbsStorageProvider< + } + } + ++ private void updatePropIfNeeded( ++ LocalPropsChangePojo propsChangeRef, ++ StorPool storPoolObjRef, ++ String propKeyRef, ++ String propValueRef ++ ) ++ throws AccessDeniedException ++ { ++ final Props storPoolProps = storPoolObjRef.getProps(storDriverAccCtx); ++ final @Nullable String currentPropValue = storPoolProps.getProp(propKeyRef); ++ if (currentPropValue == null || !currentPropValue.equals(propValueRef)) ++ { ++ errorReporter.logDebug( ++ "Storage pool \"%s\": Set property \"%s\" = \"%s\"", ++ storPoolObjRef.getName().displayValue, ++ propKeyRef, ++ propValueRef ++ ); ++ propsChangeRef.changeStorPoolProp(storPoolObjRef, propKeyRef, propValueRef); ++ } ++ } ++ + @SuppressWarnings("unchecked") + @Override + public void updateAllocatedSize(VlmProviderObject vlmDataRef) +diff --git a/satellite/src/main/java/com/linbit/linstor/layer/storage/utils/BlockSizeInfo.java b/satellite/src/main/java/com/linbit/linstor/layer/storage/utils/BlockSizeInfo.java +--- a/satellite/src/main/java/com/linbit/linstor/layer/storage/utils/BlockSizeInfo.java ++++ b/satellite/src/main/java/com/linbit/linstor/layer/storage/utils/BlockSizeInfo.java +@@ -3,8 +3,11 @@ package com.linbit.linstor.layer.storage.utils; + import com.linbit.utils.MathUtils; + import com.linbit.utils.SymbolicLinkResolver; + ++import static com.linbit.linstor.layer.storage.BlockSizeConsts.DFLT_OPT_IO_SIZE; + import static com.linbit.linstor.layer.storage.BlockSizeConsts.DFLT_IO_SIZE; ++import static com.linbit.linstor.layer.storage.BlockSizeConsts.MAX_OPT_IO_SIZE; + import static com.linbit.linstor.layer.storage.BlockSizeConsts.MAX_IO_SIZE; ++import static com.linbit.linstor.layer.storage.BlockSizeConsts.MIN_OPT_IO_SIZE; + import static com.linbit.linstor.layer.storage.BlockSizeConsts.MIN_IO_SIZE; + + import java.io.FileInputStream; +@@ -13,6 +16,8 @@ import java.nio.file.Path; + + public class BlockSizeInfo + { ++ private static final int NUMBER_BUFFER_SIZE = 32; ++ + /** + * Determines the blocksize, aka minimum I/O size, for the specified backing storage path. + * +@@ -51,7 +56,7 @@ public class BlockSizeInfo + final Path infoSourceName = blockDevice.getFileName(); + final Path infoSource = Path.of("/sys/block", infoSourceName.toString(), "queue/physical_block_size"); + +- final byte[] data = new byte[32]; ++ final byte[] data = new byte[NUMBER_BUFFER_SIZE]; + try (final FileInputStream fileIn = new FileInputStream(infoSource.toString())) + { + final int readCount = fileIn.read(data); +@@ -75,4 +80,38 @@ public class BlockSizeInfo + } + return blockSize; + } ++ ++ public static long getOptimalIoSize(final Path storageObj) ++ { ++ long optIoSize = DFLT_OPT_IO_SIZE; ++ try ++ { ++ final Path blockDevice = SymbolicLinkResolver.resolveSymLink(storageObj); ++ final Path infoSourceName = blockDevice.getFileName(); ++ final Path infoSource = Path.of("/sys/block", infoSourceName.toString(), "queue/optimal_io_size"); ++ ++ final byte[] data = new byte[NUMBER_BUFFER_SIZE]; ++ try (final FileInputStream fileIn = new FileInputStream(infoSource.toString())) ++ { ++ final int readCount = fileIn.read(data); ++ if (readCount > 0) ++ { ++ String numberStr = new String(data, 0, readCount); ++ numberStr = numberStr.trim(); ++ try ++ { ++ final long unboundedOptIoSize = Long.parseLong(numberStr); ++ optIoSize = MathUtils.bounds(MIN_OPT_IO_SIZE, unboundedOptIoSize, MAX_OPT_IO_SIZE); ++ } ++ catch (NumberFormatException ignored) ++ { ++ } ++ } ++ } ++ } ++ catch (IOException ignored) ++ { ++ } ++ return optIoSize; ++ } + } +diff --git a/server/src/main/java/com/linbit/SizeConv.java b/server/src/main/java/com/linbit/SizeConv.java +--- a/server/src/main/java/com/linbit/SizeConv.java ++++ b/server/src/main/java/com/linbit/SizeConv.java +@@ -16,6 +16,7 @@ public class SizeConv + public enum SizeUnit + { + UNIT_B, ++ UNIT_SECTORS, + UNIT_KiB, + UNIT_MiB, + UNIT_GiB, +@@ -41,6 +42,9 @@ public class SizeConv + case UNIT_B: + factor = FACTOR_B; + break; ++ case UNIT_SECTORS: ++ factor = FACTOR_SECTORS; ++ break; + case UNIT_KiB: + factor = FACTOR_KiB; + break; +@@ -111,6 +115,9 @@ public class SizeConv + case "b": + unit = SizeUnit.UNIT_B; + break; ++ case "s": ++ unit = SizeUnit.UNIT_SECTORS; ++ break; + case "k": + // fall-through + case "kb": +@@ -217,6 +224,9 @@ public class SizeConv + // Factor 1 + public static final BigInteger FACTOR_B = BigInteger.valueOf(1L); + ++ // Factor 512 ++ public static final BigInteger FACTOR_SECTORS = BigInteger.valueOf(512L); ++ + // Factor 1,024 + // Naming convention exception: SI unit capitalization rules + @SuppressWarnings("checkstyle:constantname") +diff --git a/server/src/main/java/com/linbit/linstor/layer/luks/LuksLayerSizeCalculator.java b/server/src/main/java/com/linbit/linstor/layer/luks/LuksLayerSizeCalculator.java +--- a/server/src/main/java/com/linbit/linstor/layer/luks/LuksLayerSizeCalculator.java ++++ b/server/src/main/java/com/linbit/linstor/layer/luks/LuksLayerSizeCalculator.java +@@ -1,27 +1,62 @@ + package com.linbit.linstor.layer.luks; + ++import com.linbit.SizeConv; ++import com.linbit.SizeConv.SizeUnit; + import com.linbit.exceptions.InvalidSizeException; ++import com.linbit.linstor.PriorityProps; ++import com.linbit.linstor.annotation.Nullable; ++import com.linbit.linstor.api.ApiConsts; ++import com.linbit.linstor.core.objects.AbsResource; ++import com.linbit.linstor.core.objects.AbsVolume; ++import com.linbit.linstor.core.objects.Resource; ++import com.linbit.linstor.core.objects.ResourceDefinition; ++import com.linbit.linstor.core.objects.ResourceGroup; ++import com.linbit.linstor.core.objects.Snapshot; ++import com.linbit.linstor.core.objects.SnapshotVolume; ++import com.linbit.linstor.core.objects.StorPool; ++import com.linbit.linstor.core.objects.Volume; ++import com.linbit.linstor.core.objects.VolumeDefinition; + import com.linbit.linstor.dbdrivers.DatabaseException; + import com.linbit.linstor.layer.AbsLayerSizeCalculator; ++import com.linbit.linstor.netcom.Peer; ++import com.linbit.linstor.propscon.InvalidKeyException; ++import com.linbit.linstor.propscon.ReadOnlyProps; + import com.linbit.linstor.security.AccessDeniedException; ++import com.linbit.linstor.storage.StorageConstants; + import com.linbit.linstor.storage.data.adapter.luks.LuksVlmData; + import com.linbit.linstor.storage.interfaces.categories.resource.VlmProviderObject; + import com.linbit.linstor.storage.kinds.DeviceLayerKind; + import com.linbit.linstor.storage.kinds.ExtTools; + import com.linbit.linstor.storage.kinds.ExtToolsInfo; + import com.linbit.linstor.storage.kinds.ExtToolsInfo.Version; ++import com.linbit.linstor.utils.layer.LayerVlmUtils; ++import com.linbit.utils.ShellUtils; ++import com.linbit.utils.SignedAlign; + + import javax.inject.Inject; + import javax.inject.Singleton; + ++import java.util.Iterator; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Set; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; ++ + @Singleton + public class LuksLayerSizeCalculator extends AbsLayerSizeCalculator> + { ++ public static final String LUKS2_OPT_METADATA_SIZE = "--luks2-metadata-size"; ++ public static final String LUKS2_OPT_KEYSLOTS_SIZE = "--luks2-keyslots-size"; ++ public static final String LUKS2_OPT_OFFSET = "--offset"; ++ private static final String LUKS2_OPT_ALIGN_PAYLOAD = "--align-payload"; ++ private static final long DFLT_LUKS2_METADATA_SIZE_IN_BYTES = 16L << 10; ++ private static final long DFLT_ALIGNMENT_1MIB_IN_BYTES = 1L << 20; ++ private static final long LUKS1_HEADER_SIZE_IN_KIB = 2L << 10; ++ private static final long LUKS2_HEADER_SIZE_IN_KIB = 16L << 10; ++ private static final long LUKS2_HEADER_SIZE_IN_BYTES = LUKS2_HEADER_SIZE_IN_KIB << 10; + +- // linstor calculates in KiB +- private static final int MIB = 1024; +- private static final int LUKS1_HEADER_SIZE = 2 * MIB; +- private static final int LUKS2_HEADER_SIZE = 16 * MIB; ++ private static final Pattern PATTERN_SIZE = Pattern.compile("(\\d+)([kKmMgGtT]i?[bB]?|[sS]|)"); + + @Inject + public LuksLayerSizeCalculator(AbsLayerSizeCalculatorInit initRef) +@@ -74,30 +109,266 @@ public class LuksLayerSizeCalculator extends AbsLayerSizeCalculator vlmDataRef) +- throws AccessDeniedException ++ throws AccessDeniedException, InvalidSizeException + { +- ExtToolsInfo cryptSetupInfo = vlmDataRef.getRscLayerObject() ++ @Nullable Peer peer = vlmDataRef.getRscLayerObject() + .getAbsResource() + .getNode() +- .getPeer(sysCtx) +- .getExtToolsManager() ++ .getPeer(sysCtx); ++ if (peer == null) ++ { ++ throw new InvalidSizeException( ++ "Could not calculate size of LUKS volume, since cryptsetup's version could not be determined", ++ null ++ ); ++ } ++ ExtToolsInfo cryptSetupInfo = peer.getExtToolsManager() + .getExtToolInfo(ExtTools.CRYPT_SETUP); +- long luksHeaderSize; ++ ++ final long luksHeaderSize; + if (cryptSetupInfo != null && cryptSetupInfo.isSupported()) + { + if (cryptSetupInfo.hasVersionOrHigher(new Version(2, 1))) + { +- luksHeaderSize = LUKS2_HEADER_SIZE; ++ luksHeaderSize = calcLuks2HeaderSize(vlmDataRef); + } + else + { +- luksHeaderSize = LUKS1_HEADER_SIZE; ++ luksHeaderSize = LUKS1_HEADER_SIZE_IN_KIB; + } + } + else + { +- luksHeaderSize = -1; ++ throw new InvalidSizeException( ++ "Could not calculate size of LUKS volume, since cryptsetup's version could not be determined", ++ null ++ ); + } + return luksHeaderSize; + } ++ ++ private long calcLuks2HeaderSize(VlmProviderObject vlmDataRef) throws AccessDeniedException ++ { ++ PriorityProps prioProps = getPrioProps(vlmDataRef); ++ ++ final @Nullable String userOptProp = prioProps.getProp( ++ ApiConsts.KEY_STOR_DRIVER_LUKS_FORMAT_OPTIONS, ++ ApiConsts.NAMESPC_STORAGE_DRIVER ++ ); ++ List userOptions = userOptProp != null ? ++ ShellUtils.shellSplit(userOptProp) : ++ new LinkedList<>(); ++ ++ final long alignedHeaderSizeInBytes; ++ final long unalignedHeaderSizeInBytes; ++ @Nullable Long cryptsetupOffsetInBytes = getLongOptionValue( ++ userOptions, ++ LUKS2_OPT_OFFSET, ++ SizeUnit.UNIT_SECTORS ++ ); ++ if (cryptsetupOffsetInBytes != null) ++ { ++ alignedHeaderSizeInBytes = cryptsetupOffsetInBytes; ++ } ++ else ++ { ++ @Nullable Long cryptsetupLuks2KeyslotsSize = getLongOptionValue( ++ userOptions, ++ LUKS2_OPT_KEYSLOTS_SIZE ++ ); ++ if (cryptsetupLuks2KeyslotsSize == null) ++ { ++ unalignedHeaderSizeInBytes = LUKS2_HEADER_SIZE_IN_BYTES; ++ } ++ else ++ { ++ @Nullable Long cryptsetupLuks2MetadataSize = getLongOptionValue( ++ userOptions, ++ LUKS2_OPT_METADATA_SIZE ++ ); ++ cryptsetupLuks2MetadataSize = cryptsetupLuks2MetadataSize == null ? ++ DFLT_LUKS2_METADATA_SIZE_IN_BYTES : ++ cryptsetupLuks2MetadataSize; ++ ++ unalignedHeaderSizeInBytes = 2 * cryptsetupLuks2MetadataSize + cryptsetupLuks2KeyslotsSize; ++ } ++ ++ long alignment = getAlignment(vlmDataRef, userOptions); ++ alignedHeaderSizeInBytes = new SignedAlign(alignment).ceiling(unalignedHeaderSizeInBytes); ++ } ++ return SizeConv.convert( ++ alignedHeaderSizeInBytes, ++ SizeUnit.UNIT_B, ++ SizeUnit.UNIT_KiB ++ ); ++ } ++ ++ private long getAlignment(VlmProviderObject vlmDataRef, List userOptions) throws AccessDeniedException ++ { ++ @Nullable Long cryptsetupAlignPayloadInBytes = getLongOptionValue( ++ userOptions, ++ LUKS2_OPT_ALIGN_PAYLOAD, ++ SizeUnit.UNIT_SECTORS ++ ); ++ ++ long alignment = DFLT_ALIGNMENT_1MIB_IN_BYTES; ++ if (cryptsetupAlignPayloadInBytes != null) ++ { ++ alignment = cryptsetupAlignPayloadInBytes; ++ } ++ else ++ { ++ final long maxOptIoSize = getMaxOptIoSize(vlmDataRef); ++ alignment = Math.max(alignment, maxOptIoSize); ++ } ++ return alignment; ++ } ++ ++ private long getMaxOptIoSize(VlmProviderObject vlmDataRef) throws InvalidKeyException, AccessDeniedException ++ { ++ long ret = 0; ++ Set storPoolSet = LayerVlmUtils.getStorPoolSet(vlmDataRef, sysCtx); ++ for (StorPool sp : storPoolSet) ++ { ++ @Nullable String strValue = sp.getProps(sysCtx) ++ .getProp( ++ StorageConstants.BLK_DEV_OPT_IO_SIZE, ++ StorageConstants.NAMESPACE_INTERNAL ++ ); ++ if (strValue != null) ++ { ++ try ++ { ++ long parsed = Long.parseLong(strValue); ++ ret = Math.max(parsed, ret); ++ } ++ catch (NumberFormatException ignored) ++ { ++ errorReporter.logWarning( ++ "LuksHeaderSize: Failed to parse '%s' from prop %s. Defaulting to 0 opt_io_size " + ++ "(no recommendation/hint)", ++ strValue, ++ StorageConstants.NAMESPACE_INTERNAL + "/" + StorageConstants.BLK_DEV_OPT_IO_SIZE ++ ); ++ } ++ } ++ } ++ return ret; ++ } ++ ++ private @Nullable Long getLongOptionValue(List userOptionsRef, String optRef) ++ { ++ return getLongOptionValue(userOptionsRef, optRef, SizeUnit.UNIT_B); ++ } ++ ++ @SuppressWarnings("checkstyle:magicnumber") ++ private @Nullable Long getLongOptionValue(List userOptionsRef, String optRef, SizeUnit dfltSizeUnit) ++ { ++ @Nullable Long ret = null; ++ @Nullable String val = findLastValue(userOptionsRef, optRef); ++ if (val != null && !val.isBlank()) ++ { ++ Matcher matcher = PATTERN_SIZE.matcher(val); ++ if (matcher.matches()) ++ { ++ try ++ { ++ ret = Long.parseLong(matcher.group(1)); ++ String unit = matcher.group(2); ++ SizeUnit sizeUnit; ++ if (unit.isBlank()) ++ { ++ sizeUnit = dfltSizeUnit; ++ } ++ else ++ { ++ boolean forcePowerOfTwo = unit.length() == 1 || unit.length() == 3; ++ sizeUnit = SizeUnit.parse(unit, forcePowerOfTwo); ++ } ++ ++ ret = SizeConv.convert(ret, sizeUnit, SizeUnit.UNIT_B); ++ } ++ catch (NumberFormatException ignored) ++ { ++ errorReporter.logWarning( ++ "LuksHeaderSize: Failed to parse '%s' from option '%s %s'.", ++ matcher.group(1), ++ optRef, ++ val ++ ); ++ } ++ } ++ } ++ return ret; ++ } ++ ++ private @Nullable String findLastValue(List userOptionsRef, String optRef) ++ { ++ Iterator it = userOptionsRef.iterator(); ++ @Nullable String val = null; ++ while (it.hasNext()) ++ { ++ String opt = it.next(); ++ if (opt.equals(optRef)) ++ { ++ if (it.hasNext()) ++ { ++ val = it.next(); ++ } ++ else ++ { ++ val = null; ++ } ++ } ++ else if (opt.startsWith(optRef + "=")) ++ { ++ val = opt.substring(optRef.length() + 1); ++ if (val.isBlank()) ++ { ++ val = null; ++ } ++ } ++ } ++ return val; ++ } ++ ++ private PriorityProps getPrioProps(VlmProviderObject vlmDataRef) throws AccessDeniedException ++ { ++ final AbsVolume vlm = vlmDataRef.getVolume(); ++ final AbsResource rsc = vlm.getAbsResource(); ++ final ResourceDefinition rscDfn = vlm.getResourceDefinition(); ++ final VolumeDefinition vlmDfn = vlm.getVolumeDefinition(); ++ final ResourceGroup rscGrp = rscDfn.getResourceGroup(); ++ ++ final ReadOnlyProps vlmProps; ++ final ReadOnlyProps rscProps; ++ if (vlm instanceof Volume) ++ { ++ vlmProps = ((Volume) vlm).getProps(sysCtx); ++ rscProps = ((Resource) rsc).getProps(sysCtx); ++ } ++ else ++ { ++ vlmProps = ((SnapshotVolume) vlm).getVlmProps(sysCtx); ++ rscProps = ((Snapshot) rsc).getRscProps(sysCtx); ++ } ++ ++ final PriorityProps prioProps = new PriorityProps( ++ vlmProps, ++ rscProps ++ ); ++ for (StorPool storPool : LayerVlmUtils.getStorPoolSet(vlmDataRef, sysCtx)) ++ { ++ prioProps.addProps(storPool.getProps(sysCtx)); ++ } ++ prioProps.addProps( ++ rsc.getNode().getProps(sysCtx), ++ vlmDfn.getProps(sysCtx), ++ rscDfn.getProps(sysCtx), ++ rscGrp.getVolumeGroupProps(sysCtx, vlmDfn.getVolumeNumber()), ++ rscGrp.getProps(sysCtx), ++ stltProps ++ ); ++ return prioProps; ++ } + } +diff --git a/server/src/main/java/com/linbit/linstor/layer/storage/BlockSizeConsts.java b/server/src/main/java/com/linbit/linstor/layer/storage/BlockSizeConsts.java +--- a/server/src/main/java/com/linbit/linstor/layer/storage/BlockSizeConsts.java ++++ b/server/src/main/java/com/linbit/linstor/layer/storage/BlockSizeConsts.java +@@ -13,4 +13,9 @@ public class BlockSizeConsts + + // Default value for the minimum_io_size value of non-storage layers + public static final long DFLT_SPECIAL_IO_SIZE = (1L << 12); ++ ++ // optimal_io_size may be 0 to indicate no recommendation. ++ public static final long MIN_OPT_IO_SIZE = 0; ++ public static final long DFLT_OPT_IO_SIZE = 0; ++ public static final long MAX_OPT_IO_SIZE = Long.MAX_VALUE; + } +diff --git a/server/src/main/java/com/linbit/linstor/storage/StorageConstants.java b/server/src/main/java/com/linbit/linstor/storage/StorageConstants.java +--- a/server/src/main/java/com/linbit/linstor/storage/StorageConstants.java ++++ b/server/src/main/java/com/linbit/linstor/storage/StorageConstants.java +@@ -11,6 +11,7 @@ public class StorageConstants + public static final String NAMESPACE_NVME = ApiConsts.NAMESPC_STORAGE_DRIVER + "/NVME"; + public static final String NAMESPACE_INTERNAL = NAMESPACE_STOR_DRIVER + "/internal/"; + ++ public static final String BLK_DEV_OPT_IO_SIZE = "optIoSize"; + public static final String BLK_DEV_MIN_IO_SIZE = "minIoSize"; + public static final String BLK_DEV_MIN_IO_SIZE_AUTO = "minIoSizeAuto"; + public static final String BLK_DEV_MAX_BIO_SIZE = "maxBioSize"; diff --git a/packages/system/linstor/images/piraeus-server/patches/force-metadata-check-on-disk-add.diff b/packages/system/linstor/images/piraeus-server/patches/force-metadata-check-on-disk-add.diff deleted file mode 100644 index 0c413005..00000000 --- a/packages/system/linstor/images/piraeus-server/patches/force-metadata-check-on-disk-add.diff +++ /dev/null @@ -1,63 +0,0 @@ -diff --git a/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java b/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java -index a302ee835..01967a31f 100644 ---- a/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java -+++ b/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java -@@ -371,10 +371,13 @@ public class DrbdLayer implements DeviceLayer - boolean isDiskless = drbdRscData.getAbsResource().isDrbdDiskless(workerCtx); - StateFlags rscFlags = drbdRscData.getAbsResource().getStateFlags(); - boolean isDiskRemoving = rscFlags.isSet(workerCtx, Resource.Flags.DISK_REMOVING); -+ // Check if we're in toggle-disk operation (adding disk to diskless resource) -+ boolean isDiskAdding = rscFlags.isSomeSet(workerCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING); - - boolean contProcess = isDiskless; - -- boolean processChildren = !isDiskless || isDiskRemoving; -+ // Process children when: has disk, removing disk, OR adding disk (toggle-disk) -+ boolean processChildren = !isDiskless || isDiskRemoving || isDiskAdding; - // do not process children when ONLY DRBD_DELETE flag is set (DELETE flag is still unset) - processChildren &= (!rscFlags.isSet(workerCtx, Resource.Flags.DRBD_DELETE) || - rscFlags.isSet(workerCtx, Resource.Flags.DELETE)); -@@ -570,7 +573,11 @@ public class DrbdLayer implements DeviceLayer - { - // hasMetaData needs to be run after child-resource processed - List> createMetaData = new ArrayList<>(); -- if (!drbdRscData.getAbsResource().isDrbdDiskless(workerCtx) && !skipDisk) -+ // Check if we're in toggle-disk operation (adding disk to diskless resource) -+ boolean isDiskAddingForMd = drbdRscData.getAbsResource().getStateFlags() -+ .isSomeSet(workerCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING); -+ // Create metadata when: has disk OR adding disk (toggle-disk), and skipDisk is disabled -+ if ((!drbdRscData.getAbsResource().isDrbdDiskless(workerCtx) || isDiskAddingForMd) && !skipDisk) - { - // do not try to create meta data while the resource is diskless or skipDisk is enabled - for (DrbdVlmData drbdVlmData : checkMetaData) -@@ -988,8 +995,10 @@ public class DrbdLayer implements DeviceLayer - { - List> checkMetaData = new ArrayList<>(); - Resource rsc = drbdRscData.getAbsResource(); -+ // Include DISK_ADD_REQUESTED/DISK_ADDING for toggle-disk scenario where we need to check/create metadata - if (!rsc.isDrbdDiskless(workerCtx) || -- rsc.getStateFlags().isSet(workerCtx, Resource.Flags.DISK_REMOVING) -+ rsc.getStateFlags().isSet(workerCtx, Resource.Flags.DISK_REMOVING) || -+ rsc.getStateFlags().isSomeSet(workerCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING) - ) - { - // using a dedicated list to prevent concurrentModificationException -@@ -1177,9 +1186,16 @@ public class DrbdLayer implements DeviceLayer - - boolean hasMetaData; - -+ // Check if we need to verify/create metadata -+ // Force metadata check when: -+ // 1. checkMetaData is enabled -+ // 2. volume doesn't have disk yet (diskless -> diskful transition) -+ // 3. DISK_ADD_REQUESTED/DISK_ADDING flag is set (retry scenario where storage exists but no metadata) -+ boolean isDiskAddingState = drbdVlmData.getRscLayerObject().getAbsResource().getStateFlags() -+ .isSomeSet(workerCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING); - if (drbdVlmData.checkMetaData() || -- // when adding a disk, DRBD believes that it is diskless but we still need to create metadata -- !drbdVlmData.hasDisk()) -+ !drbdVlmData.hasDisk() || -+ isDiskAddingState) - { - if (mdUtils.hasMetaData()) - { diff --git a/packages/system/linstor/images/piraeus-server/patches/retry-adjust-after-stale-bitmap.diff b/packages/system/linstor/images/piraeus-server/patches/retry-adjust-after-stale-bitmap.diff new file mode 100644 index 00000000..00959167 --- /dev/null +++ b/packages/system/linstor/images/piraeus-server/patches/retry-adjust-after-stale-bitmap.diff @@ -0,0 +1,141 @@ +diff --git a/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/DrbdAdm.java b/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/DrbdAdm.java +index 5627d1be8..ece191292 100644 +--- a/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/DrbdAdm.java ++++ b/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/DrbdAdm.java +@@ -42,6 +42,9 @@ import java.util.Arrays; + import java.util.List; + import java.util.concurrent.ArrayBlockingQueue; + import java.util.concurrent.TimeUnit; ++import java.nio.charset.StandardCharsets; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; + import java.util.stream.Collectors; + + @Singleton +@@ -56,6 +58,9 @@ public class DrbdAdm + + public static final int WAIT_CONNECT_RES_TIME = 10; + private static final long DOWN_WAIT_TIMEOUT_SEC = 5; ++ private static final long FORCE_DETACH_RETRY_WAIT_MS = 250; ++ private static final String BITMAP_LEAK_ERR_MSG = "already has a bitmap, this should not happen"; ++ private static final Pattern BITMAP_LEAK_MINOR_PATTERN = Pattern.compile("\\bminor\\s+(\\d+)\\b"); + + private final ExtCmdFactory extCmdFactory; + private final AccessContext sysCtx; +@@ -131,8 +136,38 @@ public class DrbdAdm + // command.add(resName); + command.add(drbdRscData.getSuffixedResourceName()); + // execute(Arrays.asList("drbdsetup", "show", drbdRscData.getSuffixedResourceName())); +- execute(command); +- // execute(Arrays.asList("drbdsetup", "show", drbdRscData.getSuffixedResourceName())); ++ String[] commandArr = command.toArray(new String[0]); ++ try ++ { ++ File nullDevice = new File(Platform.nullDevice()); ++ ExtCmd extCmd = extCmdFactory.create(); ++ if (Platform.isWindows()) ++ { ++ extCmd.setTimeout(TimeoutType.WAIT, 5 * 60 * 1000); ++ } ++ ++ OutputData outputData = extCmd.pipeExec(ProcessBuilder.Redirect.from(nullDevice), commandArr); ++ if ( ++ outputData.exitCode != 0 && ++ isBitmapLeakOnAttach(outputData) && ++ cleanupStaleBitmapAndRetry(extCmd, nullDevice, outputData) ++ ) ++ { ++ outputData = extCmd.pipeExec(ProcessBuilder.Redirect.from(nullDevice), commandArr); ++ } ++ if (outputData.exitCode != 0) ++ { ++ throw new ExtCmdFailedException(commandArr, outputData); ++ } ++ } ++ catch (ChildProcessTimeoutException timeoutExc) ++ { ++ throw new ExtCmdFailedException(commandArr, timeoutExc); ++ } ++ catch (IOException ioExc) ++ { ++ throw new ExtCmdFailedException(commandArr, ioExc); ++ } + + drbdRscData.setAdjustRequired(false); + } +@@ -805,6 +840,75 @@ public class DrbdAdm + } + } + ++ static boolean isBitmapLeakOnAttach(OutputData outputData) ++ { ++ return extractBitmapLeakMinor(outputData) != null; ++ } ++ ++ static @Nullable Integer extractBitmapLeakMinor(OutputData outputData) ++ { ++ String stderr = new String(outputData.stderrData, StandardCharsets.UTF_8); ++ if (!stderr.contains(BITMAP_LEAK_ERR_MSG)) ++ { ++ return null; ++ } ++ ++ Matcher matcher = BITMAP_LEAK_MINOR_PATTERN.matcher(stderr); ++ if (!matcher.find()) ++ { ++ return null; ++ } ++ ++ return Integer.parseInt(matcher.group(1)); ++ } ++ ++ private boolean cleanupStaleBitmapAndRetry( ++ ExtCmd extCmd, ++ File nullDevice, ++ OutputData outputData ++ ) ++ throws IOException, ChildProcessTimeoutException, ExtCmdFailedException ++ { ++ @Nullable Integer minor = extractBitmapLeakMinor(outputData); ++ if (minor == null) ++ { ++ return false; ++ } ++ ++ OutputData detachOut = extCmd.pipeExec( ++ ProcessBuilder.Redirect.from(nullDevice), ++ DRBDSETUP_UTIL, ++ "detach", ++ Integer.toString(minor) ++ ); ++ if (detachOut.exitCode == 0) ++ { ++ return true; ++ } ++ ++ OutputData forceDetachOut = extCmd.pipeExec( ++ ProcessBuilder.Redirect.from(nullDevice), ++ DRBDSETUP_UTIL, ++ "detach", ++ Integer.toString(minor), ++ "--force" ++ ); ++ if (forceDetachOut.exitCode != 0) ++ { ++ throw new ExtCmdFailedException(forceDetachOut.executedCommand, forceDetachOut); ++ } ++ ++ try ++ { ++ Thread.sleep(FORCE_DETACH_RETRY_WAIT_MS); ++ } ++ catch (InterruptedException ignored) ++ { ++ Thread.currentThread().interrupt(); ++ } ++ return true; ++ } ++ + public static class DrbdPrimary implements AutoCloseable + { + private final DrbdAdm drbdAdm; diff --git a/packages/system/linstor/images/piraeus-server/patches/skip-adjust-when-device-inaccessible.diff b/packages/system/linstor/images/piraeus-server/patches/skip-adjust-when-device-inaccessible.diff deleted file mode 100644 index a93d7811..00000000 --- a/packages/system/linstor/images/piraeus-server/patches/skip-adjust-when-device-inaccessible.diff +++ /dev/null @@ -1,155 +0,0 @@ -diff --git a/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java b/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java -index 49138a8fd..2f768ca0d 100644 ---- a/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java -+++ b/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java -@@ -83,6 +83,8 @@ import java.util.TreeMap; - import java.util.TreeSet; - import java.util.concurrent.atomic.AtomicBoolean; - import java.util.function.Function; -+import java.nio.file.Files; -+import java.nio.file.Paths; - - @Singleton - public class DeviceHandlerImpl implements DeviceHandler -@@ -1646,7 +1648,10 @@ public class DeviceHandlerImpl implements DeviceHandler - private void updateDiscGran(VlmProviderObject vlmData) throws DatabaseException, StorageException - { - String devicePath = vlmData.getDevicePath(); -- if (devicePath != null && vlmData.exists()) -+ // Check if device path physically exists before calling lsblk -+ // This is important for DRBD devices which might be temporarily unavailable during adjust -+ // (drbdadm adjust brings devices down/up, and kernel might not have created the device node yet) -+ if (devicePath != null && vlmData.exists() && Files.exists(Paths.get(devicePath))) - { - if (vlmData.getDiscGran() == VlmProviderObject.UNINITIALIZED_SIZE) - { -diff --git a/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java b/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java -index 01967a31f..78b8195a4 100644 ---- a/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java -+++ b/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java -@@ -592,7 +592,29 @@ public class DrbdLayer implements DeviceLayer - // The .res file might not have been generated in the prepare method since it was - // missing information from the child-layers. Now that we have processed them, we - // need to make sure the .res file exists in all circumstances. -- regenerateResFile(drbdRscData); -+ // However, if the underlying devices are not accessible (e.g., LUKS device is closed -+ // during resource deletion), we skip regenerating the res file to avoid errors -+ boolean canRegenerateResFile = true; -+ if (!skipDisk && !drbdRscData.getAbsResource().isDrbdDiskless(workerCtx)) -+ { -+ AbsRscLayerObject dataChild = drbdRscData.getChildBySuffix(RscLayerSuffixes.SUFFIX_DATA); -+ if (dataChild != null) -+ { -+ for (DrbdVlmData drbdVlmData : drbdRscData.getVlmLayerObjects().values()) -+ { -+ VlmProviderObject childVlm = dataChild.getVlmProviderObject(drbdVlmData.getVlmNr()); -+ if (childVlm == null || !childVlm.exists() || childVlm.getDevicePath() == null) -+ { -+ canRegenerateResFile = false; -+ break; -+ } -+ } -+ } -+ } -+ if (canRegenerateResFile) -+ { -+ regenerateResFile(drbdRscData); -+ } - - // createMetaData needs rendered resFile - for (DrbdVlmData drbdVlmData : createMetaData) -@@ -766,19 +788,72 @@ public class DrbdLayer implements DeviceLayer - - if (drbdRscData.isAdjustRequired()) - { -- try -+ // Check if underlying devices are accessible before adjusting -+ // This is important for encrypted resources (LUKS) where the device -+ // might be closed during deletion -+ boolean canAdjust = true; -+ -+ // IMPORTANT: Check child volumes only when disk access is actually needed. -+ // For network reconnect (StandAlone -> Connected), disk access is not required. -+ boolean needsDiskAccess = false; -+ -+ // Check if there are pending operations that require disk access -+ for (DrbdVlmData drbdVlmData : drbdRscData.getVlmLayerObjects().values()) - { -- drbdUtils.adjust( -- drbdRscData, -- false, -- skipDisk, -- false -- ); -+ Volume vlm = (Volume) drbdVlmData.getVolume(); -+ StateFlags vlmFlags = vlm.getFlags(); -+ -+ // Disk access is needed if: -+ // - creating a new volume -+ // - resizing -+ // - checking/creating metadata -+ if (!drbdVlmData.exists() || -+ drbdVlmData.checkMetaData() || -+ vlmFlags.isSomeSet(workerCtx, Volume.Flags.RESIZE, Volume.Flags.DRBD_RESIZE)) -+ { -+ needsDiskAccess = true; -+ break; -+ } -+ } -+ -+ // Check child volumes only if disk access is actually needed -+ if (needsDiskAccess && !skipDisk && !drbdRscData.getAbsResource().isDrbdDiskless(workerCtx)) -+ { -+ AbsRscLayerObject dataChild = drbdRscData.getChildBySuffix(RscLayerSuffixes.SUFFIX_DATA); -+ if (dataChild != null) -+ { -+ for (DrbdVlmData drbdVlmData : drbdRscData.getVlmLayerObjects().values()) -+ { -+ VlmProviderObject childVlm = dataChild.getVlmProviderObject(drbdVlmData.getVlmNr()); -+ if (childVlm == null || !childVlm.exists() || childVlm.getDevicePath() == null) -+ { -+ canAdjust = false; -+ break; -+ } -+ } -+ } - } -- catch (ExtCmdFailedException extCmdExc) -+ -+ if (canAdjust) -+ { -+ try -+ { -+ drbdUtils.adjust( -+ drbdRscData, -+ false, -+ skipDisk, -+ false -+ ); -+ } -+ catch (ExtCmdFailedException extCmdExc) -+ { -+ restoreBackupResFile(drbdRscData); -+ throw extCmdExc; -+ } -+ } -+ else - { -- restoreBackupResFile(drbdRscData); -- throw extCmdExc; -+ drbdRscData.setAdjustRequired(false); - } - } - -diff --git a/satellite/src/main/java/com/linbit/linstor/layer/luks/LuksLayer.java b/satellite/src/main/java/com/linbit/linstor/layer/luks/LuksLayer.java -index cdca0b6d2..89c8be9da 100644 ---- a/satellite/src/main/java/com/linbit/linstor/layer/luks/LuksLayer.java -+++ b/satellite/src/main/java/com/linbit/linstor/layer/luks/LuksLayer.java -@@ -383,6 +383,7 @@ public class LuksLayer implements DeviceLayer - vlmData.setSizeState(Size.AS_EXPECTED); - - vlmData.setOpened(true); -+ vlmData.setExists(true); - vlmData.setFailed(false); - } - } diff --git a/packages/system/linstor/templates/cluster.yaml b/packages/system/linstor/templates/cluster.yaml index ba611e17..e45b1dff 100644 --- a/packages/system/linstor/templates/cluster.yaml +++ b/packages/system/linstor/templates/cluster.yaml @@ -20,6 +20,8 @@ spec: - name: DrbdOptions/auto-diskful-allow-cleanup value: {{ .Values.linstor.autoDiskful.allowCleanup | quote }} {{- end }} + - name: DrbdOptions/Net/verify-alg + value: "crc32c" - name: DrbdOptions/Net/connect-int value: "15" - name: DrbdOptions/Net/ping-int diff --git a/packages/system/linstor/values.yaml b/packages/system/linstor/values.yaml index 3986fc4f..b860a6f6 100644 --- a/packages/system/linstor/values.yaml +++ b/packages/system/linstor/values.yaml @@ -1,7 +1,7 @@ piraeusServer: image: repository: ghcr.io/cozystack/cozystack/piraeus-server - tag: 1.32.3@sha256:64c91357affc6317d9544fc35b3ff8b2fcb065ad8fc5ed26f7a2fa8ac991a1c1 + tag: 1.33.2@sha256:553f313ab35dc2e345ef3683156d29e75c23177e2750e9af3a83aa9e23941cbb # Talos-specific workarounds (disable for generic Linux like Ubuntu/Debian) talos: enabled: true @@ -13,4 +13,4 @@ linstor: linstorCSI: image: repository: ghcr.io/cozystack/cozystack/linstor-csi - tag: v1.10.5@sha256:585ee6336036cb9e0b2183a6ab3f67e1ccb6d35a1df484d36e734444ee09eb4f + tag: v1.10.5@sha256:44154467c2f13c30ad09d932f51a935b143028bce54f877f00a51d1e43979f61 diff --git a/packages/system/monitoring/templates/alerta/alerta-db.yaml b/packages/system/monitoring/templates/alerta/alerta-db.yaml index fc03cd31..af9dc72c 100644 --- a/packages/system/monitoring/templates/alerta/alerta-db.yaml +++ b/packages/system/monitoring/templates/alerta/alerta-db.yaml @@ -5,6 +5,9 @@ metadata: name: alerta-db spec: instances: 2 + {{- $existingCluster := lookup "postgresql.cnpg.io/v1" "Cluster" .Release.Namespace "alerta-db" }} + {{- $image := dig "spec" "imageName" "ghcr.io/cloudnative-pg/postgresql:17.7-standard-trixie" $existingCluster }} + imageName: {{ if regexMatch ":17\\." $image }}ghcr.io/cloudnative-pg/postgresql:17.7-standard-trixie{{ else if regexMatch ":[0-9]+\\.[0-9]+$" $image }}{{ printf "%s-standard-trixie" $image }}{{ else }}{{ $image }}{{ end }} {{- if .Values._cluster.scheduling }} {{- $rawConstraints := get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- if $rawConstraints }} diff --git a/packages/system/monitoring/templates/alerta/alerta.yaml b/packages/system/monitoring/templates/alerta/alerta.yaml index 44046d1f..d30e698d 100644 --- a/packages/system/monitoring/templates/alerta/alerta.yaml +++ b/packages/system/monitoring/templates/alerta/alerta.yaml @@ -181,7 +181,7 @@ metadata: app: alerta annotations: {{- if eq $solver "http01" }} - acme.cert-manager.io/http01-ingress-class: {{ $ingress }} + acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }} {{- end }} cert-manager.io/cluster-issuer: {{ $clusterIssuer }} spec: diff --git a/packages/system/monitoring/templates/dashboards.yaml b/packages/system/monitoring/templates/dashboards.yaml index 01f1c009..0427bfa0 100644 --- a/packages/system/monitoring/templates/dashboards.yaml +++ b/packages/system/monitoring/templates/dashboards.yaml @@ -14,7 +14,7 @@ spec: url: http://grafana-dashboards.cozy-grafana-operator.svc/{{ . }}.json {{- end }} {{- end }} -{{- if eq .Release.Namespace "tenant-root" }} +{{- if or (eq .Release.Namespace "tenant-root") (eq .Release.Namespace "cozy-monitoring") }} {{- range (split "\n" (.Files.Get "dashboards-infra.list")) }} {{- $parts := split "/" . }} {{- if eq (len $parts) 2 }} diff --git a/packages/system/monitoring/templates/grafana/db.yaml b/packages/system/monitoring/templates/grafana/db.yaml index b20a8fd8..afc2d0d7 100644 --- a/packages/system/monitoring/templates/grafana/db.yaml +++ b/packages/system/monitoring/templates/grafana/db.yaml @@ -4,6 +4,9 @@ metadata: name: grafana-db spec: instances: 2 + {{- $existingCluster := lookup "postgresql.cnpg.io/v1" "Cluster" .Release.Namespace "grafana-db" }} + {{- $image := dig "spec" "imageName" "ghcr.io/cloudnative-pg/postgresql:17.7-standard-trixie" $existingCluster }} + imageName: {{ if regexMatch ":17\\." $image }}ghcr.io/cloudnative-pg/postgresql:17.7-standard-trixie{{ else if regexMatch ":[0-9]+\\.[0-9]+$" $image }}{{ printf "%s-standard-trixie" $image }}{{ else }}{{ $image }}{{ end }} storage: size: {{ .Values.grafana.db.size }} {{- if .Values._cluster.scheduling }} diff --git a/packages/system/monitoring/templates/grafana/grafana.yaml b/packages/system/monitoring/templates/grafana/grafana.yaml index d7f51e31..d65a7dc4 100644 --- a/packages/system/monitoring/templates/grafana/grafana.yaml +++ b/packages/system/monitoring/templates/grafana/grafana.yaml @@ -74,7 +74,7 @@ spec: metadata: annotations: {{- if eq $solver "http01" }} - acme.cert-manager.io/http01-ingress-class: "{{ $ingress }}" + acme.cert-manager.io/http01-ingress-ingressclassname: "{{ $ingress }}" {{- end }} cert-manager.io/cluster-issuer: {{ $clusterIssuer }} spec: diff --git a/packages/system/multus/Makefile b/packages/system/multus/Makefile index 9b34606d..399af728 100644 --- a/packages/system/multus/Makefile +++ b/packages/system/multus/Makefile @@ -11,3 +11,15 @@ update: wget -q https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/refs/tags/$(RELEASE)/deployments/multus-daemonset-thick.yml -O templates/multus-daemonset-thick.yml sed -i '/multus-cni/s/snapshot-thick/$(RELEASE)-thick/' templates/multus-daemonset-thick.yml && \ patch --no-backup-if-mismatch -p4 < patches/customize-deployment.patch + $(SED_INPLACE) "/ARG VERSION/ s|=.*|=$(RELEASE)|g" images/multus-cni/Dockerfile + +image: + docker buildx build images/multus-cni \ + --tag $(REGISTRY)/multus-cni:$(call settag,$(TAG)) \ + --cache-from type=registry,ref=$(REGISTRY)/multus-cni:latest \ + --cache-to type=inline \ + --metadata-file images/multus-cni.json \ + $(BUILDX_ARGS) + DIGEST=$$(yq e '."containerimage.digest"' images/multus-cni.json -o json -r) && \ + sed -i "s|image: .*multus-cni.*|image: $(REGISTRY)/multus-cni:$(TAG)@$${DIGEST}|g" templates/multus-daemonset-thick.yml + rm -f images/multus-cni.json diff --git a/packages/system/multus/images/multus-cni/Dockerfile b/packages/system/multus/images/multus-cni/Dockerfile new file mode 100644 index 00000000..feecbc6c --- /dev/null +++ b/packages/system/multus/images/multus-cni/Dockerfile @@ -0,0 +1,25 @@ +FROM --platform=$BUILDPLATFORM golang:1.24 AS build + +ARG VERSION=v4.2.3 +ARG TARGETPLATFORM + +WORKDIR /usr/src/multus-cni + +RUN curl -sSL https://github.com/k8snetworkplumbingwg/multus-cni/archive/refs/tags/${VERSION}.tar.gz | \ + tar -xzf - --strip=1 + +COPY patches /patches +RUN git init && git config user.email "build@cozystack" && git config user.name "build" && \ + git add -A && git commit -m "init" && git tag ${VERSION} && \ + git apply /patches/*.diff + +RUN ./hack/build-go.sh + +FROM debian:stable-slim +LABEL org.opencontainers.image.source=https://github.com/cozystack/cozystack +COPY --from=build /usr/src/multus-cni/bin /usr/src/multus-cni/bin +COPY --from=build /usr/src/multus-cni/LICENSE /usr/src/multus-cni/LICENSE +COPY --from=build /usr/src/multus-cni/bin/cert-approver / +WORKDIR / + +ENTRYPOINT ["/usr/src/multus-cni/bin/multus-daemon"] diff --git a/packages/system/multus/images/multus-cni/patches/fix-del-without-cache.diff b/packages/system/multus/images/multus-cni/patches/fix-del-without-cache.diff new file mode 100644 index 00000000..7595ddb2 --- /dev/null +++ b/packages/system/multus/images/multus-cni/patches/fix-del-without-cache.diff @@ -0,0 +1,49 @@ +diff --git a/pkg/multus/multus.go b/pkg/multus/multus.go +index a7941fe..eff0406 100644 +--- a/pkg/multus/multus.go ++++ b/pkg/multus/multus.go +@@ -954,33 +954,19 @@ func CmdDel(args *skel.CmdArgs, exec invoke.Exec, kubeClient *k8s.ClientInfo) er + } + + if !useCacheConf { +- // Fetch delegates again if cache is not exist and pod info can be read +- if os.IsNotExist(err) && pod != nil { +- if in.ClusterNetwork != "" { +- _, err = k8s.GetDefaultNetworks(pod, in, kubeClient, nil) +- if err != nil { +- return cmdErr(k8sArgs, "failed to get clusterNetwork/defaultNetworks: %v", err) +- } +- // First delegate is always the master plugin +- in.Delegates[0].MasterPlugin = true +- } +- +- // Get pod annotation and so on +- _, _, err := k8s.TryLoadPodDelegates(pod, in, kubeClient, nil) +- if err != nil { +- if len(in.Delegates) == 0 { +- // No delegate available so send error +- return cmdErr(k8sArgs, "failed to get delegates: %v", err) +- } +- // Get clusterNetwork before, so continue to delete +- logging.Errorf("Multus: failed to get delegates: %v, but continue to delete clusterNetwork", err) +- } +- } else { +- // The options to continue with a delete have been exhausted (cachefile + API query didn't work) +- // We cannot exit with an error as this may cause a sandbox to never get deleted. +- logging.Errorf("Multus: failed to get the cached delegates file: %v, cannot properly delete", err) ++ // If cache does not exist, ADD was never completed for this sandbox. ++ // Attempting DEL with a config reconstructed from the API will fail because ++ // delegate plugins (e.g. kube-ovn) require runtime state that only exists ++ // after a successful ADD (socket paths, cached results, etc.). ++ // Return success to allow containerd to release the sandbox name reservation. ++ if os.IsNotExist(err) { ++ logging.Verbosef("Multus: no cache found for container %s (ADD was never completed), skip DEL", args.ContainerID) + return nil + } ++ // The options to continue with a delete have been exhausted ++ // We cannot exit with an error as this may cause a sandbox to never get deleted. ++ logging.Errorf("Multus: failed to get the cached delegates file: %v, cannot properly delete", err) ++ return nil + } + + // set CNIVersion in delegate CNI config if there is no CNIVersion and multus conf have CNIVersion. diff --git a/packages/system/multus/templates/multus-daemonset-thick.yml b/packages/system/multus/templates/multus-daemonset-thick.yml index 2f00de85..99baf1af 100644 --- a/packages/system/multus/templates/multus-daemonset-thick.yml +++ b/packages/system/multus/templates/multus-daemonset-thick.yml @@ -119,6 +119,7 @@ data: "cniConfigDir": "/host/etc/cni/net.d", "multusAutoconfigDir": "/host/etc/cni/net.d", "multusConfigFile": "auto", + "multusMasterCNI": "05-cilium.conflist", "socketDir": "/host/run/multus/" } --- @@ -154,7 +155,7 @@ spec: serviceAccountName: multus containers: - name: kube-multus - image: ghcr.io/k8snetworkplumbingwg/multus-cni:v4.2.3-thick + image: ghcr.io/cozystack/cozystack/multus-cni:v1.2.4@sha256:6735ffc12e5e660951f5a42943b38c308f33774a55836e94c191001405b58ec0 command: [ "/usr/src/multus-cni/bin/multus-daemon" ] resources: requests: @@ -200,7 +201,7 @@ spec: fieldPath: spec.nodeName initContainers: - name: install-multus-binary - image: ghcr.io/k8snetworkplumbingwg/multus-cni:v4.2.3-thick + image: ghcr.io/cozystack/cozystack/multus-cni:v1.2.4@sha256:6735ffc12e5e660951f5a42943b38c308f33774a55836e94c191001405b58ec0 command: - "/usr/src/multus-cni/bin/install_multus" - "-d" diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index d2a5fc7c..f241407b 100644 --- a/packages/system/objectstorage-controller/values.yaml +++ b/packages/system/objectstorage-controller/values.yaml @@ -1,3 +1,3 @@ objectstorage: controller: - image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.2.0@sha256:05aee8bf8b292150090868163c6bb49962d874ac63858c93f8e130f53dd40b85" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.2.4@sha256:e176668b8daecc5d462b843285c907c873edeb2d888af31254b30bb02320c394" diff --git a/packages/system/seaweedfs/templates/database.yaml b/packages/system/seaweedfs/templates/database.yaml index e78caa89..b9b50c49 100644 --- a/packages/system/seaweedfs/templates/database.yaml +++ b/packages/system/seaweedfs/templates/database.yaml @@ -5,6 +5,9 @@ metadata: name: seaweedfs-db spec: instances: {{ .Values.db.replicas }} + {{- $existingCluster := lookup "postgresql.cnpg.io/v1" "Cluster" .Release.Namespace "seaweedfs-db" }} + {{- $image := dig "spec" "imageName" "ghcr.io/cloudnative-pg/postgresql:17.7-standard-trixie" $existingCluster }} + imageName: {{ if regexMatch ":17\\." $image }}ghcr.io/cloudnative-pg/postgresql:17.7-standard-trixie{{ else if regexMatch ":[0-9]+\\.[0-9]+$" $image }}{{ printf "%s-standard-trixie" $image }}{{ else }}{{ $image }}{{ end }} storage: size: {{ .Values.db.size }} {{- with .Values.db.storageClass }} diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index 679203f5..7455e91a 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -111,7 +111,7 @@ seaweedfs: nginx.ingress.kubernetes.io/client-body-timeout: "3600" nginx.ingress.kubernetes.io/client-header-timeout: "120" nginx.ingress.kubernetes.io/service-upstream: "true" - acme.cert-manager.io/http01-ingress-class: tenant-root + acme.cert-manager.io/http01-ingress-ingressclassname: tenant-root cert-manager.io/cluster-issuer: letsencrypt-prod tls: - hosts: @@ -177,7 +177,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.2.0@sha256:b508ca451c6051b64e71cedd6209c80ac0e0bea8aa6bb47e838ec9d99bf4796a" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.2.4@sha256:cc1f60ef2cb40ab61c4787e234f83e57a7b8aca0ffe7b17f4c4fa6e3de1669f4" certificates: commonName: "SeaweedFS CA" ipAddresses: []