merge main changes

This commit is contained in:
Manoj Kumar 2026-05-25 13:29:07 +05:30
commit 2aae36d3e1
No known key found for this signature in database
GPG Key ID: E952B7234D2C6F88
147 changed files with 5067 additions and 1443 deletions

View File

@ -50,7 +50,8 @@ github:
rebase: false
collaborators:
- gpordeus
- ingox
- gp-santos
- erikbocks
- Imvedansh
- Damans227

View File

@ -283,7 +283,7 @@ jobs:
# https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2004-Readme.md#mysql
sudo apt-get install -y mysql-server
sudo systemctl start mysql
sudo mysql -uroot -proot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''; FLUSH PRIVILEGES;"
sudo mysql -uroot -proot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY ''; FLUSH PRIVILEGES;"
sudo systemctl restart mysql
sudo mysql -uroot -e "SELECT VERSION();"
@ -341,7 +341,7 @@ jobs:
echo -e "Simulator CI Test Results: (only failures listed)\n"
python3 ./tools/marvin/xunit-reader.py integration-test-results/
- uses: codecov/codecov-action@v4
- uses: codecov/codecov-action@v6
with:
files: jacoco-coverage.xml
fail_ci_if_error: true

View File

@ -49,7 +49,7 @@ jobs:
cd nonoss && bash -x install-non-oss.sh && cd ..
mvn -P quality -Dsimulator -Dnoredist clean install -T$(nproc)
- uses: codecov/codecov-action@v4
- uses: codecov/codecov-action@v6
with:
files: ./client/target/site/jacoco-aggregate/jacoco.xml
fail_ci_if_error: true

View File

@ -37,12 +37,12 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
with:
category: "Security"

View File

@ -54,11 +54,11 @@ jobs:
comment_repo: ""
steps:
- name: Setup
uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.
uses: github/gh-aw/actions/setup@f01a9d118afa6e306f3645ca31e43f4ea8fb4d22 # v0.45.
with:
destination: /opt/gh-aw/
- name: Check workflow file
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
env:
GH_AW_WORKFLOW_FILE: "daily-repo-status.lock.yml"
with:
@ -96,7 +96,7 @@ jobs:
secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
steps:
- name: Setup
uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.
uses: github/gh-aw/actions/setup@f01a9d118afa6e306f3645ca31e43f4ea8fb4d22 # v0.45.
with:
destination: /opt/gh-aw/
- name: Checkout
@ -120,7 +120,7 @@ jobs:
id: checkout-
if: |
github.event.
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
env:
GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
with:
@ -132,7 +132,7 @@ jobs:
await main();
- name: Generate agentic run
id:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
with:
script: |
const fs = require('fs');
@ -469,7 +469,7 @@ jobs:
}
- name: Generate workflow
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
with:
script: |
const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs');
@ -559,7 +559,7 @@ jobs:
{{#runtime-import .github/workflows/daily-repo-status.md}}
- name: Substitute
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.
GH_AW_GITHUB_ACTOR: ${{ github.actor }}
@ -589,7 +589,7 @@ jobs:
}
});
- name: Interpolate variables and render
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.
with:
@ -667,7 +667,7 @@ jobs:
bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
- name: Redact secrets in
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
@ -682,7 +682,7 @@ jobs:
SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Safe
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v6.0.
with:
name: safe-
path: ${{ env.GH_AW_SAFE_OUTPUTS }}
@ -690,7 +690,7 @@ jobs:
- name: Ingest agent
id:
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
env:
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com"
@ -704,13 +704,13 @@ jobs:
await main();
- name: Upload sanitized agent
if: always() && env.
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v6.0.
with:
name: agent-
path: ${{ env.GH_AW_AGENT_OUTPUT }}
if-no-files-found:
- name: Upload engine output
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v6.0.
with:
name:
path: |
@ -719,7 +719,7 @@ jobs:
if-no-files-found:
- name: Parse agent logs for step
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
env:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
with:
@ -730,7 +730,7 @@ jobs:
await main();
- name: Parse MCP Gateway logs for step
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
@ -755,7 +755,7 @@ jobs:
- name: Upload agent
if: always()
continue-on-error:
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v6.0.
with:
name: agent-
path: |
@ -784,12 +784,12 @@ jobs:
total_count: ${{ steps.missing_tool.outputs.total_count }}
steps:
- name: Setup
uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.
uses: github/gh-aw/actions/setup@f01a9d118afa6e306f3645ca31e43f4ea8fb4d22 # v0.45.
with:
destination: /opt/gh-aw/
- name: Download agent output
continue-on-error:
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v6.0.
with:
name: agent-
path: /tmp/gh-aw/safeoutputs/
@ -800,7 +800,7 @@ jobs:
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
- name: Process No-Op
id:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_NOOP_MAX:
@ -816,7 +816,7 @@ jobs:
await main();
- name: Record Missing
id:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Daily Repo Status"
@ -831,7 +831,7 @@ jobs:
await main();
- name: Handle Agent
id:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Daily Repo Status"
@ -851,7 +851,7 @@ jobs:
await main();
- name: Handle No-Op
id:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Daily Repo Status"
@ -881,18 +881,18 @@ jobs:
success: ${{ steps.parse_results.outputs.success }}
steps:
- name: Setup
uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.
uses: github/gh-aw/actions/setup@f01a9d118afa6e306f3645ca31e43f4ea8fb4d22 # v0.45.
with:
destination: /opt/gh-aw/
- name: Download agent
continue-on-error:
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v6.0.
with:
name: agent-
path: /tmp/gh-aw/threat-detection/
- name: Download agent output
continue-on-error:
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v6.0.
with:
name: agent-
path: /tmp/gh-aw/threat-detection/
@ -902,7 +902,7 @@ jobs:
run: |
echo "Agent output-types: $AGENT_OUTPUT_TYPES"
- name: Setup threat
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
env:
WORKFLOW_NAME: "Daily Repo Status"
WORKFLOW_DESCRIPTION: "This workflow creates daily repo status reports. It gathers recent repository\nactivity (issues, PRs, discussions, releases, code changes) and generates\nengaging GitHub issues with productivity insights, community highlights,\nand project recommendations."
@ -955,7 +955,7 @@ jobs:
XDG_CONFIG_HOME: /home/
- name: Parse threat detection
id:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
@ -964,7 +964,7 @@ jobs:
await main();
- name: Upload threat detection
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v6.0.
with:
name: threat-detection.
path: /tmp/gh-aw/threat-detection/detection.
@ -993,12 +993,12 @@ jobs:
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup
uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.
uses: github/gh-aw/actions/setup@f01a9d118afa6e306f3645ca31e43f4ea8fb4d22 # v0.45.
with:
destination: /opt/gh-aw/
- name: Download agent output
continue-on-error:
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v6.0.
with:
name: agent-
path: /tmp/gh-aw/safeoutputs/
@ -1009,7 +1009,7 @@ jobs:
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
- name: Process Safe
id:
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"labels\":[\"report\",\"daily-status\"],\"max\":1,\"title_prefix\":\"[repo-status] \"},\"missing_data\":{},\"missing_tool\":{}}"

View File

@ -38,7 +38,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Login to Docker Registry
uses: docker/login-action@v2
uses: docker/login-action@v4
with:
registry: ${{ secrets.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USER }}

View File

@ -53,11 +53,11 @@ jobs:
comment_repo: ""
steps:
- name: Setup Scripts
uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0
uses: github/gh-aw/actions/setup@f01a9d118afa6e306f3645ca31e43f4ea8fb4d22 # v0.71.1
with:
destination: /opt/gh-aw/actions
- name: Check workflow file timestamps
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_WORKFLOW_FILE: "issue-triage-agent.lock.yml"
with:
@ -91,7 +91,7 @@ jobs:
secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
steps:
- name: Setup Scripts
uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0
uses: github/gh-aw/actions/setup@f01a9d118afa6e306f3645ca31e43f4ea8fb4d22 # v0.71.1
with:
destination: /opt/gh-aw/actions
- name: Checkout repository
@ -113,7 +113,7 @@ jobs:
echo "Git configured with standard GitHub Actions identity"
- name: Generate agentic run info
id: generate_aw_info
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const fs = require('fs');
@ -167,7 +167,7 @@ jobs:
run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.18.0
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
@ -459,7 +459,7 @@ jobs:
}
GH_AW_MCP_CONFIG_EOF
- name: Generate workflow overview
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs');
@ -552,7 +552,7 @@ jobs:
{{#runtime-import .github/workflows/issue-triage-agent.md}}
GH_AW_PROMPT_EOF
- name: Substitute placeholders
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_GITHUB_ACTOR: ${{ github.actor }}
@ -582,7 +582,7 @@ jobs:
}
});
- name: Interpolate variables and render templates
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
@ -661,7 +661,7 @@ jobs:
bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
- name: Redact secrets in logs
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
@ -676,7 +676,7 @@ jobs:
SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Safe Outputs
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: safe-output
path: ${{ env.GH_AW_SAFE_OUTPUTS }}
@ -684,7 +684,7 @@ jobs:
- name: Ingest agent output
id: collect_output
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com"
@ -698,13 +698,13 @@ jobs:
await main();
- name: Upload sanitized agent output
if: always() && env.GH_AW_AGENT_OUTPUT
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: agent-output
path: ${{ env.GH_AW_AGENT_OUTPUT }}
if-no-files-found: warn
- name: Upload engine output files
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: agent_outputs
path: |
@ -713,7 +713,7 @@ jobs:
if-no-files-found: ignore
- name: Parse agent logs for step summary
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
with:
@ -724,7 +724,7 @@ jobs:
await main();
- name: Parse MCP Gateway logs for step summary
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
@ -749,7 +749,7 @@ jobs:
- name: Upload agent artifacts
if: always()
continue-on-error: true
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: agent-artifacts
path: |
@ -780,12 +780,12 @@ jobs:
total_count: ${{ steps.missing_tool.outputs.total_count }}
steps:
- name: Setup Scripts
uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0
uses: github/gh-aw/actions/setup@f01a9d118afa6e306f3645ca31e43f4ea8fb4d22 # v0.71.1
with:
destination: /opt/gh-aw/actions
- name: Download agent output artifact
continue-on-error: true
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: agent-output
path: /tmp/gh-aw/safeoutputs/
@ -796,7 +796,7 @@ jobs:
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
- name: Process No-Op Messages
id: noop
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_NOOP_MAX: 1
@ -812,7 +812,7 @@ jobs:
await main();
- name: Record Missing Tool
id: missing_tool
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
@ -827,7 +827,7 @@ jobs:
await main();
- name: Handle Agent Failure
id: handle_agent_failure
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
@ -846,7 +846,7 @@ jobs:
await main();
- name: Handle No-Op Message
id: handle_noop_message
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
@ -874,18 +874,18 @@ jobs:
success: ${{ steps.parse_results.outputs.success }}
steps:
- name: Setup Scripts
uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0
uses: github/gh-aw/actions/setup@f01a9d118afa6e306f3645ca31e43f4ea8fb4d22 # v0.71.1
with:
destination: /opt/gh-aw/actions
- name: Download agent artifacts
continue-on-error: true
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: agent-artifacts
path: /tmp/gh-aw/threat-detection/
- name: Download agent output artifact
continue-on-error: true
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: agent-output
path: /tmp/gh-aw/threat-detection/
@ -895,7 +895,7 @@ jobs:
run: |
echo "Agent output-types: $AGENT_OUTPUT_TYPES"
- name: Setup threat detection
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
WORKFLOW_NAME: "Issue Triage Agent"
WORKFLOW_DESCRIPTION: "No description provided"
@ -948,7 +948,7 @@ jobs:
XDG_CONFIG_HOME: /home/runner
- name: Parse threat detection results
id: parse_results
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
@ -957,7 +957,7 @@ jobs:
await main();
- name: Upload threat detection log
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: threat-detection.log
path: /tmp/gh-aw/threat-detection/detection.log
@ -987,12 +987,12 @@ jobs:
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup Scripts
uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0
uses: github/gh-aw/actions/setup@f01a9d118afa6e306f3645ca31e43f4ea8fb4d22 # v0.71.1
with:
destination: /opt/gh-aw/actions
- name: Download agent output artifact
continue-on-error: true
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: agent-output
path: /tmp/gh-aw/safeoutputs/
@ -1003,7 +1003,7 @@ jobs:
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
- name: Process Safe Outputs
id: process_safe_outputs
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"add_labels\":{\"allowed\":[\"bug\",\"feature\",\"enhancement\",\"documentation\",\"question\",\"help-wanted\",\"good-first-issue\"]},\"missing_data\":{},\"missing_tool\":{}}"

View File

@ -17,16 +17,14 @@
name: "PR Merge Conflict Check"
on:
push:
pull_request:
types: [opened, synchronize, reopened]
schedule:
- cron: '*/10 * * * *'
workflow_dispatch:
permissions: # added using https://github.com/step-security/secure-workflows
contents: read
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
group: "gh-aw-${{ github.workflow }}"
jobs:
triage:
@ -35,7 +33,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Conflict Check
uses: eps1lon/actions-label-merge-conflict@v2.0.0
uses: eps1lon/actions-label-merge-conflict@v3.0.3
with:
repoToken: "${{ secrets.GITHUB_TOKEN }}"
dirtyLabel: "status:has-conflicts"

View File

@ -34,7 +34,7 @@ jobs:
- uses: actions/checkout@v6
- name: Set up Node
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 16
@ -55,7 +55,7 @@ jobs:
npm run lint
npm run test:unit
- uses: codecov/codecov-action@v4
- uses: codecov/codecov-action@v6
if: github.repository == 'apache/cloudstack'
with:
working-directory: ui

View File

@ -51,6 +51,7 @@ public class VirtualMachineTO {
private long minRam;
private long maxRam;
private long requestedRam;
private String hostName;
private String arch;
private String os;
@ -207,15 +208,20 @@ public class VirtualMachineTO {
return minRam;
}
public void setRam(long minRam, long maxRam) {
public void setRam(long minRam, long maxRam, long requestedRam) {
this.minRam = minRam;
this.maxRam = maxRam;
this.requestedRam = requestedRam;
}
public long getMaxRam() {
return maxRam;
}
public long getRequestedRam() {
return requestedRam;
}
public String getHostName() {
return hostName;
}

View File

@ -26,17 +26,19 @@ public interface Investigator extends Adapter {
* Returns if the vm is still alive.
*
* @param vm to work on.
* @return true if vm is alive, otherwise false
*/
public boolean isVmAlive(VirtualMachine vm, Host host) throws UnknownVM;
boolean isVmAlive(VirtualMachine vm, Host host) throws UnknownVM;
public Status isAgentAlive(Host agent);
/**
* Returns the agent status of the host.
*
* @param host
* @return status of the host agent
*/
Status getHostAgentStatus(Host host);
class UnknownVM extends Exception {
/**
*
*/
private static final long serialVersionUID = 1L;
};
}

View File

@ -63,6 +63,12 @@ public class ProvisionCertificateCmd extends BaseAsyncCmd {
description = "Name of the CA service provider, otherwise the default configured provider plugin will be used")
private String provider;
@Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN,
description = "When true, uses SSH to re-provision the agent's certificate, bypassing the NIO agent connection. " +
"Use this when agents are disconnected due to a CA change. Supported for KVM hosts and SystemVMs. Default is false",
since = "4.23.0")
private Boolean forced;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -79,6 +85,10 @@ public class ProvisionCertificateCmd extends BaseAsyncCmd {
return provider;
}
public boolean isForced() {
return forced != null && forced;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@ -90,7 +100,7 @@ public class ProvisionCertificateCmd extends BaseAsyncCmd {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find host by ID: " + getHostId());
}
boolean result = caManager.provisionCertificate(host, getReconnect(), getProvider());
boolean result = caManager.provisionCertificate(host, getReconnect(), getProvider(), isForced());
SuccessResponse response = new SuccessResponse(getCommandName());
response.setSuccess(result);
setResponseObject(response);

View File

@ -87,6 +87,7 @@ public final class ConfigureHAForHostCmd extends BaseAsyncCmd {
final HostHAResponse response = new HostHAResponse();
response.setId(resourceUuid);
response.setProvider(getHaProvider().toLowerCase());
response.setStatus(result);
response.setResponseName(getCommandName());
setResponseObject(response);
}

View File

@ -22,7 +22,7 @@ import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.UserResponse;
import org.apache.cloudstack.api.ApiArgValidator;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.UserAccount;
@ -35,7 +35,7 @@ public class GetUserCmd extends BaseCmd {
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.USER_API_KEY, type = CommandType.STRING, required = true, description = "API key of the user")
@Parameter(name = ApiConstants.USER_API_KEY, type = CommandType.STRING, required = true, description = "API key of the user", validations = {ApiArgValidator.NotNullOrEmpty})
private String apiKey;
/////////////////////////////////////////////////////

View File

@ -23,6 +23,8 @@ import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
import com.trilead.ssh2.Connection;
import org.apache.cloudstack.framework.ca.CAProvider;
import org.apache.cloudstack.framework.ca.CAService;
import org.apache.cloudstack.framework.ca.Certificate;
@ -39,7 +41,10 @@ public interface CAManager extends CAService, Configurable, PluggableService {
ConfigKey<String> CAProviderPlugin = new ConfigKey<>("Advanced", String.class,
"ca.framework.provider.plugin",
"root",
"The CA provider plugin that is used for secure CloudStack management server-agent communication for encryption and authentication. Restart management server(s) when changed.", true);
"The CA provider plugin used for CloudStack internal certificate management (MS-agent encryption and authentication). " +
"The default 'root' provider auto-generates a CA on first startup, but also supports user-provided custom CA material " +
"via the ca.plugin.root.private.key, ca.plugin.root.public.key, and ca.plugin.root.ca.certificate settings. " +
"Restart management server(s) when changed.", false);
ConfigKey<Integer> CertKeySize = new ConfigKey<>("Advanced", Integer.class,
"ca.framework.cert.keysize",
@ -85,6 +90,12 @@ public interface CAManager extends CAService, Configurable, PluggableService {
"The actual implementation will depend on the configured CA provider.",
false);
ConfigKey<Boolean> CaInjectDefaultTruststore = new ConfigKey<>("Advanced", Boolean.class,
"ca.framework.inject.default.truststore", "true",
"When true, injects the CA provider's certificate into the JVM default truststore on management server startup. " +
"This allows outgoing HTTPS connections from the management server to trust servers with certificates signed by the configured CA. " +
"Restart management server(s) when changed.", false);
/**
* Returns a list of available CA provider plugins
* @return returns list of CAProvider
@ -130,12 +141,26 @@ public interface CAManager extends CAService, Configurable, PluggableService {
boolean revokeCertificate(final BigInteger certSerial, final String certCn, final String provider);
/**
* Provisions certificate for given active and connected agent host
* Provisions certificate for given agent host.
* When forced=true, uses SSH to re-provision bypassing the NIO agent connection (for disconnected agents).
* @param host
* @param reconnect
* @param provider
* @param forced when true, provisions via SSH instead of NIO; supports KVM hosts and SystemVMs
* @return returns success/failure as boolean
*/
boolean provisionCertificate(final Host host, final Boolean reconnect, final String provider);
boolean provisionCertificate(final Host host, final Boolean reconnect, final String provider, final boolean forced);
/**
* Provisions certificate for a KVM host using an existing SSH connection.
* Runs keystore-setup to generate a CSR, issues a certificate, then runs keystore-cert-import.
* Used during host discovery and for forced re-provisioning when the NIO agent is unreachable.
* @param sshConnection active SSH connection to the KVM host
* @param agentIp IP address of the KVM host agent
* @param agentHostname hostname of the KVM host agent
* @param caProvider optional CA provider plugin name (null uses default)
*/
void provisionCertificateViaSsh(Connection sshConnection, String agentIp, String agentHostname, String caProvider);
/**
* Setups up a new keystore and generates CSR for a host

View File

@ -722,17 +722,17 @@
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${cs.bcprov.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>${cs.bcprov.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk15on</artifactId>
<artifactId>bctls-jdk18on</artifactId>
<version>${cs.bcprov.version}</version>
</dependency>
</dependencies>
@ -912,13 +912,13 @@
</artifactItem>
<artifactItem>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<artifactId>bcprov-jdk18on</artifactId>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</artifactItem>
<artifactItem>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<artifactId>bcpkix-jdk18on</artifactId>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</artifactItem>
@ -942,7 +942,7 @@
</artifactItem>
<artifactItem>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk15on</artifactId>
<artifactId>bctls-jdk18on</artifactId>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</artifactItem>
@ -977,9 +977,9 @@
<exclude>org.apache.tomcat.embed:tomcat-embed-core</exclude>
<exclude>org.apache.geronimo.specs:geronimo-servlet_3.0_spec</exclude>
<exclude>org.apache.geronimo.specs:geronimo-javamail_1.4_spec</exclude>
<exclude>org.bouncycastle:bcprov-jdk15on</exclude>
<exclude>org.bouncycastle:bcpkix-jdk15on</exclude>
<exclude>org.bouncycastle:bctls-jdk15on</exclude>
<exclude>org.bouncycastle:bcprov-jdk18on</exclude>
<exclude>org.bouncycastle:bcpkix-jdk18on</exclude>
<exclude>org.bouncycastle:bctls-jdk18on</exclude>
<exclude>com.mysql:mysql-connector-j</exclude>
<exclude>org.apache.cloudstack:cloud-plugin-storage-volume-storpool</exclude>
<exclude>org.apache.cloudstack:cloud-plugin-storage-volume-linstor</exclude>

View File

@ -38,6 +38,8 @@ public class CheckOnHostAnswer extends Answer {
public CheckOnHostAnswer(CheckOnHostCommand cmd, String details) {
super(cmd, false, details);
determined = false;
alive = false;
}
public boolean isDetermined() {
@ -47,5 +49,4 @@ public class CheckOnHostAnswer extends Answer {
public boolean isAlive() {
return alive;
}
}

View File

@ -24,7 +24,7 @@ import com.cloud.host.Host;
public class CheckOnHostCommand extends Command {
HostTO host;
boolean reportCheckFailureIfOneStorageIsDown;
boolean reportIfHeartBeatFailedForOneStoragePool;
protected CheckOnHostCommand() {
}
@ -34,17 +34,17 @@ public class CheckOnHostCommand extends Command {
setWait(20);
}
public CheckOnHostCommand(Host host, boolean reportCheckFailureIfOneStorageIsDown) {
public CheckOnHostCommand(Host host, boolean reportIfHeartBeatFailedForOneStoragePool) {
this(host);
this.reportCheckFailureIfOneStorageIsDown = reportCheckFailureIfOneStorageIsDown;
this.reportIfHeartBeatFailedForOneStoragePool = reportIfHeartBeatFailedForOneStoragePool;
}
public HostTO getHost() {
return host;
}
public boolean isCheckFailedOnOneStorage() {
return reportCheckFailureIfOneStorageIsDown;
public boolean shouldReportIfHeartBeatFailedForOneStoragePool() {
return reportIfHeartBeatFailedForOneStoragePool;
}
@Override

View File

@ -30,6 +30,7 @@ public class ScaleVmCommand extends Command {
Integer maxSpeed;
long minRam;
long maxRam;
private boolean limitCpuUseChange;
public VirtualMachineTO getVm() {
return vm;
@ -43,7 +44,7 @@ public class ScaleVmCommand extends Command {
return cpus;
}
public ScaleVmCommand(String vmName, int cpus, Integer minSpeed, Integer maxSpeed, long minRam, long maxRam, boolean limitCpuUse) {
public ScaleVmCommand(String vmName, int cpus, Integer minSpeed, Integer maxSpeed, long minRam, long maxRam, boolean limitCpuUse, Double cpuQuotaPercentage, boolean limitCpuUseChange) {
super();
this.vmName = vmName;
this.cpus = cpus;
@ -52,6 +53,8 @@ public class ScaleVmCommand extends Command {
this.minRam = minRam;
this.maxRam = maxRam;
this.vm = new VirtualMachineTO(1L, vmName, null, cpus, minSpeed, maxSpeed, minRam, maxRam, null, null, false, limitCpuUse, null);
this.vm.setCpuQuotaPercentage(cpuQuotaPercentage);
this.limitCpuUseChange = limitCpuUseChange;
}
public void setCpus(int cpus) {
@ -102,6 +105,10 @@ public class ScaleVmCommand extends Command {
return vm;
}
public boolean getLimitCpuUseChange() {
return limitCpuUseChange;
}
@Override
public boolean executeInSequence() {
return true;

18
debian/changelog vendored
View File

@ -2,14 +2,20 @@ cloudstack (4.23.0.0-SNAPSHOT) unstable; urgency=low
* Update the version to 4.23.0.0-SNAPSHOT
-- the Apache CloudStack project <dev@cloudstack.apache.org> Fri, 22 May 2026 10:20:00 -0300
cloudstack (4.22.1.0) unstable; urgency=low
* Update the version to 4.22.1.0
-- the Apache CloudStack project <dev@cloudstack.apache.org> Mon, 11 May 2026 20:26:07 +0530
cloudstack (4.22.0.0) unstable; urgency=low
* Update the version to 4.22.0.0
-- the Apache CloudStack project <dev@cloudstack.apache.org> Thu, 30 Oct 2025 19:23:55 +0530
cloudstack (4.23.0.0-SNAPSHOT-SNAPSHOT) unstable; urgency=low
* Update the version to 4.23.0.0-SNAPSHOT-SNAPSHOT
-- the Apache CloudStack project <dev@cloudstack.apache.org> Thu, Aug 28 11:58:36 2025 +0530
cloudstack (4.21.0.0) unstable; urgency=low
* Update the version to 4.21.0.0

View File

@ -133,6 +133,20 @@ public interface CapacityManager {
"capacity.calculate.workers", "1",
"Number of worker threads to be used for capacities calculation", true);
ConfigKey<Integer> KvmMemoryDynamicScalingCapacity = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED,
Integer.class, "kvm.memory.dynamic.scaling.capacity", "0",
"Defines the maximum memory capacity in MiB for which VMs can be dynamically scaled to with KVM. " +
"The 'kvm.memory.dynamic.scaling.capacity' setting's value will be used to define the value of the " +
"'<maxMemory />' element of domain XMLs. If it is set to a value less than or equal to '0', then the host's memory capacity will be considered.",
true, ConfigKey.Scope.Cluster);
ConfigKey<Integer> KvmCpuDynamicScalingCapacity = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED,
Integer.class, "kvm.cpu.dynamic.scaling.capacity", "0",
"Defines the maximum vCPU capacity for which VMs can be dynamically scaled to with KVM. " +
"The 'kvm.cpu.dynamic.scaling.capacity' setting's value will be used to define the value of the " +
"'<vcpu />' element of domain XMLs. If it is set to a value less than or equal to '0', then the host's CPU cores capacity will be considered.",
true, ConfigKey.Scope.Cluster);
public boolean releaseVmCapacity(VirtualMachine vm, boolean moveFromReserved, boolean moveToReservered, Long hostId);
void allocateVmCapacity(VirtualMachine vm, boolean fromLastHost);

View File

@ -75,10 +75,10 @@ public interface HighAvailabilityManager extends Manager {
+ " which are registered for the HA event that were successful and are now ready to be purged.",
true, Cluster);
public static final ConfigKey<Boolean> KvmHAFenceHostIfHeartbeatFailsOnStorage = new ConfigKey<>("Advanced", Boolean.class, "kvm.ha.fence.on.storage.heartbeat.failure", "false",
ConfigKey<Boolean> KvmHAFenceHostIfHeartbeatFailsOnStorage = new ConfigKey<>("Advanced", Boolean.class, "kvm.ha.fence.on.storage.heartbeat.failure", "false",
"Proceed fencing the host even the heartbeat failed for only one storage pool", false, ConfigKey.Scope.Zone);
public enum WorkType {
enum WorkType {
Migration, // Migrating VMs off of a host.
Stop, // Stops a VM for storage pool migration purposes. This should be obsolete now.
CheckStop, // Checks if a VM has been stopped.

View File

@ -50,6 +50,7 @@ import javax.inject.Inject;
import javax.naming.ConfigurationException;
import javax.persistence.EntityExistsException;
import com.cloud.hypervisor.KVMGuru;
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
@ -5162,7 +5163,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
try {
result = retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome);
} catch (Exception ex) {
throw new RuntimeException("Unhandled exception", ex);
throw new RuntimeException("Unable to reconfigure VM.", ex);
}
if (result != null) {
@ -5175,22 +5176,29 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
private VMInstanceVO orchestrateReConfigureVm(String vmUuid, ServiceOffering oldServiceOffering, ServiceOffering newServiceOffering,
boolean reconfiguringOnExistingHost) throws ResourceUnavailableException, ConcurrentOperationException {
final VMInstanceVO vm = _vmDao.findByUuid(vmUuid);
VMInstanceVO vm = _vmDao.findByUuid(vmUuid);
HostVO hostVo = _hostDao.findById(vm.getHostId());
Long clustedId = hostVo.getClusterId();
Float memoryOvercommitRatio = CapacityManager.MemOverprovisioningFactor.valueIn(clustedId);
Float cpuOvercommitRatio = CapacityManager.CpuOverprovisioningFactor.valueIn(clustedId);
boolean divideMemoryByOverprovisioning = HypervisorGuruBase.VmMinMemoryEqualsMemoryDividedByMemOverprovisioningFactor.valueIn(clustedId);
boolean divideCpuByOverprovisioning = HypervisorGuruBase.VmMinCpuSpeedEqualsCpuSpeedDividedByCpuOverprovisioningFactor.valueIn(clustedId);
Long clusterId = hostVo.getClusterId();
Float memoryOvercommitRatio = CapacityManager.MemOverprovisioningFactor.valueIn(clusterId);
Float cpuOvercommitRatio = CapacityManager.CpuOverprovisioningFactor.valueIn(clusterId);
boolean divideMemoryByOverprovisioning = HypervisorGuruBase.VmMinMemoryEqualsMemoryDividedByMemOverprovisioningFactor.valueIn(clusterId);
boolean divideCpuByOverprovisioning = HypervisorGuruBase.VmMinCpuSpeedEqualsCpuSpeedDividedByCpuOverprovisioningFactor.valueIn(clusterId);
int minMemory = (int)(newServiceOffering.getRamSize() / (divideMemoryByOverprovisioning ? memoryOvercommitRatio : 1));
int minSpeed = (int)(newServiceOffering.getSpeed() / (divideCpuByOverprovisioning ? cpuOvercommitRatio : 1));
ScaleVmCommand scaleVmCommand =
new ScaleVmCommand(vm.getInstanceName(), newServiceOffering.getCpu(), minSpeed,
newServiceOffering.getSpeed(), minMemory * 1024L * 1024L, newServiceOffering.getRamSize() * 1024L * 1024L, newServiceOffering.getLimitCpuUse());
Double cpuQuotaPercentage = null;
if (newServiceOffering.getLimitCpuUse() && vm.getHypervisorType().equals(HypervisorType.KVM)) {
KVMGuru kvmGuru = (KVMGuru) _hvGuruMgr.getGuru(vm.getHypervisorType());
cpuQuotaPercentage = kvmGuru.getCpuQuotaPercentage(minSpeed, hostVo.getSpeed());
}
boolean limitCpuUseChange = oldServiceOffering.getLimitCpuUse() != newServiceOffering.getLimitCpuUse();
ScaleVmCommand scaleVmCommand = new ScaleVmCommand(vm.getInstanceName(), newServiceOffering.getCpu(), minSpeed, newServiceOffering.getSpeed(),
minMemory * 1024L * 1024L, newServiceOffering.getRamSize() * 1024L * 1024L,
newServiceOffering.getLimitCpuUse(), cpuQuotaPercentage, limitCpuUseChange);
scaleVmCommand.getVirtualMachine().setId(vm.getId());
scaleVmCommand.getVirtualMachine().setUuid(vm.getUuid());
@ -5219,16 +5227,20 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
throw new CloudRuntimeException("Unable to scale vm due to " + (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails()));
}
upgradeVmDb(vm.getId(), newServiceOffering, oldServiceOffering);
if (reconfiguringOnExistingHost) {
_capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId());
}
boolean vmUpgraded = upgradeVmDb(vm.getId(), newServiceOffering, oldServiceOffering);
if (vmUpgraded) {
vm = _vmDao.findById(vm.getId());
}
if (vm.getType().equals(VirtualMachine.Type.User)) {
_userVmMgr.generateUsageEvent(vm, vm.isDisplayVm(), EventTypes.EVENT_VM_DYNAMIC_SCALE);
}
if (reconfiguringOnExistingHost) {
vm.setServiceOfferingId(oldServiceOffering.getId());
_capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId());
vm.setServiceOfferingId(newServiceOffering.getId());
_capacityMgr.allocateVmCapacity(vm, false);
}

View File

@ -19,9 +19,21 @@ package com.cloud.network.dao;
import com.cloud.network.vo.PublicIpQuarantineVO;
import com.cloud.utils.db.GenericDao;
import java.util.Date;
import java.util.List;
public interface PublicIpQuarantineDao extends GenericDao<PublicIpQuarantineVO, Long> {
PublicIpQuarantineVO findByPublicIpAddressId(long publicIpAddressId);
PublicIpQuarantineVO findByIpAddress(String publicIpAddress);
/**
* Returns a list of public IP addresses that are actively quarantined at the specified date and the previous owner differs from the specified user.
*
* @param userId used to check against the IP's previous owner;
* @param date used to check if the quarantine is active;
* @return a list of PublicIpQuarantineVOs.
*/
List<PublicIpQuarantineVO> listQuarantinedIpAddressesToUser(Long userId, Date date);
}

View File

@ -26,6 +26,8 @@ import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.Date;
import java.util.List;
@Component
public class PublicIpQuarantineDaoImpl extends GenericDaoBase<PublicIpQuarantineVO, Long> implements PublicIpQuarantineDao {
@ -33,6 +35,8 @@ public class PublicIpQuarantineDaoImpl extends GenericDaoBase<PublicIpQuarantine
private SearchBuilder<IPAddressVO> ipAddressSearchBuilder;
private SearchBuilder<PublicIpQuarantineVO> quarantinedIpAddressesSearch;
@Inject
IPAddressDao ipAddressDao;
@ -47,8 +51,16 @@ public class PublicIpQuarantineDaoImpl extends GenericDaoBase<PublicIpQuarantine
publicIpAddressByIdSearch.join("quarantineJoin", ipAddressSearchBuilder, ipAddressSearchBuilder.entity().getId(),
publicIpAddressByIdSearch.entity().getPublicIpAddressId(), JoinBuilder.JoinType.INNER);
quarantinedIpAddressesSearch = createSearchBuilder();
quarantinedIpAddressesSearch.and("previousOwnerId", quarantinedIpAddressesSearch.entity().getPreviousOwnerId(), SearchCriteria.Op.NEQ);
quarantinedIpAddressesSearch.and();
quarantinedIpAddressesSearch.op("removedIsNull", quarantinedIpAddressesSearch.entity().getRemoved(), SearchCriteria.Op.NULL);
quarantinedIpAddressesSearch.and("endDate", quarantinedIpAddressesSearch.entity().getEndDate(), SearchCriteria.Op.GT);
quarantinedIpAddressesSearch.cp();
ipAddressSearchBuilder.done();
publicIpAddressByIdSearch.done();
quarantinedIpAddressesSearch.done();
}
@Override
@ -68,4 +80,14 @@ public class PublicIpQuarantineDaoImpl extends GenericDaoBase<PublicIpQuarantine
return findOneBy(sc, filter);
}
@Override
public List<PublicIpQuarantineVO> listQuarantinedIpAddressesToUser(Long userId, Date date) {
SearchCriteria<PublicIpQuarantineVO> sc = quarantinedIpAddressesSearch.create();
sc.setParameters("previousOwnerId", userId);
sc.setParameters("endDate", date);
return searchIncludingRemoved(sc, null, false, false);
}
}

View File

@ -1073,7 +1073,7 @@ public class SystemVmTemplateRegistration {
}
Hypervisor.HypervisorType hypervisorType = templateDetails.getHypervisorType();
updateSystemVMEntries(templateId, hypervisorType);
updateConfigurationParams(hypervisorType, templateDetails.getName(), zoneId);
updateConfigurationParams(hypervisorType, templateVO.getName(), zoneId);
}
protected void updateTemplateUrlChecksumAndGuestOsId(VMTemplateVO templateVO,

View File

@ -132,6 +132,24 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` (
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.networks', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1');
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1');
-- Creates the 'kvm.memory.dynamic.scaling.capacity' and, for already active ACS environments,
-- initializes it with the value of the setting 'vm.serviceoffering.ram.size.max'
INSERT INTO `cloud`.`configuration` (`category`, `instance`, `component`, `name`, `value`, `default_value`, `updated`, `scope`, `is_dynamic`, `group_id`, `subgroup_id`, `display_text`, `description`)
SELECT 'Advanced', 'DEFAULT', 'CapacityManager', 'kvm.memory.dynamic.scaling.capacity', `cfg`.`value`, 0, NULL, 4, 1, 6, 27,
'KVM memory dynamic scaling capacity', 'Defines the maximum memory capacity in MiB for which VMs can be dynamically scaled to with KVM. The ''kvm.memory.dynamic.scaling.capacity'' setting''s value will be used to define the value of the ''<maxMemory />'' element of domain XMLs. If it is set to a value less than or equal to ''0'', then the host''s memory capacity will be considered.'
FROM `cloud`.`configuration` `cfg`
WHERE NOT EXISTS (SELECT 1 FROM `cloud`.`configuration` WHERE `name` = 'kvm.memory.dynamic.scaling.capacity')
AND `cfg`.`name` = 'vm.serviceoffering.ram.size.max';
-- Creates the 'kvm.cpu.dynamic.scaling.capacity' and, for already active ACS environments,
-- initializes it with the value of the setting 'vm.serviceoffering.cpu.cores.max'
INSERT INTO `cloud`.`configuration` (`category`, `instance`, `component`, `name`, `value`, `default_value`, `updated`, `scope`, `is_dynamic`, `group_id`, `subgroup_id`, `display_text`, `description`)
SELECT 'Advanced', 'DEFAULT', 'CapacityManager', 'kvm.cpu.dynamic.scaling.capacity', `cfg`.`value`, 0, NULL, 4, 1, 6, 27,
'KVM CPU dynamic scaling capacity', 'Defines the maximum vCPU capacity for which VMs can be dynamically scaled to with KVM. The ''kvm.cpu.dynamic.scaling.capacity'' setting''s value will be used to define the value of the ''<vcpu />'' element of domain XMLs. If it is set to a value less than or equal to ''0'', then the host''s CPU cores capacity will be considered.'
FROM `cloud`.`configuration` `cfg`
WHERE NOT EXISTS (SELECT 1 FROM `cloud`.`configuration` WHERE `name` = 'kvm.cpu.dynamic.scaling.capacity')
AND `cfg`.`name` = 'vm.serviceoffering.cpu.cores.max';
-- ======================================================================
-- DNS Framework Schema
-- ======================================================================

View File

@ -570,18 +570,19 @@ public class SystemVmTemplateRegistrationTest {
SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails =
Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class);
VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
String templateName = "templateName";
when(templateVO.getName()).thenReturn(templateName);
GuestOSVO guestOS = Mockito.mock(GuestOSVO.class);
when(templateDetails.getGuestOs()).thenReturn("Debian");
when(templateDetails.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
when(templateDetails.getName()).thenReturn("templateName");
when(vmTemplateDao.findById(templateId)).thenReturn(templateVO);
when(guestOSDao.findOneByDisplayName("Debian")).thenReturn(guestOS);
when(guestOS.getId()).thenReturn(10L);
when(vmTemplateDao.update(templateVO.getId(), templateVO)).thenReturn(true);
doNothing().when(systemVmTemplateRegistration).updateSystemVMEntries(templateId, Hypervisor.HypervisorType.KVM);
doNothing().when(systemVmTemplateRegistration).updateConfigurationParams(Hypervisor.HypervisorType.KVM,
"templateName", zoneId);
templateName, zoneId);
systemVmTemplateRegistration.updateRegisteredTemplateDetails(templateId, templateDetails, zoneId);
@ -590,7 +591,7 @@ public class SystemVmTemplateRegistrationTest {
verify(vmTemplateDao).update(templateVO.getId(), templateVO);
verify(systemVmTemplateRegistration).updateSystemVMEntries(templateId, Hypervisor.HypervisorType.KVM);
verify(systemVmTemplateRegistration).updateConfigurationParams(Hypervisor.HypervisorType.KVM,
"templateName", zoneId);
templateName, zoneId);
}
@Test
@ -620,16 +621,17 @@ public class SystemVmTemplateRegistrationTest {
SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails =
Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class);
VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
String templateName = "templateName";
when(templateVO.getName()).thenReturn(templateName);
when(templateDetails.getGuestOs()).thenReturn("NonExistentOS");
when(templateDetails.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
when(templateDetails.getName()).thenReturn("templateName");
when(vmTemplateDao.findById(templateId)).thenReturn(templateVO);
when(guestOSDao.findOneByDisplayName("NonExistentOS")).thenReturn(null);
when(vmTemplateDao.update(templateVO.getId(), templateVO)).thenReturn(true);
doNothing().when(systemVmTemplateRegistration).updateSystemVMEntries(templateId, Hypervisor.HypervisorType.KVM);
doNothing().when(systemVmTemplateRegistration).updateConfigurationParams(Hypervisor.HypervisorType.KVM,
"templateName", zoneId);
templateName, zoneId);
systemVmTemplateRegistration.updateRegisteredTemplateDetails(templateId, templateDetails, zoneId);
@ -637,7 +639,7 @@ public class SystemVmTemplateRegistrationTest {
verify(vmTemplateDao).update(templateVO.getId(), templateVO);
verify(systemVmTemplateRegistration).updateSystemVMEntries(templateId, Hypervisor.HypervisorType.KVM);
verify(systemVmTemplateRegistration).updateConfigurationParams(Hypervisor.HypervisorType.KVM,
"templateName", zoneId);
templateName, zoneId);
}
@Test

View File

@ -395,9 +395,9 @@ public class VolumeServiceImpl implements VolumeService {
}
// Find out if the volume is at state of download_in_progress on secondary storage
VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(volume.getId());
if (volumeStore != null) {
if (volumeStore.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS) {
VolumeDataStoreVO volumeOnImageStore = _volumeStoreDao.findByVolume(volume.getId());
if (volumeOnImageStore != null) {
if (volumeOnImageStore.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS) {
String msg = String.format("Volume: %s is currently being uploaded; can't delete it.", volume);
logger.debug(msg);
result.setSuccess(false);
@ -416,10 +416,10 @@ public class VolumeServiceImpl implements VolumeService {
if (!volumeExistsOnPrimary(vol)) {
// not created on primary store
if (volumeStore == null) {
if (volumeOnImageStore == null) {
// also not created on secondary store
if (logger.isDebugEnabled()) {
logger.debug("Marking volume that was never created as destroyed: " + vol);
logger.debug("Marking volume that was never created as destroyed: {}", vol);
}
VMTemplateVO template = templateDao.findById(vol.getTemplateId());
if (template != null && !template.isDeployAsIs()) {
@ -435,11 +435,21 @@ public class VolumeServiceImpl implements VolumeService {
if (volume.getDataStore().getRole() == DataStoreRole.Image) {
// no need to change state in volumes table
volume.processEventOnly(Event.DestroyRequested);
if (volumeOnImageStore == null) {
logger.debug("Volume {} doesn't exist on image store, no need to delete", vol);
future.complete(result);
return future;
}
} else if (volume.getDataStore().getRole() == DataStoreRole.Primary) {
if (vol.getState() == Volume.State.Expunging) {
logger.info("Volume {} is already in Expunging, retrying", volume);
}
volume.processEvent(Event.ExpungeRequested);
if (!volumeExistsOnPrimary(vol)) {
logger.debug("Volume {} doesn't exist on primary storage, no need to delete", vol);
future.complete(result);
return future;
}
}
DeleteVolumeContext<VolumeApiResult> context = new DeleteVolumeContext<>(null, vo, future);
@ -460,13 +470,11 @@ public class VolumeServiceImpl implements VolumeService {
private boolean volumeExistsOnPrimary(VolumeVO vol) {
Long poolId = vol.getPoolId();
if (poolId == null) {
return false;
}
PrimaryDataStore primaryStore = dataStoreMgr.getPrimaryDataStore(poolId);
if (primaryStore == null) {
return false;
}
@ -476,8 +484,7 @@ public class VolumeServiceImpl implements VolumeService {
}
String volumePath = vol.getPath();
if (volumePath == null || volumePath.trim().isEmpty()) {
if (StringUtils.isBlank(volumePath)) {
return false;
}

View File

@ -1663,14 +1663,14 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
public List<String> getExtensionReservedResourceDetails(long extensionId) {
ExtensionDetailsVO detailsVO = extensionDetailsDao.findDetail(extensionId,
ApiConstants.RESERVED_RESOURCE_DETAILS);
if (detailsVO == null || !StringUtils.isNotBlank(detailsVO.getValue())) {
return Collections.emptyList();
}
List<String> reservedDetails = new ArrayList<>();
String[] parts = detailsVO.getValue().split(",");
for (String part : parts) {
if (StringUtils.isNotBlank(part)) {
reservedDetails.add(part.trim());
if (detailsVO != null && StringUtils.isNotBlank(detailsVO.getValue())) {
String[] parts = detailsVO.getValue().split(",");
for (String part : parts) {
String trimmedPart = part.trim();
if (StringUtils.isNotBlank(trimmedPart)) {
reservedDetails.add(trimmedPart);
}
}
}
addInbuiltExtensionReservedResourceDetails(extensionId, reservedDetails);

View File

@ -117,7 +117,7 @@ Requires: ipset
Requires: perl
Requires: rsync
Requires: cifs-utils
Requires: edk2-ovmf
Requires: (edk2-ovmf or qemu-ovmf-x86_64)
Requires: swtpm
Requires: (python3-libvirt or python3-libvirt-python)
Requires: (qemu-img or qemu-tools)

View File

@ -1 +0,0 @@
el8

View File

@ -0,0 +1 @@
../el8/cloud-ipallocator.rc

View File

@ -0,0 +1 @@
../el8/cloud.limits

750
packaging/suse15/cloud.spec Normal file
View File

@ -0,0 +1,750 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
%define __os_install_post %{nil}
%global debug_package %{nil}
%global __requires_exclude libc\\.so\\..*|libc\\.so\\.6\\(GLIBC_.*\\)
%define _binaries_in_noarch_packages_terminate_build 0
# DISABLE the post-percentinstall java repacking and line number stripping
# we need to find a way to just disable the java repacking and line number stripping, but not the autodeps
Name: cloudstack
Summary: CloudStack IaaS Platform
#http://fedoraproject.org/wiki/PackageNamingGuidelines#Pre-Release_packages
%define _maventag %{_fullver}
Release: %{_rel}
Version: %{_ver}
License: ASL 2.0
Vendor: Apache CloudStack <dev@cloudstack.apache.org>
Packager: Apache CloudStack <dev@cloudstack.apache.org>
Group: System Environment/Libraries
# FIXME do groups for every single one of the subpackages
Source0: %{name}-%{_maventag}.tgz
BuildRoot: %{_tmppath}/%{name}-%{_maventag}-%{release}-build
BuildArch: noarch
BuildRequires: (java-11-openjdk-devel or java-17-openjdk-devel or java-21-openjdk-devel)
#BuildRequires: ws-commons-util
BuildRequires: jpackage-utils
BuildRequires: gcc
BuildRequires: glibc-devel
BuildRequires: /usr/bin/mkisofs
BuildRequires: python3-setuptools
BuildRequires: wget
BuildRequires: nodejs
%description
CloudStack is a highly-scalable elastic, open source,
intelligent IaaS cloud implementation.
%package management
Summary: CloudStack management server UI
Requires: (java-17-openjdk or java-21-openjdk)
Requires: (tzdata-java or timezone-java)
Requires: python3
Requires: bash
Requires: gawk
Requires: which
Requires: file
Requires: tar
Requires: bzip2
Requires: gzip
Requires: unzip
Requires: (/sbin/mount.nfs or /usr/sbin/mount.nfs)
Requires: (openssh-clients or openssh)
Requires: (nfs-utils or nfs-client)
Requires: iproute
Requires: wget
Requires: (mysql or mariadb or mysql8.4)
Requires: sudo
Requires: /sbin/service
Requires: /sbin/chkconfig
Requires: /usr/bin/ssh-keygen
Requires: (genisoimage or mkisofs or xorrisofs)
Requires: ipmitool
Requires: %{name}-common = %{_ver}
Requires: (iptables-services or iptables)
Requires: rng-tools
Requires: (qemu-img or qemu-tools)
Requires: python3-pip
Requires: python3-six
Requires: python3-protobuf
Requires: python3-setuptools
Requires: (libgcrypt > 1.8.3 or libgcrypt20)
Group: System Environment/Libraries
%description management
The CloudStack management server is the central point of coordination,
management, and intelligence in CloudStack.
%package common
Summary: Apache CloudStack common files and scripts
Requires: python3
Group: System Environment/Libraries
%description common
The Apache CloudStack files shared between agent and management server
%global __requires_exclude libc\\.so\\..*|libc\\.so\\.6\\(GLIBC_.*\\)|^(libuuid\\.so\\.1|/usr/bin/python)$
%package agent
Summary: CloudStack Agent for KVM hypervisors
Requires: (openssh-clients or openssh)
Requires: (java-17-openjdk or java-21-openjdk)
Requires: (tzdata-java or timezone-java)
Requires: %{name}-common = %{_ver}
Requires: libvirt
Requires: libvirt-daemon-driver-storage-rbd
Requires: ebtables
Requires: iptables
Requires: ethtool
Requires: (net-tools or net-tools-deprecated)
Requires: iproute
Requires: ipset
Requires: perl
Requires: rsync
Requires: cifs-utils
Requires: (edk2-ovmf or qemu-ovmf-x86_64)
Requires: swtpm
Requires: (python3-libvirt or python3-libvirt-python)
Requires: (qemu-img or qemu-tools)
Requires: qemu-kvm
Requires: cryptsetup
Requires: rng-tools
Requires: (libgcrypt > 1.8.3 or libgcrypt20)
Requires: (selinux-tools if selinux-tools)
Requires: sysstat
Provides: cloud-agent
Group: System Environment/Libraries
%description agent
The CloudStack agent for KVM hypervisors
%package baremetal-agent
Summary: CloudStack baremetal agent
Requires: tftp-server
Requires: xinetd
Requires: syslinux
Requires: chkconfig
Requires: dhcp
Requires: httpd
Group: System Environment/Libraries
%description baremetal-agent
The CloudStack baremetal agent
%package usage
Summary: CloudStack Usage calculation server
Requires: (java-17-openjdk or java-21-openjdk)
Requires: (tzdata-java or timezone-java)
Group: System Environment/Libraries
%description usage
The CloudStack usage calculation service
%package ui
Summary: CloudStack UI
Group: System Environment/Libraries
%description ui
The CloudStack UI
%package marvin
Summary: Apache CloudStack Marvin library
Requires: python3-pip
Requires: gcc
Requires: python3-devel
Requires: libffi-devel
Requires: openssl-devel
Group: System Environment/Libraries
%description marvin
Apache CloudStack Marvin library
%package integration-tests
Summary: Apache CloudStack Marvin integration tests
Requires: %{name}-marvin = %{_ver}
Group: System Environment/Libraries
%description integration-tests
Apache CloudStack Marvin integration tests
%if "%{_ossnoss}" == "noredist"
%package mysql-ha
Summary: Apache CloudStack Balancing Strategy for MySQL
Group: System Environmnet/Libraries
%description mysql-ha
Apache CloudStack Balancing Strategy for MySQL
%endif
%prep
echo Doing CloudStack build
%setup -q -n %{name}-%{_maventag}
%build
cp packaging/suse15/replace.properties build/replace.properties
echo VERSION=%{_maventag} >> build/replace.properties
echo PACKAGE=%{name} >> build/replace.properties
touch build/gitrev.txt
echo $(git rev-parse HEAD) > build/gitrev.txt
if [ "%{_ossnoss}" == "NOREDIST" -o "%{_ossnoss}" == "noredist" ] ; then
echo "Adding noredist flag to the maven build"
FLAGS="$FLAGS -Dnoredist"
fi
if [ "%{_sim}" == "SIMULATOR" -o "%{_sim}" == "simulator" ] ; then
echo "Adding simulator flag to the maven build"
FLAGS="$FLAGS -Dsimulator"
fi
if [ \"%{_temp}\" != "" ]; then
echo "Adding flags to package requested templates"
FLAGS="$FLAGS `rpm --eval %{?_temp}`"
fi
mvn -Psystemvm,developer $FLAGS clean package
cd ui && npm install && npm run build && cd ..
%install
[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT}
# Common directories
mkdir -p ${RPM_BUILD_ROOT}%{_bindir}
mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/agent
mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/ipallocator
mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/work
mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/temp
mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/mnt
mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/management
mkdir -p ${RPM_BUILD_ROOT}%{_initrddir}
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/default
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/profile.d
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d
# Common
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site
mkdir -p ${RPM_BUILD_ROOT}/usr/bin
cp -r scripts/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts
install -D systemvm/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms/
install python/lib/cloud_utils.py ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site/cloud_utils.py
cp -r python/lib/cloudutils ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site/
python3 -m py_compile ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site/cloud_utils.py
python3 -m compileall ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site/cloudutils
cp build/gitrev.txt ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts
cp packaging/suse15/cloudstack-sccs ${RPM_BUILD_ROOT}/usr/bin
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts/network/cisco
cp -r plugins/network-elements/cisco-vnmc/src/main/scripts/network/cisco/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts/network/cisco
# Management
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/lib
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cks/conf
mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/management
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/systemd/system/%{name}-management.service.d
mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/run
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel
# Setup Jetty
ln -sf /etc/%{name}/management ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/conf
ln -sf /var/log/%{name}/management ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/logs
install -D client/target/utilities/bin/cloud-migrate-databases ${RPM_BUILD_ROOT}%{_bindir}/%{name}-migrate-databases
install -D client/target/utilities/bin/cloud-set-guest-password ${RPM_BUILD_ROOT}%{_bindir}/%{name}-set-guest-password
install -D client/target/utilities/bin/cloud-set-guest-sshkey ${RPM_BUILD_ROOT}%{_bindir}/%{name}-set-guest-sshkey
install -D client/target/utilities/bin/cloud-setup-databases ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-databases
install -D client/target/utilities/bin/cloud-setup-encryption ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-encryption
install -D client/target/utilities/bin/cloud-setup-management ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-management
install -D client/target/utilities/bin/cloud-setup-baremetal ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-baremetal
install -D client/target/utilities/bin/cloud-sysvmadm ${RPM_BUILD_ROOT}%{_bindir}/%{name}-sysvmadm
install -D client/target/utilities/bin/cloud-update-xenserver-licenses ${RPM_BUILD_ROOT}%{_bindir}/%{name}-update-xenserver-licenses
# Bundle cmk in cloudstack-management
wget https://github.com/apache/cloudstack-cloudmonkey/releases/latest/download/cmk.linux.x86-64 -O ${RPM_BUILD_ROOT}%{_bindir}/cmk
chmod +x ${RPM_BUILD_ROOT}%{_bindir}/cmk
cp -r client/target/utilities/scripts/db/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup
cp -r plugins/integrations/kubernetes-service/src/main/resources/conf/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cks/conf
cp -r client/target/cloud-client-ui-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/
cp -r client/target/classes/META-INF/webapp ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp
cp ui/dist/config.json ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/
cp -r ui/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp/
rm -f ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp/config.json
ln -sf /etc/%{name}/management/config.json ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp/config.json
mv ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cloud-client-ui-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/lib/cloudstack-%{_maventag}.jar
cp client/target/lib/*jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/lib/
# Don't package the scripts in the management webapp
rm -rf ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/scripts
rm -rf ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/vms
for name in db.properties server.properties log4j-cloud.xml environment.properties java.security.ciphers
do
cp client/target/conf/$name ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/$name
done
ln -sf log4j-cloud.xml ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/log4j2.xml
install python/bindir/cloud-external-ipallocator.py ${RPM_BUILD_ROOT}%{_bindir}/%{name}-external-ipallocator.py
install -D client/target/pythonlibs/jasypt-1.9.3.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/jasypt-1.9.3.jar
install -D utils/target/cloud-utils-%{_maventag}-bundled.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/%{name}-utils.jar
install -D packaging/suse15/cloud-ipallocator.rc ${RPM_BUILD_ROOT}%{_initrddir}/%{name}-ipallocator
install -D packaging/suse15/cloud.limits ${RPM_BUILD_ROOT}%{_sysconfdir}/security/limits.d/cloud
install -D packaging/suse15/filelimit.conf ${RPM_BUILD_ROOT}%{_sysconfdir}/systemd/system/%{name}-management.service.d
install -D packaging/systemd/cloudstack-management.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-management.service
install -D packaging/systemd/cloudstack-management.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-management
install -D server/target/conf/cloudstack-sudoers ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d/%{name}-management
touch ${RPM_BUILD_ROOT}%{_localstatedir}/run/%{name}-management.pid
#install -D server/target/conf/cloudstack-catalina.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-catalina
install -D server/target/conf/cloudstack-management.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-management
install -D plugins/integrations/kubernetes-service/src/main/resources/conf/etcd-node.yml ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cks/conf/etcd-node.yml
install -D plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cks/conf/k8s-control-node.yml
install -D plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node-add.yml ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cks/conf/k8s-control-node-add.yml
install -D plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-node.yml ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cks/conf/k8s-node.yml
# SystemVM template
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/templates/systemvm
cp -r engine/schema/dist/systemvm-templates/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/templates/systemvm
rm -rf ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/templates/systemvm/sha512sum.txt
# Sample Extensions
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/extensions
cp -r extensions/* ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/extensions
ln -sf %{_sysconfdir}/%{name}/extensions ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/extensions
# UI
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/ui
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/
cp -r client/target/classes/META-INF/webapp/WEB-INF ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui
cp ui/dist/config.json ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/ui/
cp -r ui/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/
rm -f ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/config.json
ln -sf /etc/%{name}/ui/config.json ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/config.json
# Package mysql-connector-python (bundled to avoid dependency on external community repo)
# Version 8.0.31 is the last version supporting Python 3.6 (EL8)
wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/08/1f/42d74bae9dd6dcfec67c9ed0f3fa482b1ae5ac5f117ca82ab589ecb3ca19/mysql_connector_python-8.0.31-py2.py3-none-any.whl
# Version 8.3.0 supports Python 3.8 to 3.12 (EL9, EL10)
wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/53/ed/26a4b8cacb8852c6fd97d2d58a7f2591c41989807ea82bd8d9725a4e6937/mysql_connector_python-8.3.0-py2.py3-none-any.whl
chmod 440 ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d/%{name}-management
chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/mnt
chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/management
chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/work
chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/temp
chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/management
chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/agent
# KVM Agent
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent
mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/agent
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/plugins
install -D packaging/systemd/cloudstack-agent.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-agent.service
install -D packaging/systemd/cloudstack-rolling-maintenance@.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-rolling-maintenance@.service
install -D packaging/systemd/cloudstack-agent.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-agent
install -D agent/target/transformed/agent.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/agent.properties
install -D agent/target/transformed/uefi.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/uefi.properties
install -D agent/target/transformed/environment.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/environment.properties
install -D agent/target/transformed/log4j-cloud.xml ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/log4j-cloud.xml
install -D agent/target/transformed/cloud-setup-agent ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-agent
install -D agent/target/transformed/cloudstack-agent-upgrade ${RPM_BUILD_ROOT}%{_bindir}/%{name}-agent-upgrade
install -D agent/target/transformed/cloud-guest-tool ${RPM_BUILD_ROOT}%{_bindir}/%{name}-guest-tool
install -D agent/target/transformed/libvirtqemuhook ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/libvirtqemuhook
install -D agent/target/transformed/rolling-maintenance ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/rolling-maintenance
install -D agent/target/transformed/cloud-ssh ${RPM_BUILD_ROOT}%{_bindir}/%{name}-ssh
install -D agent/target/transformed/cloudstack-agent-profile.sh ${RPM_BUILD_ROOT}%{_sysconfdir}/profile.d/%{name}-agent-profile.sh
install -D agent/target/transformed/cloudstack-agent.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-agent
install -D plugins/hypervisors/kvm/target/cloud-plugin-hypervisor-kvm-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%name-agent/lib/cloud-plugin-hypervisor-kvm-%{_maventag}.jar
cp plugins/hypervisors/kvm/target/dependencies/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
cp plugins/storage/volume/storpool/target/*.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
cp plugins/storage/volume/linstor/target/*.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib
# Usage server
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/lib
install -D usage/target/cloud-usage-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/cloud-usage-%{_maventag}.jar
install -D usage/target/transformed/db.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage/db.properties
install -D usage/target/transformed/log4j-cloud_usage.xml ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage/log4j-cloud.xml
cp usage/target/dependencies/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/lib/
cp client/target/lib/mysql*jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/lib/
install -D packaging/systemd/cloudstack-usage.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-usage.service
install -D packaging/systemd/cloudstack-usage.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-usage
mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/usage/
install -D usage/target/transformed/cloudstack-usage.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-usage
# Marvin
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-marvin
cp tools/marvin/dist/Marvin-*.tar.gz ${RPM_BUILD_ROOT}%{_datadir}/%{name}-marvin/
# integration-tests
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-integration-tests
cp -r test/integration/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-integration-tests/
# MYSQL HA
if [ "x%{_ossnoss}" == "xnoredist" ] ; then
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-mysql-ha/lib
cp -r plugins/database/mysql-ha/target/cloud-plugin-database-mysqlha-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-mysql-ha/lib
fi
#License files from whisker
install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-management-%{version}/NOTICE
install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-management-%{version}/LICENSE
install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-common-%{version}/NOTICE
install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-common-%{version}/LICENSE
install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-agent-%{version}/NOTICE
install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-agent-%{version}/LICENSE
install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-usage-%{version}/NOTICE
install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-usage-%{version}/LICENSE
install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-ui-%{version}/NOTICE
install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-ui-%{version}/LICENSE
install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-marvin-%{version}/NOTICE
install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-marvin-%{version}/LICENSE
install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-integration-tests-%{version}/NOTICE
install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-integration-tests-%{version}/LICENSE
%clean
[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT}
%posttrans common
unalias cp
python_dir=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")
if [ ! -z $python_dir ];then
cp -f -r /usr/share/cloudstack-common/python-site/* $python_dir/
fi
%preun management
/usr/bin/systemctl stop cloudstack-management || true
/usr/bin/systemctl disable cloudstack-management || true
%pre management
id cloud > /dev/null 2>&1 || /usr/sbin/useradd -M -U -c "CloudStack unprivileged user" \
-r -s /bin/sh -d %{_localstatedir}/cloudstack/management cloud || true
rm -rf %{_localstatedir}/cache/cloudstack
# in case of upgrade to 4.9+ copy commands.properties if not exists in /etc/cloudstack/management/
if [ "$1" == "2" ] ; then
if [ -f "%{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/commands.properties" ] && [ ! -f "%{_sysconfdir}/%{name}/management/commands.properties" ] ; then
cp -p %{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/commands.properties %{_sysconfdir}/%{name}/management/commands.properties
fi
fi
# Remove old tomcat symlinks and env config file
if [ -L "%{_datadir}/%{name}-management/lib" ]
then
rm -f %{_datadir}/%{name}-management/bin
rm -f %{_datadir}/%{name}-management/lib
rm -f %{_datadir}/%{name}-management/temp
rm -f %{_datadir}/%{name}-management/work
rm -f %{_sysconfdir}/default/%{name}-management
fi
%post management
# Install mysql-connector-python wheel
# Detect Python version to install compatible wheel
if python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3, 7) else 1)'; then
pip3 install %{_datadir}/%{name}-management/setup/wheel/mysql_connector_python-8.3.0-py2.py3-none-any.whl
else
pip3 install %{_datadir}/%{name}-management/setup/wheel/mysql_connector_python-8.0.31-py2.py3-none-any.whl
fi
/usr/bin/systemctl enable cloudstack-management > /dev/null 2>&1 || true
/usr/bin/systemctl enable --now rngd > /dev/null 2>&1 || true
grep -s -q "db.cloud.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" || sed -i -e "\$adb.cloud.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties"
grep -s -q "db.usage.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" || sed -i -e "\$adb.usage.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties"
grep -s -q "db.simulator.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" || sed -i -e "\$adb.simulator.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties"
# Update DB properties having master and slave(s), with source and replica(s) respectively (for inclusiveness)
grep -s -q "^db.cloud.slaves=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.cloud.slaves=/db.cloud.replicas=/g" "%{_sysconfdir}/%{name}/management/db.properties"
grep -s -q "^db.cloud.secondsBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.cloud.secondsBeforeRetryMaster=/db.cloud.secondsBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties"
grep -s -q "^db.cloud.queriesBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.cloud.queriesBeforeRetryMaster=/db.cloud.queriesBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties"
grep -s -q "^db.usage.slaves=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.usage.slaves=/db.usage.replicas=/g" "%{_sysconfdir}/%{name}/management/db.properties"
grep -s -q "^db.usage.secondsBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.usage.secondsBeforeRetryMaster=/db.usage.secondsBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties"
grep -s -q "^db.usage.queriesBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.usage.queriesBeforeRetryMaster=/db.usage.queriesBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties"
if [ ! -f %{_datadir}/cloudstack-common/scripts/vm/hypervisor/xenserver/vhd-util ] ; then
echo Please download vhd-util from http://download.cloudstack.org/tools/vhd-util and put it in
echo %{_datadir}/cloudstack-common/scripts/vm/hypervisor/xenserver/
fi
if [ -f %{_sysconfdir}/sysconfig/%{name}-management ] ; then
rm -f %{_sysconfdir}/sysconfig/%{name}-management
fi
chown -R cloud:cloud /var/log/cloudstack/management
chown -R cloud:cloud /usr/share/cloudstack-management/templates
find /usr/share/cloudstack-management/templates -type d -exec chmod 0770 {} \;
systemctl daemon-reload
%posttrans management
# Print help message
if [ -f "/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text" ];then
sed -i "s,^ACS_VERSION=.*,ACS_VERSION=%{_maventag},g" /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text
/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text management
fi
%preun agent
/sbin/service cloudstack-agent stop || true
if [ "$1" == "0" ] ; then
/sbin/chkconfig --del cloudstack-agent > /dev/null 2>&1 || true
fi
%pre agent
# save old configs if they exist (for upgrade). Otherwise we may lose them
# when the old packages are erased. There are a lot of properties files here.
if [ -d "%{_sysconfdir}/cloud" ] ; then
mv %{_sysconfdir}/cloud %{_sysconfdir}/cloud.rpmsave
fi
%posttrans agent
if [ "$1" == "2" ] ; then
echo "Running %{_bindir}/%{name}-agent-upgrade to update bridge name for upgrade from CloudStack 4.0.x (and before) to CloudStack 4.1 (and later)"
%{_bindir}/%{name}-agent-upgrade
fi
if [ ! -d %{_sysconfdir}/libvirt/hooks ] ; then
mkdir %{_sysconfdir}/libvirt/hooks
fi
cp -a ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/libvirtqemuhook %{_sysconfdir}/libvirt/hooks/qemu
mkdir -m 0755 -p /usr/share/cloudstack-agent/tmp
/usr/bin/systemctl restart libvirtd
/usr/bin/systemctl enable cloudstack-agent > /dev/null 2>&1 || true
/usr/bin/systemctl enable cloudstack-rolling-maintenance@p > /dev/null 2>&1 || true
/usr/bin/systemctl enable --now rngd > /dev/null 2>&1 || true
# if saved agent.properties from upgrade exist, copy them over
if [ -f "%{_sysconfdir}/cloud.rpmsave/agent/agent.properties" ]; then
mv %{_sysconfdir}/%{name}/agent/agent.properties %{_sysconfdir}/%{name}/agent/agent.properties.rpmnew
cp -p %{_sysconfdir}/cloud.rpmsave/agent/agent.properties %{_sysconfdir}/%{name}/agent
# make sure we only do this on the first install of this RPM, don't want to overwrite on a reinstall
mv %{_sysconfdir}/cloud.rpmsave/agent/agent.properties %{_sysconfdir}/cloud.rpmsave/agent/agent.properties.rpmsave
fi
# if saved uefi.properties from upgrade exist, copy them over
if [ -f "%{_sysconfdir}/cloud.rpmsave/agent/uefi.properties" ]; then
mv %{_sysconfdir}/%{name}/agent/uefi.properties %{_sysconfdir}/%{name}/agent/uefi.properties.rpmnew
cp -p %{_sysconfdir}/cloud.rpmsave/agent/uefi.properties %{_sysconfdir}/%{name}/agent
# make sure we only do this on the first install of this RPM, don't want to overwrite on a reinstall
mv %{_sysconfdir}/cloud.rpmsave/agent/uefi.properties %{_sysconfdir}/cloud.rpmsave/agent/uefi.properties.rpmsave
fi
systemctl daemon-reload
# Print help message
if [ -f "/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text" ];then
sed -i "s,^ACS_VERSION=.*,ACS_VERSION=%{_maventag},g" /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text
/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text agent
fi
%pre usage
id cloud > /dev/null 2>&1 || /usr/sbin/useradd -M -U -c "CloudStack unprivileged user" \
-r -s /bin/sh -d %{_localstatedir}/cloudstack/management cloud|| true
%preun usage
/sbin/service cloudstack-usage stop || true
if [ "$1" == "0" ] ; then
/sbin/chkconfig --del cloudstack-usage > /dev/null 2>&1 || true
fi
%post usage
if [ -f "%{_sysconfdir}/%{name}/management/db.properties" ]; then
echo "Replacing usage server's db.properties with a link to the management server's db.properties"
rm -f %{_sysconfdir}/%{name}/usage/db.properties
ln -s %{_sysconfdir}/%{name}/management/db.properties %{_sysconfdir}/%{name}/usage/db.properties
/usr/bin/systemctl enable cloudstack-usage > /dev/null 2>&1 || true
fi
if [ -f "%{_sysconfdir}/%{name}/management/key" ]; then
echo "Replacing usage server's key with a link to the management server's key"
rm -f %{_sysconfdir}/%{name}/usage/key
ln -s %{_sysconfdir}/%{name}/management/key %{_sysconfdir}/%{name}/usage/key
fi
if [ ! -f "%{_sysconfdir}/%{name}/usage/key" ]; then
ln -s %{_sysconfdir}/%{name}/management/key %{_sysconfdir}/%{name}/usage/key
fi
mkdir -p /usr/local/libexec
if [ ! -f "/usr/local/libexec/sanity-check-last-id" ]; then
echo 1 > /usr/local/libexec/sanity-check-last-id
fi
chown cloud:cloud /usr/local/libexec/sanity-check-last-id
%posttrans usage
# Print help message
if [ -f "/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text" ];then
sed -i "s,^ACS_VERSION=.*,ACS_VERSION=%{_maventag},g" /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text
/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text usage
fi
%post marvin
pip3 install --upgrade https://files.pythonhosted.org/packages/08/1f/42d74bae9dd6dcfec67c9ed0f3fa482b1ae5ac5f117ca82ab589ecb3ca19/mysql_connector_python-8.0.31-py2.py3-none-any.whl
pip3 install --upgrade /usr/share/cloudstack-marvin/Marvin-*.tar.gz
#No default permission as the permission setup is complex
%files management
%defattr(-,root,root,-)
%dir %{_datadir}/%{name}-management
%dir %attr(0770,root,cloud) %{_localstatedir}/%{name}/mnt
%dir %attr(0770,cloud,cloud) %{_localstatedir}/%{name}/management
%dir %attr(0770,root,cloud) %{_localstatedir}/cache/%{name}/management
%dir %attr(0770,root,cloud) %{_localstatedir}/log/%{name}/management
%config(noreplace) %{_sysconfdir}/default/%{name}-management
%config(noreplace) %{_sysconfdir}/sudoers.d/%{name}-management
%config(noreplace) %{_sysconfdir}/security/limits.d/cloud
%config(noreplace) %{_sysconfdir}/systemd/system/%{name}-management.service.d
%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/db.properties
%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/server.properties
%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/config.json
%config(noreplace) %{_sysconfdir}/%{name}/management/log4j-cloud.xml
%config(noreplace) %{_sysconfdir}/%{name}/management/log4j2.xml
%config(noreplace) %{_sysconfdir}/%{name}/management/environment.properties
%config(noreplace) %{_sysconfdir}/%{name}/management/java.security.ciphers
%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-management
%attr(0644,root,root) %{_unitdir}/%{name}-management.service
%attr(0755,cloud,cloud) %{_localstatedir}/run/%{name}-management.pid
%attr(0755,root,root) %{_bindir}/%{name}-setup-management
%attr(0755,root,root) %{_bindir}/%{name}-update-xenserver-licenses
%{_datadir}/%{name}-management/conf
%{_datadir}/%{name}-management/lib/*.jar
%{_datadir}/%{name}-management/logs
%{_datadir}/%{name}-management/templates
%{_datadir}/%{name}-management/extensions
%attr(0755,root,root) %{_bindir}/%{name}-setup-databases
%attr(0755,root,root) %{_bindir}/%{name}-migrate-databases
%attr(0755,root,root) %{_bindir}/%{name}-set-guest-password
%attr(0755,root,root) %{_bindir}/%{name}-set-guest-sshkey
%attr(0755,root,root) %{_bindir}/%{name}-sysvmadm
%attr(0755,root,root) %{_bindir}/%{name}-setup-encryption
%attr(0755,root,root) %{_bindir}/cmk
%{_datadir}/%{name}-management/cks/conf/*.yml
%{_datadir}/%{name}-management/setup/*.sql
%{_datadir}/%{name}-management/setup/*.sh
%{_datadir}/%{name}-management/setup/server-setup.xml
%{_datadir}/%{name}-management/webapp/*
%dir %attr(0770, cloud, cloud) %{_datadir}/%{name}-management/templates
%dir %attr(0770, cloud, cloud) %{_datadir}/%{name}-management/templates/systemvm
%attr(0644, cloud, cloud) %{_datadir}/%{name}-management/templates/systemvm/*
%attr(0755,root,root) %{_bindir}/%{name}-external-ipallocator.py
%attr(0755,root,root) %{_initrddir}/%{name}-ipallocator
%dir %attr(0770,root,root) %{_localstatedir}/log/%{name}/ipallocator
%{_defaultdocdir}/%{name}-management-%{version}/LICENSE
%{_defaultdocdir}/%{name}-management-%{version}/NOTICE
%{_datadir}/%{name}-management/setup/wheel/*.whl
%dir %attr(0755,cloud,cloud) %{_sysconfdir}/%{name}/extensions
%attr(0755,cloud,cloud) %{_sysconfdir}/%{name}/extensions/*
%files agent
%attr(0755,root,root) %{_bindir}/%{name}-setup-agent
%attr(0755,root,root) %{_bindir}/%{name}-agent-upgrade
%attr(0755,root,root) %{_bindir}/%{name}-guest-tool
%attr(0755,root,root) %{_bindir}/%{name}-ssh
%attr(0644,root,root) %{_unitdir}/%{name}-agent.service
%attr(0644,root,root) %{_unitdir}/%{name}-rolling-maintenance@.service
%config(noreplace) %{_sysconfdir}/default/%{name}-agent
%attr(0644,root,root) %{_sysconfdir}/profile.d/%{name}-agent-profile.sh
%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-agent
%attr(0755,root,root) %{_datadir}/%{name}-common/scripts/network/cisco
%config(noreplace) %{_sysconfdir}/%{name}/agent
%dir %{_localstatedir}/log/%{name}/agent
%attr(0644,root,root) %{_datadir}/%{name}-agent/lib/*.jar
%attr(0755,root,root) %{_datadir}/%{name}-agent/lib/libvirtqemuhook
%attr(0755,root,root) %{_datadir}/%{name}-agent/lib/rolling-maintenance
%dir %{_datadir}/%{name}-agent/plugins
%{_defaultdocdir}/%{name}-agent-%{version}/LICENSE
%{_defaultdocdir}/%{name}-agent-%{version}/NOTICE
%files common
%dir %attr(0755,root,root) %{_datadir}/%{name}-common/python-site/cloudutils
%dir %attr(0755,root,root) %{_datadir}/%{name}-common/vms
%attr(0755,root,root) %{_datadir}/%{name}-common/scripts
%attr(0755,root,root) /usr/bin/cloudstack-sccs
%attr(0644, root, root) %{_datadir}/%{name}-common/vms/agent.zip
%attr(0644, root, root) %{_datadir}/%{name}-common/vms/cloud-scripts.tgz
%attr(0644, root, root) %{_datadir}/%{name}-common/vms/patch-sysvms.sh
%attr(0644,root,root) %{_datadir}/%{name}-common/python-site/cloud_utils.py
%attr(0644,root,root) %{_datadir}/%{name}-common/python-site/__pycache__/*
%attr(0644,root,root) %{_datadir}/%{name}-common/python-site/cloudutils/*
%attr(0644, root, root) %{_datadir}/%{name}-common/lib/jasypt-1.9.3.jar
%attr(0644, root, root) %{_datadir}/%{name}-common/lib/%{name}-utils.jar
%{_defaultdocdir}/%{name}-common-%{version}/LICENSE
%{_defaultdocdir}/%{name}-common-%{version}/NOTICE
%files ui
%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/ui/config.json
%{_datadir}/%{name}-ui/*
%{_defaultdocdir}/%{name}-ui-%{version}/LICENSE
%{_defaultdocdir}/%{name}-ui-%{version}/NOTICE
%files usage
%attr(0644,root,root) %{_unitdir}/%{name}-usage.service
%config(noreplace) %{_sysconfdir}/default/%{name}-usage
%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-usage
%attr(0644,root,root) %{_datadir}/%{name}-usage/*.jar
%attr(0644,root,root) %{_datadir}/%{name}-usage/lib/*.jar
%dir %attr(0770,root,cloud) %{_localstatedir}/log/%{name}/usage
%attr(0644,root,root) %{_sysconfdir}/%{name}/usage/db.properties
%attr(0644,root,root) %{_sysconfdir}/%{name}/usage/log4j-cloud.xml
%{_defaultdocdir}/%{name}-usage-%{version}/LICENSE
%{_defaultdocdir}/%{name}-usage-%{version}/NOTICE
%files marvin
%attr(0644,root,root) %{_datadir}/%{name}-marvin/Marvin*.tar.gz
%{_defaultdocdir}/%{name}-marvin-%{version}/LICENSE
%{_defaultdocdir}/%{name}-marvin-%{version}/NOTICE
%files integration-tests
%attr(0755,root,root) %{_datadir}/%{name}-integration-tests/*
%{_defaultdocdir}/%{name}-integration-tests-%{version}/LICENSE
%{_defaultdocdir}/%{name}-integration-tests-%{version}/NOTICE
%if "%{_ossnoss}" == "noredist"
%files mysql-ha
%defattr(0644,cloud,cloud,0755)
%attr(0644,root,root) %{_datadir}/%{name}-mysql-ha/lib/*
%endif
%files baremetal-agent
%attr(0755,root,root) %{_bindir}/cloudstack-setup-baremetal
%changelog
* Thu Dec 22 2022 Rohit Yadav <rohit@apache.org> 4.18.0
- Add support for EL9
* Fri Oct 14 2022 Daan Hoogland <daan.hoogland@gmail.com> 4.18.0
- initialising sanity check pointer file
* Tue Jun 29 2021 David Jumani <dj.davidjumani1994@gmail.com> 4.16.0
- Adding SUSE 15 support
* Thu Apr 30 2015 Rohit Yadav <bhaisaab@apache.org> 4.6.0
- Remove awsapi package
* Wed Nov 19 2014 Hugo Trippaers <hugo@apache.org> 4.6.0
- Create a specific spec for CentOS 7
* Fri Jul 4 2014 Hugo Trippaers <hugo@apache.org> 4.5.0
- Add a package for the mysql ha module
* Fri Oct 5 2012 Hugo Trippaers <hugo@apache.org> 4.1.0
- new style spec file

View File

@ -0,0 +1 @@
../el8/cloudstack-agent.te

View File

@ -0,0 +1 @@
../el8/cloudstack-sccs

View File

@ -0,0 +1 @@
../el8/filelimit.conf

View File

@ -0,0 +1,65 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
DBUSER=cloud
DBPW=cloud
DBROOTPW=
MSLOG=vmops.log
APISERVERLOG=api.log
DBHOST=localhost
DBDRIVER=jdbc:mysql
COMPONENTS-SPEC=components-premium.xml
REMOTEHOST=localhost
AGENTCLASSPATH=
AGENTLOG=/var/log/cloudstack/agent/agent.log
AGENTLOGDIR=/var/log/cloudstack/agent/
AGENTSYSCONFDIR=/etc/cloudstack/agent
APISERVERLOG=/var/log/cloudstack/management/apilog.log
BINDIR=/usr/bin
COMMONLIBDIR=/usr/share/cloudstack-common
CONFIGUREVARS=
DEPSCLASSPATH=
DOCDIR=
IPALOCATORLOG=/var/log/cloudstack/management/ipallocator.log
JAVADIR=/usr/share/java
LIBEXECDIR=/usr/libexec
LOCKDIR=/var/lock
MSCLASSPATH=
MSCONF=/etc/cloudstack/management
MSENVIRON=/usr/share/cloudstack-management
MSLOG=/var/log/cloudstack/management/management-server.log
MSLOGDIR=/var/log/cloudstack/management/
MSMNTDIR=/var/cloudstack/mnt
MSUSER=cloud
PIDDIR=/var/run
PLUGINJAVADIR=/usr/share/cloudstack-management/plugin
PREMIUMJAVADIR=/usr/share/cloudstack-management/premium
PYTHONDIR=/usr/share/cloudstack-common/python-site/
SERVERSYSCONFDIR=/etc/sysconfig
SETUPDATADIR=/usr/share/cloudstack-management/setup
SYSCONFDIR=/etc/sysconfig
SYSTEMCLASSPATH=
SYSTEMJARS=
USAGECLASSPATH=
USAGELOG=/var/log/cloudstack/usage/usage.log
USAGESYSCONFDIR=/etc/sysconfig
EXTENSIONSDEPLOYMENTMODE=production
GUESTNVRAMTEMPLATELEGACY=/usr/share/qemu/ovmf-x86_64-vars.bin
GUESTLOADERLEGACY=/usr/share/qemu/ovmf-x86_64-code.bin
GUESTNVRAMTEMPLATESECURE=/usr/share/qemu/ovmf-x86_64-ms-vars.bin
GUESTLOADERSECURE=/usr/share/qemu/ovmf-x86_64-ms-code.bin
GUESTNVRAMPATH=/var/lib/libvirt/qemu/nvram/

View File

@ -17,7 +17,7 @@
JAVA_OPTS="-Djava.security.properties=/etc/cloudstack/management/java.security.ciphers -Djava.awt.headless=true -Xmx2G -XX:+UseParallelGC -XX:MaxGCPauseMillis=500 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:ErrorFile=/var/log/cloudstack/management/cloudstack-management.err --add-opens=java.base/java.lang=ALL-UNNAMED --add-exports=java.base/sun.security.x509=ALL-UNNAMED"
CLASSPATH="/usr/share/cloudstack-management/lib/*:/etc/cloudstack/management:/usr/share/cloudstack-common:/usr/share/cloudstack-management/setup:/usr/share/cloudstack-management:/usr/share/java/mysql-connector-java.jar:/usr/share/cloudstack-mysql-ha/lib/*"
CLASSPATH="/usr/share/cloudstack-management/lib/*:/etc/cloudstack/management:/usr/share/cloudstack-common:/usr/share/cloudstack-management/setup:/usr/share/cloudstack-management:/usr/share/cloudstack-mysql-ha/lib/*"
BOOTSTRAP_CLASS=org.apache.cloudstack.ServerDaemon

View File

@ -17,7 +17,7 @@
JAVA_OPTS="-Xms256m -Xmx2048m --add-opens=java.base/java.lang=ALL-UNNAMED"
CLASSPATH="/usr/share/cloudstack-usage/*:/usr/share/cloudstack-usage/lib/*:/usr/share/cloudstack-mysql-ha/lib/*:/etc/cloudstack/usage:/usr/share/java/mysql-connector-java.jar"
CLASSPATH="/usr/share/cloudstack-usage/*:/usr/share/cloudstack-usage/lib/*:/usr/share/cloudstack-mysql-ha/lib/*:/etc/cloudstack/usage"
JAVA_CLASS=com.cloud.usage.UsageServer

View File

@ -40,17 +40,17 @@ public final class RootCACustomTrustManager implements X509TrustManager {
private boolean authStrictness = true;
private boolean allowExpiredCertificate = true;
private CrlDao crlDao;
private X509Certificate caCertificate;
private List<X509Certificate> caCertificates;
private Map<String, X509Certificate> activeCertMap;
public RootCACustomTrustManager(final String clientAddress, final boolean authStrictness, final boolean allowExpiredCertificate, final Map<String, X509Certificate> activeCertMap, final X509Certificate caCertificate, final CrlDao crlDao) {
public RootCACustomTrustManager(final String clientAddress, final boolean authStrictness, final boolean allowExpiredCertificate, final Map<String, X509Certificate> activeCertMap, final List<X509Certificate> caCertificates, final CrlDao crlDao) {
if (StringUtils.isNotEmpty(clientAddress)) {
this.clientAddress = clientAddress.replace("/", "").split(":")[0];
}
this.authStrictness = authStrictness;
this.allowExpiredCertificate = allowExpiredCertificate;
this.activeCertMap = activeCertMap;
this.caCertificate = caCertificate;
this.caCertificates = caCertificates;
this.crlDao = crlDao;
}
@ -151,6 +151,6 @@ public final class RootCACustomTrustManager implements X509TrustManager {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{caCertificate};
return caCertificates.toArray(new X509Certificate[0]);
}
}

View File

@ -40,7 +40,6 @@ import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
@ -60,6 +59,7 @@ import org.apache.cloudstack.framework.ca.CAProvider;
import org.apache.cloudstack.framework.ca.Certificate;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.config.ValidatedConfigKey;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.utils.security.CertUtils;
import org.apache.cloudstack.utils.security.KeyStoreUtils;
@ -92,6 +92,7 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
private static KeyPair caKeyPair = null;
private static X509Certificate caCertificate = null;
private static List<X509Certificate> caCertificates = null;
private static KeyStore managementKeyStore = null;
@Inject
@ -103,20 +104,25 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
/////////////// Root CA Settings ///////////////////
////////////////////////////////////////////////////
private static ConfigKey<String> rootCAPrivateKey = new ConfigKey<>("Hidden", String.class,
"ca.plugin.root.private.key",
null,
"The ROOT CA private key.", true);
private static ConfigKey<String> rootCAPrivateKey = new ValidatedConfigKey<>("Hidden", String.class,
"ca.plugin.root.private.key", null,
"The ROOT CA private key in PEM format. " +
"When set along with the public key and certificate, CloudStack uses this custom CA instead of auto-generating one. " +
"All three ca.plugin.root.* keys must be set together. Restart management server(s) when changed.",
false, ConfigKey.Scope.Global, null, RootCAProvider::validatePrivateKeyPem);
private static ConfigKey<String> rootCAPublicKey = new ConfigKey<>("Hidden", String.class,
"ca.plugin.root.public.key",
null,
"The ROOT CA public key.", true);
private static ConfigKey<String> rootCAPublicKey = new ValidatedConfigKey<>("Hidden", String.class,
"ca.plugin.root.public.key", null,
"The ROOT CA public key in PEM format (X.509/SPKI: must start with '-----BEGIN PUBLIC KEY-----'). " +
"Required when providing a custom CA. Restart management server(s) when changed.",
false, ConfigKey.Scope.Global, null, RootCAProvider::validatePublicKeyPem);
private static ConfigKey<String> rootCACertificate = new ConfigKey<>("Hidden", String.class,
"ca.plugin.root.ca.certificate",
null,
"The ROOT CA certificate.", true);
private static ConfigKey<String> rootCACertificate = new ValidatedConfigKey<>("Hidden", String.class,
"ca.plugin.root.ca.certificate", null,
"The CA certificate(s) in PEM format (must start with '-----BEGIN CERTIFICATE-----'). " +
"For intermediate CAs, concatenate the signing cert first, followed by intermediate(s) and root. " +
"Required when providing a custom CA. Restart management server(s) when changed.",
false, ConfigKey.Scope.Global, null, RootCAProvider::validateCACertificatePem);
private static ConfigKey<String> rootCAIssuerDN = new ConfigKey<>("Advanced", String.class,
"ca.plugin.root.issuer.dn",
@ -151,7 +157,7 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
caCertificate, caKeyPair, keyPair.getPublic(),
subject, CAManager.CertSignatureAlgorithm.value(),
validityDays, domainNames, ipAddresses);
return new Certificate(clientCertificate, keyPair.getPrivate(), Collections.singletonList(caCertificate));
return new Certificate(clientCertificate, keyPair.getPrivate(), caCertificates);
}
private Certificate generateCertificateUsingCsr(final String csr, final List<String> names, final List<String> ips, final int validityDays) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, CertificateException, SignatureException, IOException, OperatorCreationException {
@ -205,7 +211,7 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
caCertificate, caKeyPair, request.getPublicKey(),
subject, CAManager.CertSignatureAlgorithm.value(),
validityDays, dnsNames, ipAddresses);
return new Certificate(clientCertificate, null, Collections.singletonList(caCertificate));
return new Certificate(clientCertificate, null, caCertificates);
}
////////////////////////////////////////////////////////
@ -219,7 +225,7 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
@Override
public List<X509Certificate> getCaCertificate() {
return Collections.singletonList(caCertificate);
return caCertificates;
}
@Override
@ -254,8 +260,8 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
private KeyStore getCaKeyStore() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException {
final KeyStore ks = KeyStore.getInstance("JKS");
ks.load(null, null);
if (caKeyPair != null && caCertificate != null) {
ks.setKeyEntry(caAlias, caKeyPair.getPrivate(), getKeyStorePassphrase(), new X509Certificate[]{caCertificate});
if (caKeyPair != null && CollectionUtils.isNotEmpty(caCertificates)) {
ks.setKeyEntry(caAlias, caKeyPair.getPrivate(), getKeyStorePassphrase(), caCertificates.toArray(new X509Certificate[0]));
} else {
return null;
}
@ -274,7 +280,7 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
final boolean authStrictness = rootCAAuthStrictness.value();
final boolean allowExpiredCertificate = rootCAAllowExpiredCert.value();
TrustManager[] tms = new TrustManager[]{new RootCACustomTrustManager(remoteAddress, authStrictness, allowExpiredCertificate, certMap, caCertificate, crlDao)};
TrustManager[] tms = new TrustManager[]{new RootCACustomTrustManager(remoteAddress, authStrictness, allowExpiredCertificate, certMap, caCertificates, crlDao)};
sslContext.init(kmf.getKeyManagers(), tms, new SecureRandom());
final SSLEngine sslEngine = sslContext.createSSLEngine();
@ -316,33 +322,39 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
if (!configDao.update(rootCAPrivateKey.key(), rootCAPrivateKey.category(), CertUtils.privateKeyToPem(keyPair.getPrivate()))) {
logger.error("Failed to save RootCA private key");
}
caKeyPair = keyPair;
} catch (final NoSuchProviderException | NoSuchAlgorithmException | IOException e) {
logger.error("Failed to generate/save RootCA private/public keys due to exception:", e);
}
return loadRootCAKeyPair();
return caKeyPair != null && caKeyPair.getPrivate() != null && caKeyPair.getPublic() != null;
}
private boolean saveNewRootCACertificate() {
boolean saveNewRootCACertificate() {
if (caKeyPair == null) {
throw new CloudRuntimeException("Cannot issue self-signed root CA certificate as CA keypair is not initialized");
}
try {
logger.debug("Generating root CA certificate");
final X509Certificate rootCaCertificate = CertUtils.generateV3Certificate(
final X509Certificate generatedCACert = CertUtils.generateV3Certificate(
null, caKeyPair, caKeyPair.getPublic(),
rootCAIssuerDN.value(), CAManager.CertSignatureAlgorithm.value(),
getCaValidityDays(), null, null);
if (!configDao.update(rootCACertificate.key(), rootCACertificate.category(), CertUtils.x509CertificateToPem(rootCaCertificate))) {
if (!configDao.update(rootCACertificate.key(), rootCACertificate.category(), CertUtils.x509CertificateToPem(generatedCACert))) {
logger.error("Failed to update RootCA public/x509 certificate");
}
caCertificates = new ArrayList<>(java.util.Collections.singletonList(generatedCACert));
caCertificate = generatedCACert;
} catch (final CertificateException | NoSuchAlgorithmException | NoSuchProviderException | SignatureException | InvalidKeyException | OperatorCreationException | IOException e) {
logger.error("Failed to generate RootCA certificate from private/public keys due to exception:", e);
return false;
}
return loadRootCACertificate();
return caCertificate != null;
}
private boolean loadRootCAKeyPair() {
if (caKeyPair != null) {
return true;
}
if (StringUtils.isAnyEmpty(rootCAPublicKey.value(), rootCAPrivateKey.value())) {
return false;
}
@ -355,14 +367,35 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
return caKeyPair.getPrivate() != null && caKeyPair.getPublic() != null;
}
private boolean loadRootCACertificate() {
boolean loadRootCACertificate() {
if (caCertificate != null && CollectionUtils.isNotEmpty(caCertificates)) {
return true;
}
caCertificate = null;
caCertificates = null;
if (StringUtils.isEmpty(rootCACertificate.value())) {
return false;
}
try {
caCertificate = CertUtils.pemToX509Certificate(rootCACertificate.value());
caCertificate.verify(caKeyPair.getPublic());
} catch (final IOException | CertificateException | NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchProviderException e) {
final List<X509Certificate> loadedCerts = CertUtils.pemToX509Certificates(rootCACertificate.value());
if (CollectionUtils.isEmpty(loadedCerts)) {
logger.error("No certificates found in ca.plugin.root.ca.certificate");
return false;
}
final X509Certificate loadedCACert = loadedCerts.get(0);
// Verify key ownership without enforcing self-signature
if (!loadedCACert.getPublicKey().equals(caKeyPair.getPublic())) {
logger.error("The public key in the CA certificate does not match the configured CA public key");
return false;
}
if (loadedCerts.size() > 1) {
logger.info("Loaded CA certificate chain with {} certificate(s)", loadedCerts.size());
}
caCertificates = loadedCerts;
caCertificate = loadedCACert;
} catch (final IOException | CertificateException e) {
logger.error("Failed to load saved RootCA certificate due to exception:", e);
return false;
}
@ -389,9 +422,15 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
try {
managementKeyStore = KeyStore.getInstance("JKS");
managementKeyStore.load(null, null);
managementKeyStore.setCertificateEntry(caAlias, caCertificate);
int caIndex = 0;
for (final X509Certificate cert : caCertificates) {
managementKeyStore.setCertificateEntry(caAlias + "-" + caIndex++, cert);
}
final List<X509Certificate> fullChain = new ArrayList<>();
fullChain.add(serverCertificate.getClientCertificate());
fullChain.addAll(caCertificates);
managementKeyStore.setKeyEntry(managementAlias, serverCertificate.getPrivateKey(), getKeyStorePassphrase(),
new X509Certificate[]{serverCertificate.getClientCertificate(), caCertificate});
fullChain.toArray(new X509Certificate[0]));
} catch (final CertificateException | NoSuchAlgorithmException | KeyStoreException | IOException e) {
logger.error("Failed to load root CA management-server keystore due to exception: ", e);
return false;
@ -421,14 +460,63 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
}
private boolean setupCA() {
if (!loadRootCAKeyPair() && !saveNewRootCAKeypair()) {
logger.error("Failed to save and load root CA keypair");
return false;
private static void validatePrivateKeyPem(String value) {
if (StringUtils.isEmpty(value)) return;
try {
CertUtils.pemToPrivateKey(value);
} catch (InvalidKeySpecException | IOException e) {
throw new IllegalArgumentException(
"ca.plugin.root.private.key is not a valid PEM private key: " + e.getMessage());
}
if (!loadRootCACertificate() && !saveNewRootCACertificate()) {
logger.error("Failed to save and load root CA certificate");
return false;
}
private static void validatePublicKeyPem(String value) {
if (StringUtils.isEmpty(value)) return;
try {
CertUtils.pemToPublicKey(value);
} catch (InvalidKeySpecException | IOException e) {
throw new IllegalArgumentException(
"ca.plugin.root.public.key is not a valid PEM public key: " + e.getMessage());
}
}
static void validateCACertificatePem(String value) {
if (StringUtils.isEmpty(value)) return;
try {
final List<X509Certificate> certs = CertUtils.pemToX509Certificates(value);
if (CollectionUtils.isEmpty(certs)) {
throw new IllegalArgumentException(
"ca.plugin.root.ca.certificate contains no certificates");
}
} catch (IOException | CertificateException e) {
throw new IllegalArgumentException(
"ca.plugin.root.ca.certificate is not a valid PEM certificate: " + e.getMessage());
}
}
private boolean setupCA() {
if (!loadRootCAKeyPair()) {
if (hasUserProvidedCAKeys()) {
logger.error("Failed to load user-provided CA keys from configuration. " +
"Check that ca.plugin.root.private.key, ca.plugin.root.public.key, and " +
"ca.plugin.root.ca.certificate are all set and in the correct PEM format. " +
"Overwriting with auto-generated keys.");
}
if (!saveNewRootCAKeypair()) {
logger.error("Failed to save and load root CA keypair");
return false;
}
}
if (!loadRootCACertificate()) {
if (hasUserProvidedCAKeys()) {
logger.error("Failed to load user-provided CA certificate. " +
"Check that ca.plugin.root.ca.certificate is set and in PEM format. " +
"Overwriting with auto-generated certificate.");
}
if (!saveNewRootCACertificate()) {
logger.error("Failed to save and load root CA certificate");
return false;
}
}
if (!loadManagementKeyStore()) {
logger.error("Failed to check and configure management server keystore");
@ -437,10 +525,16 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
return true;
}
private boolean hasUserProvidedCAKeys() {
return StringUtils.isNotEmpty(rootCAPublicKey.value())
|| StringUtils.isNotEmpty(rootCAPrivateKey.value())
|| StringUtils.isNotEmpty(rootCACertificate.value());
}
@Override
public boolean start() {
managementCertificateCustomSAN = CAManager.CertManagementCustomSubjectAlternativeName.value();
return loadRootCAKeyPair() && loadRootCAKeyPair() && loadManagementKeyStore();
return loadRootCAKeyPair() && loadRootCACertificate() && loadManagementKeyStore();
}
@Override

View File

@ -23,9 +23,11 @@ import java.math.BigInteger;
import java.security.KeyPair;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import org.apache.cloudstack.utils.security.CertUtils;
import org.junit.Assert;
@ -63,14 +65,14 @@ public class RootCACustomTrustManagerTest {
@Test
public void testAuthNotStrictWithInvalidCert() throws Exception {
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, caCertificate, crlDao);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, Collections.singletonList(caCertificate), crlDao);
trustManager.checkClientTrusted(null, null);
}
@Test
public void testAuthNotStrictWithRevokedCert() throws Exception {
Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(new CrlVO());
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, caCertificate, crlDao);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, Collections.singletonList(caCertificate), crlDao);
trustManager.checkClientTrusted(new X509Certificate[]{caCertificate}, "RSA");
Assert.assertTrue(certMap.containsKey(clientIp));
Assert.assertEquals(certMap.get(clientIp), caCertificate);
@ -79,7 +81,7 @@ public class RootCACustomTrustManagerTest {
@Test
public void testAuthNotStrictWithInvalidCertOwnership() throws Exception {
Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, caCertificate, crlDao);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, Collections.singletonList(caCertificate), crlDao);
trustManager.checkClientTrusted(new X509Certificate[]{caCertificate}, "RSA");
Assert.assertTrue(certMap.containsKey(clientIp));
Assert.assertEquals(certMap.get(clientIp), caCertificate);
@ -88,14 +90,14 @@ public class RootCACustomTrustManagerTest {
@Test(expected = CertificateException.class)
public void testAuthNotStrictWithDenyExpiredCertAndOwnership() throws Exception {
Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, false, certMap, caCertificate, crlDao);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, false, certMap, Collections.singletonList(caCertificate), crlDao);
trustManager.checkClientTrusted(new X509Certificate[]{expiredClientCertificate}, "RSA");
}
@Test
public void testAuthNotStrictWithAllowExpiredCertAndOwnership() throws Exception {
Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, caCertificate, crlDao);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, Collections.singletonList(caCertificate), crlDao);
trustManager.checkClientTrusted(new X509Certificate[]{expiredClientCertificate}, "RSA");
Assert.assertTrue(certMap.containsKey(clientIp));
Assert.assertEquals(certMap.get(clientIp), expiredClientCertificate);
@ -103,35 +105,50 @@ public class RootCACustomTrustManagerTest {
@Test(expected = CertificateException.class)
public void testAuthStrictWithInvalidCert() throws Exception {
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, Collections.singletonList(caCertificate), crlDao);
trustManager.checkClientTrusted(null, null);
}
@Test(expected = CertificateException.class)
public void testAuthStrictWithRevokedCert() throws Exception {
Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(new CrlVO());
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, Collections.singletonList(caCertificate), crlDao);
trustManager.checkClientTrusted(new X509Certificate[]{caCertificate}, "RSA");
}
@Test(expected = CertificateException.class)
public void testAuthStrictWithInvalidCertOwnership() throws Exception {
Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, Collections.singletonList(caCertificate), crlDao);
trustManager.checkClientTrusted(new X509Certificate[]{caCertificate}, "RSA");
}
@Test(expected = CertificateException.class)
public void testAuthStrictWithDenyExpiredCertAndOwnership() throws Exception {
Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, false, certMap, caCertificate, crlDao);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, false, certMap, Collections.singletonList(caCertificate), crlDao);
trustManager.checkClientTrusted(new X509Certificate[]{expiredClientCertificate}, "RSA");
}
@Test
public void testGetAcceptedIssuersWithChain() throws Exception {
final KeyPair rootKeyPair = CertUtils.generateRandomKeyPair(1024);
final X509Certificate rootCert = CertUtils.generateV3Certificate(null, rootKeyPair, rootKeyPair.getPublic(),
"CN=root", "SHA256withRSA", 365, null, null);
final List<X509Certificate> chain = Arrays.asList(caCertificate, rootCert);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(
clientIp, false, true, certMap, chain, crlDao);
final X509Certificate[] issuers = trustManager.getAcceptedIssuers();
Assert.assertEquals(2, issuers.length);
Assert.assertEquals(caCertificate, issuers[0]);
Assert.assertEquals(rootCert, issuers[1]);
}
@Test
public void testAuthStrictWithAllowExpiredCertAndOwnership() throws Exception {
Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao);
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, Collections.singletonList(caCertificate), crlDao);
Assert.assertTrue(trustManager.getAcceptedIssuers() != null);
Assert.assertTrue(trustManager.getAcceptedIssuers().length == 1);
Assert.assertEquals(trustManager.getAcceptedIssuers()[0], caCertificate);

View File

@ -31,6 +31,7 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@ -38,6 +39,7 @@ import javax.net.ssl.SSLEngine;
import org.apache.cloudstack.framework.ca.Certificate;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.utils.security.CertUtils;
import org.apache.cloudstack.utils.security.SSLUtils;
import org.bouncycastle.asn1.x509.GeneralName;
@ -49,7 +51,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
@RunWith(MockitoJUnitRunner.class)
@ -75,7 +76,7 @@ public class RootCAProviderTest {
addField(provider, "caKeyPair", caKeyPair);
addField(provider, "caCertificate", caCertificate);
addField(provider, "caKeyPair", caKeyPair);
addField(provider, "caCertificates", Collections.singletonList(caCertificate));
}
@After
@ -129,6 +130,46 @@ public class RootCAProviderTest {
certificate.getClientCertificate().verify(caCertificate.getPublicKey());
}
@Test
public void testGetCaCertificateWithChain() throws Exception {
final KeyPair rootKeyPair = CertUtils.generateRandomKeyPair(1024);
final X509Certificate rootCert = CertUtils.generateV3Certificate(null, rootKeyPair, rootKeyPair.getPublic(),
"CN=root", "SHA256withRSA", 365, null, null);
final KeyPair intermediateKeyPair = CertUtils.generateRandomKeyPair(1024);
final X509Certificate intermediateCert = CertUtils.generateV3Certificate(rootCert, rootKeyPair,
intermediateKeyPair.getPublic(), "CN=intermediate", "SHA256withRSA", 365, null, null);
final List<X509Certificate> chain = Arrays.asList(intermediateCert, rootCert);
addField(provider, "caKeyPair", intermediateKeyPair);
addField(provider, "caCertificate", intermediateCert);
addField(provider, "caCertificates", chain);
Assert.assertEquals(2, provider.getCaCertificate().size());
Assert.assertEquals(intermediateCert, provider.getCaCertificate().get(0));
Assert.assertEquals(rootCert, provider.getCaCertificate().get(1));
}
@Test
public void testIssueCertificateWithoutCsrAndChain() throws Exception {
final KeyPair rootKeyPair = CertUtils.generateRandomKeyPair(1024);
final X509Certificate rootCert = CertUtils.generateV3Certificate(null, rootKeyPair, rootKeyPair.getPublic(),
"CN=root", "SHA256withRSA", 365, null, null);
final KeyPair intermediateKeyPair = CertUtils.generateRandomKeyPair(1024);
final X509Certificate intermediateCert = CertUtils.generateV3Certificate(rootCert, rootKeyPair,
intermediateKeyPair.getPublic(), "CN=intermediate", "SHA256withRSA", 365, null, null);
addField(provider, "caKeyPair", intermediateKeyPair);
addField(provider, "caCertificate", intermediateCert);
addField(provider, "caCertificates", Arrays.asList(intermediateCert, rootCert));
final Certificate certificate = provider.issueCertificate(Arrays.asList("domain1.com"), null, 1);
Assert.assertNotNull(certificate);
Assert.assertEquals(2, certificate.getCaCertificates().size());
Assert.assertEquals(intermediateCert, certificate.getCaCertificates().get(0));
Assert.assertEquals(rootCert, certificate.getCaCertificates().get(1));
certificate.getClientCertificate().verify(intermediateKeyPair.getPublic());
}
@Test
public void testRevokeCertificate() throws Exception {
Assert.assertTrue(provider.revokeCertificate(CertUtils.generateRandomBigInt(), "anyString"));
@ -177,8 +218,8 @@ public class RootCAProviderTest {
}
@Test
public void testIsManagementCertificateNoMatch() {
ReflectionTestUtils.setField(provider, "managementCertificateCustomSAN", "cloudstack");
public void testIsManagementCertificateNoMatch() throws Exception {
addField(provider, "managementCertificateCustomSAN", "cloudstack");
try {
X509Certificate certificate = Mockito.mock(X509Certificate.class);
List<List<?>> altNames = new ArrayList<>();
@ -193,9 +234,9 @@ public class RootCAProviderTest {
}
@Test
public void testIsManagementCertificateMatch() {
public void testIsManagementCertificateMatch() throws Exception {
String customSAN = "cloudstack";
ReflectionTestUtils.setField(provider, "managementCertificateCustomSAN", customSAN);
addField(provider, "managementCertificateCustomSAN", customSAN);
try {
X509Certificate certificate = Mockito.mock(X509Certificate.class);
List<List<?>> altNames = new ArrayList<>();
@ -208,4 +249,58 @@ public class RootCAProviderTest {
Assert.fail(String.format("Exception occurred: %s", e.getMessage()));
}
}
@Test
public void testLoadRootCACertificateWithMismatchedCert() throws Exception {
KeyPair otherKeyPair = CertUtils.generateRandomKeyPair(1024);
X509Certificate mismatchedCert = CertUtils.generateV3Certificate(null, otherKeyPair, otherKeyPair.getPublic(), "CN=other", "SHA256withRSA", 365, null, null);
String mismatchedPem = CertUtils.x509CertificateToPem(mismatchedCert);
ConfigKey<String> mockCertKey = Mockito.mock(ConfigKey.class);
Mockito.when(mockCertKey.value()).thenReturn(mismatchedPem);
addField(provider, "rootCACertificate", mockCertKey);
addField(provider, "caCertificate", null);
addField(provider, "caCertificates", null);
Boolean result = provider.loadRootCACertificate();
Assert.assertFalse(result);
Assert.assertNull(provider.getCaCertificate());
}
@Test
public void testSaveNewRootCACertificateWithStaleCache() throws Exception {
ConfigurationDao configDao = Mockito.mock(ConfigurationDao.class);
addField(provider, "configDao", configDao);
ConfigKey<String> mockCertKey = Mockito.mock(ConfigKey.class);
Mockito.when(mockCertKey.key()).thenReturn("ca.plugin.root.ca.certificate");
Mockito.when(mockCertKey.category()).thenReturn("Hidden");
addField(provider, "rootCACertificate", mockCertKey);
ConfigKey<String> mockIssuerKey = Mockito.mock(ConfigKey.class);
Mockito.when(mockIssuerKey.value()).thenReturn("CN=ca.cloudstack.apache.org");
addField(provider, "rootCAIssuerDN", mockIssuerKey);
addField(provider, "caCertificate", null);
addField(provider, "caCertificates", null);
Mockito.when(configDao.update(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn(true);
Boolean result = provider.saveNewRootCACertificate();
Assert.assertTrue(result);
Assert.assertNotNull(provider.getCaCertificate());
Assert.assertEquals(1, provider.getCaCertificate().size());
}
@Test
public void testValidateCACertificatePem() throws Exception {
String truncatedPem = "-----BEGIN CERTIFICATE-----\nMIICxTCCAa0CAQAw\n";
try {
RootCAProvider.validateCACertificatePem(truncatedPem);
Assert.fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
Assert.assertTrue(e.getMessage().contains("is not a valid PEM certificate"));
}
}
}

View File

@ -48,6 +48,8 @@ import org.apache.http.HttpHeaders;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import javax.net.ssl.SSLContext;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.entity.ContentType;
@ -97,7 +99,9 @@ public class WebhookDeliveryThread implements Runnable {
protected void setHttpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
if (webhook.isSslVerification()) {
httpClient = HttpClients.createDefault();
httpClient = HttpClients.custom()
.setSSLContext(SSLContext.getDefault())
.build();
return;
}
httpClient = HttpClients

View File

@ -41,15 +41,15 @@ public class HypervInvestigator extends AdapterBase implements Investigator {
@Override
public boolean isVmAlive(com.cloud.vm.VirtualMachine vm, Host host) throws UnknownVM {
Status status = isAgentAlive(host);
Status status = getHostAgentStatus(host);
if (status == null) {
throw new UnknownVM();
}
return status == Status.Up ? true : null;
return status == Status.Up;
}
@Override
public Status isAgentAlive(Host agent) {
public Status getHostAgentStatus(Host agent) {
if (agent.getHypervisorType() != Hypervisor.HypervisorType.Hyperv) {
return null;
}

View File

@ -19,10 +19,7 @@
package com.cloud.ha;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.CheckOnHostCommand;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.Status;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor;
@ -34,11 +31,12 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
import org.apache.cloudstack.ha.HAManager;
import org.apache.cloudstack.kvm.ha.KVMHostActivityChecker;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class KVMInvestigator extends AdapterBase implements Investigator {
@ -54,13 +52,15 @@ public class KVMInvestigator extends AdapterBase implements Investigator {
private HAManager haManager;
@Inject
private DataStoreProviderManager dataStoreProviderMgr;
@Inject
private KVMHostActivityChecker hostActivityChecker;
@Override
public boolean isVmAlive(com.cloud.vm.VirtualMachine vm, Host host) throws UnknownVM {
if (haManager.isHAEligible(host)) {
return haManager.isVMAliveOnHost(host);
}
Status status = isAgentAlive(host);
Status status = getHostAgentStatus(host);
logger.debug("HA: HOST is ineligible legacy state {} for host {}", status, host);
if (status == null) {
throw new UnknownVM();
@ -73,86 +73,41 @@ public class KVMInvestigator extends AdapterBase implements Investigator {
}
@Override
public Status isAgentAlive(Host agent) {
if (agent.getHypervisorType() != Hypervisor.HypervisorType.KVM && agent.getHypervisorType() != Hypervisor.HypervisorType.LXC) {
public Status getHostAgentStatus(Host host) {
if (host.getHypervisorType() != Hypervisor.HypervisorType.KVM && host.getHypervisorType() != Hypervisor.HypervisorType.LXC) {
return null;
}
if (haManager.isHAEligible(agent)) {
return haManager.getHostStatus(agent);
if (haManager.isHAEligible(host)) {
return haManager.getHostStatusFromHAConfig(host);
}
List<StoragePoolVO> clusterPools = _storagePoolDao.findPoolsInClusters(Arrays.asList(agent.getClusterId()), null);
boolean storageSupportHA = storageSupportHa(clusterPools);
if (!storageSupportHA) {
List<StoragePoolVO> zonePools = _storagePoolDao.findZoneWideStoragePoolsByHypervisor(agent.getDataCenterId(), agent.getHypervisorType());
storageSupportHA = storageSupportHa(zonePools);
List<StoragePoolVO> clusterPools = _storagePoolDao.findPoolsInClusters(Collections.singletonList(host.getClusterId()), null);
boolean storageSupportsHA = storageSupportsHA(clusterPools);
if (!storageSupportsHA) {
List<StoragePoolVO> zonePools = _storagePoolDao.findZoneWideStoragePoolsByHypervisor(host.getDataCenterId(), host.getHypervisorType());
storageSupportsHA = storageSupportsHA(zonePools);
}
if (!storageSupportHA) {
logger.warn("Agent investigation was requested on host {}, but host does not support investigation because it has no NFS storage. Skipping investigation.", agent);
if (!storageSupportsHA) {
logger.warn("Agent investigation was requested on host {}, but host does not support investigation" +
" because it has no HA supported storage. Skipping investigation.", host);
return null;
}
Status hostStatus = null;
Status neighbourStatus = null;
boolean reportFailureIfOneStorageIsDown = HighAvailabilityManager.KvmHAFenceHostIfHeartbeatFailsOnStorage.value();
CheckOnHostCommand cmd = new CheckOnHostCommand(agent, reportFailureIfOneStorageIsDown);
try {
Answer answer = _agentMgr.easySend(agent.getId(), cmd);
if (answer != null) {
hostStatus = answer.getResult() ? Status.Down : Status.Up;
}
} catch (Exception e) {
logger.debug("Failed to send command to host: {}", agent);
}
if (hostStatus == null) {
hostStatus = Status.Disconnected;
}
List<HostVO> neighbors = _resourceMgr.listHostsInClusterByStatus(agent.getClusterId(), Status.Up);
for (HostVO neighbor : neighbors) {
if (neighbor.getId() == agent.getId()
|| (neighbor.getHypervisorType() != Hypervisor.HypervisorType.KVM && neighbor.getHypervisorType() != Hypervisor.HypervisorType.LXC)) {
continue;
}
logger.debug("Investigating host:{} via neighbouring host:{}", agent, neighbor);
try {
Answer answer = _agentMgr.easySend(neighbor.getId(), cmd);
if (answer != null) {
neighbourStatus = answer.getResult() ? Status.Down : Status.Up;
logger.debug("Neighbouring host:{} returned status:{} for the investigated host:{}", neighbor, neighbourStatus, agent);
if (neighbourStatus == Status.Up) {
break;
}
}
} catch (Exception e) {
logger.debug("Failed to send command to host: {}", neighbor);
}
}
if (neighbourStatus == Status.Up && (hostStatus == Status.Disconnected || hostStatus == Status.Down)) {
hostStatus = Status.Disconnected;
}
if (neighbourStatus == Status.Down && (hostStatus == Status.Disconnected || hostStatus == Status.Down)) {
hostStatus = Status.Down;
}
logger.debug("HA: HOST is ineligible legacy state {} for host {}", hostStatus, agent);
return hostStatus;
return hostActivityChecker.getHostAgentStatus(host);
}
private boolean storageSupportHa(List<StoragePoolVO> pools) {
boolean storageSupportHA = false;
private boolean storageSupportsHA(List<StoragePoolVO> pools) {
for (StoragePoolVO pool : pools) {
DataStoreProvider storeProvider = dataStoreProviderMgr.getDataStoreProvider(pool.getStorageProviderName());
DataStoreDriver storeDriver = storeProvider.getDataStoreDriver();
if (storeDriver instanceof PrimaryDataStoreDriver) {
PrimaryDataStoreDriver primaryStoreDriver = (PrimaryDataStoreDriver)storeDriver;
if (primaryStoreDriver.isStorageSupportHA(pool.getPoolType())) {
storageSupportHA = true;
break;
return true;
}
}
}
return storageSupportHA;
return false;
}
}

View File

@ -35,10 +35,9 @@ import com.cloud.agent.properties.AgentPropertiesFileHandler;
public class KVMHABase {
protected Logger logger = LogManager.getLogger(getClass());
private long _timeout = 60000; /* 1 minutes */
protected long _heartBeatUpdateTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HEARTBEAT_UPDATE_TIMEOUT);
protected long _heartBeatUpdateFreq = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_FREQUENCY);
protected long _heartBeatUpdateFreqInMs = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_FREQUENCY);
protected long _heartBeatUpdateMaxTries = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_MAX_TRIES);
protected long _heartBeatUpdateRetrySleep = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_RETRY_SLEEP);
protected long _heartBeatUpdateRetrySleepInMs = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_RETRY_SLEEP);
public static enum PoolType {
PrimaryStorage, SecondaryStorage
@ -138,7 +137,7 @@ public class KVMHABase {
/* Can't find the mount point? */
/* we need to mount it under poolName */
if (poolName != null) {
Script mount = new Script("/bin/bash", 60000);
Script mount = new Script("/bin/bash", _timeout);
mount.add("-c");
mount.add("mount " + mountSource + " " + destPath);
String result = mount.execute();
@ -154,7 +153,6 @@ public class KVMHABase {
}
protected String getMountPoint(HAStoragePool storagePool) {
StoragePool pool = null;
String poolName = null;
try {
@ -171,7 +169,6 @@ public class KVMHABase {
}
poolName = pool.getName();
}
} catch (LibvirtException e) {
logger.debug("Ignoring libvirt error.", e);
} finally {
@ -234,7 +231,7 @@ public class KVMHABase {
return result;
}
public Boolean checkingHeartBeat() {
public Boolean hasHeartBeat() {
// TODO Auto-generated method stub
return null;
}

View File

@ -26,44 +26,43 @@ import com.cloud.agent.api.to.HostTO;
public class KVMHAChecker extends KVMHABase implements Callable<Boolean> {
private List<HAStoragePool> storagePools;
private HostTO host;
private boolean reportFailureIfOneStorageIsDown;
private boolean reportIfHeartBeatFailedForOneStoragePool;
public KVMHAChecker(List<HAStoragePool> pools, HostTO host, boolean reportFailureIfOneStorageIsDown) {
public KVMHAChecker(List<HAStoragePool> pools, HostTO host, boolean reportIfHeartBeatFailedForOneStoragePool) {
this.storagePools = pools;
this.host = host;
this.reportFailureIfOneStorageIsDown = reportFailureIfOneStorageIsDown;
this.reportIfHeartBeatFailedForOneStoragePool = reportIfHeartBeatFailedForOneStoragePool;
}
/*
* True means heartbeaing is on going, or we can't get it's status. False
* means heartbeating is stopped definitely
* True means heart beating is on going, or we can't get it's status.
* False means heart beating is stopped definitely.
*/
@Override
public Boolean checkingHeartBeat() {
boolean validResult = false;
String hostAndPools = String.format("host IP [%s] in pools [%s]", host.getPrivateNetwork().getIp(), storagePools.stream().map(pool -> pool.getPoolUUID()).collect(Collectors.joining(", ")));
logger.debug(String.format("Checking heart beat with KVMHAChecker for %s", hostAndPools));
public Boolean hasHeartBeat() {
String hostAndPools = String.format("host IP [%s] in pools [%s]", host.getPrivateNetwork().getIp(),
storagePools.stream().map(pool -> pool.getPoolUUID()).collect(Collectors.joining(", ")));
logger.debug("Checking heart beat with KVMHAChecker for {}", hostAndPools);
boolean heartBeatCheckResult = false;
for (HAStoragePool pool : storagePools) {
validResult = pool.getPool().checkingHeartBeat(pool, host);
if (reportFailureIfOneStorageIsDown && !validResult) {
heartBeatCheckResult = pool.getPool().hasHeartBeat(pool, host);
if (reportIfHeartBeatFailedForOneStoragePool && !heartBeatCheckResult) {
break;
}
}
if (!validResult) {
logger.warn(String.format("All checks with KVMHAChecker for %s considered it as dead. It may cause a shutdown of the host.", hostAndPools));
if (!heartBeatCheckResult) {
logger.warn("All checks with KVMHAChecker for {} considered it as dead. It may cause a shutdown of the host.", hostAndPools);
}
return validResult;
return heartBeatCheckResult;
}
@Override
public Boolean call() throws Exception {
// logger.addAppender(new org.apache.log4j.ConsoleAppender(new
// org.apache.log4j.PatternLayout(), "System.out"));
return checkingHeartBeat();
return hasHeartBeat();
}
}

View File

@ -34,53 +34,49 @@ import java.util.concurrent.ConcurrentHashMap;
public class KVMHAMonitor extends KVMHABase implements Runnable {
private final Map<String, HAStoragePool> storagePool = new ConcurrentHashMap<>();
private final Map<String, HAStoragePool> haStoragePools = new ConcurrentHashMap<>();
private final boolean rebootHostAndAlertManagementOnHeartbeatTimeout;
private final String hostPrivateIp;
public KVMHAMonitor(HAStoragePool pool, String host) {
if (pool != null) {
storagePool.put(pool.getPoolUUID(), pool);
}
public KVMHAMonitor(String host) {
hostPrivateIp = host;
rebootHostAndAlertManagementOnHeartbeatTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.REBOOT_HOST_AND_ALERT_MANAGEMENT_ON_HEARTBEAT_TIMEOUT);
}
public void addStoragePool(HAStoragePool pool) {
synchronized (storagePool) {
storagePool.put(pool.getPoolUUID(), pool);
synchronized (haStoragePools) {
haStoragePools.put(pool.getPoolUUID(), pool);
}
}
public void removeStoragePool(String uuid) {
synchronized (storagePool) {
HAStoragePool pool = storagePool.get(uuid);
synchronized (haStoragePools) {
HAStoragePool pool = haStoragePools.get(uuid);
if (pool != null) {
Script.runSimpleBashScript("umount " + pool.getMountDestPath());
storagePool.remove(uuid);
haStoragePools.remove(uuid);
}
}
}
public List<HAStoragePool> getStoragePools() {
synchronized (storagePool) {
return new ArrayList<>(storagePool.values());
synchronized (haStoragePools) {
return new ArrayList<>(haStoragePools.values());
}
}
public HAStoragePool getStoragePool(String uuid) {
synchronized (storagePool) {
return storagePool.get(uuid);
synchronized (haStoragePools) {
return haStoragePools.get(uuid);
}
}
protected void runHeartBeat() {
synchronized (storagePool) {
synchronized (haStoragePools) {
Set<String> removedPools = new HashSet<>();
for (String uuid : storagePool.keySet()) {
HAStoragePool primaryStoragePool = storagePool.get(uuid);
for (String uuid : haStoragePools.keySet()) {
HAStoragePool primaryStoragePool = haStoragePools.get(uuid);
if (HighAvailabilityManager.LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT.contains(primaryStoragePool.getPool().getType())) {
checkForNotExistingLibvirtStoragePools(removedPools, uuid);
if (removedPools.contains(uuid)) {
@ -91,7 +87,7 @@ public class KVMHAMonitor extends KVMHABase implements Runnable {
result = executePoolHeartBeatCommand(uuid, primaryStoragePool, result);
if (result != null && rebootHostAndAlertManagementOnHeartbeatTimeout) {
logger.warn(String.format("Write heartbeat for pool [%s] failed: %s; stopping cloudstack-agent.", uuid, result));
logger.warn("Write heartbeat for pool [{}] failed: {}; stopping cloudstack-agent.", uuid, result);
primaryStoragePool.getPool().createHeartBeatCommand(primaryStoragePool, null, false);;
}
}
@ -104,20 +100,18 @@ public class KVMHAMonitor extends KVMHABase implements Runnable {
}
private String executePoolHeartBeatCommand(String uuid, HAStoragePool primaryStoragePool, String result) {
for (int i = 1; i <= _heartBeatUpdateMaxTries; i++) {
for (int attempt = 1; attempt <= _heartBeatUpdateMaxTries; attempt++) {
result = primaryStoragePool.getPool().createHeartBeatCommand(primaryStoragePool, hostPrivateIp, true);
if (result != null) {
logger.warn(String.format("Write heartbeat for pool [%s] failed: %s; try: %s of %s.", uuid, result, i, _heartBeatUpdateMaxTries));
try {
Thread.sleep(_heartBeatUpdateRetrySleep);
} catch (InterruptedException e) {
logger.debug("[IGNORED] Interrupted between heartbeat retries.", e);
}
} else {
if (result == null) {
break;
}
logger.warn("Write heartbeat for pool [{}] failed: {}; try: {} of {}.", uuid, result, attempt, _heartBeatUpdateMaxTries);
try {
Thread.sleep(_heartBeatUpdateRetrySleepInMs);
} catch (InterruptedException e) {
logger.debug("[IGNORED] Interrupted between heartbeat retries.", e);
}
}
return result;
}
@ -128,21 +122,21 @@ public class KVMHAMonitor extends KVMHABase implements Runnable {
StoragePool storage = conn.storagePoolLookupByUUIDString(uuid);
if (storage == null || storage.getInfo().state != StoragePoolState.VIR_STORAGE_POOL_RUNNING) {
if (storage == null) {
logger.debug(String.format("Libvirt storage pool [%s] not found, removing from HA list.", uuid));
logger.debug("Libvirt storage pool [{}] not found, removing from HA list.", uuid);
} else {
logger.debug(String.format("Libvirt storage pool [%s] found, but not running, removing from HA list.", uuid));
logger.debug("Libvirt storage pool [{}] found, but not running, removing from HA list.", uuid);
}
removedPools.add(uuid);
}
logger.debug(String.format("Found NFS storage pool [%s] in libvirt, continuing.", uuid));
logger.debug("Found NFS storage pool [{}] in libvirt, continuing.", uuid);
} catch (LibvirtException e) {
logger.debug(String.format("Failed to lookup libvirt storage pool [%s].", uuid), e);
logger.debug("Failed to lookup libvirt storage pool [{}].", uuid, e);
if (e.toString().contains("pool not found")) {
logger.debug(String.format("Removing pool [%s] from HA monitor since it was deleted.", uuid));
logger.debug("Removing pool [{}] from HA monitor since it was deleted.", uuid);
removedPools.add(uuid);
}
}
@ -155,11 +149,10 @@ public class KVMHAMonitor extends KVMHABase implements Runnable {
runHeartBeat();
try {
Thread.sleep(_heartBeatUpdateFreq);
Thread.sleep(_heartBeatUpdateFreqInMs);
} catch (InterruptedException e) {
logger.debug("[IGNORED] Interrupted between heartbeats.", e);
}
}
}
}

View File

@ -39,12 +39,12 @@ public class KVMHAVMActivityChecker extends KVMHABase implements Callable<Boolea
}
@Override
public Boolean checkingHeartBeat() {
return this.storagePool.getPool().vmActivityCheck(storagePool, host, activityScriptTimeout, volumeUuidList, vmActivityCheckPath, suspectTimeInSeconds);
public Boolean hasHeartBeat() {
return this.storagePool.getPool().hasVmActivity(storagePool, host, activityScriptTimeout, volumeUuidList, vmActivityCheckPath, suspectTimeInSeconds);
}
@Override
public Boolean call() throws Exception {
return checkingHeartBeat();
return hasHeartBeat();
}
}

View File

@ -124,6 +124,7 @@ import org.libvirt.DomainSnapshot;
import org.libvirt.LibvirtException;
import org.libvirt.MemoryStatistic;
import org.libvirt.Network;
import org.libvirt.SchedLongParameter;
import org.libvirt.SchedParameter;
import org.libvirt.SchedUlongParameter;
import org.libvirt.Secret;
@ -883,6 +884,25 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
SUCCESS, FAILURE, IGNORE
}
public enum CpuSchedulerParameter {
CPU_SHARES("cpu_shares"), PERIOD("vcpu_period"), QUOTA("vcpu_quota");
private String name;
CpuSchedulerParameter(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return getName();
}
}
protected BridgeType bridgeType;
protected StorageSubsystemCommandHandler storageHandler;
@ -1388,9 +1408,9 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
final String[] info = NetUtils.getNetworkParams(privateNic);
kvmhaMonitor = new KVMHAMonitor(null, info[0]);
final Thread ha = new Thread(kvmhaMonitor);
ha.start();
kvmhaMonitor = new KVMHAMonitor(info[0]);
final Thread haMonitorThread = new Thread(kvmhaMonitor);
haMonitorThread.start();
storagePoolManager = new KVMStoragePoolManager(storageLayer, kvmhaMonitor);
@ -2965,25 +2985,63 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
protected void setQuotaAndPeriod(VirtualMachineTO vmTO, CpuTuneDef ctd) {
if (vmTO.isLimitCpuUse() && vmTO.getCpuQuotaPercentage() != null) {
Double cpuQuotaPercentage = vmTO.getCpuQuotaPercentage();
int period = CpuTuneDef.DEFAULT_PERIOD;
int quota = (int) (period * cpuQuotaPercentage);
if (quota < CpuTuneDef.MIN_QUOTA) {
LOGGER.info("Calculated quota (" + quota + ") below the minimum (" + CpuTuneDef.MIN_QUOTA + ") for VM domain " + vmTO.getUuid() + ", setting it to minimum " +
"and calculating period instead of using the default");
quota = CpuTuneDef.MIN_QUOTA;
period = (int) ((double) quota / cpuQuotaPercentage);
if (period > CpuTuneDef.MAX_PERIOD) {
LOGGER.info("Calculated period (" + period + ") exceeds the maximum (" + CpuTuneDef.MAX_PERIOD +
"), setting it to the maximum");
period = CpuTuneDef.MAX_PERIOD;
}
}
ctd.setQuota(quota);
ctd.setPeriod(period);
LOGGER.info("Setting quota=" + quota + ", period=" + period + " to VM domain " + vmTO.getUuid());
Pair<Integer, Long> periodAndQuota = getPeriodAndQuota(cpuQuotaPercentage);
ctd.setPeriod(periodAndQuota.first());
ctd.setQuota(periodAndQuota.second());
LOGGER.info("Setting quota = [{}] and period = [{}] to VM domain [{}].", periodAndQuota.second(), periodAndQuota.first(), vmTO.getUuid());
}
}
/**
* Calculates the CPU period and quota based on the quota percentage defined by the Management Server
* @param cpuQuotaPercentage CPU quota percentage defined by the Management Server
* @return The period and quota to be defined for the VM's domain
*/
protected Pair<Integer, Long> getPeriodAndQuota(double cpuQuotaPercentage) {
int period = CpuTuneDef.DEFAULT_PERIOD;
long quota = (long) (period * cpuQuotaPercentage);
if (quota < CpuTuneDef.MIN_QUOTA) {
LOGGER.info("Calculated quota ({}) below the minimum ({}), setting it to minimum and calculating period instead of using the default", quota, CpuTuneDef.MIN_QUOTA);
quota = CpuTuneDef.MIN_QUOTA;
period = (int) ((double) quota / cpuQuotaPercentage);
if (period > CpuTuneDef.MAX_PERIOD) {
LOGGER.info("Calculated period ({}) exceeds the maximum ({}), setting it to the maximum", period, CpuTuneDef.MAX_PERIOD);
period = CpuTuneDef.MAX_PERIOD;
}
}
LOGGER.info("Calculated period = [{}] and quota = [{}] given the [{}] quota percentage.", period, quota, cpuQuotaPercentage);
return new Pair<>(period, quota);
}
/**
* Dynamically updates the domain's "vcpu_quota" and "period" fields of the CPU tune definition.
* This is required because the values of the fields must change according to the new CPU speed of the VM.
* When the CPU limitation is removed from the domain, the "vcpu_quota" field is set to 17,592,186,044,415.
* @param domain VM's domain.
* @param vmTO VM's transfer object, which contains the required fields to update the "vcpu_quota" and "period" fields.
* @param limitCpuUseChange Indicates whether the CPU limitation for the VM has changed.
* @throws org.libvirt.LibvirtException
**/
public void updateCpuQuotaAndPeriod(Domain domain, VirtualMachineTO vmTO, boolean limitCpuUseChange) throws LibvirtException {
if (hypervisorLibvirtVersion < MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_TUNE || (!limitCpuUseChange && !vmTO.isLimitCpuUse())) {
logger.info("Not updating the [{}] and [{}] for the [{}] domain, because [{}].",
CpuSchedulerParameter.QUOTA, CpuSchedulerParameter.PERIOD, domain.getName(), hypervisorLibvirtVersion < MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_TUNE ?
"the current Libvirt version does not support CPU tune" : "it was not requested to remove, change or apply CPU limitation for the instance.");
return;
}
if (limitCpuUseChange && !vmTO.isLimitCpuUse()) {
logger.info("Updating the [{}] of the [{}] domain to [{}], because CPU limitation has been removed.", CpuSchedulerParameter.QUOTA, domain.getName(), CpuTuneDef.MAX_CPU_QUOTA);
LibvirtComputingResource.setQuota(domain, CpuTuneDef.MAX_CPU_QUOTA);
return;
}
Pair<Integer, Long> periodAndQuota = getPeriodAndQuota(vmTO.getCpuQuotaPercentage());
LibvirtComputingResource.setPeriod(domain, periodAndQuota.first());
LibvirtComputingResource.setQuota(domain, periodAndQuota.second());
}
protected void enlightenWindowsVm(VirtualMachineTO vmTO, FeaturesDef features) {
if (vmTO.getOs().contains("Windows PV")) {
// If OS is Windows PV, then enable the features. Features supported on Windows 2008 and later
@ -3445,10 +3503,10 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
grd.setMemBalloning(!noMemBalloon);
Long maxRam = ByteScaleUtils.bytesToKibibytes(vmTO.getMaxRam());
grd.setMemorySize(maxRam);
grd.setCurrentMem(getCurrentMemAccordingToMemBallooning(vmTO, maxRam));
long requestedRam = ByteScaleUtils.bytesToKibibytes(vmTO.getRequestedRam());
long minRam = ByteScaleUtils.bytesToKibibytes(vmTO.getMinRam());
grd.setCurrentMem(getCurrentMemAccordingToMemBallooning(vmTO, requestedRam, minRam));
grd.setMaxMemory(ByteScaleUtils.bytesToKibibytes(vmTO.getMaxRam()));
int vcpus = vmTO.getCpus();
Integer maxVcpus = vmTO.getVcpuMaxLimit();
@ -3459,18 +3517,19 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
return grd;
}
protected long getCurrentMemAccordingToMemBallooning(VirtualMachineTO vmTO, long maxRam) {
long retVal = maxRam;
protected long getCurrentMemAccordingToMemBallooning(VirtualMachineTO vmTO, long requestedRam, long minRam) {
if (noMemBalloon) {
LOGGER.warn(String.format("Setting VM's [%s] current memory as max memory [%s] due to memory ballooning is disabled. If you are using a custom service offering, verify if memory ballooning really should be disabled.", vmTO.toString(), maxRam));
} else if (vmTO != null && vmTO.getType() != VirtualMachine.Type.User) {
LOGGER.warn(String.format("Setting System VM's [%s] current memory as max memory [%s].", vmTO.toString(), maxRam));
} else {
long minRam = ByteScaleUtils.bytesToKibibytes(vmTO.getMinRam());
LOGGER.debug(String.format("Setting VM's [%s] current memory as min memory [%s] due to memory ballooning is enabled.", vmTO.toString(), minRam));
retVal = minRam;
LOGGER.warn("Setting VM's [{}] current memory as requested memory [{}] due to memory ballooning is disabled.", vmTO.toString(), requestedRam);
return requestedRam;
}
return retVal;
if (vmTO != null && vmTO.getType() != VirtualMachine.Type.User) {
LOGGER.warn("Setting System VM's [{}] current memory as requested memory [{}].", vmTO.toString(), requestedRam);
return requestedRam;
}
LOGGER.debug("Setting VM's [{}] current memory as min memory [{}] due to memory ballooning is enabled.", vmTO.toString(), minRam);
return minRam;
}
/**
@ -6255,29 +6314,59 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
**/
public static Integer getCpuShares(Domain dm) throws LibvirtException {
for (SchedParameter c : dm.getSchedulerParameters()) {
if (c.field.equals("cpu_shares")) {
if (c.field.equals(CpuSchedulerParameter.CPU_SHARES.getName())) {
return Integer.parseInt(c.getValueAsString());
}
}
LOGGER.warn(String.format("Could not get cpu_shares of domain: [%s]. Returning default value of 0. ", dm.getName()));
LOGGER.warn("Could not get [{}] of domain: [{}]. Returning default value of 0. ", CpuSchedulerParameter.CPU_SHARES.getName(), dm.getName());
return 0;
}
/**
* Sets the cpu_shares (priority) of the running VM <br/>
* Updates the cpu_shares (priority) of the running VM.
* @param dm domain of the VM.
* @param cpuShares new priority of the running VM.
* @throws org.libvirt.LibvirtException
**/
public static void setCpuShares(Domain dm, Integer cpuShares) throws LibvirtException {
LOGGER.info("Dynamically updating the [{}] of the [{}] VM to [{}].", CpuSchedulerParameter.CPU_SHARES.getName(), dm.getName(), cpuShares);
SchedUlongParameter[] params = new SchedUlongParameter[1];
params[0] = new SchedUlongParameter();
params[0].field = "cpu_shares";
params[0].field = CpuSchedulerParameter.CPU_SHARES.getName();
params[0].value = cpuShares;
dm.setSchedulerParameters(params);
}
/**
* Updates the period of the running VM.
* @param domain domain of the VM.
* @param period new period of the running VM.
**/
public static void setPeriod(Domain domain, int period) throws LibvirtException {
LOGGER.info("Dynamically updating the [{}] of the [{}] VM to [{}].", CpuSchedulerParameter.PERIOD.getName(), domain.getName(), period);
SchedUlongParameter[] params = new SchedUlongParameter[1];
params[0] = new SchedUlongParameter();
params[0].field = CpuSchedulerParameter.PERIOD.getName();
params[0].value = period;
domain.setSchedulerParameters(params);
}
/**
* Updates the quota of the running VM.
* @param domain domain of the VM.
* @param quota new quota of the running VM.
**/
public static void setQuota(Domain domain, long quota) throws LibvirtException {
LOGGER.info("Dynamically updating the [{}] of the [{}] VM to [{}].", CpuSchedulerParameter.QUOTA.getName(), domain.getName(), quota);
SchedLongParameter[] params = new SchedLongParameter[1];
params[0] = new SchedLongParameter();
params[0].field = CpuSchedulerParameter.QUOTA.getName();
params[0].value = quota;
domain.setSchedulerParameters(params);
}
/**
* Set up a libvirt secret for a volume. If Libvirt says that a secret already exists for this volume path, we use its uuid.
* The UUID of the secret needs to be prescriptive such that we can register the same UUID on target host during live migration

View File

@ -542,7 +542,7 @@ public class LibvirtDomainXMLParser {
final String quota = getTagValue("quota", cpuTuneDefElement);
if (StringUtils.isNotBlank(quota)) {
cpuTuneDef.setQuota((Integer.parseInt(quota)));
cpuTuneDef.setQuota((Long.parseLong(quota)));
}
final String period = getTagValue("period", cpuTuneDefElement);

View File

@ -443,15 +443,15 @@ public class LibvirtVMDef {
}
public static class GuestResourceDef {
private long memory;
private long maxMemory;
private long currentMemory = -1;
private int vcpu = -1;
private int maxVcpu = -1;
private boolean memoryBalloning = false;
private int memoryBalloonStatsPeriod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MEMBALLOON_STATS_PERIOD);
public void setMemorySize(long mem) {
this.memory = mem;
public void setMaxMemory(long mem) {
this.maxMemory = mem;
}
public void setCurrentMem(long currMem) {
@ -484,8 +484,8 @@ public class LibvirtVMDef {
response.append(String.format("<memory>%s</memory>\n", this.currentMemory));
response.append(String.format("<currentMemory>%s</currentMemory>\n", this.currentMemory));
if (this.memory > this.currentMemory) {
response.append(String.format("<maxMemory slots='16' unit='KiB'>%s</maxMemory>\n", this.memory));
if (this.maxMemory > this.currentMemory) {
response.append(String.format("<maxMemory slots='16' unit='KiB'>%s</maxMemory>\n", this.maxMemory));
response.append(String.format("<cpu> <numa> <cell id='0' cpus='0-%s' memory='%s' unit='KiB'/> </numa> </cpu>\n", this.maxVcpu - 1, this.currentMemory));
}
@ -1920,11 +1920,12 @@ public class LibvirtVMDef {
public static class CpuTuneDef {
private int _shares = 0;
private int quota = 0;
private long quota = 0;
private int period = 0;
static final int DEFAULT_PERIOD = 10000;
static final int MIN_QUOTA = 1000;
static final int MAX_PERIOD = 1000000;
public static final long MAX_CPU_QUOTA = 17592186044415L;
public void setShares(int shares) {
_shares = shares;
@ -1934,11 +1935,11 @@ public class LibvirtVMDef {
return _shares;
}
public int getQuota() {
public long getQuota() {
return quota;
}
public void setQuota(int quota) {
public void setQuota(long quota) {
this.quota = quota;
}

View File

@ -26,6 +26,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.CheckOnHostAnswer;
import com.cloud.agent.api.CheckOnHostCommand;
import com.cloud.agent.api.to.HostTO;
import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool;
@ -45,20 +46,21 @@ public final class LibvirtCheckOnHostCommandWrapper extends CommandWrapper<Check
final List<HAStoragePool> pools = monitor.getStoragePools();
final HostTO host = command.getHost();
final KVMHAChecker ha = new KVMHAChecker(pools, host, command.isCheckFailedOnOneStorage());
final KVMHAChecker ha = new KVMHAChecker(pools, host, command.shouldReportIfHeartBeatFailedForOneStoragePool());
final Future<Boolean> future = executors.submit(ha);
try {
final Boolean result = future.get();
if (result) {
return new Answer(command, false, "Heart is beating...");
final Boolean hasHeartBeat = future.get();
if (hasHeartBeat) {
return new CheckOnHostAnswer(command, true, "Heart is beating");
} else {
return new Answer(command);
return new CheckOnHostAnswer(command, "Heart is not beating");
}
} catch (final InterruptedException e) {
return new Answer(command, false, "CheckOnHostCommand: can't get status of host: InterruptedException");
return new CheckOnHostAnswer(command, "CheckOnHostCommand: can't get status of host: InterruptedException");
} catch (final ExecutionException e) {
return new Answer(command, false, "CheckOnHostCommand: can't get status of host: ExecutionException");
return new CheckOnHostAnswer(command, "CheckOnHostCommand: can't get status of host: ExecutionException");
}
}
}

View File

@ -49,8 +49,8 @@ public final class LibvirtCheckVMActivityOnStoragePoolCommandWrapper extends Com
KVMStoragePool primaryPool = storagePoolMgr.getStoragePool(pool.getType(), pool.getUuid());
if (primaryPool.isPoolSupportHA()) {
final HAStoragePool nfspool = monitor.getStoragePool(pool.getUuid());
final KVMHAVMActivityChecker ha = new KVMHAVMActivityChecker(nfspool, command.getHost(), command.getVolumeList(), libvirtComputingResource.getVmActivityCheckPath(), command.getSuspectTimeInSeconds());
final HAStoragePool haPool = monitor.getStoragePool(pool.getUuid());
final KVMHAVMActivityChecker ha = new KVMHAVMActivityChecker(haPool, command.getHost(), command.getVolumeList(), libvirtComputingResource.getVmActivityCheckPath(), command.getSuspectTimeInSeconds());
final Future<Boolean> future = executors.submit(ha);
try {
final Boolean result = future.get();

View File

@ -49,10 +49,11 @@ public class LibvirtScaleVmCommandWrapper extends CommandWrapper<ScaleVmCommand,
conn = libvirtUtilitiesHelper.getConnectionByVmName(vmName);
Domain dm = conn.domainLookupByName(vmName);
logger.debug(String.format("Scaling %s.", scalingDetails));
logger.debug("Scaling {}.", scalingDetails);
scaleMemory(dm, newMemory, vmDefinition);
scaleVcpus(dm, newVcpus, vmDefinition);
updateCpuShares(dm, newCpuShares);
libvirtComputingResource.updateCpuQuotaAndPeriod(dm, vmSpec, command.getLimitCpuUseChange());
return new ScaleVmAnswer(command, true, String.format("Successfully scaled %s.", scalingDetails));
} catch (LibvirtException | CloudRuntimeException e) {
@ -74,7 +75,7 @@ public class LibvirtScaleVmCommandWrapper extends CommandWrapper<ScaleVmCommand,
if (oldCpuShares < newCpuShares) {
LibvirtComputingResource.setCpuShares(dm, newCpuShares);
logger.info(String.format("Successfully increased cpu_shares of VM [%s] from [%s] to [%s].", dm.getName(), oldCpuShares, newCpuShares));
logger.info("Successfully increased cpu_shares of VM [{}] from [{}] to [{}].", dm.getName(), oldCpuShares, newCpuShares);
}
}

View File

@ -208,12 +208,12 @@ public class IscsiAdmStoragePool implements KVMStoragePool {
}
@Override
public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) {
public Boolean hasHeartBeat(HAStoragePool pool, HostTO host) {
return null;
}
@Override
public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) {
public Boolean hasVmActivity(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) {
return null;
}

View File

@ -33,35 +33,33 @@ import com.cloud.storage.Storage.StoragePoolType;
public interface KVMStoragePool {
public static final long HeartBeatUpdateTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HEARTBEAT_UPDATE_TIMEOUT);
public static final long HeartBeatUpdateFreq = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_FREQUENCY);
public static final long HeartBeatUpdateMaxTries = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_MAX_TRIES);
public static final long HeartBeatUpdateRetrySleep = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_RETRY_SLEEP);
public static final long HeartBeatCheckerTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_CHECKER_TIMEOUT);
long HeartBeatUpdateTimeoutInMs = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HEARTBEAT_UPDATE_TIMEOUT);
long HeartBeatUpdateFreqInMs = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_FREQUENCY);
long HeartBeatCheckerTimeoutInMs = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_CHECKER_TIMEOUT);
public default KVMPhysicalDisk createPhysicalDisk(String volumeUuid, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, Long usableSize, byte[] passphrase) {
default KVMPhysicalDisk createPhysicalDisk(String volumeUuid, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, Long usableSize, byte[] passphrase) {
return createPhysicalDisk(volumeUuid, format, provisioningType, size, passphrase);
}
public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase);
KVMPhysicalDisk createPhysicalDisk(String volumeUuid, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase);
public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, Storage.ProvisioningType provisioningType, long size, byte[] passphrase);
KVMPhysicalDisk createPhysicalDisk(String volumeUuid, Storage.ProvisioningType provisioningType, long size, byte[] passphrase);
public boolean connectPhysicalDisk(String volumeUuid, Map<String, String> details);
boolean connectPhysicalDisk(String volumeUuid, Map<String, String> details);
public KVMPhysicalDisk getPhysicalDisk(String volumeUuid);
KVMPhysicalDisk getPhysicalDisk(String volumeUuid);
public boolean disconnectPhysicalDisk(String volumeUuid);
boolean disconnectPhysicalDisk(String volumeUuid);
public boolean deletePhysicalDisk(String volumeUuid, Storage.ImageFormat format);
boolean deletePhysicalDisk(String volumeUuid, Storage.ImageFormat format);
public List<KVMPhysicalDisk> listPhysicalDisks();
List<KVMPhysicalDisk> listPhysicalDisks();
public String getUuid();
String getUuid();
public long getCapacity();
long getCapacity();
public long getUsed();
long getUsed();
default Long getCapacityIops() {
return null;
@ -71,51 +69,51 @@ public interface KVMStoragePool {
return null;
}
public long getAvailable();
long getAvailable();
public boolean refresh();
boolean refresh();
public boolean isExternalSnapshot();
boolean isExternalSnapshot();
public String getLocalPath();
String getLocalPath();
public String getSourceHost();
String getSourceHost();
public String getSourceDir();
String getSourceDir();
public int getSourcePort();
int getSourcePort();
public String getAuthUserName();
String getAuthUserName();
public String getAuthSecret();
String getAuthSecret();
public StoragePoolType getType();
StoragePoolType getType();
public boolean delete();
boolean delete();
PhysicalDiskFormat getDefaultFormat();
public boolean createFolder(String path);
boolean createFolder(String path);
public boolean supportsConfigDriveIso();
boolean supportsConfigDriveIso();
public Map<String, String> getDetails();
Map<String, String> getDetails();
default String getLocalPathFor(String relativePath) {
return String.format("%s%s%s", getLocalPath(), File.separator, relativePath);
}
public boolean isPoolSupportHA();
boolean isPoolSupportHA();
public String getHearthBeatPath();
String getHearthBeatPath();
public String createHeartBeatCommand(HAStoragePool primaryStoragePool, String hostPrivateIp, boolean hostValidation);
String createHeartBeatCommand(HAStoragePool primaryStoragePool, String hostPrivateIp, boolean hostValidation);
public String getStorageNodeId();
String getStorageNodeId();
public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host);
Boolean hasHeartBeat(HAStoragePool pool, HostTO host);
public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration);
Boolean hasVmActivity(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration);
default LibvirtVMDef.DiskDef.BlockIOSize getSupportedLogicalBlockSize() {
return null;

View File

@ -2047,7 +2047,7 @@ public class KVMStorageProcessor implements StorageProcessor {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new CloudRuntimeException(e);
logger.trace("Thread that was tracking the progress for backup of VM [{}] was interrupted. Ignoring.", vmName);
}
}

View File

@ -345,16 +345,14 @@ public class LibvirtStoragePool implements KVMStoragePool {
public String createHeartBeatCommand(HAStoragePool primaryStoragePool, String hostPrivateIp, boolean hostValidation) {
Script cmd = new Script(primaryStoragePool.getPool().getHearthBeatPath(), HeartBeatUpdateTimeout, logger);
Script cmd = new Script(primaryStoragePool.getPool().getHearthBeatPath(), HeartBeatUpdateTimeoutInMs, logger);
cmd.add("-i", primaryStoragePool.getPoolIp());
cmd.add("-p", primaryStoragePool.getPoolMountSourcePath());
cmd.add("-m", primaryStoragePool.getMountDestPath());
if (hostValidation) {
cmd.add("-h", hostPrivateIp);
}
if (!hostValidation) {
} else {
cmd.add("-c");
}
@ -372,53 +370,53 @@ public class LibvirtStoragePool implements KVMStoragePool {
}
@Override
public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) {
boolean validResult = false;
public Boolean hasHeartBeat(HAStoragePool pool, HostTO host) {
String hostIp = host.getPrivateNetwork().getIp();
Script cmd = new Script(getHearthBeatPath(), HeartBeatCheckerTimeout, logger);
Script cmd = new Script(getHearthBeatPath(), HeartBeatCheckerTimeoutInMs, logger);
cmd.add("-i", pool.getPoolIp());
cmd.add("-p", pool.getPoolMountSourcePath());
cmd.add("-m", pool.getMountDestPath());
cmd.add("-h", hostIp);
cmd.add("-r");
cmd.add("-t", String.valueOf(HeartBeatUpdateFreq / 1000));
cmd.add("-t", String.valueOf(HeartBeatUpdateFreqInMs / 1000));
OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
String result = cmd.execute(parser);
String parsedLine = parser.getLine();
logger.debug(String.format("Checking heart beat with KVMHAChecker [{command=\"%s\", result: \"%s\", log: \"%s\", pool: \"%s\"}].", cmd.toString(), result, parsedLine,
pool.getPoolIp()));
logger.debug("Checking heart beat for host IP {} with KVMHAChecker [{command=\"{}\", result: \"{}\", log: \"{}\", pool: \"{}\"}].", hostIp, cmd.toString(), result, parsedLine, pool.getPoolIp());
if (result == null && parsedLine.contains("DEAD")) {
logger.warn(String.format("Checking heart beat with KVMHAChecker command [%s] returned [%s]. [%s]. It may cause a shutdown of host IP [%s].", cmd.toString(),
result, parsedLine, hostIp));
logger.warn("Checking heart beat for host IP {} with KVMHAChecker command [{}] returned [{}]. It may cause a shutdown of the host.", hostIp, cmd.toString(), parsedLine);
return false;
} else {
validResult = true;
logger.debug("Checking heart beat for host IP {} with KVMHAChecker command [{}] succeeded.", hostIp, cmd.toString());
return true;
}
return validResult;
}
@Override
public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) {
public Boolean hasVmActivity(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) {
String hostIp = host.getPrivateNetwork().getIp();
Script cmd = new Script(vmActivityCheckPath, activityScriptTimeout.getStandardSeconds(), logger);
cmd.add("-i", pool.getPoolIp());
cmd.add("-p", pool.getPoolMountSourcePath());
cmd.add("-m", pool.getMountDestPath());
cmd.add("-h", host.getPrivateNetwork().getIp());
cmd.add("-h", hostIp);
cmd.add("-u", volumeUUIDListString);
cmd.add("-t", String.valueOf(String.valueOf(System.currentTimeMillis() / 1000)));
cmd.add("-t", String.valueOf(System.currentTimeMillis() / 1000));
cmd.add("-d", String.valueOf(duration));
OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
String result = cmd.execute(parser);
String parsedLine = parser.getLine();
logger.debug(String.format("Checking heart beat with KVMHAVMActivityChecker [{command=\"%s\", result: \"%s\", log: \"%s\", pool: \"%s\"}].", cmd.toString(), result, parsedLine, pool.getPoolIp()));
logger.debug("Checking VM activity for host IP {} with KVMHAVMActivityChecker [{command=\"{}\", result: \"{}\", log: \"{}\", pool: \"{}\"}].", hostIp, cmd.toString(), result, parsedLine, pool.getPoolIp());
if (result == null && parsedLine.contains("DEAD")) {
logger.warn(String.format("Checking heart beat with KVMHAVMActivityChecker command [%s] returned [%s]. It is [%s]. It may cause a shutdown of host IP [%s].", cmd.toString(), result, parsedLine, host.getPrivateNetwork().getIp()));
logger.warn("Checking VM activity for host IP {} with KVMHAVMActivityChecker command [{}] returned [{}]. It may cause a shutdown of the host.", hostIp, cmd.toString(), parsedLine);
return false;
} else {
logger.debug("Checking VM activity for host IP {} with KVMHAVMActivityChecker command [{}] succeeded.", hostIp, cmd.toString());
return true;
}
}

View File

@ -225,13 +225,13 @@ public class MultipathSCSIPool implements KVMStoragePool {
}
@Override
public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) {
public Boolean hasHeartBeat(HAStoragePool pool, HostTO host) {
return null;
}
@Override
public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout,
String volumeUUIDListString, String vmActivityCheckPath, long duration) {
public Boolean hasVmActivity(HAStoragePool pool, HostTO host, Duration activityScriptTimeout,
String volumeUUIDListString, String vmActivityCheckPath, long duration) {
return null;
}

View File

@ -236,12 +236,12 @@ public class ScaleIOStoragePool implements KVMStoragePool {
}
@Override
public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) {
public Boolean hasHeartBeat(HAStoragePool pool, HostTO host) {
return null;
}
@Override
public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) {
public Boolean hasVmActivity(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) {
return null;
}
}

View File

@ -19,38 +19,37 @@ package org.apache.cloudstack.kvm.ha;
import org.apache.cloudstack.framework.config.ConfigKey;
public class KVMHAConfig {
public interface KVMHAConfig {
public static final ConfigKey<Long> KvmHAHealthCheckTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.health.check.timeout", "10",
ConfigKey<Long> KvmHAHealthCheckTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.health.check.timeout", "10",
"The maximum length of time, in seconds, expected for an health check to complete.", true, ConfigKey.Scope.Cluster);
public static final ConfigKey<Long> KvmHAActivityCheckTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.activity.check.timeout", "60",
ConfigKey<Long> KvmHAActivityCheckTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.activity.check.timeout", "60",
"The maximum length of time, in seconds, expected for an activity check to complete.", true, ConfigKey.Scope.Cluster);
public static final ConfigKey<Long> KvmHAActivityCheckInterval = new ConfigKey<>("Advanced", Long.class, "kvm.ha.activity.check.interval", "60",
ConfigKey<Long> KvmHAActivityCheckInterval = new ConfigKey<>("Advanced", Long.class, "kvm.ha.activity.check.interval", "60",
"The interval, in seconds, between activity checks.", true, ConfigKey.Scope.Cluster);
public static final ConfigKey<Long> KvmHAActivityCheckMaxAttempts = new ConfigKey<>("Advanced", Long.class, "kvm.ha.activity.check.max.attempts", "10",
ConfigKey<Long> KvmHAActivityCheckMaxAttempts = new ConfigKey<>("Advanced", Long.class, "kvm.ha.activity.check.max.attempts", "10",
"The maximum number of activity check attempts to perform before deciding to recover or degrade a resource.", true, ConfigKey.Scope.Cluster);
public static final ConfigKey<Double> KvmHAActivityCheckFailureThreshold = new ConfigKey<>("Advanced", Double.class, "kvm.ha.activity.check.failure.ratio", "0.7",
ConfigKey<Double> KvmHAActivityCheckFailureThreshold = new ConfigKey<>("Advanced", Double.class, "kvm.ha.activity.check.failure.ratio", "0.7",
"The activity check failure threshold ratio. This is used with the activity check maximum attempts for deciding to recover or degrade a resource. For most environments, please keep this value above 0.5.",
true, ConfigKey.Scope.Cluster);
public static final ConfigKey<Long> KvmHADegradedMaxPeriod = new ConfigKey<>("Advanced", Long.class, "kvm.ha.degraded.max.period", "300",
ConfigKey<Long> KvmHADegradedMaxPeriod = new ConfigKey<>("Advanced", Long.class, "kvm.ha.degraded.max.period", "300",
"The maximum length of time, in seconds, a resource can be in degraded state where only health checks are performed.", true, ConfigKey.Scope.Cluster);
public static final ConfigKey<Long> KvmHARecoverTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.recover.timeout", "60",
ConfigKey<Long> KvmHARecoverTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.recover.timeout", "60",
"The maximum length of time, in seconds, expected for a recovery operation to complete.", true, ConfigKey.Scope.Cluster);
public static final ConfigKey<Long> KvmHARecoverWaitPeriod = new ConfigKey<>("Advanced", Long.class, "kvm.ha.recover.wait.period", "600",
ConfigKey<Long> KvmHARecoverWaitPeriod = new ConfigKey<>("Advanced", Long.class, "kvm.ha.recover.wait.period", "600",
"The maximum length of time, in seconds, to wait for a resource to recover.", true, ConfigKey.Scope.Cluster);
public static final ConfigKey<Long> KvmHARecoverAttemptThreshold = new ConfigKey<>("Advanced", Long.class, "kvm.ha.recover.failure.threshold", "1",
ConfigKey<Long> KvmHARecoverAttemptThreshold = new ConfigKey<>("Advanced", Long.class, "kvm.ha.recover.failure.threshold", "1",
"The maximum recovery attempts to be made for a resource, after which the resource is fenced. The recovery counter resets when a health check passes for a resource.",
true, ConfigKey.Scope.Cluster);
public static final ConfigKey<Long> KvmHAFenceTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.fence.timeout", "60",
ConfigKey<Long> KvmHAFenceTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.fence.timeout", "60",
"The maximum length of time, in seconds, expected for a fence operation to complete.", true, ConfigKey.Scope.Cluster);
}

View File

@ -68,17 +68,18 @@ public final class KVMHAProvider extends HAAbstractHostProvider implements HAPro
@Override
public boolean recover(Host r) throws HARecoveryException {
logger.debug("Recover the host {}", r);
try {
if (outOfBandManagementService.isOutOfBandManagementEnabled(r)){
if (outOfBandManagementService.isOutOfBandManagementEnabled(r)) {
final OutOfBandManagementResponse resp = outOfBandManagementService.executePowerOperation(r, PowerOperation.RESET, null);
return resp.getSuccess();
} else {
logger.warn("OOBM recover operation failed for the host {}", r);
return false;
}
} catch (Exception e){
} catch (Exception e) {
logger.warn("OOBM service is not configured or enabled for this host {} error is {}", r, e.getMessage());
throw new HARecoveryException(String.format(" OOBM service is not configured or enabled for this host %s", r), e);
throw new HARecoveryException(String.format("OOBM service is not configured or enabled for this host %s", r), e);
}
}

View File

@ -19,6 +19,7 @@ package org.apache.cloudstack.kvm.ha;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.CheckOnHostAnswer;
import com.cloud.agent.api.CheckOnHostCommand;
import com.cloud.agent.api.CheckVMActivityOnStoragePoolCommand;
import com.cloud.dc.dao.ClusterDao;
@ -61,7 +62,7 @@ public class KVMHostActivityChecker extends AdapterBase implements ActivityCheck
@Inject
private AgentManager agentMgr;
@Inject
private PrimaryDataStoreDao storagePool;
private PrimaryDataStoreDao storagePoolDao;
@Inject
private StorageManager storageManager;
@Inject
@ -70,11 +71,11 @@ public class KVMHostActivityChecker extends AdapterBase implements ActivityCheck
@Override
public boolean isActive(Host r, DateTime suspectTime) throws HACheckerException {
try {
return isVMActivityOnHost(r, suspectTime);
return hasVMActivityOnHost(r, suspectTime);
} catch (HACheckerException e) {
//Re-throwing the exception to avoid poluting the 'HACheckerException' already thrown
//Re-throwing the exception to avoid polluting the 'HACheckerException' already thrown
throw e;
} catch (Exception e){
} catch (Exception e) {
String message = String.format("Operation timed out, probably the %s is not reachable.", r.toString());
logger.warn(message, e);
throw new HACheckerException(message, e);
@ -83,82 +84,115 @@ public class KVMHostActivityChecker extends AdapterBase implements ActivityCheck
@Override
public boolean isHealthy(Host r) {
return isAgentActive(r);
return isHostAgentUp(r);
}
private boolean isAgentActive(Host agent) {
if (agent.getHypervisorType() != Hypervisor.HypervisorType.KVM && agent.getHypervisorType() != Hypervisor.HypervisorType.LXC) {
throw new IllegalStateException(String.format("Calling KVM investigator for non KVM Host of type [%s].", agent.getHypervisorType()));
private boolean isHostAgentUp(Host host) {
if (host.getHypervisorType() != Hypervisor.HypervisorType.KVM && host.getHypervisorType() != Hypervisor.HypervisorType.LXC) {
throw new IllegalStateException(String.format("Calling KVM investigator for non KVM Host of type [%s].", host.getHypervisorType()));
}
Status hostStatus = Status.Unknown;
Status neighbourStatus = Status.Unknown;
final CheckOnHostCommand cmd = new CheckOnHostCommand(agent, HighAvailabilityManager.KvmHAFenceHostIfHeartbeatFailsOnStorage.value());
try {
logger.debug(String.format("Checking %s status...", agent.toString()));
Answer answer = agentMgr.easySend(agent.getId(), cmd);
if (answer != null) {
hostStatus = answer.getResult() ? Status.Down : Status.Up;
logger.debug(String.format("%s has the status [%s].", agent.toString(), hostStatus));
if ( hostStatus == Status.Up ){
return true;
Status hostStatus = getHostAgentStatus(host);
logger.debug("{} has the status [{}].", host.toString(), hostStatus);
return hostStatus == Status.Up;
}
public Status getHostAgentStatus(Host host) {
if (host.getHypervisorType() != Hypervisor.HypervisorType.KVM && host.getHypervisorType() != Hypervisor.HypervisorType.LXC) {
return null;
}
Status hostStatusFromItself = checkHostStatusWithSameHost(host);
if (hostStatusFromItself == Status.Up) {
return Status.Up;
}
Status hostStatusFromNeighbour = checkHostStatusWithNeighbourHosts(host);
Status hostStatus = hostStatusFromItself;
if (hostStatusFromNeighbour == Status.Up && (hostStatusFromItself == Status.Disconnected || hostStatusFromItself == Status.Down)) {
hostStatus = Status.Disconnected;
}
if (hostStatusFromNeighbour == Status.Down && (hostStatusFromItself == Status.Disconnected || hostStatusFromItself == Status.Down)) {
hostStatus = Status.Down;
}
logger.debug("HA: HOST is ineligible legacy state {} for host {}", hostStatus, host);
return hostStatus;
}
private Status checkHostStatusWithSameHost(Host host) {
Status hostStatus;
boolean reportFailureIfOneStorageIsDown = HighAvailabilityManager.KvmHAFenceHostIfHeartbeatFailsOnStorage.value();
final CheckOnHostCommand cmd = new CheckOnHostCommand(host, reportFailureIfOneStorageIsDown);
try {
logger.debug("Checking {} status...", host.toString());
Answer answer = agentMgr.easySend(host.getId(), cmd);
if (answer != null) {
if (answer.getResult()) {
hostStatus = ((CheckOnHostAnswer)answer).isAlive() ? Status.Up : Status.Down;
} else {
logger.debug("{} is not active according to itself, details: {}.", host.toString(), answer.getDetails());
hostStatus = Status.Down;
}
}
else {
logger.debug(String.format("Setting %s to \"Disconnected\" status.", agent.toString()));
logger.debug("{} has the status [{}].", host.toString(), hostStatus);
} else {
logger.debug("Setting {} to \"Disconnected\" status.", host.toString());
hostStatus = Status.Disconnected;
}
} catch (Exception e) {
logger.warn(String.format("Failed to send command CheckOnHostCommand to %s.", agent.toString()), e);
logger.warn("Failed to send command CheckOnHostCommand to {}.", host.toString(), e);
hostStatus = Status.Disconnected;
}
List<HostVO> neighbors = resourceManager.listHostsInClusterByStatus(agent.getClusterId(), Status.Up);
return hostStatus;
}
private Status checkHostStatusWithNeighbourHosts(Host host) {
Status hostStatusFromNeighbour = Status.Unknown;
boolean reportFailureIfOneStorageIsDown = HighAvailabilityManager.KvmHAFenceHostIfHeartbeatFailsOnStorage.value();
final CheckOnHostCommand cmd = new CheckOnHostCommand(host, reportFailureIfOneStorageIsDown);
List<HostVO> neighbors = resourceManager.listHostsInClusterByStatus(host.getClusterId(), Status.Up);
for (HostVO neighbor : neighbors) {
if (neighbor.getId() == agent.getId() || (neighbor.getHypervisorType() != Hypervisor.HypervisorType.KVM && neighbor.getHypervisorType() != Hypervisor.HypervisorType.LXC)) {
if (neighbor.getId() == host.getId()
|| (neighbor.getHypervisorType() != Hypervisor.HypervisorType.KVM && neighbor.getHypervisorType() != Hypervisor.HypervisorType.LXC)) {
continue;
}
try {
logger.debug(String.format("Investigating %s via neighbouring %s.", agent.toString(), neighbor.toString()));
logger.debug("Investigating {} via neighboring {}.", host.toString(), neighbor.toString());
Answer answer = agentMgr.easySend(neighbor.getId(), cmd);
if (answer != null) {
neighbourStatus = answer.getResult() ? Status.Down : Status.Up;
logger.debug(String.format("Neighbouring %s returned status [%s] for the investigated %s.", neighbor.toString(), neighbourStatus, agent.toString()));
if (neighbourStatus == Status.Up) {
break;
if (answer.getResult()) {
hostStatusFromNeighbour = ((CheckOnHostAnswer)answer).isAlive() ? Status.Up : Status.Down;
logger.debug("Neighboring {} returned status [{}] for the investigated {}.", neighbor.toString(), hostStatusFromNeighbour, host.toString());
if (hostStatusFromNeighbour == Status.Up) {
return hostStatusFromNeighbour;
}
} else {
logger.debug("{} is not active according to neighbor {}, details: {}.", host.toString(), neighbor.toString(), answer.getDetails());
}
} else {
logger.debug(String.format("Neighbouring %s is Disconnected.", neighbor.toString()));
logger.debug("Neighboring {} is Disconnected.", neighbor.toString());
}
} catch (Exception e) {
logger.warn(String.format("Failed to send command CheckOnHostCommand to %s.", neighbor.toString()), e);
logger.warn("Failed to send command CheckOnHostCommand to neighbor {}.", neighbor.toString(), e);
}
}
if (neighbourStatus == Status.Up && (hostStatus == Status.Disconnected || hostStatus == Status.Down)) {
hostStatus = Status.Disconnected;
}
if (neighbourStatus == Status.Down && (hostStatus == Status.Disconnected || hostStatus == Status.Down)) {
hostStatus = Status.Down;
}
logger.debug(String.format("%s has the status [%s].", agent.toString(), hostStatus));
return hostStatus == Status.Up;
return hostStatusFromNeighbour;
}
private boolean isVMActivityOnHost(Host agent, DateTime suspectTime) throws HACheckerException {
if (agent.getHypervisorType() != Hypervisor.HypervisorType.KVM && agent.getHypervisorType() != Hypervisor.HypervisorType.LXC) {
throw new IllegalStateException(String.format("Calling KVM investigator for non KVM Host of type [%s].", agent.getHypervisorType()));
private boolean hasVMActivityOnHost(Host host, DateTime suspectTime) throws HACheckerException {
if (host.getHypervisorType() != Hypervisor.HypervisorType.KVM && host.getHypervisorType() != Hypervisor.HypervisorType.LXC) {
throw new IllegalStateException(String.format("Calling KVM investigator for non KVM Host of type [%s].", host.getHypervisorType()));
}
boolean activityStatus = true;
HashMap<StoragePool, List<Volume>> poolVolMap = getVolumeUuidOnHost(agent);
for (StoragePool pool : poolVolMap.keySet()) {
activityStatus = verifyActivityOfStorageOnHost(poolVolMap, pool, agent, suspectTime, activityStatus);
HashMap<StoragePool, List<Volume>> poolVolumeMap = getStoragePoolAndVolumeInfoOnHost(host);
for (StoragePool pool : poolVolumeMap.keySet()) {
activityStatus = verifyActivityOfStorageOnHost(poolVolumeMap, pool, host, suspectTime, activityStatus);
if (!activityStatus) {
logger.warn("It seems that the storage pool [{}] does not have activity on {}.", pool, agent);
logger.warn("It seems that the storage pool [{}] does not have activity on {}.", pool, host);
break;
}
}
@ -166,66 +200,64 @@ public class KVMHostActivityChecker extends AdapterBase implements ActivityCheck
return activityStatus;
}
protected boolean verifyActivityOfStorageOnHost(HashMap<StoragePool, List<Volume>> poolVolMap, StoragePool pool, Host agent, DateTime suspectTime, boolean activityStatus) throws HACheckerException, IllegalStateException {
protected boolean verifyActivityOfStorageOnHost(HashMap<StoragePool, List<Volume>> poolVolMap, StoragePool pool, Host host, DateTime suspectTime, boolean activityStatus) throws HACheckerException, IllegalStateException {
List<Volume> volume_list = poolVolMap.get(pool);
final CheckVMActivityOnStoragePoolCommand cmd = new CheckVMActivityOnStoragePoolCommand(agent, pool, volume_list, suspectTime);
final CheckVMActivityOnStoragePoolCommand cmd = new CheckVMActivityOnStoragePoolCommand(host, pool, volume_list, suspectTime);
logger.debug("Checking VM activity for {} on storage pool [{}].", agent.toString(), pool);
logger.debug("Checking VM activity for {} on storage pool [{}].", host.toString(), pool);
try {
Answer answer = storageManager.sendToPool(pool, getNeighbors(agent), cmd);
Answer answer = storageManager.sendToPool(pool, getNeighbors(host), cmd);
if (answer != null) {
activityStatus = !answer.getResult();
logger.debug("{} {} activity on storage pool [{}]", agent.toString(), activityStatus ? "has" : "does not have", pool);
logger.debug("{} {} activity on storage pool [{}]", host.toString(), activityStatus ? "has" : "does not have", pool);
} else {
String message = String.format("Did not get a valid response for VM activity check for %s on storage pool [%s].", agent.toString(), pool);
String message = String.format("Did not get a valid response for VM activity check for %s on storage pool [%s].", host.toString(), pool);
logger.debug(message);
throw new IllegalStateException(message);
}
} catch (StorageUnavailableException e){
String message = String.format("Storage [%s] is unavailable to do the check, probably the %s is not reachable.", pool, agent);
} catch (StorageUnavailableException e) {
String message = String.format("Storage [%s] is unavailable to do the check, probably the %s is not reachable.", pool, host);
logger.warn(message, e);
throw new HACheckerException(message, e);
}
return activityStatus;
}
private HashMap<StoragePool, List<Volume>> getVolumeUuidOnHost(Host agent) {
List<VMInstanceVO> vm_list = vmInstanceDao.listByHostId(agent.getId());
List<VolumeVO> volume_list = new ArrayList<VolumeVO>();
for (VirtualMachine vm : vm_list) {
private HashMap<StoragePool, List<Volume>> getStoragePoolAndVolumeInfoOnHost(Host host) {
List<VMInstanceVO> vmListOnHost = vmInstanceDao.listByHostId(host.getId());
List<VolumeVO> volumeListOnHost = new ArrayList<>();
for (VirtualMachine vm : vmListOnHost) {
logger.debug("Retrieving volumes of VM [{}]...", vm);
List<VolumeVO> vm_volume_list = volumeDao.findByInstance(vm.getId());
volume_list.addAll(vm_volume_list);
List<VolumeVO> volumeListOfVM = volumeDao.findByInstance(vm.getId());
volumeListOnHost.addAll(volumeListOfVM);
}
HashMap<StoragePool, List<Volume>> poolVolMap = new HashMap<StoragePool, List<Volume>>();
for (Volume vol : volume_list) {
StoragePool sp = storagePool.findById(vol.getPoolId());
logger.debug("Retrieving storage pool [{}] of volume [{}]...", sp, vol);
if (!poolVolMap.containsKey(sp)) {
List<Volume> list = new ArrayList<Volume>();
list.add(vol);
HashMap<StoragePool, List<Volume>> poolVolumeMap = new HashMap<>();
for (Volume volume : volumeListOnHost) {
StoragePool pool = storagePoolDao.findById(volume.getPoolId());
logger.debug("Retrieving storage pool [{}] of volume [{}]...", pool, volume);
if (!poolVolumeMap.containsKey(pool)) {
List<Volume> volList = new ArrayList<>();
volList.add(volume);
poolVolMap.put(sp, list);
poolVolumeMap.put(pool, volList);
} else {
poolVolMap.get(sp).add(vol);
poolVolumeMap.get(pool).add(volume);
}
}
return poolVolMap;
return poolVolumeMap;
}
public long[] getNeighbors(Host agent) {
List<Long> neighbors = new ArrayList<Long>();
List<HostVO> cluster_hosts = resourceManager.listHostsInClusterByStatus(agent.getClusterId(), Status.Up);
logger.debug("Retrieving all \"Up\" hosts from cluster [{}]...", clusterDao.findById(agent.getClusterId()));
for (HostVO host : cluster_hosts) {
if (host.getId() == agent.getId() || (host.getHypervisorType() != Hypervisor.HypervisorType.KVM && host.getHypervisorType() != Hypervisor.HypervisorType.LXC)) {
public long[] getNeighbors(Host host) {
List<Long> neighbors = new ArrayList<>();
List<HostVO> clusterHosts = resourceManager.listHostsInClusterByStatus(host.getClusterId(), Status.Up);
logger.debug("Retrieving all \"Up\" hosts from cluster [{}]...", clusterDao.findById(host.getClusterId()));
for (HostVO clusterHost : clusterHosts) {
if (clusterHost.getId() == host.getId() || (clusterHost.getHypervisorType() != Hypervisor.HypervisorType.KVM && clusterHost.getHypervisorType() != Hypervisor.HypervisorType.LXC)) {
continue;
}
neighbors.add(host.getId());
neighbors.add(clusterHost.getId());
}
return ArrayUtils.toPrimitive(neighbors.toArray(new Long[neighbors.size()]));
}
}

View File

@ -3133,7 +3133,7 @@ public class LibvirtComputingResourceTest {
assertNotNull(wrapper);
final Answer answer = wrapper.execute(command, libvirtComputingResourceMock);
assertTrue(answer.getResult());
assertFalse(answer.getResult());
verify(libvirtComputingResourceMock, times(1)).getMonitor();
}
@ -5642,35 +5642,45 @@ public class LibvirtComputingResourceTest {
Mockito.verify(vmDef, times(1)).addComp(any());
}
public void validateGetCurrentMemAccordingToMemBallooningWithoutMemBalooning(){
@Test
public void getCurrentMemAccordingToMemBallooningTestValidateCurrentMemoryWithoutMemBallooning(){
VirtualMachineTO vmTo = Mockito.mock(VirtualMachineTO.class);
Mockito.when(vmTo.getType()).thenReturn(Type.User);
LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource();
libvirtComputingResource.noMemBalloon = true;
long maxMemory = 2048;
long requestedMemory = 1024 * 1024;
long minMemory = 512 * 1024;
long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, maxMemory);
Assert.assertEquals(maxMemory, currentMemory);
Mockito.verify(vmTo, Mockito.times(0)).getMinRam();
long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, requestedMemory, minMemory);
Assert.assertEquals(requestedMemory, currentMemory);
}
@Test
public void validateGetCurrentMemAccordingToMemBallooningWithtMemBalooning(){
public void getCurrentMemAccordingToMemBallooningTestValidateCurrentMemoryWithMemoryBallooning(){
LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource();
libvirtComputingResource.noMemBalloon = false;
long maxMemory = 2048;
long minMemory = ByteScaleUtils.mebibytesToBytes(64);
VirtualMachineTO vmTo = Mockito.mock(VirtualMachineTO.class);
Mockito.when(vmTo.getType()).thenReturn(Type.User);
Mockito.when(vmTo.getMinRam()).thenReturn(minMemory);
long requestedMemory = 1024 * 1024;
long minMemory = 512 * 1024;
long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, maxMemory);
Assert.assertEquals(ByteScaleUtils.bytesToKibibytes(minMemory), currentMemory);
Mockito.verify(vmTo).getMinRam();
long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, requestedMemory, minMemory);
Assert.assertEquals(minMemory, currentMemory);
}
@Test
public void getCurrentMemAccordingToMemBallooningTestValidateCurrentMemoryForSystemVms() {
LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource();
libvirtComputingResource.noMemBalloon = false;
VirtualMachineTO vmTo = Mockito.mock(VirtualMachineTO.class);
Mockito.when(vmTo.getType()).thenReturn(Type.SecondaryStorageVm);
long requestedMemory = 1024 * 1024;
long minMemory = 512 * 1024;
long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, requestedMemory, minMemory);
Assert.assertEquals(requestedMemory, currentMemory);
}
@Test
public void validateCreateGuestResourceDefWithVcpuMaxLimit(){
LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource();
@ -7234,4 +7244,82 @@ public class LibvirtComputingResourceTest {
libvirtComputingResourceSpy.getInterface(connMock, vmName, invalidMacAddress);
}
@Test
public void updateCpuQuotaAndPeriodTestAssertPeriodAndQuotaAreNotUpdatedWhenLibvirtVersionIsLessThanTheMinimum() throws LibvirtException {
libvirtComputingResourceSpy.hypervisorLibvirtVersion = 8999;
libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, null, false);
Mockito.verify(domainMock, Mockito.never()).setSchedulerParameters(Mockito.any());
}
@Test
public void updateCpuQuotaAndPeriodTestAssertPeriodAndQuotaAreNotUpdatedWhenThereIsNoCapCapChangeAndNoCpuLimitationIsApplied() throws LibvirtException {
Mockito.when(vmTO.isLimitCpuUse()).thenReturn(false);
libvirtComputingResourceSpy.hypervisorLibvirtVersion = 9000;
libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, vmTO, false);
Mockito.verify(domainMock, Mockito.never()).setSchedulerParameters(Mockito.any());
}
@Test
public void updateCpuQuotaAndPeriodTestAssertQuotaIsRemovedWhenThereIsCpuCapChangeAndNoCpuLimitationIsApplied() throws LibvirtException {
Mockito.when(vmTO.isLimitCpuUse()).thenReturn(false);
Mockito.when(domainMock.getName()).thenReturn("i-2-10-VM");
libvirtComputingResourceSpy.hypervisorLibvirtVersion = 9000;
libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, vmTO, true);
Mockito.verify(domainMock, Mockito.times(1)).setSchedulerParameters(Mockito.any());
}
@Test
public void updateCpuQuotaAndPeriodTestAssertPeriodAndQuotaAreUpdatedWhenThereIsNotCpuCapChangeAndCpuLimitationIsApplied() throws LibvirtException {
Mockito.when(vmTO.isLimitCpuUse()).thenReturn(true);
double cpuQuotaPercentage = 0.03;
Mockito.when(vmTO.getCpuQuotaPercentage()).thenReturn(cpuQuotaPercentage);
Mockito.doReturn(new Pair<>(1000, 300L)).when(libvirtComputingResourceSpy).getPeriodAndQuota(cpuQuotaPercentage);
Mockito.when(domainMock.getName()).thenReturn("i-2-10-VM");
libvirtComputingResourceSpy.hypervisorLibvirtVersion = 9000;
libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, vmTO, false);
Mockito.verify(domainMock, Mockito.times(2)).setSchedulerParameters(Mockito.any());
}
@Test
public void updateCpuQuotaAndPeriodTestAssertPeriodAndQuotaAreUpdatedWhenThereIsCpuCapChangeAndCpuLimitationIsApplied() throws LibvirtException {
Mockito.when(vmTO.isLimitCpuUse()).thenReturn(true);
double cpuQuotaPercentage = 0.03;
Mockito.when(vmTO.getCpuQuotaPercentage()).thenReturn(cpuQuotaPercentage);
Mockito.doReturn(new Pair<>(1000, 300L)).when(libvirtComputingResourceSpy).getPeriodAndQuota(cpuQuotaPercentage);
Mockito.when(domainMock.getName()).thenReturn("i-2-10-VM");
libvirtComputingResourceSpy.hypervisorLibvirtVersion = 9000;
libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, vmTO, true);
Mockito.verify(domainMock, Mockito.times(2)).setSchedulerParameters(Mockito.any());
}
@Test
public void getPeriodAndQuotaTestAssertQuotaIsEqualToPeriodMultipliedByQuotaPercentage() {
double cpuQuotaPercentage = 0.3;
int expectedPeriod = CpuTuneDef.DEFAULT_PERIOD;
long expectedQuota = (long) (expectedPeriod * cpuQuotaPercentage);
Pair<Integer, Long> expectedResult = new Pair<>(expectedPeriod, expectedQuota);
Pair<Integer, Long> result = libvirtComputingResourceSpy.getPeriodAndQuota(cpuQuotaPercentage);
Assert.assertEquals(expectedResult, result);
}
@Test
public void getPeriodAndQuotaTestQuotaIsEqualToMinimumWhenRequired() {
double cpuQuotaPercentage = 0.03;
long expectedQuota = CpuTuneDef.MIN_QUOTA;
int expectedPeriod = (int) ((double) expectedQuota / cpuQuotaPercentage);
Pair<Integer, Long> expectedResult = new Pair<>(expectedPeriod, expectedQuota);
Pair<Integer, Long> result = libvirtComputingResourceSpy.getPeriodAndQuota(cpuQuotaPercentage);
Assert.assertEquals(expectedResult, result);
}
@Test
public void getPeriodAndQuotaTestPeriodIsEqualToMaximumWhenRequired() {
double cpuQuotaPercentage = 0.0003;
long expectedQuota = CpuTuneDef.MIN_QUOTA;
int expectedPeriod = CpuTuneDef.MAX_PERIOD;
Pair<Integer, Long> expectedResult = new Pair<>(expectedPeriod, expectedQuota);
Pair<Integer, Long> result = libvirtComputingResourceSpy.getPeriodAndQuota(cpuQuotaPercentage);
Assert.assertEquals(expectedResult, result);
}
}

View File

@ -54,13 +54,13 @@ public class SimulatorInvestigator extends AdapterBase implements Investigator {
}
@Override
public Status isAgentAlive(Host agent) {
public Status getHostAgentStatus(Host agent) {
if (agent.getHypervisorType() != HypervisorType.Simulator) {
return null;
}
if (haManager.isHAEligible(agent)) {
return haManager.getHostStatus(agent);
return haManager.getHostStatusFromHAConfig(agent);
}
CheckOnHostCommand cmd = new CheckOnHostCommand(agent);

View File

@ -28,7 +28,7 @@ public class VmwareInvestigator extends AdapterBase implements Investigator {
}
@Override
public Status isAgentAlive(Host agent) {
public Status getHostAgentStatus(Host agent) {
if (agent.getHypervisorType() == HypervisorType.VMware)
return Status.Disconnected;

View File

@ -124,16 +124,6 @@
<version>${cs.hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>${cs.bcprov.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk15on</artifactId>
<version>${cs.bcprov.version}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>

View File

@ -203,13 +203,13 @@
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.2</version>
<version>${cs.minio.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio-admin</artifactId>
<version>8.5.2</version>
<version>${cs.minio.version}</version>
<scope>compile</scope>
</dependency>
<dependency>

View File

@ -46,12 +46,24 @@
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.2</version>
<version>${cs.minio.version}</version>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio-admin</artifactId>
<version>8.5.2</version>
<version>${cs.minio.version}</version>
</dependency>
<!-- Pin okhttp3 to the version required by minio 8.6.0+, overriding the older
version transitively pulled by influxdb-java -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
</project>

View File

@ -46,12 +46,12 @@
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.2</version>
<version>${cs.minio.version}</version>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio-admin</artifactId>
<version>8.5.2</version>
<version>${cs.minio.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -217,13 +217,14 @@ public class AdaptiveDataStoreLifeCycleImpl extends BasePrimaryDataStoreLifeCycl
// validate the provided details are correct/valid for the provider
api.validate();
// if we have user-provided capacity bytes, validate they do not exceed the manaaged storage capacity bytes
// User-provided capacityBytes always wins; validate against storage stats only when
// the provider could actually report them. If the provider cannot (empty pod with no
// footprint, no quota set, transient probe failure), fall through and use what the
// user supplied rather than failing the whole registration.
ProviderVolumeStorageStats stats = api.getManagedStorageStats();
if (capacityBytes != null && capacityBytes != 0 && stats != null) {
if (stats.getCapacityInBytes() > 0) {
if (stats.getCapacityInBytes() < capacityBytes) {
throw new InvalidParameterValueException("Capacity bytes provided exceeds the capacity of the storage endpoint: provided by user: " + capacityBytes + ", storage capacity from storage provider: " + stats.getCapacityInBytes());
}
if (capacityBytes != null && capacityBytes > 0) {
if (stats != null && stats.getCapacityInBytes() > 0 && stats.getCapacityInBytes() < capacityBytes) {
throw new InvalidParameterValueException("Provided capacity bytes exceed the capacity of the storage endpoint: provided by user: " + capacityBytes + ", storage capacity from storage provider: " + stats.getCapacityInBytes());
}
parameters.setCapacityBytes(capacityBytes);
}

View File

@ -23,7 +23,8 @@ import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@ -88,6 +89,9 @@ public class FlashArrayAdapter implements ProviderAdapter {
static final ObjectMapper mapper = new ObjectMapper();
public String pod = null;
public String hostgroup = null;
private static final DateTimeFormatter DELETION_TIMESTAMP_FORMAT =
DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneOffset.UTC);
private String username;
private String password;
private String accessToken;
@ -200,28 +204,63 @@ public class FlashArrayAdapter implements ProviderAdapter {
@Override
public void delete(ProviderAdapterContext context, ProviderAdapterDataObject dataObject) {
// first make sure we are disconnected
removeVlunsAll(context, pod, dataObject.getExternalName());
String fullName = normalizeName(pod, dataObject.getExternalName());
FlashArrayVolume volume = new FlashArrayVolume();
// Snapshots live under /volume-snapshots and already use the array's
// reserved form <volume>.<suffix>, which legitimately contains ".".
// The stricter [A-Za-z0-9_-] naming rule applies to regular volume
// names and free-form rename targets, not to these reserved snapshot
// names. Since FlashArray snapshot names are system-defined rather
// than arbitrary rename targets, we skip the usual timestamped rename
// and only mark snapshots destroyed; the array's own ".N" suffix
// already disambiguates them in the recycle bin.
if (dataObject.getType() == ProviderAdapterDataObject.Type.SNAPSHOT) {
try {
FlashArrayVolume destroy = new FlashArrayVolume();
destroy.setDestroyed(true);
PATCH("/volume-snapshots?names=" + fullName, destroy, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
});
} catch (CloudRuntimeException e) {
String msg = e.getMessage();
if (msg != null && (msg.contains("No such volume or snapshot")
|| msg.contains("Volume does not exist"))) {
return;
}
throw e;
}
return;
}
// rename as we delete so it doesn't conflict if the template or volume is ever recreated
// pure keeps the volume(s) around in a Destroyed bucket for a period of time post delete
String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new java.util.Date());
volume.setExternalName(fullName + "-" + timestamp);
// first make sure we are disconnected
removeVlunsAll(context, pod, dataObject.getExternalName());
// Rename then destroy: FlashArray keeps destroyed volumes in a recycle
// bin (default 24h) from which they can be recovered. Renaming with a
// deletion timestamp gives operators a forensic trail when browsing the
// array - they can see when each destroyed copy was deleted on the
// CloudStack side. FlashArray rejects a single PATCH that combines
// {name, destroyed}, so the rename and the destroy must be issued as
// two separate requests each carrying only its own field.
// Use UTC so the rename suffix is stable regardless of the management
// server's local timezone or DST changes - operators correlating the
// CloudStack delete event with the array's audit log get a consistent
// wall-clock value.
String timestamp = DELETION_TIMESTAMP_FORMAT.format(java.time.Instant.now());
String renamedName = fullName + "-" + timestamp;
try {
PATCH("/volumes?names=" + fullName, volume, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
FlashArrayVolume rename = new FlashArrayVolume();
rename.setExternalName(renamedName);
PATCH("/volumes?names=" + fullName, rename, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
});
// now delete it with new name
volume.setDestroyed(true);
PATCH("/volumes?names=" + fullName + "-" + timestamp, volume, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
FlashArrayVolume destroy = new FlashArrayVolume();
destroy.setDestroyed(true);
PATCH("/volumes?names=" + renamedName, destroy, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
});
} catch (CloudRuntimeException e) {
if (e.toString().contains("Volume does not exist")) {
String msg = e.getMessage();
if (msg != null && msg.contains("Volume does not exist")) {
return;
} else {
throw e;

View File

@ -514,6 +514,17 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
ApiCallRcList answers = api.resourceDefinitionDelete(rd.getName());
checkLinstorAnswersThrow(answers);
deleted = true;
// LINSTOR can return success here while the resource lingers in DELETING state
// on the controller (down peer, lost quorum, etc.). Confirm it's actually gone
// if not, log a WARN so operators can clear it manually. Don't throw: the
// CloudStack-side accounting has already moved on.
if (!LinstorUtil.waitForResourceDefinitionDeleted(api, rd.getName(),
LinstorUtil.DEFAULT_RD_DELETE_VERIFY_TIMEOUT_MILLIS)) {
logger.warn("Linstor: resource {} still present {}ms after delete returned success — " +
"may be stuck in DELETING. Check the LINSTOR controller (linstor resource list).",
rd.getName(), LinstorUtil.DEFAULT_RD_DELETE_VERIFY_TIMEOUT_MILLIS);
}
}
}
return deleted;
@ -585,8 +596,8 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
Path propFile = diskPath.getParent().resolve("template.properties");
if (Files.exists(propFile)) {
java.util.Properties templateProps = new java.util.Properties();
try {
templateProps.load(new FileInputStream(propFile.toFile()));
try (FileInputStream in = new FileInputStream(propFile.toFile())) {
templateProps.load(in);
String desc = templateProps.getProperty("description");
if (desc != null && desc.startsWith("SystemVM Template")) {
return true;

View File

@ -228,11 +228,11 @@ public class LinstorStoragePool implements KVMStoragePool {
public String createHeartBeatCommand(HAStoragePool pool, String hostPrivateIp,
boolean hostValidation) {
LOGGER.trace(String.format("Linstor.createHeartBeatCommand: %s, %s, %b", pool.getPoolIp(), hostPrivateIp, hostValidation));
boolean isStorageNodeUp = checkingHeartBeat(pool, null);
boolean isStorageNodeUp = hasHeartBeat(pool, null);
if (!isStorageNodeUp && !hostValidation) {
//restart the host
LOGGER.debug(String.format("The host [%s] will be restarted because the health check failed for the storage pool [%s]", hostPrivateIp, pool.getPool().getType()));
Script cmd = new Script(pool.getPool().getHearthBeatPath(), Duration.millis(HeartBeatUpdateTimeout), LOGGER);
Script cmd = new Script(pool.getPool().getHearthBeatPath(), Duration.millis(HeartBeatUpdateTimeoutInMs), LOGGER);
cmd.add("-c");
cmd.execute();
return "Down";
@ -258,7 +258,7 @@ public class LinstorStoragePool implements KVMStoragePool {
}
@Override
public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) {
public Boolean hasHeartBeat(HAStoragePool pool, HostTO host) {
String hostName;
if (host == null) {
hostName = localNodeName;
@ -274,7 +274,7 @@ public class LinstorStoragePool implements KVMStoragePool {
}
private String executeDrbdSetupStatus(OutputInterpreter.AllLinesParser parser) {
Script sc = new Script("drbdsetup", Duration.millis(HeartBeatUpdateTimeout), LOGGER);
Script sc = new Script("drbdsetup", Duration.millis(HeartBeatUpdateTimeoutInMs), LOGGER);
sc.add("status");
sc.add("--json");
return sc.execute(parser);
@ -329,7 +329,7 @@ public class LinstorStoragePool implements KVMStoragePool {
}
private String executeDrbdEventsNow(OutputInterpreter.AllLinesParser parser) {
Script sc = new Script("drbdsetup", Duration.millis(HeartBeatUpdateTimeout), LOGGER);
Script sc = new Script("drbdsetup", Duration.millis(HeartBeatUpdateTimeoutInMs), LOGGER);
sc.add("events2");
sc.add("--now");
return sc.execute(parser);
@ -369,8 +369,8 @@ public class LinstorStoragePool implements KVMStoragePool {
}
@Override
public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) {
public Boolean hasVmActivity(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) {
LOGGER.trace(String.format("Linstor.vmActivityCheck: %s, %s", pool.getPoolIp(), host.getPrivateNetwork().getIp()));
return checkingHeartBeat(pool, host);
return hasHeartBeat(pool, host);
}
}

View File

@ -232,6 +232,20 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
}
logger.info("Linstor: Deleted resource {}", rscDefName);
// LINSTOR can return success on the delete API call while the resource lingers in
// DELETING state (peer issues, lost quorum, satellite down). Verify the resource is
// actually gone if not, log a WARN so operators see it. We deliberately do NOT
// throw here: the volume is already considered gone on the CloudStack side, and
// throwing would leave the CS DB and LINSTOR in different states.
if (!LinstorUtil.waitForResourceDefinitionDeleted(linstorApi, rscDefName,
LinstorUtil.DEFAULT_RD_DELETE_VERIFY_TIMEOUT_MILLIS))
{
logger.warn("Linstor: resource {} still present {}ms after delete returned success — " +
"may be stuck in DELETING. Check the LINSTOR controller (linstor resource list) " +
"and clear manually if the resource has no live peers.",
rscDefName, LinstorUtil.DEFAULT_RD_DELETE_VERIFY_TIMEOUT_MILLIS);
}
} catch (ApiException apiEx)
{
logger.error("Linstor: ApiEx - " + apiEx.getMessage());

View File

@ -401,6 +401,58 @@ public class LinstorUtil {
.collect(Collectors.toList());
}
/**
* Default per-call timeout for {@link #waitForResourceDefinitionDeleted}. Long enough for a
* healthy LINSTOR controller to finish a normal delete; short enough not to block the calling
* thread for too long if the delete is genuinely stuck. Used both from the management server
* (e.g. {@code LinstorPrimaryDataStoreDriverImpl}) and from KVM agent paths.
*/
public static final long DEFAULT_RD_DELETE_VERIFY_TIMEOUT_MILLIS = 30_000L;
/**
* Returns {@code true} if the named resource definition is no longer present on the LINSTOR
* controller. Used after a {@code resourceDefinitionDelete} to verify the delete actually
* completed (LINSTOR can return success on the API call while the resource lingers in
* DELETING state due to peer issues, lost quorum, or down satellites). Uses the
* controller-side name filter rather than scanning every RD on the cluster (cheap even
* when polled once per second from {@link #waitForResourceDefinitionDeleted}).
*/
public static boolean isResourceDefinitionGone(DevelopersApi api, String rscName) throws ApiException {
List<ResourceDefinition> matching =
api.resourceDefinitionList(Collections.singletonList(rscName), false, null, null, null);
return matching == null || matching.isEmpty();
}
/**
* Polls the controller until the named resource definition is gone or the timeout elapses.
* Returns {@code true} if the resource was confirmed gone, {@code false} if it was still
* present (or the controller kept erroring) at the deadline. Callers should NOT throw on a
* {@code false} return the upstream API call already reported success and the operator
* may need to investigate manually. Log a WARN with the resource name instead.
*/
public static boolean waitForResourceDefinitionDeleted(DevelopersApi api, String rscName, long timeoutMillis) {
final long deadline = System.currentTimeMillis() + timeoutMillis;
while (true) {
try {
if (isResourceDefinitionGone(api, rscName)) {
return true;
}
} catch (ApiException e) {
LOGGER.debug("LINSTOR delete-verify poll failed for {}: {}", rscName, e.getMessage());
// Keep polling controller may be transiently unavailable.
}
if (System.currentTimeMillis() >= deadline) {
return false;
}
try {
Thread.sleep(1_000L);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return false;
}
}
}
/**
* Returns a pair list of resource-definitions with ther 1:1 mapped resource-group objects that start with the
* resource name `startWith`

View File

@ -314,6 +314,58 @@ public class LinstorDataMotionStrategy implements DataMotionStrategy {
return true;
}
/**
* Verify that the destination KVM host is a registered LINSTOR satellite on the controller
* backing every destination pool involved in this migration. Throws CloudRuntimeException
* with a clear message when it isn't, instead of letting the resource creation later fail
* obscurely inside auto-placement.
*
* Best-effort: a transient controller error during this check does not block the migration
* we log a warning and let the downstream resource-create surface the real issue. Only a
* confirmed "host not in node list" outcome aborts the migration up-front.
*/
private void verifyDestinationIsLinstorSatellite(Map<VolumeInfo, DataStore> volumeDataStoreMap, Host destHost) {
if (destHost == null || destHost.getName() == null) {
// Without a destination host name to match, the only sensible thing is to let the
// existing flow run and report whatever it would have reported.
return;
}
for (Map.Entry<VolumeInfo, DataStore> entry : volumeDataStoreMap.entrySet()) {
DataStore destDataStore = entry.getValue();
StoragePoolVO destStoragePool = _storagePool.findById(destDataStore.getId());
if (destStoragePool == null
|| destStoragePool.getPoolType() != Storage.StoragePoolType.Linstor) {
continue;
}
DevelopersApi api = LinstorUtil.getLinstorAPI(destStoragePool.getHostAddress());
try {
List<String> nodes = LinstorUtil.getLinstorNodeNames(api);
if (nodes == null) {
logger.warn("LINSTOR controller {} returned null node list; skipping pre-flight",
destStoragePool.getHostAddress());
return;
}
if (!nodes.contains(destHost.getName())) {
throw new CloudRuntimeException(String.format(
"Cannot migrate to host '%s': it is not a registered LINSTOR satellite on " +
"controller %s (pool '%s'). Known satellites: %s. Either register the " +
"host with `linstor node create` or pick a different destination.",
destHost.getName(),
destStoragePool.getHostAddress(),
destStoragePool.getName(),
nodes));
}
} catch (ApiException apiEx) {
// Don't block migration on a transient controller hiccup log and let the
// downstream resource creation handle the real failure.
logger.warn("LINSTOR pre-flight check could not contact controller {}: {}; " +
"letting downstream resource creation proceed",
destStoragePool.getHostAddress(), apiEx.getBestMessage());
return;
}
}
}
@Override
public void copyAsync(Map<VolumeInfo, DataStore> volumeDataStoreMap, VirtualMachineTO vmTO, Host srcHost,
Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
@ -323,6 +375,15 @@ public class LinstorDataMotionStrategy implements DataMotionStrategy {
String.format("Invalid hypervisor type [%s]. Only KVM supported", srcHost.getHypervisorType()));
}
// Pre-flight: verify the destination KVM host is registered as a satellite on the
// LINSTOR controller backing each destination pool. Without this check, resource
// creation falls through to the resource-group's auto-placement filters and may
// either silently place the resource on the wrong node or fail with an opaque
// auto-place error from the LINSTOR API. Failing fast here gives operators a clear
// actionable message instead of having to correlate the live-migration failure with
// an unrelated LINSTOR controller log entry.
verifyDestinationIsLinstorSatellite(volumeDataStoreMap, destHost);
String errMsg = null;
VMInstanceVO vmInstance = _vmDao.findById(vmTO.getId());
vmTO.setState(vmInstance.getState());

View File

@ -198,11 +198,11 @@ public class StorPoolStoragePool implements KVMStoragePool {
@Override
public String createHeartBeatCommand(HAStoragePool primaryStoragePool, String hostPrivateIp, boolean hostValidation) {
boolean isStorageNodeUp = checkingHeartBeat(primaryStoragePool, null);
boolean isStorageNodeUp = hasHeartBeat(primaryStoragePool, null);
if (!isStorageNodeUp && !hostValidation) {
//restart the host
logger.debug(String.format("The host [%s] will be restarted because the health check failed for the storage pool [%s]", hostPrivateIp, primaryStoragePool.getPool().getType()));
Script cmd = new Script(primaryStoragePool.getPool().getHearthBeatPath(), HeartBeatUpdateTimeout, logger);
Script cmd = new Script(primaryStoragePool.getPool().getHearthBeatPath(), HeartBeatUpdateTimeoutInMs, logger);
cmd.add("-c");
cmd.execute();
return "Down";
@ -240,7 +240,7 @@ public class StorPoolStoragePool implements KVMStoragePool {
}
@Override
public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) {
public Boolean hasHeartBeat(HAStoragePool pool, HostTO host) {
boolean isNodeWorking = false;
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
@ -300,8 +300,8 @@ public class StorPoolStoragePool implements KVMStoragePool {
}
@Override
public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUuidListString, String vmActivityCheckPath, long duration) {
return checkingHeartBeat(pool, host);
public Boolean hasVmActivity(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUuidListString, String vmActivityCheckPath, long duration) {
return hasHeartBeat(pool, host);
}
@Override

View File

@ -172,6 +172,18 @@
<groupId>org.apache.directory.shared</groupId>
<artifactId>shared-ldap-schema</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcutil-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -191,12 +203,40 @@
<artifactId>apacheds-core</artifactId>
<version>${ads.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcutil-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-protocol-ldap</artifactId>
<version>${ads.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcutil-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>

23
pom.xml
View File

@ -132,7 +132,7 @@
<cs.axiom.version>1.2.8</cs.axiom.version>
<cs.axis2.version>1.6.4</cs.axis2.version>
<cs.batik.version>1.14</cs.batik.version>
<cs.bcprov.version>1.70</cs.bcprov.version>
<cs.bcprov.version>1.83</cs.bcprov.version>
<cs.cglib.version>3.3.0</cs.cglib.version>
<cs.checkstyle-lib.version>8.18</cs.checkstyle-lib.version>
<cs.cron-utils.version>9.2.0</cs.cron-utils.version>
@ -170,7 +170,7 @@
<cs.libvirt-java.version>0.5.3</cs.libvirt-java.version>
<cs.mail.version>1.5.0-b01</cs.mail.version>
<cs.mustache.version>0.9.14</cs.mustache.version>
<cs.mysql.version>8.0.33</cs.mysql.version>
<cs.mysql.version>8.4.0</cs.mysql.version>
<cs.neethi.version>2.0.4</cs.neethi.version>
<cs.nitro.version>10.1</cs.nitro.version>
<cs.opensaml.version>2.6.6</cs.opensaml.version>
@ -191,6 +191,7 @@
<cs.ini.version>0.5.4</cs.ini.version>
<cs.caffeine.version>3.1.7</cs.caffeine.version>
<cs.protobuf.version>3.25.5</cs.protobuf.version>
<cs.minio.version>8.6.0</cs.minio.version>
</properties>
<distributionManagement>
@ -583,17 +584,17 @@
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>${cs.bcprov.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${cs.bcprov.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk15on</artifactId>
<artifactId>bctls-jdk18on</artifactId>
<version>${cs.bcprov.version}</version>
</dependency>
<dependency>
@ -668,6 +669,18 @@
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcutil-jdk15on</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>

View File

@ -70,8 +70,8 @@ elif [ ! -f "$CACERT_FILE" ]; then
fi
# Import cacerts into the keystore
awk '/-----BEGIN CERTIFICATE-----?/{n++}{print > "cloudca." n }' "$CACERT_FILE"
for caChain in $(ls cloudca.*); do
awk 'BEGIN{n=0} /-----BEGIN CERTIFICATE-----/{n++} n>0{print > "cloudca." n }' "$CACERT_FILE"
for caChain in $(ls cloudca.* 2>/dev/null); do
keytool -delete -noprompt -alias "$caChain" -keystore "$KS_FILE" -storepass "$KS_PASS" > /dev/null 2>&1 || true
keytool -import -noprompt -storepass "$KS_PASS" -trustcacerts -alias "$caChain" -file "$caChain" -keystore "$KS_FILE" > /dev/null 2>&1
done
@ -137,6 +137,22 @@ if [ -f "$SYSTEM_FILE" ]; then
chmod 644 /usr/local/share/ca-certificates/cloudstack/ca.crt
update-ca-certificates > /dev/null 2>&1 || true
# Import CA cert(s) into realhostip.keystore so the SSVM JVM
# (which overrides the truststore via -Djavax.net.ssl.trustStore in _run.sh)
# can trust servers signed by the CloudStack CA
REALHOSTIP_KS_FILE="$(dirname "$(dirname "$PROPS_FILE")")/certs/realhostip.keystore"
REALHOSTIP_PASS="vmops.com"
if [ -f "$REALHOSTIP_KS_FILE" ]; then
awk 'BEGIN{n=0} /-----BEGIN CERTIFICATE-----/{n++} n>0{print > "cloudca." n }' "$CACERT_FILE"
for caChain in $(ls cloudca.* 2>/dev/null); do
keytool -delete -noprompt -alias "$caChain" -keystore "$REALHOSTIP_KS_FILE" \
-storepass "$REALHOSTIP_PASS" > /dev/null 2>&1 || true
keytool -import -noprompt -trustcacerts -alias "$caChain" -file "$caChain" \
-keystore "$REALHOSTIP_KS_FILE" -storepass "$REALHOSTIP_PASS" > /dev/null 2>&1
done
rm -f cloudca.*
fi
# Ensure cloud service is running in systemvm
if [ "$MODE" == "ssh" ]; then
systemctl start cloud > /dev/null 2>&1

View File

@ -75,7 +75,7 @@ fi
#delete VMs on this mountpoint
deleteVMs() {
local mountPoint=$1
vmPids=$(ps aux| grep qemu | grep "$mountPoint" | awk '{print $2}' 2> /dev/null)
vmPids=$(ps aux | grep qemu | grep "$mountPoint" | awk '{print $2}' 2> /dev/null)
if [ $? -gt 0 ]
then
return
@ -93,7 +93,7 @@ deleteVMs() {
}
#checking is there the same nfs server mounted under $MountPoint?
mounts=$(cat /proc/mounts |grep nfs|grep $MountPoint)
mounts=$(cat /proc/mounts | grep nfs | grep $MountPoint)
if [ $? -gt 0 ]
then
# remount it

View File

@ -5034,16 +5034,13 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
ex.addProxyObject(template.getUuid(), "templateId");
throw ex;
}
if (!template.isPublicTemplate() && caller.getType() == Account.Type.DOMAIN_ADMIN) {
Account template_acc = accountMgr.getAccount(template.getAccountId());
DomainVO domain = _domainDao.findById(template_acc.getDomainId());
accountMgr.checkAccess(caller, domain);
}
// if template is not public, perform permission check here
else if (!template.isPublicTemplate() && caller.getType() != Account.Type.ADMIN) {
accountMgr.checkAccess(caller, null, false, template);
} else if (template.isPublicTemplate()) {
} else if (template.isPublicTemplate() || caller.getType() != Account.Type.ADMIN) {
// if template is not public or non-admin caller, perform permission check here
accountMgr.checkAccess(caller, null, false, template);
}

View File

@ -1214,6 +1214,6 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager,
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {CpuOverprovisioningFactor, MemOverprovisioningFactor, StorageCapacityDisableThreshold, StorageOverprovisioningFactor,
StorageAllocatedCapacityDisableThreshold, StorageOperationsExcludeCluster, ImageStoreNFSVersion, SecondaryStorageCapacityThreshold,
StorageAllocatedCapacityDisableThresholdForVolumeSize, CapacityCalculateWorkers };
StorageAllocatedCapacityDisableThresholdForVolumeSize, CapacityCalculateWorkers, KvmMemoryDynamicScalingCapacity, KvmCpuDynamicScalingCapacity };
}
}

View File

@ -38,7 +38,7 @@ public class CheckOnAgentInvestigator extends AdapterBase implements Investigato
}
@Override
public Status isAgentAlive(Host agent) {
public Status getHostAgentStatus(Host agent) {
return null;
}

View File

@ -42,6 +42,9 @@ import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.ha.HAConfig;
import org.apache.cloudstack.ha.HAResource;
import org.apache.cloudstack.ha.dao.HAConfigDao;
import org.apache.cloudstack.managed.context.ManagedContext;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.management.ManagementServerHost;
@ -223,6 +226,8 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
@Inject
ConfigurationDao _configDao;
@Inject
HAConfigDao _haConfigDao;
@Inject
VolumeOrchestrationService volumeMgr;
String _instance;
@ -237,25 +242,53 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
long _timeBetweenCleanups;
String _haTag = null;
protected HighAvailabilityManagerImpl() {
}
private boolean vmHasPendingHAJob(final List<HaWorkVO> pendingHaWorks, final VMInstanceVO vm) {
Optional<HaWorkVO> item = pendingHaWorks.stream()
.filter(h -> h.getInstanceId() == vm.getId())
.reduce((first, second) -> second);
if (item.isPresent() && (item.get().getTimesTried() < _maxRetries ||
!item.get().canScheduleNew(_timeBetweenFailures))) {
logger.debug(String.format("Skipping HA on %s as there is already a running HA job for it", vm));
logger.debug("Skipping HA on {} as there is already a running HA job for it", vm);
return true;
}
return false;
}
protected HighAvailabilityManagerImpl() {
private boolean isHostHAInspectionInProgress(long hostId) {
final HAConfig haConfig = _haConfigDao.findHAResource(hostId, HAResource.ResourceType.Host);
if (haConfig == null || !haConfig.isEnabled()) {
return false;
}
HAConfig.HAState state = haConfig.getState();
logger.debug("Checking Host HA inspection is in progress or not for the host {} from HAConfig, HA state is {}", hostId, state);
if (state == HAConfig.HAState.Suspect || state == HAConfig.HAState.Checking) {
return true;
}
if (state == HAConfig.HAState.Recovered || state == HAConfig.HAState.Available) {
// If the host HA state is Recovered, it indicates that the host has restarted successfully.
// If the host HA state is Available, it means the host has restarted successfully and the recovery waiting period has completed.
// In both states, the agent can connect as soon as the host is ready (and can move to Suspect -> Checking HA state if the agent connection fails again before Fencing).
final HostVO host = _hostDao.findById(hostId);
if (host != null && host.getStatus() != Status.Up) {
logger.debug("{} is in {} status and HA state is {}, considering Host HA inspection is still in progress" +
" until we are sure the host is ready after a recovery wait period and agent is connected/Up", host, host.getStatus(), state);
return true;
}
}
return false;
}
@Override
public Status investigate(final long hostId) {
final HostVO host = _hostDao.findById(hostId);
if (host == null) {
logger.warn("Host with id {} is removed or doesn't exists.", hostId);
return Status.Alert;
}
@ -270,7 +303,7 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
Status hostState = null;
for (Investigator investigator : investigators) {
hostState = investigator.isAgentAlive(host);
hostState = investigator.getHostAgentStatus(host);
if (hostState != null) {
if (logger.isDebugEnabled()) {
logger.debug("{} was able to determine host {} is in {}", investigator.getName(), host, hostState.toString());
@ -278,7 +311,7 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
return hostState;
}
if (logger.isDebugEnabled()) {
logger.debug(investigator.getName() + " unable to determine the state of the host. Moving on.");
logger.debug("{} unable to determine the state of the host. Moving on.", investigator.getName());
}
}
@ -570,9 +603,9 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
}
protected Long restart(final HaWorkVO work) {
logger.debug("RESTART with HAWORK");
logger.debug("RESTART with HA WORK");
List<HaWorkVO> items = _haDao.listFutureHaWorkForVm(work.getInstanceId(), work.getId());
if (items.size() > 0) {
if (!items.isEmpty()) {
StringBuilder str = new StringBuilder("Cancelling this work item because newer ones have been scheduled. Work Ids = [");
for (HaWorkVO item : items) {
str.append(item.getId()).append(", ");
@ -583,7 +616,7 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
}
items = _haDao.listRunningHaWorkForVm(work.getInstanceId());
if (items.size() > 0) {
if (!items.isEmpty()) {
StringBuilder str = new StringBuilder("Waiting because there's HA work being executed on an item currently. Work Ids =[");
for (HaWorkVO item : items) {
str.append(item.getId()).append(", ");
@ -597,21 +630,21 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
VirtualMachine vm = _itMgr.findById(work.getInstanceId());
if (vm == null) {
logger.info("Unable to find vm: " + vmId);
logger.info("Unable to find vm: {}", vmId);
return null;
}
if (checkAndCancelWorkIfNeeded(work)) {
return null;
}
logger.info("HA on " + vm);
logger.info("HA on {}", vm);
if (vm.getState() != work.getPreviousState() || vm.getUpdated() != work.getUpdateTime()) {
logger.info("VM " + vm + " has been changed. Current State = " + vm.getState() + " Previous State = " + work.getPreviousState() + " last updated = " +
vm.getUpdated() + " previous updated = " + work.getUpdateTime());
logger.info("VM {} has been changed. Current State = {} Previous State = {} last updated = {} previous updated = {}",
vm, vm.getState(), work.getPreviousState(), vm.getUpdated(), work.getUpdateTime());
return null;
}
if (vm.getHostId() != null && !vm.getHostId().equals(work.getHostId())) {
logger.info("VM " + vm + " has been changed. Current host id = " + vm.getHostId() + " Previous host id = " + work.getHostId());
logger.info("VM {} has been changed. Current host id = {} Previous host id = {}", vm, vm.getHostId(), work.getHostId());
return null;
}
@ -628,10 +661,13 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
boolean isHostRemoved = false;
if (host == null) {
host = _hostDao.findByIdIncludingRemoved(work.getHostId());
if (host != null) {
logger.debug("VM {} is now no longer on host {} as the host is removed", vm, host);
isHostRemoved = true;
if (host == null) {
logger.debug("VM {} is now no longer on host {}, the host doesn't exist", vm, work.getHostId());
return null;
}
logger.debug("VM {} is now no longer on host {} as the host is removed", vm, host);
isHostRemoved = true;
}
DataCenterVO dcVO = _dcDao.findById(host.getDataCenterId());
@ -652,40 +688,39 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
try
{
alive = investigator.isVmAlive(vm, host);
logger.info(investigator.getName() + " found " + vm + " to be alive? " + alive);
logger.info("{} found {} to be alive? {}", investigator.getName(), vm, alive);
break;
} catch (UnknownVM e) {
logger.info(investigator.getName() + " could not find " + vm);
logger.info("{} could not find {}", investigator.getName(), vm);
}
}
boolean fenced = false;
if (alive == null) {
logger.debug("Fencing off VM that we don't know the state of");
logger.debug("Fencing off VM {} that we don't know the state of", vm);
for (FenceBuilder fb : fenceBuilders) {
Boolean result = fb.fenceOff(vm, host);
logger.info("Fencer " + fb.getName() + " returned " + result);
logger.info("Fencer {} returned {}", fb.getName(), result);
if (result != null && result) {
fenced = true;
break;
}
}
} else if (!alive) {
fenced = true;
} else {
logger.debug("VM {} is found to be alive by {}", vm, investigator.getName());
logger.debug("VM {} is found to be alive by {} on host {}", vm, investigator.getName(), host);
if (host.getStatus() == Status.Up) {
logger.info(vm + " is alive and host is up. No need to restart it.");
logger.info("{} is alive and host {} is up. No need to restart it.", vm, host);
return null;
} else {
logger.debug("Rescheduling because the host is not up but the vm is alive");
logger.debug("Rescheduling because the host {} is not up but the vm {} is alive", host, vm);
return (System.currentTimeMillis() >> 10) + _investigateRetryInterval;
}
}
if (!fenced) {
logger.debug("We were unable to fence off the VM " + vm);
logger.debug("We were unable to fence off the VM {}", vm);
_alertMgr.sendAlert(alertType, vm.getDataCenterId(), vm.getPodIdToDeployIn(), "Unable to restart " + vm.getHostName() +
" which was running on host " + hostDesc, "Insufficient capacity to restart VM, name: " + vm.getHostName() + ", id: " + vmId +
" which was running on host " + hostDesc);
@ -728,15 +763,15 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
if (!ForceHA.value() && !vm.isHaEnabled()) {
if (logger.isDebugEnabled()) {
logger.debug("VM is not HA enabled so we're done.");
logger.debug("VM {} is not HA enabled so we're done.", vm);
}
return null; // VM doesn't require HA
}
if ((host == null || host.getRemoved() != null || host.getState() != Status.Up)
if ((host.getRemoved() != null || host.getState() != Status.Up)
&& !volumeMgr.canVmRestartOnAnotherServer(vm.getId())) {
if (logger.isDebugEnabled()) {
logger.debug("VM can not restart on another server.");
logger.debug("VM {} can not restart on another server.", vm);
}
return null;
}
@ -777,13 +812,13 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
if (started != null && started.getState() == VirtualMachine.State.Running) {
String message = String.format("HA starting VM: %s (%s)", started.getHostName(), started.getInstanceName());
HostVO hostVmHasStarted = _hostDao.findById(started.getHostId());
logger.info(String.format("HA is now restarting %s on %s", started, hostVmHasStarted));
logger.info("HA is now restarting {} on {}", started, hostVmHasStarted);
_alertMgr.sendAlert(alertType, vm.getDataCenterId(), vm.getPodIdToDeployIn(), message, message);
return null;
}
if (logger.isDebugEnabled()) {
logger.debug("Rescheduling VM " + vm.toString() + " to try again in " + _restartRetryInterval);
logger.debug("Rescheduling VM {} to try again in {}", vm.toString(), _restartRetryInterval);
}
} catch (final InsufficientCapacityException e) {
logger.warn("Unable to restart " + vm.toString() + " due to " + e.getMessage());
@ -815,6 +850,9 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
if (!CancellableWorkReasonTypes.contains(work.getReasonType())) {
return false;
}
if (isHostHAInspectionInProgress(work.getHostId())) {
return false;
}
Status hostStatus = investigate(work.getHostId());
if (!Status.Up.equals(hostStatus)) {
return false;
@ -825,13 +863,14 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
}
public Long migrate(final HaWorkVO work) {
logger.debug("MIGRATE with HA WORK");
long vmId = work.getInstanceId();
long srcHostId = work.getHostId();
HostVO srcHost = _hostDao.findById(srcHostId);
VMInstanceVO vm = _instanceDao.findById(vmId);
if (vm == null) {
logger.info("Unable to find vm: " + vmId + ", skipping migrate.");
logger.info("Unable to find vm: {}, skipping migrate.", vmId);
return null;
}
if (checkAndCancelWorkIfNeeded(work)) {
@ -840,11 +879,11 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
logger.info("Migration attempt: for {} from {}. Starting attempt: {}/{} times.", vm, srcHost, 1 + work.getTimesTried(), _maxRetries);
if (VirtualMachine.State.Stopped.equals(vm.getState())) {
logger.info(String.format("vm %s is Stopped, skipping migrate.", vm));
logger.info("vm {} is Stopped, skipping migrate.", vm);
return null;
}
if (VirtualMachine.State.Running.equals(vm.getState()) && srcHostId != vm.getHostId()) {
logger.info(String.format("VM %s is running on a different host %s, skipping migration", vm, vm.getHostId()));
logger.info("VM {} is running on a different host {}, skipping migration", vm, vm.getHostId());
return null;
}
@ -879,7 +918,7 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
final HaWorkVO work = new HaWorkVO(vm.getId(), vm.getType(), WorkType.Destroy, Step.Scheduled, hostId, vm.getState(), 0, vm.getUpdated(), reasonType);
_haDao.persist(work);
if (logger.isDebugEnabled()) {
logger.debug("Scheduled " + work.toString());
logger.debug("{}}", work.toString());
}
wakeupWorkers();
return true;
@ -897,7 +936,7 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
}
private void destroyVM(VirtualMachine vm, boolean expunge) throws OperationTimedoutException, AgentUnavailableException {
logger.info("Destroying " + vm.toString());
logger.info("Destroying {}", vm.toString());
if (VirtualMachine.Type.ConsoleProxy.equals(vm.getType())) {
consoleProxyManager.destroyProxy(vm.getId());
} else if (VirtualMachine.Type.SecondaryStorageVm.equals(vm.getType())) {
@ -908,9 +947,10 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
}
protected Long destroyVM(final HaWorkVO work) {
logger.debug("DESTROY with HA WORK");
final VirtualMachine vm = _itMgr.findById(work.getInstanceId());
if (vm == null) {
logger.info("No longer can find VM " + work.getInstanceId() + ". Throwing away " + work);
logger.info("No longer can find VM {}. Throwing away {}", work.getInstanceId(), work);
return null;
}
if (checkAndCancelWorkIfNeeded(work)) {
@ -944,20 +984,21 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
}
protected Long stopVM(final HaWorkVO work) throws ConcurrentOperationException {
logger.debug("STOP with HA WORK");
VirtualMachine vm = _itMgr.findById(work.getInstanceId());
if (vm == null) {
logger.info("No longer can find VM " + work.getInstanceId() + ". Throwing away " + work);
logger.info("No longer can find VM {}. Throwing away {}", work.getInstanceId(), work);
work.setStep(Step.Done);
return null;
}
if (checkAndCancelWorkIfNeeded(work)) {
return null;
}
logger.info("Stopping " + vm);
logger.info("Stopping {}", vm);
try {
if (work.getWorkType() == WorkType.Stop) {
_itMgr.advanceStop(vm.getUuid(), false);
logger.info("Successfully stopped " + vm);
logger.info("Successfully stopped {}", vm);
return null;
} else if (work.getWorkType() == WorkType.CheckStop) {
if ((vm.getState() != work.getPreviousState()) || vm.getUpdated() != work.getUpdateTime() || vm.getHostId() == null ||
@ -969,7 +1010,7 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
}
_itMgr.advanceStop(vm.getUuid(), false);
logger.info("Stop for " + vm + " was successful");
logger.info("Stop for {} was successful", vm);
return null;
} else if (work.getWorkType() == WorkType.ForceStop) {
if ((vm.getState() != work.getPreviousState()) || vm.getUpdated() != work.getUpdateTime() || vm.getHostId() == null ||
@ -981,13 +1022,13 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
}
_itMgr.advanceStop(vm.getUuid(), true);
logger.info("Stop for " + vm + " was successful");
logger.info("Stop for {} was successful", vm);
return null;
} else {
assert false : "Who decided there's other steps but didn't modify the guy who does the work?";
}
} catch (final ResourceUnavailableException e) {
logger.debug("Agnet is not available" + e.getMessage());
logger.debug("Agent is not available" + e.getMessage());
} catch (OperationTimedoutException e) {
logger.debug("operation timed out: " + e.getMessage());
}
@ -1043,7 +1084,8 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
try {
if (vm != null && !VmHaEnabled.valueIn(vm.getDataCenterId())) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("VM high availability manager is disabled, rescheduling the HA work %s, for the VM %s (id) to retry later in case VM high availability manager is enabled on retry attempt", work, vm.getName(), vm.getId()));
logger.debug("VM high availability manager is disabled, rescheduling the HA work {} for the VM {} ({}) " +
"to retry later in case VM high availability manager is enabled on retry attempt", work, vm.getName(), vm.getId());
}
long nextTime = getRescheduleTime(wt);
rescheduleWork(work, nextTime);
@ -1065,13 +1107,13 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
}
if (nextTime == null) {
logger.info("Completed work " + work + ". Took " + (work.getTimesTried() + 1) + "/" + _maxRetries + " attempts.");
logger.info("Completed work {}. Took {}/{} attempts.", work, work.getTimesTried() + 1, _maxRetries);
work.setStep(Step.Done);
} else {
rescheduleWork(work, nextTime.longValue());
}
} catch (Exception e) {
logger.warn("Encountered unhandled exception during HA process, reschedule work", e);
logger.warn("Encountered unhandled exception during HA process, reschedule work {}", work, e);
long nextTime = getRescheduleTime(wt);
rescheduleWork(work, nextTime);
@ -1085,11 +1127,11 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
} finally {
if (!Step.Done.equals(work.getStep())) {
if (work.getTimesTried() >= _maxRetries) {
logger.warn("Giving up, retried max " + work.getTimesTried() + "/" + _maxRetries + " times for work: " + work);
logger.warn("Giving up, retried max {}/{} times for work: {}", work.getTimesTried(), _maxRetries, work);
work.setStep(Step.Done);
} else {
logger.warn("Rescheduling work " + work + " to try again at " + new Date(work.getTimeToTry() << 10) +
". Finished attempt " + work.getTimesTried() + "/" + _maxRetries + " times.");
logger.warn("Rescheduling work {} to try again at {}. Finished attempt {}/{} times.",
work, new Date(work.getTimeToTry() << 10), work.getTimesTried(), _maxRetries);
}
}
_haDao.update(work.getId(), work);

View File

@ -74,7 +74,7 @@ public class KVMFencer extends AdapterBase implements FenceBuilder {
@Override
public Boolean fenceOff(VirtualMachine vm, Host host) {
if (host.getHypervisorType() != HypervisorType.KVM && host.getHypervisorType() != HypervisorType.LXC) {
logger.warn("Don't know how to fence non kvm hosts " + host.getHypervisorType());
logger.warn("Don't know how to fence non kvm hosts {}", host.getHypervisorType());
return null;
}
@ -97,11 +97,8 @@ public class KVMFencer extends AdapterBase implements FenceBuilder {
FenceAnswer answer;
try {
answer = (FenceAnswer)_agentMgr.send(h.getId(), fence);
} catch (AgentUnavailableException e) {
logger.info("Moving on to the next host because " + h.toString() + " is unavailable", e);
continue;
} catch (OperationTimedoutException e) {
logger.info("Moving on to the next host because " + h.toString() + " is unavailable", e);
} catch (AgentUnavailableException | OperationTimedoutException e) {
logger.info("Moving on to the next host because {} is unavailable", h.toString(), e);
continue;
}
if (answer != null && answer.getResult()) {
@ -115,7 +112,7 @@ public class KVMFencer extends AdapterBase implements FenceBuilder {
"Fencing off host " + host.getId() + " did not succeed after asking " + i + " hosts. " +
"Check Agent logs for more information.");
logger.error("Unable to fence off " + vm.toString() + " on " + host.toString());
logger.error("Unable to fence off {} on {}", vm.toString(), host.toString());
return false;
}

View File

@ -104,7 +104,7 @@ public class ManagementIPSystemVMInvestigator extends AbstractInvestigatorImpl {
}
@Override
public Status isAgentAlive(Host agent) {
public Status getHostAgentStatus(Host agent) {
return null;
}

View File

@ -103,7 +103,7 @@ public class UserVmDomRInvestigator extends AbstractInvestigatorImpl {
}
@Override
public Status isAgentAlive(Host agent) {
public Status getHostAgentStatus(Host agent) {
if (logger.isDebugEnabled()) {
logger.debug("checking if agent ({}) is alive", agent);
}

View File

@ -46,7 +46,7 @@ public class XenServerInvestigator extends AdapterBase implements Investigator {
}
@Override
public Status isAgentAlive(Host agent) {
public Status getHostAgentStatus(Host agent) {
if (agent.getHypervisorType() != HypervisorType.XenServer) {
return null;
}
@ -74,7 +74,7 @@ public class XenServerInvestigator extends AdapterBase implements Investigator {
@Override
public boolean isVmAlive(VirtualMachine vm, Host host) throws UnknownVM {
Status status = isAgentAlive(host);
Status status = getHostAgentStatus(host);
if (status == null) {
throw new UnknownVM();
}

View File

@ -20,7 +20,7 @@ import com.cloud.agent.api.Command;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.NicTO;
import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.configuration.ConfigurationManagerImpl;
import com.cloud.capacity.CapacityManager;
import com.cloud.event.EventTypes;
import com.cloud.event.UsageEventUtils;
import com.cloud.host.HostVO;
@ -44,6 +44,7 @@ import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineProfile;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.storage.command.CopyCommand;
import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
@ -54,9 +55,7 @@ import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.utils.bytescale.ByteScaleUtils;
import org.apache.commons.lang3.math.NumberUtils;
public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru {
@Inject
@ -130,30 +129,47 @@ public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru {
* @param vmProfile vm profile
*/
protected void setVmQuotaPercentage(VirtualMachineTO to, VirtualMachineProfile vmProfile) {
if (to.isLimitCpuUse()) {
VirtualMachine vm = vmProfile.getVirtualMachine();
HostVO host = hostDao.findById(vm.getHostId());
if (host == null) {
logger.warn("Host is not available. Skipping setting CPU quota percentage for VM: {}", vm);
return;
}
logger.debug("Limiting CPU usage for VM: {} on host: {}", vm, host);
double hostMaxSpeed = getHostCPUSpeed(host);
double maxSpeed = getVmSpeed(to);
try {
BigDecimal percent = new BigDecimal(maxSpeed / hostMaxSpeed);
percent = percent.setScale(2, RoundingMode.HALF_DOWN);
if (percent.compareTo(new BigDecimal(1)) == 1) {
logger.debug("VM {} CPU MHz exceeded host {} CPU MHz, limiting VM CPU to the host maximum", vm, host);
percent = new BigDecimal(1);
}
to.setCpuQuotaPercentage(percent.doubleValue());
logger.debug("Host: {} max CPU speed = {} MHz, VM: {} max CPU speed = {} MHz. " +
"Setting CPU quota percentage as: {}",
host, hostMaxSpeed, vm, maxSpeed, percent.doubleValue());
} catch (NumberFormatException e) {
logger.error("Error calculating VM: {} quota percentage, it will not be set. Error: {}", vm, e.getMessage(), e);
if (!to.isLimitCpuUse()) {
return;
}
VirtualMachine vm = vmProfile.getVirtualMachine();
HostVO host = hostDao.findById(vm.getHostId());
if (host == null) {
logger.warn("Host is not available. Skipping setting CPU quota percentage for VM: [{}].", vm);
return;
}
logger.debug("Limiting CPU usage for VM: [{}] on host: [{}].", vm, host);
double maxSpeed = getVmSpeed(to);
double hostMaxSpeed = getHostCPUSpeed(host);
Double cpuQuotaPercentage = getCpuQuotaPercentage(maxSpeed, hostMaxSpeed);
if (cpuQuotaPercentage != null) {
to.setCpuQuotaPercentage(cpuQuotaPercentage);
}
}
/**
* Calculates the VM quota percentage based on the VM and host CPU speeds.
* @param vmSpeeed Speed of the VM.
* @param hostSpeed Speed of the host.
* @return The VM quota percentage.
*/
public Double getCpuQuotaPercentage(double vmSpeeed, double hostSpeed) {
logger.debug("Calculating CPU quota percentage for VM with speed [{}] on host with speed [{}].", vmSpeeed, hostSpeed);
try {
BigDecimal percent = new BigDecimal(vmSpeeed / hostSpeed);
percent = percent.setScale(2, RoundingMode.HALF_DOWN);
if (percent.compareTo(new BigDecimal(1)) > 0) {
logger.debug("VM CPU speed exceeded host CPU speed and, therefore, limiting VM CPU quota to the host maximum.");
percent = new BigDecimal(1);
}
double quotaPercentage = percent.doubleValue();
logger.info("Calculated CPU quota percentage for VM with speed [{}] on host with speed [{}] is [{}].", vmSpeeed, hostSpeed, quotaPercentage);
return quotaPercentage;
} catch (NumberFormatException e) {
logger.info("Could not calculate CPU quota percentage for VM with speed [{}] on host with speed [{}]. Therefore, CPU limitation will not be set for the domain.", vmSpeeed, hostSpeed);
return null;
}
}
@ -214,28 +230,31 @@ public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru {
Pair<Long, Integer> max = getHostMaxMemoryAndCpuCores(hostVo, virtualMachine, vmDescription);
Long maxHostMemory = max.first();
Integer maxHostCpuCore = max.second();
Integer maxHostCpuCores = max.second();
long minMemory = virtualMachineTo.getMinRam();
Long maxMemory = virtualMachineTo.getMaxRam();
int minCpuCores = virtualMachineTo.getCpus();
Integer maxCpuCores = minCpuCores;
long requestedMemory = maxMemory;
ServiceOfferingVO serviceOfferingVO = serviceOfferingDao.findById(virtualMachineProfile.getId(), virtualMachineProfile.getServiceOfferingId());
if (isVmDynamicScalable(serviceOfferingVO, virtualMachineTo, virtualMachine)) {
int minCpuCores = virtualMachineTo.getCpus();
int maxCpuCores = minCpuCores;
if (isVmDynamicScalable(virtualMachineTo, virtualMachine)) {
ServiceOfferingVO serviceOfferingVO = serviceOfferingDao.findById(virtualMachineProfile.getId(), virtualMachineProfile.getServiceOfferingId());
serviceOfferingDao.loadDetails(serviceOfferingVO);
maxMemory = getVmMaxMemory(serviceOfferingVO, vmDescription, maxHostMemory);
maxCpuCores = getVmMaxCpuCores(serviceOfferingVO, vmDescription, maxHostCpuCore);
Long clusterId = hostVo != null ? hostVo.getClusterId() : null;
maxMemory = getVmMaxMemory(serviceOfferingVO, vmDescription, maxHostMemory, clusterId);
maxCpuCores = getVmMaxCpuCores(serviceOfferingVO, vmDescription, maxHostCpuCores, clusterId);
}
virtualMachineTo.setRam(minMemory, maxMemory);
virtualMachineTo.setRam(minMemory, maxMemory, requestedMemory);
virtualMachineTo.setCpus(minCpuCores);
virtualMachineTo.setVcpuMaxLimit(maxCpuCores);
}
protected boolean isVmDynamicScalable(ServiceOfferingVO serviceOfferingVO, VirtualMachineTO virtualMachineTo, VirtualMachine virtualMachine) {
return serviceOfferingVO.isDynamic() && virtualMachineTo.isEnableDynamicallyScaleVm() && UserVmManager.EnableDynamicallyScaleVm.valueIn(virtualMachine.getDataCenterId());
protected boolean isVmDynamicScalable(VirtualMachineTO virtualMachineTo, VirtualMachine virtualMachine) {
return virtualMachineTo.isEnableDynamicallyScaleVm() && UserVmManager.EnableDynamicallyScaleVm.valueIn(virtualMachine.getDataCenterId());
}
protected Pair<Long, Integer> getHostMaxMemoryAndCpuCores(HostVO host, VirtualMachine virtualMachine, String vmDescription){
@ -263,53 +282,34 @@ public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru {
return new Pair<>(maxHostMemory, maxHostCpuCore);
}
protected Long getVmMaxMemory(ServiceOfferingVO serviceOfferingVO, String vmDescription, Long maxHostMemory) {
String serviceOfferingDescription = serviceOfferingVO.toString();
protected Long getVmMaxMemory(ServiceOfferingVO serviceOfferingVO, String vmDescription, Long maxHostMemory, Long clusterId) {
Long maxMemory;
Integer customOfferingMaxMemory = NumberUtils.createInteger(serviceOfferingVO.getDetail(ApiConstants.MAX_MEMORY));
Integer maxMemoryConfig = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_RAM_SIZE.value();
if (customOfferingMaxMemory != null) {
logger.debug(String.format("Using 'Custom unconstrained' %s max memory value [%sMb] as %s memory.", serviceOfferingDescription, customOfferingMaxMemory, vmDescription));
maxMemory = ByteScaleUtils.mebibytesToBytes(customOfferingMaxMemory);
ConfigKey<Integer> maxMemoryConfig = CapacityManager.KvmMemoryDynamicScalingCapacity;
Integer maxMemoryConfigValue = maxMemoryConfig.valueIn(clusterId);
logger.info("[{}] is a dynamically scalable service offering. Using config [{}] value [{}] in cluster [ID: {}] as max [{}] memory.",
serviceOfferingVO.toString(), maxMemoryConfig.key(), maxMemoryConfigValue, clusterId, vmDescription);
if (maxMemoryConfigValue > 0) {
maxMemory = ByteScaleUtils.mebibytesToBytes(maxMemoryConfigValue);
} else {
String maxMemoryConfigKey = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_RAM_SIZE.key();
logger.info(String.format("%s is a 'Custom unconstrained' service offering. Using config [%s] value [%s] as max %s memory.",
serviceOfferingDescription, maxMemoryConfigKey, maxMemoryConfig, vmDescription));
if (maxMemoryConfig > 0) {
maxMemory = ByteScaleUtils.mebibytesToBytes(maxMemoryConfig);
} else {
logger.info(String.format("Config [%s] has value less or equal '0'. Using %s host or last host max memory [%s] as VM max memory in the hypervisor.", maxMemoryConfigKey, vmDescription, maxHostMemory));
maxMemory = maxHostMemory;
}
logger.info("Config [{}] in cluster [ID: {}] has value less or equal '0'. Using [{}] host or last host max memory [{}] as VM max memory in the hypervisor.",
maxMemoryConfig.key(), clusterId, vmDescription, maxHostMemory);
maxMemory = maxHostMemory;
}
return maxMemory;
}
protected Integer getVmMaxCpuCores(ServiceOfferingVO serviceOfferingVO, String vmDescription, Integer maxHostCpuCore) {
String serviceOfferingDescription = serviceOfferingVO.toString();
protected Integer getVmMaxCpuCores(ServiceOfferingVO serviceOfferingVO, String vmDescription, Integer maxHostCpuCores, Long clusterId) {
Integer maxCpuCores;
Integer customOfferingMaxCpuCores = NumberUtils.createInteger(serviceOfferingVO.getDetail(ApiConstants.MAX_CPU_NUMBER));
Integer maxCpuCoresConfig = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_CPU_CORES.value();
if (customOfferingMaxCpuCores != null) {
logger.debug(String.format("Using 'Custom unconstrained' %s max cpu cores [%s] as %s cpu cores.", serviceOfferingDescription, customOfferingMaxCpuCores, vmDescription));
maxCpuCores = customOfferingMaxCpuCores;
ConfigKey<Integer> maxCpuCoresConfig = CapacityManager.KvmCpuDynamicScalingCapacity;
Integer maxCpuCoresConfigValue = maxCpuCoresConfig.valueIn(clusterId);
logger.info("[{}] is a dynamically scalable service offering. Using config [{}] value [{}] in cluster [ID: {}] as max [{}] CPU cores.",
serviceOfferingVO.toString(), maxCpuCoresConfig.key(), maxCpuCoresConfigValue, clusterId, vmDescription);
if (maxCpuCoresConfigValue > 0) {
maxCpuCores = maxCpuCoresConfigValue;
} else {
String maxCpuCoreConfigKey = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_CPU_CORES.key();
logger.info(String.format("%s is a 'Custom unconstrained' service offering. Using config [%s] value [%s] as max %s cpu cores.",
serviceOfferingDescription, maxCpuCoreConfigKey, maxCpuCoresConfig, vmDescription));
if (maxCpuCoresConfig > 0) {
maxCpuCores = maxCpuCoresConfig;
} else {
logger.info(String.format("Config [%s] has value less or equal '0'. Using %s host or last host max cpu cores [%s] as VM cpu cores in the hypervisor.", maxCpuCoreConfigKey, vmDescription, maxHostCpuCore));
maxCpuCores = maxHostCpuCore;
}
logger.info("Config [{}] in cluster [ID: {}] has value less or equal '0'. Using [{}] host or last host max CPU cores [{}] as VM CPU cores in the hypervisor.",
maxCpuCoresConfig.key(), clusterId, vmDescription, maxHostCpuCores);
maxCpuCores = maxHostCpuCores;
}
return maxCpuCores;
}

View File

@ -21,7 +21,6 @@ import static com.cloud.configuration.ConfigurationManagerImpl.ADD_HOST_ON_SERVI
import java.net.InetAddress;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -32,11 +31,8 @@ import javax.naming.ConfigurationException;
import org.apache.cloudstack.agent.lb.IndirectAgentLB;
import org.apache.cloudstack.ca.CAManager;
import org.apache.cloudstack.ca.SetupCertificateCommand;
import org.apache.cloudstack.direct.download.DirectDownloadManager;
import org.apache.cloudstack.framework.ca.Certificate;
import org.apache.cloudstack.utils.cache.LazyCache;
import org.apache.cloudstack.utils.security.KeyStoreUtils;
import com.cloud.agent.AgentManager;
import com.cloud.agent.Listener;
@ -66,7 +62,6 @@ import com.cloud.resource.DiscovererBase;
import com.cloud.resource.ResourceStateAdapter;
import com.cloud.resource.ServerResource;
import com.cloud.resource.UnableDeleteHostException;
import com.cloud.utils.PasswordGenerator;
import com.cloud.utils.StringUtils;
import com.cloud.utils.UuidUtils;
import com.cloud.utils.exception.CloudRuntimeException;
@ -174,55 +169,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
throw new CloudRuntimeException("Cannot secure agent communication because SSH connection is invalid for host IP=" + agentIp);
}
Integer validityPeriod = CAManager.CertValidityPeriod.value();
if (validityPeriod < 1) {
validityPeriod = 1;
}
String keystorePassword = PasswordGenerator.generateRandomPassword(16);
final SSHCmdHelper.SSHCmdResult keystoreSetupResult = SSHCmdHelper.sshExecuteCmdWithResult(sshConnection,
String.format("sudo /usr/share/cloudstack-common/scripts/util/%s " +
"/etc/cloudstack/agent/agent.properties " +
"/etc/cloudstack/agent/%s " +
"%s %d " +
"/etc/cloudstack/agent/%s",
KeyStoreUtils.KS_SETUP_SCRIPT,
KeyStoreUtils.KS_FILENAME,
keystorePassword,
validityPeriod,
KeyStoreUtils.CSR_FILENAME));
if (!keystoreSetupResult.isSuccess()) {
throw new CloudRuntimeException("Failed to setup keystore on the KVM host: " + agentIp);
}
final Certificate certificate = caManager.issueCertificate(keystoreSetupResult.getStdOut(), Arrays.asList(agentHostname, agentIp), Collections.singletonList(agentIp), null, null);
if (certificate == null || certificate.getClientCertificate() == null) {
throw new CloudRuntimeException("Failed to issue certificates for KVM host agent: " + agentIp);
}
final SetupCertificateCommand certificateCommand = new SetupCertificateCommand(certificate);
final SSHCmdHelper.SSHCmdResult setupCertResult = SSHCmdHelper.sshExecuteCmdWithResult(sshConnection,
String.format("sudo /usr/share/cloudstack-common/scripts/util/%s " +
"/etc/cloudstack/agent/agent.properties %s " +
"/etc/cloudstack/agent/%s %s " +
"/etc/cloudstack/agent/%s \"%s\" " +
"/etc/cloudstack/agent/%s \"%s\" " +
"/etc/cloudstack/agent/%s \"%s\"",
KeyStoreUtils.KS_IMPORT_SCRIPT,
keystorePassword,
KeyStoreUtils.KS_FILENAME,
KeyStoreUtils.SSH_MODE,
KeyStoreUtils.CERT_FILENAME,
certificateCommand.getEncodedCertificate(),
KeyStoreUtils.CACERT_FILENAME,
certificateCommand.getEncodedCaCertificates(),
KeyStoreUtils.PKEY_FILENAME,
certificateCommand.getEncodedPrivateKey()));
if (setupCertResult != null && !setupCertResult.isSuccess()) {
throw new CloudRuntimeException("Failed to setup certificate in the KVM agent's keystore file, please see logs and configure manually!");
}
caManager.provisionCertificateViaSsh(sshConnection, agentIp, agentHostname, null);
if (logger.isDebugEnabled()) {
logger.debug("Succeeded to import certificate in the keystore for agent on the KVM host: " + agentIp + ". Agent secured and trusted.");

View File

@ -539,6 +539,9 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
AssignIpAddressSearch.and("allocated", AssignIpAddressSearch.entity().getAllocatedTime(), Op.NULL);
AssignIpAddressSearch.and("vlanId", AssignIpAddressSearch.entity().getVlanId(), Op.IN);
AssignIpAddressSearch.and("forSystemVms", AssignIpAddressSearch.entity().isForSystemVms(), Op.EQ);
AssignIpAddressSearch.and("id", AssignIpAddressSearch.entity().getId(), Op.NIN);
AssignIpAddressSearch.and("requestedAddress", AssignIpAddressSearch.entity().getAddress(), Op.EQ);
AssignIpAddressSearch.and("routerAddress", AssignIpAddressSearch.entity().getAddress(), Op.NEQ);
SearchBuilder<VlanVO> vlanSearch = _vlanDao.createSearchBuilder();
vlanSearch.and("type", vlanSearch.entity().getVlanType(), Op.EQ);
@ -945,10 +948,23 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
if (podId != null) {
sc = AssignIpAddressFromPodVlanSearch.create();
sc.setJoinParameters("podVlanMapSB", "podId", podId);
errorMessage.append(" pod id=" + podId);
errorMessage.append(" pod id=").append(podId);
} else {
sc = AssignIpAddressSearch.create();
errorMessage.append(" zone id=" + dcId);
errorMessage.append(" zone id=").append(dcId);
}
if (lockOneRow) {
logger.debug("Listing quarantined public IPs to ignore on search for public IP for system VM. The IPs ignored will be the ones that: were not associated to account [{}]; were not removed yet; and with quarantine end dates after [{}].", owner.getUuid(), new Date());
List<PublicIpQuarantineVO> quarantinedAddresses = publicIpQuarantineDao.listQuarantinedIpAddressesToUser(owner.getId(), new Date());
List<Long> quarantinedAddressesIDs = quarantinedAddresses.stream().map(PublicIpQuarantineVO::getPublicIpAddressId).collect(Collectors.toList());
logger.debug("Found addresses with the following IDs: [{}] that will be ignored when searching for available public IPs.", quarantinedAddressesIDs);
if (CollectionUtils.isNotEmpty(quarantinedAddressesIDs)) {
sc.setParameters("id", quarantinedAddressesIDs.toArray());
}
}
sc.setParameters("dc", dcId);
@ -956,11 +972,11 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
// for direct network take ip addresses only from the vlans belonging to the network
if (vlanUse == VlanType.DirectAttached) {
sc.setJoinParameters("vlan", "networkId", guestNetworkId);
errorMessage.append(", network id=" + guestNetworkId);
errorMessage.append(", network id=").append(guestNetworkId);
}
if (requestedGateway != null) {
sc.setJoinParameters("vlan", "vlanGateway", requestedGateway);
errorMessage.append(", requested gateway=" + requestedGateway);
errorMessage.append(", requested gateway=").append(requestedGateway);
}
sc.setJoinParameters("vlan", "type", vlanUse);
@ -970,38 +986,39 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
NetworkDetailVO routerIpDetail = _networkDetailsDao.findDetail(network.getId(), ApiConstants.ROUTER_IP);
routerIpAddress = routerIpDetail != null ? routerIpDetail.getValue() : null;
}
if (requestedIp != null) {
sc.addAnd("address", SearchCriteria.Op.EQ, requestedIp);
errorMessage.append(": requested ip " + requestedIp + " is not available");
sc.setParameters("requestedAddress", requestedIp);
errorMessage.append(": requested ip ").append(requestedIp).append(" is not available");
} else if (routerIpAddress != null) {
sc.addAnd("address", Op.NEQ, routerIpAddress);
sc.setParameters("routerAddress", routerIpAddress);
}
boolean ascOrder = ! forSystemVms;
Filter filter = new Filter(IPAddressVO.class, "forSystemVms", ascOrder, 0l, 1l);
Filter filter = new Filter(IPAddressVO.class, "forSystemVms", ascOrder, 0L, 1L);
filter.addOrderBy(IPAddressVO.class,"vlanId", true);
List<IPAddressVO> addrs = new ArrayList<>();
List<IPAddressVO> addresses = new ArrayList<>();
if (forSystemVms) {
// Get Public IPs for system vms in dedicated ranges
sc.setParameters("forSystemVms", true);
if (lockOneRow) {
addrs = _ipAddressDao.lockRows(sc, filter, true);
addresses = _ipAddressDao.lockRows(sc, filter, true);
} else {
addrs = new ArrayList<>(_ipAddressDao.search(sc, null));
addresses = new ArrayList<>(_ipAddressDao.search(sc, null));
}
}
if ((!lockOneRow || (lockOneRow && CollectionUtils.isEmpty(addrs))) &&
if ((!lockOneRow || (lockOneRow && CollectionUtils.isEmpty(addresses))) &&
!(forSystemVms && SystemVmPublicIpReservationModeStrictness.value())) {
sc.setParameters("forSystemVms", false);
// If owner has dedicated Public IP ranges, fetch IP from the dedicated range
// Otherwise fetch IP from the system pool
// Checking if network is null in the case of system VM's. At the time of allocation of IP address to systemVm, no network is present.
if (network == null || !(network.getGuestType() == GuestType.Shared && zone.getNetworkType() == NetworkType.Advanced)) {
List<AccountVlanMapVO> maps = _accountVlanMapDao.listAccountVlanMapsByAccount(owner.getId());
for (AccountVlanMapVO map : maps) {
List<AccountVlanMapVO> accountVlanMaps = _accountVlanMapDao.listAccountVlanMapsByAccount(owner.getId());
for (AccountVlanMapVO map : accountVlanMaps) {
if (vlanDbIds == null || vlanDbIds.contains(map.getVlanDbId()))
dedicatedVlanDbIds.add(map.getVlanDbId());
}
@ -1020,10 +1037,10 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
if (!dedicatedVlanDbIds.isEmpty()) {
fetchFromDedicatedRange = true;
sc.setParameters("vlanId", dedicatedVlanDbIds.toArray());
errorMessage.append(", vlanId id=" + Arrays.toString(dedicatedVlanDbIds.toArray()));
errorMessage.append(", vlanId id=").append(Arrays.toString(dedicatedVlanDbIds.toArray()));
} else if (!nonDedicatedVlanDbIds.isEmpty()) {
sc.setParameters("vlanId", nonDedicatedVlanDbIds.toArray());
errorMessage.append(", vlanId id=" + Arrays.toString(nonDedicatedVlanDbIds.toArray()));
errorMessage.append(", vlanId id=").append(Arrays.toString(nonDedicatedVlanDbIds.toArray()));
} else {
if (podId != null) {
InsufficientAddressCapacityException ex = new InsufficientAddressCapacityException("Insufficient address capacity", Pod.class, podId);
@ -1037,13 +1054,13 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
}
}
if (lockOneRow) {
addrs = _ipAddressDao.lockRows(sc, filter, true);
addresses = _ipAddressDao.lockRows(sc, filter, true);
} else {
addrs = new ArrayList<>(_ipAddressDao.search(sc, null));
addresses = new ArrayList<>(_ipAddressDao.search(sc, null));
}
// If all the dedicated IPs of the owner are in use fetch an IP from the system pool
if ((!lockOneRow || (lockOneRow && addrs.size() == 0)) && fetchFromDedicatedRange && vlanUse == VlanType.VirtualNetwork) {
if ((!lockOneRow || (lockOneRow && addresses.isEmpty())) && fetchFromDedicatedRange && vlanUse == VlanType.VirtualNetwork) {
// Verify if account is allowed to acquire IPs from the system
boolean useSystemIps = UseSystemPublicIps.valueIn(owner.getId());
if (useSystemIps && !nonDedicatedVlanDbIds.isEmpty()) {
@ -1051,15 +1068,15 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
sc.setParameters("vlanId", nonDedicatedVlanDbIds.toArray());
errorMessage.append(", vlanId id=" + Arrays.toString(nonDedicatedVlanDbIds.toArray()));
if (lockOneRow) {
addrs = _ipAddressDao.lockRows(sc, filter, true);
addresses = _ipAddressDao.lockRows(sc, filter, true);
} else {
addrs.addAll(_ipAddressDao.search(sc, null));
addresses.addAll(_ipAddressDao.search(sc, null));
}
}
}
}
if (lockOneRow && addrs.size() == 0) {
if (lockOneRow && addresses.isEmpty()) {
if (podId != null) {
InsufficientAddressCapacityException ex = new InsufficientAddressCapacityException("Insufficient address capacity", Pod.class, podId);
// for now, we hardcode the table names, but we should ideally do a lookup for the tablename from the VO object.
@ -1073,13 +1090,12 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
}
if (lockOneRow) {
assert (addrs.size() == 1) : "Return size is incorrect: " + addrs.size();
IpAddress ipAddress = addrs.get(0);
boolean ipCanBeAllocated = canPublicIpAddressBeAllocated(ipAddress, owner);
IPAddressVO allocatableIp = addresses.get(0);
if (!ipCanBeAllocated) {
throw new InsufficientAddressCapacityException(String.format("Failed to allocate public IP address [%s] as it is in quarantine.", ipAddress.getAddress()),
DataCenter.class, dcId);
boolean isPublicIpAllocatable = canPublicIpAddressBeAllocated(allocatableIp, owner);
if (!isPublicIpAllocatable) {
throw new InsufficientAddressCapacityException(String.format("Failed to allocate public IP [%s] as it is in quarantine.", allocatableIp.getAddress()), DataCenter.class, dcId);
}
}
@ -1088,12 +1104,12 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
try {
_resourceLimitMgr.checkResourceLimit(owner, ResourceType.public_ip);
} catch (ResourceAllocationException ex) {
logger.warn("Failed to allocate resource of type " + ex.getResourceType() + " for account " + owner);
logger.warn("Failed to allocate resource of type {} for account {}", ex.getResourceType(), owner);
throw new AccountLimitException("Maximum number of public IP addresses for account: " + owner.getAccountName() + " has been exceeded.");
}
}
return addrs;
return addresses;
}
@DB
@ -2558,26 +2574,27 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
PublicIpQuarantineVO publicIpQuarantineVO = publicIpQuarantineDao.findByPublicIpAddressId(ip.getId());
if (publicIpQuarantineVO == null) {
logger.debug(String.format("Public IP address [%s] is not in quarantine; therefore, it is allowed to be allocated.", ip));
logger.debug("Public IP address [{}] is not in quarantine; therefore, it is allowed to be allocated.", ip);
return true;
}
if (!isPublicIpAddressStillInQuarantine(publicIpQuarantineVO, new Date())) {
logger.debug(String.format("Public IP address [%s] is no longer in quarantine; therefore, it is allowed to be allocated.", ip));
logger.debug("Public IP address [{}] is no longer in quarantine; therefore, it is allowed to be allocated.", ip);
removePublicIpAddressFromQuarantine(publicIpQuarantineVO.getId(), "IP was removed from quarantine because it was no longer in quarantine.");
return true;
}
Account previousOwner = _accountMgr.getAccount(publicIpQuarantineVO.getPreviousOwnerId());
if (Objects.equals(previousOwner.getUuid(), newOwner.getUuid())) {
logger.debug(String.format("Public IP address [%s] is in quarantine; however, the Public IP previous owner [%s] is the same as the new owner [%s]; therefore the IP" +
" can be allocated. The public IP address will be removed from quarantine.", ip, previousOwner, newOwner));
logger.debug("Public IP address [{}] is in quarantine; however, the Public IP previous owner [{}] is the same as the new owner [{}]; therefore the IP" +
" can be allocated. The public IP address will be removed from quarantine.", ip, previousOwner, newOwner);
removePublicIpAddressFromQuarantine(publicIpQuarantineVO.getId(), "IP was removed from quarantine because it has been allocated by the previous owner");
return true;
}
logger.error(String.format("Public IP address [%s] is in quarantine and the previous owner [%s] is different than the new owner [%s]; therefore, the IP cannot be " +
"allocated.", ip, previousOwner, newOwner));
logger.error("Public IP address [{}] is in quarantine and the previous owner [{}] is different than the new owner [{}]; therefore, the IP cannot be " +
"allocated.", ip, previousOwner, newOwner);
return false;
}
@ -2628,7 +2645,7 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
publicIpQuarantineVO.setRemovalReason(removalReason);
publicIpQuarantineVO.setRemoverAccountId(removerAccountId);
logger.debug(String.format("Removing public IP Address [%s] from quarantine by updating the removed date to [%s].", ipAddress, removedDate));
logger.debug("Removing public IP Address [{}] from quarantine by updating the removed date to [{}].", ipAddress, removedDate);
publicIpQuarantineDao.persist(publicIpQuarantineVO);
}

View File

@ -1707,16 +1707,15 @@ public class ManagementServerImpl extends MutualExclusiveIdsManagerBase implemen
if (CollectionUtils.isEmpty(suitableHosts)) {
logger.warn("No suitable hosts found.");
} else {
logger.debug("Hosts having capacity and are suitable for migration: {}", suitableHosts);
return suitableHosts;
}
logger.debug("Hosts having capacity and are suitable for migration: {}", suitableHosts);
// Only list hosts of the same architecture as the source Host in a multi-arch zone
if (!suitableHosts.isEmpty()) {
List<CPU.CPUArch> clusterArchs = ApiDBUtils.listZoneClustersArchs(vm.getDataCenterId());
if (CollectionUtils.isNotEmpty(clusterArchs) && clusterArchs.size() > 1) {
suitableHosts = suitableHosts.stream().filter(h -> h.getArch() == srcHost.getArch()).collect(Collectors.toList());
}
List<CPU.CPUArch> clusterArchs = ApiDBUtils.listZoneClustersArchs(vm.getDataCenterId());
if (CollectionUtils.isNotEmpty(clusterArchs) && clusterArchs.size() > 1) {
suitableHosts = suitableHosts.stream().filter(h -> h.getArch() == srcHost.getArch()).collect(Collectors.toList());
}
return suitableHosts;
@ -3036,7 +3035,7 @@ public class ManagementServerImpl extends MutualExclusiveIdsManagerBase implemen
final String hypervisor = cmd.getHypervisor();
final String hypervisorVersion = cmd.getHypervisorVersion();
//throw exception if hypervisor name is not passed, but version is
//throw exception if hypervisor name is not passed, but a version is
if (hypervisorVersion != null && (hypervisor == null || hypervisor.isEmpty())) {
throw new InvalidParameterValueException("Hypervisor version parameter cannot be used without specifying a hypervisor : XenServer, KVM or VMware");
}
@ -3054,7 +3053,7 @@ public class ManagementServerImpl extends MutualExclusiveIdsManagerBase implemen
final SearchCriteria<GuestOSHypervisorVO> sc = sb.create();
if (id != null) {
sc.setParameters("id", SearchCriteria.Op.EQ, id);
sc.setParameters("id", id);
}
if (osTypeId != null) {

View File

@ -1392,20 +1392,19 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
final String accountNameFinal = accountName;
final Long domainIdFinal = domainId;
final String accountUUIDFinal = accountUUID;
final String resolvedAccountUUID = accountUUID != null ? accountUUID : UUID.randomUUID().toString();
// Check role escalation before the transaction this is a read-only check
// that iterates all API commands and doesn't need a write transaction open.
AccountVO requestedAccount = new AccountVO(accountNameFinal, domainIdFinal, networkDomain, accountType, roleId, resolvedAccountUUID);
checkRoleEscalation(getCurrentCallingAccount(), requestedAccount);
Pair<Long, Account> pair = Transaction.execute(new TransactionCallback<>() {
@Override
public Pair<Long, Account> doInTransaction(TransactionStatus status) {
// create account
String accountUUID = accountUUIDFinal;
if (accountUUID == null) {
accountUUID = UUID.randomUUID().toString();
}
AccountVO account = createAccount(accountNameFinal, accountType, roleId, domainIdFinal, networkDomain, details, accountUUID);
AccountVO account = createAccount(accountNameFinal, accountType, roleId, domainIdFinal, networkDomain, details, resolvedAccountUUID);
long accountId = account.getId();
checkRoleEscalation(getCurrentCallingAccount(), account);
// create the first user for the account
UserVO user = createUser(accountId, userName, password, firstName, lastName, email, timezone, userUUID, source);
@ -3851,6 +3850,11 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
@Override
public UserAccount getUserByApiKey(String apiKey) {
ApiKeyPairVO keyPair = apiKeyPairDao.findByApiKey(apiKey);
if (keyPair == null) {
return null;
}
return userAccountDao.findById(keyPair.getUserId());
}

View File

@ -111,6 +111,10 @@ public interface UserVmManager extends UserVmService {
ConfigKey<Boolean> AllowDifferentHostTagsOfferingsForVmScale = new ConfigKey<>("Advanced", Boolean.class, "allow.different.host.tags.offerings.for.vm.scale", "false",
"Enables/Disable allowing to change a VM offering to offerings with different host tags", true);
ConfigKey<Boolean> AutoMigrateVmOnLiveScaleInsufficientCapacity = new ConfigKey<>("Advanced", Boolean.class, "auto.migrate.vm.on.live.scale.insufficient.capacity",
"true", "Defines whether a VM should be automatically migrated to a suitable host when the current host " +
"lacks sufficient compute capacity to live scale the instance. Defaults to true.", true, ConfigKey.Scope.Cluster);
static final int MAX_USER_DATA_LENGTH_BYTES = 2048;
public static final String CKS_NODE = "cksnode";

Some files were not shown because too many files have changed in this diff Show More