mirror of https://github.com/apache/cloudstack.git
Merge branch 'main' of https://github.com/apache/cloudstack into dedup-sonar-workflows
This commit is contained in:
commit
584203c236
|
|
@ -51,10 +51,12 @@ github:
|
|||
|
||||
collaborators:
|
||||
- ingox
|
||||
- gpordeus
|
||||
- gp-santos
|
||||
- erikbocks
|
||||
- Imvedansh
|
||||
- Damans227
|
||||
- jmsperu
|
||||
- GaOrtiga
|
||||
|
||||
protected_branches: ~
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
# 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.
|
||||
|
||||
name: 'Install CloudStack Non-OSS'
|
||||
description: 'Clones and installs the shapeblue/cloudstack-nonoss repository.'
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install cloudstack-nonoss
|
||||
shell: bash
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/shapeblue/cloudstack-nonoss.git nonoss
|
||||
cd nonoss
|
||||
bash -x install-non-oss.sh
|
||||
cd ..
|
||||
rm -fr nonoss
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
# 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.
|
||||
|
||||
name: 'Setup CloudStack Environment'
|
||||
description: 'Sets up JDK (with Maven cache), optionally Python, and optionally APT build dependencies for CloudStack.'
|
||||
|
||||
inputs:
|
||||
java-version:
|
||||
description: 'The JDK version to use'
|
||||
required: false
|
||||
default: '17'
|
||||
install-python:
|
||||
description: 'Whether to install Python 3.10'
|
||||
required: false
|
||||
default: 'false'
|
||||
install-apt-deps:
|
||||
description: 'Whether to install CloudStack APT build dependencies'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set up JDK ${{ inputs.java-version }}
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
java-version: ${{ inputs.java-version }}
|
||||
distribution: 'adopt'
|
||||
architecture: x64
|
||||
cache: 'maven'
|
||||
|
||||
- name: Set up Python
|
||||
if: ${{ inputs.install-python == 'true' }}
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: '3.10'
|
||||
architecture: x64
|
||||
|
||||
- name: Install Build Dependencies
|
||||
if: ${{ inputs.install-apt-deps == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y git uuid-runtime genisoimage netcat-openbsd ipmitool build-essential libgcrypt20 libgpg-error-dev libgpg-error0 libopenipmi0 libpython3-dev libssl-dev libffi-dev python3-openssl python3-dev python3-setuptools
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"entries": {
|
||||
"actions/github-script@v9.0.0": {
|
||||
"repo": "actions/github-script",
|
||||
"version": "v9.0.0",
|
||||
"sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3"
|
||||
},
|
||||
"github/gh-aw-actions/setup@v0.76.1": {
|
||||
"repo": "github/gh-aw-actions/setup",
|
||||
"version": "v0.76.1",
|
||||
"sha": "46d564922b082d0db93244972e8005ea6904ee5f"
|
||||
}
|
||||
},
|
||||
"containers": {
|
||||
"ghcr.io/github/gh-aw-firewall/agent:0.18.0": {
|
||||
"image": "ghcr.io/github/gh-aw-firewall/agent:0.18.0",
|
||||
"digest": "sha256:ab84dfc7f5998cb8cd0c596526dd573b7e7d06c6a740266a1e6df879fa16c866",
|
||||
"pinned_image": "ghcr.io/github/gh-aw-firewall/agent:0.18.0@sha256:ab84dfc7f5998cb8cd0c596526dd573b7e7d06c6a740266a1e6df879fa16c866"
|
||||
},
|
||||
"ghcr.io/github/gh-aw-firewall/agent:0.25.55": {
|
||||
"image": "ghcr.io/github/gh-aw-firewall/agent:0.25.55",
|
||||
"digest": "sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731",
|
||||
"pinned_image": "ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731"
|
||||
},
|
||||
"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55": {
|
||||
"image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55",
|
||||
"digest": "sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3",
|
||||
"pinned_image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55@sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3"
|
||||
},
|
||||
"ghcr.io/github/gh-aw-firewall/squid:0.18.0": {
|
||||
"image": "ghcr.io/github/gh-aw-firewall/squid:0.18.0",
|
||||
"digest": "sha256:82a5d062a5612a57a43a171a5b79ddbb690a86a8ddda02339cc1675131ae9f8b",
|
||||
"pinned_image": "ghcr.io/github/gh-aw-firewall/squid:0.18.0@sha256:82a5d062a5612a57a43a171a5b79ddbb690a86a8ddda02339cc1675131ae9f8b"
|
||||
},
|
||||
"ghcr.io/github/gh-aw-firewall/squid:0.25.55": {
|
||||
"image": "ghcr.io/github/gh-aw-firewall/squid:0.25.55",
|
||||
"digest": "sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca",
|
||||
"pinned_image": "ghcr.io/github/gh-aw-firewall/squid:0.25.55@sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca"
|
||||
},
|
||||
"ghcr.io/github/gh-aw-mcpg:v0.1.4": {
|
||||
"image": "ghcr.io/github/gh-aw-mcpg:v0.1.4",
|
||||
"digest": "sha256:0acf25aa1d409f9c73be9e39ac84f4bd4b90d8bfa1db4dc6d7f47d38ccd58914",
|
||||
"pinned_image": "ghcr.io/github/gh-aw-mcpg:v0.1.4@sha256:0acf25aa1d409f9c73be9e39ac84f4bd4b90d8bfa1db4dc6d7f47d38ccd58914"
|
||||
},
|
||||
"ghcr.io/github/gh-aw-mcpg:v0.3.19": {
|
||||
"image": "ghcr.io/github/gh-aw-mcpg:v0.3.19",
|
||||
"digest": "sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f",
|
||||
"pinned_image": "ghcr.io/github/gh-aw-mcpg:v0.3.19@sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f"
|
||||
},
|
||||
"ghcr.io/github/github-mcp-server:v0.30.3": {
|
||||
"image": "ghcr.io/github/github-mcp-server:v0.30.3",
|
||||
"digest": "sha256:a2b5fb79b1cee851bfc3532dfe480c3dc5736974ca9d93a7a9f68e52ce4b62a0",
|
||||
"pinned_image": "ghcr.io/github/github-mcp-server:v0.30.3@sha256:a2b5fb79b1cee851bfc3532dfe480c3dc5736974ca9d93a7a9f68e52ce4b62a0"
|
||||
}
|
||||
}
|
||||
}
|
||||
73
.github/aw/imports/github/gh-aw/359795d49ada21681ab616bd4cbcb144a7387115/.github_workflows_shared_reporting.md
generated
vendored
Normal file
73
.github/aw/imports/github/gh-aw/359795d49ada21681ab616bd4cbcb144a7387115/.github_workflows_shared_reporting.md
generated
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
# Report formatting guidelines
|
||||
---
|
||||
|
||||
## Report Structure Guidelines
|
||||
|
||||
### 1. Header Levels
|
||||
**Use h3 (###) or lower for all headers in your issue report to maintain proper document hierarchy.**
|
||||
|
||||
When creating GitHub issues or discussions:
|
||||
- Use `###` (h3) for main sections (e.g., "### Test Summary")
|
||||
- Use `####` (h4) for subsections (e.g., "#### Device-Specific Results")
|
||||
- Never use `##` (h2) or `#` (h1) in reports - these are reserved for titles
|
||||
|
||||
### 2. Progressive Disclosure
|
||||
**Wrap detailed test results in `<details><summary>Section Name</summary>` tags to improve readability and reduce scrolling.**
|
||||
|
||||
Use collapsible sections for:
|
||||
- Verbose details (full test logs, raw data)
|
||||
- Secondary information (minor warnings, extra context)
|
||||
- Per-item breakdowns when there are many items
|
||||
|
||||
Always keep critical information visible (summary, critical issues, key metrics).
|
||||
|
||||
### 3. Report Structure Pattern
|
||||
|
||||
1. **Overview**: 1-2 paragraphs summarizing key findings
|
||||
2. **Critical Information**: Show immediately (summary stats, critical issues)
|
||||
3. **Details**: Use `<details><summary>Section Name</summary>` for expanded content
|
||||
4. **Context**: Add helpful metadata (workflow run, date, trigger)
|
||||
|
||||
### Design Principles (Airbnb-Inspired)
|
||||
|
||||
Reports should:
|
||||
- **Build trust through clarity**: Most important info immediately visible
|
||||
- **Exceed expectations**: Add helpful context like trends, comparisons
|
||||
- **Create delight**: Use progressive disclosure to reduce overwhelm
|
||||
- **Maintain consistency**: Follow patterns across all reports
|
||||
|
||||
### Example Report Structure
|
||||
|
||||
```markdown
|
||||
### Summary
|
||||
- Key metric 1: value
|
||||
- Key metric 2: value
|
||||
- Status: ✅/⚠️/❌
|
||||
|
||||
### Critical Issues
|
||||
[Always visible - these are important]
|
||||
|
||||
<details>
|
||||
<summary>View Detailed Results</summary>
|
||||
|
||||
[Comprehensive details, logs, traces]
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>View All Warnings</summary>
|
||||
|
||||
[Minor issues and potential problems]
|
||||
|
||||
</details>
|
||||
|
||||
### Recommendations
|
||||
[Actionable next steps - keep visible]
|
||||
```
|
||||
|
||||
## Workflow Run References
|
||||
|
||||
- Format run IDs as links: `[§12345](https://github.com/owner/repo/actions/runs/12345)`
|
||||
- Include up to 3 most relevant run URLs at end under `**References:**`
|
||||
- Do NOT add footer attribution (system adds automatically)
|
||||
|
|
@ -31,6 +31,8 @@ updates:
|
|||
github-actions-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
ignore:
|
||||
- dependency-name: "github/gh-aw-actions/**" # Managed by gh aw compile. Version-locked to the gh-aw compiler; do not bump.
|
||||
cooldown:
|
||||
default-days: 7
|
||||
- package-ecosystem: "maven"
|
||||
|
|
|
|||
|
|
@ -16,40 +16,27 @@
|
|||
# under the License.
|
||||
|
||||
name: Build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v5
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'maven'
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-env
|
||||
with:
|
||||
python-version: '3.10'
|
||||
architecture: 'x64'
|
||||
|
||||
- name: Install Build Dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y git uuid-runtime genisoimage netcat ipmitool build-essential libgcrypt20 libgpg-error-dev libgpg-error0 libopenipmi0 ipmitool libpython3-dev libssl-dev libffi-dev python3-openssl python3-dev python3-setuptools
|
||||
|
||||
install-python: 'true'
|
||||
install-apt-deps: 'true'
|
||||
- name: Env details
|
||||
run: |
|
||||
uname -a
|
||||
|
|
@ -60,9 +47,8 @@ jobs:
|
|||
free -m
|
||||
nproc
|
||||
git status
|
||||
|
||||
- name: Install Non-OSS
|
||||
uses: ./.github/actions/install-nonoss
|
||||
- name: Noredist Build
|
||||
run: |
|
||||
git clone https://github.com/shapeblue/cloudstack-nonoss.git nonoss && cd nonoss && bash -x install-non-oss.sh && cd ..
|
||||
rm -fr nonoss
|
||||
mvn -B -P developer,systemvm -Dsimulator -Dnoredist clean install -T$(nproc)
|
||||
|
|
|
|||
|
|
@ -16,21 +16,56 @@
|
|||
# under the License.
|
||||
|
||||
name: Simulator CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository == 'apache/cloudstack'
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-env
|
||||
with:
|
||||
install-python: 'true'
|
||||
install-apt-deps: 'true'
|
||||
- name: Env details
|
||||
run: |
|
||||
uname -a
|
||||
whoami
|
||||
javac -version
|
||||
mvn -v
|
||||
python3 --version
|
||||
free -m
|
||||
nproc
|
||||
git status
|
||||
ipmitool -V
|
||||
- name: Build with Maven
|
||||
run: |
|
||||
mvn -B -P developer,systemvm -Dsimulator clean install -DskipTests=true -T$(nproc)
|
||||
- name: Archive artifacts
|
||||
run: |
|
||||
mkdir -p /tmp/artifacts
|
||||
tar -czf /tmp/artifacts/targets.tar.gz $(find . -name "target" -type d) tools/marvin/dist engine/schema/dist utils/conf
|
||||
tar -czf /tmp/artifacts/m2-cloudstack.tar.gz -C ~/.m2/repository org/apache/cloudstack
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: build-artifacts
|
||||
path: /tmp/artifacts/
|
||||
test:
|
||||
needs: build
|
||||
if: github.repository == 'apache/cloudstack'
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
@ -215,30 +250,16 @@ jobs:
|
|||
smoke/test_list_service_offerings
|
||||
smoke/test_list_storage_pools
|
||||
smoke/test_list_volumes"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v5
|
||||
persist-credentials: false
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-env
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
architecture: 'x64'
|
||||
|
||||
- name: Install Build Dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y git uuid-runtime genisoimage netcat-openbsd ipmitool build-essential libgcrypt20 libgpg-error-dev libgpg-error0 libopenipmi0 ipmitool libpython3-dev libssl-dev libffi-dev python3-openssl python3-dev python3-setuptools
|
||||
|
||||
install-python: 'true'
|
||||
install-apt-deps: 'true'
|
||||
- name: Setup IPMI Tool for CloudStack
|
||||
run: |
|
||||
# Create cloudstack-common directory if it doesn't exist
|
||||
|
|
@ -256,55 +277,43 @@ jobs:
|
|||
/usr/share/cloudstack-common/ipmitool -C3 $@
|
||||
EOF
|
||||
sudo chmod 755 /usr/bin/ipmitool
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python3 -m pip install --user --upgrade urllib3 lxml paramiko nose texttable ipmisim pyopenssl pycryptodome mock flask netaddr pylint pycodestyle six astroid pynose
|
||||
|
||||
- name: Install jacoco dependencies
|
||||
run: |
|
||||
wget https://github.com/jacoco/jacoco/releases/download/v0.8.10/jacoco-0.8.10.zip
|
||||
unzip jacoco-0.8.10.zip -d jacoco
|
||||
|
||||
- name: Env details
|
||||
run: |
|
||||
uname -a
|
||||
whoami
|
||||
javac -version
|
||||
mvn -v
|
||||
python3 --version
|
||||
free -m
|
||||
nproc
|
||||
git status
|
||||
ipmitool -V
|
||||
|
||||
- name: Setup MySQL Server
|
||||
run: |
|
||||
# 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();"
|
||||
|
||||
- name: Build with Maven
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: build-artifacts
|
||||
path: /tmp/artifacts/
|
||||
- name: Extract artifacts
|
||||
run: |
|
||||
mvn -B -P developer,systemvm -Dsimulator clean install -DskipTests=true -T$(nproc)
|
||||
|
||||
tar -xzf /tmp/artifacts/targets.tar.gz
|
||||
mkdir -p ~/.m2/repository
|
||||
tar -xzf /tmp/artifacts/m2-cloudstack.tar.gz -C ~/.m2/repository
|
||||
- name: Setup Simulator Prerequisites
|
||||
run: |
|
||||
sudo python3 -m pip install --upgrade netaddr mysql-connector-python
|
||||
python3 -m pip install --user --upgrade tools/marvin/dist/[mM]arvin-*.tar.gz
|
||||
mvn -q -Pdeveloper -pl developer -Ddeploydb
|
||||
mvn -q -Pdeveloper -pl developer -Ddeploydb-simulator
|
||||
|
||||
- name: Generate jacoco-coverage.sh
|
||||
run: |
|
||||
echo "java -jar jacoco/lib/jacococli.jar report jacoco-it.exec \\" > jacoco-report.sh
|
||||
find . | grep "target/classes" | sed 's/\/classes\//\/classes /g' | awk '{print "--classfiles", $1, "\\"}' | sort |uniq >> jacoco-report.sh
|
||||
find . | grep "src/main/java" | sed 's/\/java\//\/java /g' | awk '{print "--sourcefiles", $1, "\\"}' | sort | uniq >> jacoco-report.sh
|
||||
echo "--xml jacoco-coverage.xml" >> jacoco-report.sh
|
||||
|
||||
- name: Start CloudStack Management Server with Simulator
|
||||
run: |
|
||||
export MAVEN_OPTS="-Xmx4096m -XX:MaxMetaspaceSize=800m -Djava.security.egd=file:/dev/urandom -javaagent:jacoco/lib/jacocoagent.jar=address=*,port=36320,output=tcpserver --add-opens=java.base/java.lang=ALL-UNNAMED --add-exports=java.base/sun.security.x509=ALL-UNNAMED --add-opens=java.base/jdk.internal.reflect=ALL-UNNAMED"
|
||||
|
|
@ -315,7 +324,6 @@ jobs:
|
|||
set -e
|
||||
echo -e "\nStarting Advanced Zone DataCenter deployment"
|
||||
python3 tools/marvin/marvin/deployDataCenter.py -i setup/dev/advdualzone.cfg 2>&1 || true
|
||||
|
||||
- name: Run Integration Tests with Simulator
|
||||
run: |
|
||||
mkdir -p integration-test-results/smoke/misc
|
||||
|
|
@ -335,13 +343,12 @@ jobs:
|
|||
bash jacoco-report.sh
|
||||
mvn -Dsimulator -pl client jetty:stop 2>&1
|
||||
find /tmp//MarvinLogs -type f -exec echo -e "Printing marvin logs {} :\n" \; -exec cat {} \;
|
||||
|
||||
- name: Integration Tests Result
|
||||
run: |
|
||||
echo -e "Simulator CI Test Results: (only failures listed)\n"
|
||||
python3 ./tools/marvin/xunit-reader.py integration-test-results/
|
||||
|
||||
- uses: codecov/codecov-action@v6
|
||||
- uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
with:
|
||||
files: jacoco-coverage.xml
|
||||
fail_ci_if_error: true
|
||||
|
|
|
|||
|
|
@ -1,59 +0,0 @@
|
|||
# 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.
|
||||
|
||||
name: Coverage Check
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository == 'apache/cloudstack'
|
||||
name: codecov
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'maven'
|
||||
|
||||
- name: Build CloudStack with Quality Checks
|
||||
run: |
|
||||
git clone https://github.com/shapeblue/cloudstack-nonoss.git nonoss
|
||||
cd nonoss && bash -x install-non-oss.sh && cd ..
|
||||
mvn -P quality -Dsimulator -Dnoredist clean install -T$(nproc)
|
||||
|
||||
- uses: codecov/codecov-action@v6
|
||||
with:
|
||||
files: ./client/target/site/jacoco-aggregate/jacoco.xml
|
||||
fail_ci_if_error: true
|
||||
flags: unittests
|
||||
verbose: true
|
||||
name: codecov
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
|
@ -35,14 +35,16 @@ jobs:
|
|||
language: ["actions"]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
with:
|
||||
category: "Security"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -22,15 +22,19 @@ tools:
|
|||
# reading issues, pull requests and comments from 3rd-parties
|
||||
# If in a private repo this has no particular effect.
|
||||
lockdown: false
|
||||
min-integrity: none # This workflow is allowed to examine and comment on any issues
|
||||
|
||||
safe-outputs:
|
||||
mentions: false
|
||||
allowed-github-references: []
|
||||
create-issue:
|
||||
title-prefix: "[repo-status] "
|
||||
labels: [report, daily-status]
|
||||
source: githubnext/agentics/workflows/daily-repo-status.md@d19056381ba48cb1f7c78510c23069701fa7ae87
|
||||
close-older-issues: true
|
||||
source: githubnext/agentics/workflows/repo-status.md@main
|
||||
---
|
||||
|
||||
# Daily Repo Status
|
||||
# Repo Status
|
||||
|
||||
Create an upbeat daily status report for the repo as a GitHub issue.
|
||||
|
||||
|
|
|
|||
|
|
@ -35,10 +35,10 @@ concurrency:
|
|||
jobs:
|
||||
build:
|
||||
if: github.repository == 'apache/cloudstack'
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ${{ secrets.DOCKER_REGISTRY }}
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
|
|
@ -47,7 +47,9 @@ jobs:
|
|||
- name: Set Docker repository name
|
||||
run: echo "DOCKER_REPOSITORY=apache" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set ACS version
|
||||
run: echo "ACS_VERSION=$(grep '<version>' pom.xml | head -2 | tail -1 | cut -d'>' -f2 |cut -d'<' -f1)" >> $GITHUB_ENV
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
on:
|
||||
schedule: 0 14 * * 1-5
|
||||
schedule: daily around 14:00 on weekdays
|
||||
workflow_dispatch: null
|
||||
permissions:
|
||||
issues: read
|
||||
imports:
|
||||
- github/gh-aw/.github/workflows/shared/reporting.md@94662b1dee8ce96c876ba9f33b3ab8be32de82a4
|
||||
- github/gh-aw/.github/workflows/shared/reporting.md@359795d49ada21681ab616bd4cbcb144a7387115
|
||||
safe-outputs:
|
||||
add-comment: {}
|
||||
add-labels:
|
||||
|
|
@ -17,10 +17,12 @@ safe-outputs:
|
|||
- question
|
||||
- help-wanted
|
||||
- good-first-issue
|
||||
source: github/gh-aw/.github/workflows/issue-triage-agent.md@94662b1dee8ce96c876ba9f33b3ab8be32de82a4
|
||||
emoji: 🔧
|
||||
source: github/gh-aw/.github/workflows/issue-triage-agent.md@359795d49ada21681ab616bd4cbcb144a7387115
|
||||
strict: true
|
||||
timeout-minutes: 5
|
||||
tools:
|
||||
cli-proxy: true
|
||||
github:
|
||||
toolsets:
|
||||
- issues
|
||||
|
|
@ -45,7 +47,7 @@ Hi @{author}! I've categorized this issue as **{label_name}** based on the follo
|
|||
**Reasoning**: {brief_explanation_of_why_this_label}
|
||||
|
||||
<details>
|
||||
<summary><b>View Triage Details</b></summary>
|
||||
<summary>View Triage Details</summary>
|
||||
|
||||
#### Analysis
|
||||
- **Keywords detected**: {list_of_keywords_that_matched}
|
||||
|
|
@ -76,3 +78,15 @@ For efficiency, if multiple issues are triaged in a single run:
|
|||
3. Optionally: Create a discussion summarizing all triage actions for that run
|
||||
|
||||
This provides both per-issue context and batch visibility.
|
||||
|
||||
## Labels
|
||||
|
||||
- `bug`: Indicates a problem or error in the code that needs fixing.
|
||||
- `feature`: Represents a new feature request or enhancement to existing functionality.
|
||||
- `enhancement`: Suggests improvements to existing features or code.
|
||||
- `documentation`: Pertains to issues related to documentation, such as missing or unclear docs.
|
||||
- `question`: Used for issues that are asking for clarification or have questions about the project.
|
||||
- `help-wanted`: Indicates that the issue is a good candidate for external contributions and help
|
||||
- `good-first-issue`: Marks issues that are suitable for newcomers to the project, often with simpler scope.
|
||||
|
||||
{{#runtime-import shared/noop-reminder.md}}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
name: Main Branch Sonar Quality Check
|
||||
name: Sonar Quality Check (Main)
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -26,6 +26,10 @@ permissions:
|
|||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
sonar:
|
||||
if: github.repository == 'apache/cloudstack'
|
||||
|
|
@ -33,5 +37,4 @@ jobs:
|
|||
with:
|
||||
is_pr: false
|
||||
secrets:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
|
|
|||
|
|
@ -29,14 +29,14 @@ concurrency:
|
|||
jobs:
|
||||
triage:
|
||||
permissions:
|
||||
pull-requests: write # for eps1lon/actions-label-merge-conflict to label PRs
|
||||
runs-on: ubuntu-22.04
|
||||
pull-requests: write # for eps1lon/actions-label-merge-conflict to label PRs
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Conflict Check
|
||||
uses: eps1lon/actions-label-merge-conflict@v3.0.3
|
||||
with:
|
||||
repoToken: "${{ secrets.GITHUB_TOKEN }}"
|
||||
dirtyLabel: "status:has-conflicts"
|
||||
removeOnDirtyLabel: "status:ready-for-review"
|
||||
continueOnMissingPermissions: true
|
||||
commentOnDirty: "This pull request has merge conflicts. Dear author, please fix the conflicts and sync your branch with the base branch."
|
||||
- name: Conflict Check
|
||||
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
|
||||
with:
|
||||
repoToken: "${{ secrets.GITHUB_TOKEN }}"
|
||||
dirtyLabel: "status:has-conflicts"
|
||||
removeOnDirtyLabel: "status:ready-for-review"
|
||||
continueOnMissingPermissions: true
|
||||
commentOnDirty: "This pull request has merge conflicts. Dear author, please fix the conflicts and sync your branch with the base branch."
|
||||
|
|
|
|||
|
|
@ -29,17 +29,23 @@ concurrency:
|
|||
jobs:
|
||||
pre-commit:
|
||||
name: Run pre-commit
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check Out
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'pip'
|
||||
- name: Install
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pre-commit
|
||||
run: pip install pre-commit
|
||||
- name: Set PY
|
||||
run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
|
||||
- uses: actions/cache@v5
|
||||
- name: Cache pre-commit environments
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
|
|
|
|||
|
|
@ -16,32 +16,27 @@
|
|||
# under the License.
|
||||
|
||||
name: License Check
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v5
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
architecture: x64
|
||||
cache: maven
|
||||
persist-credentials: false
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-env
|
||||
- name: Install Non-OSS
|
||||
uses: ./.github/actions/install-nonoss
|
||||
- name: RAT licence checks
|
||||
run: |
|
||||
git clone https://github.com/shapeblue/cloudstack-nonoss.git nonoss && cd nonoss && bash -x install-non-oss.sh && cd ..
|
||||
rm -fr nonoss
|
||||
mvn -P developer,systemvm -Dsimulator -Dnoredist -pl . org.apache.rat:apache-rat-plugin:0.12:check
|
||||
- name: Rat Report
|
||||
if: always()
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ permissions:
|
|||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
sonar:
|
||||
|
|
@ -34,5 +34,4 @@ jobs:
|
|||
with:
|
||||
is_pr: true
|
||||
secrets:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ on:
|
|||
type: boolean
|
||||
required: true
|
||||
secrets:
|
||||
GITHUB_TOKEN:
|
||||
required: true
|
||||
SONAR_TOKEN:
|
||||
required: false
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
|
||||
with:
|
||||
stale-issue-message: 'This issue is stale because it has been open for 120 days with no activity. It may be removed by administrators of this project at any time. Remove the stale label or comment to request for removal of it to prevent this.'
|
||||
stale-pr-message: 'This PR is stale because it has been open for 120 days with no activity. It may be removed by administrators of this project at any time. Remove the stale label or comment to request for removal of it to prevent this.'
|
||||
|
|
@ -41,7 +41,7 @@ jobs:
|
|||
days-before-pr-close: 240
|
||||
exempt-issue-labels: 'gsoc,good-first-issue,long-term-plan'
|
||||
exempt-pr-labels: 'status:ready-for-merge,status:needs-testing,status:on-hold'
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
|
||||
with:
|
||||
stale-issue-label: 'archive'
|
||||
days-before-stale: 240
|
||||
|
|
|
|||
|
|
@ -28,15 +28,19 @@ permissions:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'npm'
|
||||
cache-dependency-path: 'ui/package-lock.json'
|
||||
|
||||
- name: Env details
|
||||
run: |
|
||||
|
|
@ -55,7 +59,7 @@ jobs:
|
|||
npm run lint
|
||||
npm run test:unit
|
||||
|
||||
- uses: codecov/codecov-action@v6
|
||||
- uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
if: github.repository == 'apache/cloudstack'
|
||||
with:
|
||||
working-directory: ui
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Sec
|
|||
The following provides more details on the included cryptographic software:
|
||||
|
||||
* CloudStack makes use of JaSypt cryptographic libraries.
|
||||
* CloudStack has a system requirement of MySQL, and uses native database encryption functionality.
|
||||
* CloudStack requires a MySQL-compatible database (MariaDB or MySQL), and uses native database encryption functionality.
|
||||
* CloudStack makes use of the Bouncy Castle general-purpose encryption library.
|
||||
* CloudStack can optionally interact with and control OpenSwan-based VPNs.
|
||||
* CloudStack has a dependency on and makes use of JSch - a java SSH2 implementation.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,8 @@ public class ApiConstants {
|
|||
public static final String BACKUP_VM_OFFERING_REMOVED = "vmbackupofferingremoved";
|
||||
public static final String IS_BACKUP_VM_EXPUNGED = "isbackupvmexpunged";
|
||||
public static final String BACKUP_TOTAL = "backuptotal";
|
||||
public static final String BALANCE = "balance";
|
||||
public static final String BALANCES = "balances";
|
||||
public static final String BASE64_IMAGE = "base64image";
|
||||
public static final String BGP_PEERS = "bgppeers";
|
||||
public static final String BGP_PEER_IDS = "bgppeerids";
|
||||
|
|
@ -171,6 +173,7 @@ public class ApiConstants {
|
|||
public static final String DATACENTER_NAME = "datacentername";
|
||||
public static final String DATADISKS_DETAILS = "datadisksdetails";
|
||||
public static final String DATADISK_OFFERING_LIST = "datadiskofferinglist";
|
||||
public static final String DATE = "date";
|
||||
public static final String DEFAULT_VALUE = "defaultvalue";
|
||||
public static final String DELETE_PROTECTION = "deleteprotection";
|
||||
public static final String DESCRIPTION = "description";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import javax.inject.Inject;
|
|||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseAsyncCmd;
|
||||
|
|
@ -102,6 +103,16 @@ public class AssignVirtualMachineToBackupOfferingCmd extends BaseAsyncCmd {
|
|||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return vmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.VirtualMachine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN;
|
||||
|
|
|
|||
|
|
@ -123,7 +123,12 @@ public class CreateBackupCmd extends BaseAsyncCreateCmd {
|
|||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.Backup;
|
||||
return ApiCommandResourceType.VirtualMachine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return vmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import javax.inject.Inject;
|
|||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
|
|
@ -81,7 +82,7 @@ public class CreateBackupScheduleCmd extends BaseCmd {
|
|||
@Parameter(name = ApiConstants.QUIESCE_VM,
|
||||
type = CommandType.BOOLEAN,
|
||||
required = false,
|
||||
description = "Quiesce the instance before checkpointing the disks for backup. Applicable only to NAS backup provider. " +
|
||||
description = "Quiesce the Instance before checkpointing the disks for backup. Applicable only to NAS backup provider. " +
|
||||
"The filesystem is frozen before the backup starts and thawed immediately after. " +
|
||||
"Requires the instance to have the QEMU Guest Agent installed and running.",
|
||||
since = "4.21.0")
|
||||
|
|
@ -139,4 +140,14 @@ public class CreateBackupScheduleCmd extends BaseCmd {
|
|||
public long getEntityOwnerId() {
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return vmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.VirtualMachine;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import javax.inject.Inject;
|
|||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseAsyncCmd;
|
||||
|
|
@ -99,6 +100,16 @@ public class RemoveVirtualMachineFromBackupOfferingCmd extends BaseAsyncCmd {
|
|||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return vmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.VirtualMachine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import javax.inject.Inject;
|
|||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.ACL;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseAsyncCmd;
|
||||
|
|
@ -127,4 +128,14 @@ public class RestoreVolumeFromBackupAndAttachToVMCmd extends BaseAsyncCmd {
|
|||
public String getEventDescription() {
|
||||
return "Restoring volume "+ volumeUuid + " from backup " + getResourceUuid(ApiConstants.BACKUP_ID) + " and attaching it to Instance " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return vmId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.VirtualMachine;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -716,17 +716,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>
|
||||
|
|
@ -906,13 +906,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>
|
||||
|
|
@ -936,7 +936,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>
|
||||
|
|
@ -971,9 +971,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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,3 +131,21 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` (
|
|||
-- Add the 'keep_mac_address_on_public_nic' column to the 'cloud.networks' and 'cloud.vpc' tables
|
||||
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';
|
||||
|
|
|
|||
|
|
@ -150,8 +150,9 @@ public class QuotaManagerImpl extends ManagerBase implements QuotaManager {
|
|||
return;
|
||||
}
|
||||
|
||||
Date startDate = accountQuotaUsages.get(0).getStartDate();
|
||||
Date endDate = accountQuotaUsages.get(0).getEndDate();
|
||||
QuotaUsageVO firstQuotaUsage = accountQuotaUsages.get(0);
|
||||
Date startDate = firstQuotaUsage.getStartDate();
|
||||
Date endDate = firstQuotaUsage.getEndDate();
|
||||
Date lastQuotaUsageEndDate = accountQuotaUsages.get(accountQuotaUsages.size() - 1).getEndDate();
|
||||
|
||||
LinkedHashSet<Pair<Date, Date>> periods = accountQuotaUsages.stream()
|
||||
|
|
@ -215,7 +216,7 @@ public class QuotaManagerImpl extends ManagerBase implements QuotaManager {
|
|||
logger.debug(String.format("Persisting the first quota balance [%s] for account [%s].", firstBalance, accountToString));
|
||||
_quotaBalanceDao.saveQuotaBalance(firstBalance);
|
||||
} else {
|
||||
QuotaBalanceVO lastRealBalance = _quotaBalanceDao.findLastBalanceEntry(accountId, domainId, startDate);
|
||||
QuotaBalanceVO lastRealBalance = _quotaBalanceDao.getLastQuotaBalanceEntry(accountId, domainId, startDate);
|
||||
|
||||
if (lastRealBalance == null) {
|
||||
logger.warn("Account [{}] has quota usage entries, however it does not have a quota balance.", accountToString);
|
||||
|
|
@ -244,7 +245,7 @@ public class QuotaManagerImpl extends ManagerBase implements QuotaManager {
|
|||
}
|
||||
|
||||
protected BigDecimal aggregateCreditBetweenDates(Long accountId, Long domainId, Date startDate, Date endDate, String accountToString) {
|
||||
List<QuotaBalanceVO> creditsReceived = _quotaBalanceDao.findCreditBalance(accountId, domainId, startDate, endDate);
|
||||
List<QuotaBalanceVO> creditsReceived = _quotaBalanceDao.findCreditBalances(accountId, domainId, startDate, endDate);
|
||||
logger.debug("Account [{}] has [{}] credit entries before [{}].", accountToString, creditsReceived.size(),
|
||||
DateUtil.displayDateInTimezone(usageAggregationTimeZone, endDate));
|
||||
|
||||
|
|
|
|||
|
|
@ -28,16 +28,14 @@ public interface QuotaBalanceDao extends GenericDao<QuotaBalanceVO, Long> {
|
|||
|
||||
QuotaBalanceVO saveQuotaBalance(QuotaBalanceVO qb);
|
||||
|
||||
List<QuotaBalanceVO> findCreditBalance(Long accountId, Long domainId, Date startDate, Date endDate);
|
||||
List<QuotaBalanceVO> findCreditBalances(Long accountId, Long domainId, Date startDate, Date endDate);
|
||||
|
||||
QuotaBalanceVO findLastBalanceEntry(Long accountId, Long domainId, Date beforeThis);
|
||||
QuotaBalanceVO getLastQuotaBalanceEntry(Long accountId, Long domainId, Date beforeThis);
|
||||
|
||||
QuotaBalanceVO findLaterBalanceEntry(Long accountId, Long domainId, Date afterThis);
|
||||
|
||||
List<QuotaBalanceVO> findQuotaBalance(Long accountId, Long domainId, Date startDate, Date endDate);
|
||||
List<QuotaBalanceVO> listQuotaBalances(Long accountId, Long domainId, Date startDate, Date endDate);
|
||||
|
||||
List<QuotaBalanceVO> lastQuotaBalanceVO(Long accountId, Long domainId, Date startDate);
|
||||
|
||||
BigDecimal lastQuotaBalance(Long accountId, Long domainId, Date startDate);
|
||||
BigDecimal getLastQuotaBalance(Long accountId, Long domainId);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,14 @@ package org.apache.cloudstack.quota.dao;
|
|||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.utils.db.Filter;
|
||||
|
|
@ -32,160 +35,104 @@ import com.cloud.utils.db.SearchCriteria;
|
|||
import com.cloud.utils.db.Transaction;
|
||||
import com.cloud.utils.db.TransactionCallback;
|
||||
import com.cloud.utils.db.TransactionLegacy;
|
||||
import com.cloud.utils.db.TransactionStatus;
|
||||
|
||||
@Component
|
||||
public class QuotaBalanceDaoImpl extends GenericDaoBase<QuotaBalanceVO, Long> implements QuotaBalanceDao {
|
||||
private static final Logger logger = LogManager.getLogger(QuotaBalanceDaoImpl.class);
|
||||
|
||||
@Override
|
||||
public QuotaBalanceVO findLastBalanceEntry(final Long accountId, final Long domainId, final Date beforeThis) {
|
||||
return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback<QuotaBalanceVO>() {
|
||||
@Override
|
||||
public QuotaBalanceVO doInTransaction(final TransactionStatus status) {
|
||||
List<QuotaBalanceVO> quotaBalanceEntries = new ArrayList<>();
|
||||
Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", false, 0L, 1L);
|
||||
QueryBuilder<QuotaBalanceVO> qb = QueryBuilder.create(QuotaBalanceVO.class);
|
||||
qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId);
|
||||
qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId);
|
||||
qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0);
|
||||
public QuotaBalanceVO getLastQuotaBalanceEntry(final Long accountId, final Long domainId, final Date beforeThis) {
|
||||
return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback<QuotaBalanceVO>) status -> {
|
||||
Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", false, 0L, 1L);
|
||||
QueryBuilder<QuotaBalanceVO> qb = getQuotaBalanceQueryBuilder(accountId, domainId);
|
||||
qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0);
|
||||
|
||||
if (beforeThis != null) {
|
||||
qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.LT, beforeThis);
|
||||
quotaBalanceEntries = search(qb.create(), filter);
|
||||
return !quotaBalanceEntries.isEmpty() ? quotaBalanceEntries.get(0) : null;
|
||||
}
|
||||
|
||||
List<QuotaBalanceVO> quotaBalanceEntries = search(qb.create(), filter);
|
||||
return !quotaBalanceEntries.isEmpty() ? quotaBalanceEntries.get(0) : null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuotaBalanceVO findLaterBalanceEntry(final Long accountId, final Long domainId, final Date afterThis) {
|
||||
return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback<QuotaBalanceVO>() {
|
||||
@Override
|
||||
public QuotaBalanceVO doInTransaction(final TransactionStatus status) {
|
||||
List<QuotaBalanceVO> quotaBalanceEntries = new ArrayList<>();
|
||||
Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", true, 0L, 1L);
|
||||
QueryBuilder<QuotaBalanceVO> qb = QueryBuilder.create(QuotaBalanceVO.class);
|
||||
qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId);
|
||||
qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId);
|
||||
qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0);
|
||||
qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.GT, afterThis);
|
||||
quotaBalanceEntries = search(qb.create(), filter);
|
||||
return quotaBalanceEntries.size() > 0 ? quotaBalanceEntries.get(0) : null;
|
||||
}
|
||||
return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback<QuotaBalanceVO>) status -> {
|
||||
Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", true, 0L, 1L);
|
||||
QueryBuilder<QuotaBalanceVO> qb = getQuotaBalanceQueryBuilder(accountId, domainId);
|
||||
qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0);
|
||||
qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.GT, afterThis);
|
||||
|
||||
List<QuotaBalanceVO> quotaBalanceEntries = search(qb.create(), filter);
|
||||
return !quotaBalanceEntries.isEmpty() ? quotaBalanceEntries.get(0) : null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuotaBalanceVO saveQuotaBalance(final QuotaBalanceVO qb) {
|
||||
return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback<QuotaBalanceVO>() {
|
||||
@Override
|
||||
public QuotaBalanceVO doInTransaction(final TransactionStatus status) {
|
||||
return persist(qb);
|
||||
return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback<QuotaBalanceVO>) status -> persist(qb));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QuotaBalanceVO> findCreditBalances(final Long accountId, final Long domainId, final Date startDate, final Date endDate) {
|
||||
return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback<List<QuotaBalanceVO>>) status -> {
|
||||
if (ObjectUtils.anyNull(startDate, endDate) || startDate.after(endDate)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", true, 0L, Long.MAX_VALUE);
|
||||
QueryBuilder<QuotaBalanceVO> qb = getQuotaBalanceQueryBuilder(accountId, domainId);
|
||||
qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.GT, 0);
|
||||
qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.BETWEEN, startDate, endDate);
|
||||
|
||||
return search(qb.create(), filter);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QuotaBalanceVO> findCreditBalance(final Long accountId, final Long domainId, final Date lastbalancedate, final Date beforeThis) {
|
||||
return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback<List<QuotaBalanceVO>>() {
|
||||
@Override
|
||||
public List<QuotaBalanceVO> doInTransaction(final TransactionStatus status) {
|
||||
if ((lastbalancedate != null) && (beforeThis != null) && lastbalancedate.before(beforeThis)) {
|
||||
Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", true, 0L, Long.MAX_VALUE);
|
||||
QueryBuilder<QuotaBalanceVO> qb = QueryBuilder.create(QuotaBalanceVO.class);
|
||||
qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId);
|
||||
qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId);
|
||||
qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.GT, 0);
|
||||
qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.BETWEEN, lastbalancedate, beforeThis);
|
||||
return search(qb.create(), filter);
|
||||
} else {
|
||||
return new ArrayList<QuotaBalanceVO>();
|
||||
}
|
||||
public List<QuotaBalanceVO> listQuotaBalances(final Long accountId, final Long domainId, final Date startDate, final Date endDate) {
|
||||
return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback<List<QuotaBalanceVO>>) status -> {
|
||||
QueryBuilder<QuotaBalanceVO> qb = getQuotaBalanceQueryBuilder(accountId, domainId);
|
||||
qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0);
|
||||
if (startDate != null) {
|
||||
qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.GTEQ, startDate);
|
||||
}
|
||||
if (endDate != null) {
|
||||
qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.LTEQ, endDate);
|
||||
}
|
||||
|
||||
Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", true);
|
||||
return listBy(qb.create(), filter);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QuotaBalanceVO> findQuotaBalance(final Long accountId, final Long domainId, final Date startDate, final Date endDate) {
|
||||
return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback<List<QuotaBalanceVO>>() {
|
||||
@Override
|
||||
public List<QuotaBalanceVO> doInTransaction(final TransactionStatus status) {
|
||||
List<QuotaBalanceVO> quotaUsageRecords = null;
|
||||
QueryBuilder<QuotaBalanceVO> qb = QueryBuilder.create(QuotaBalanceVO.class);
|
||||
if (accountId != null) {
|
||||
qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId);
|
||||
}
|
||||
if (domainId != null) {
|
||||
qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId);
|
||||
}
|
||||
if ((startDate != null) && (endDate != null) && startDate.before(endDate)) {
|
||||
qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.BETWEEN, startDate, endDate);
|
||||
} else {
|
||||
return Collections.<QuotaBalanceVO> emptyList();
|
||||
}
|
||||
quotaUsageRecords = listBy(qb.create());
|
||||
if (quotaUsageRecords.size() == 0) {
|
||||
quotaUsageRecords.addAll(lastQuotaBalanceVO(accountId, domainId, startDate));
|
||||
}
|
||||
return quotaUsageRecords;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QuotaBalanceVO> lastQuotaBalanceVO(final Long accountId, final Long domainId, final Date pivotDate) {
|
||||
return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback<List<QuotaBalanceVO>>() {
|
||||
@Override
|
||||
public List<QuotaBalanceVO> doInTransaction(final TransactionStatus status) {
|
||||
List<QuotaBalanceVO> quotaUsageRecords = null;
|
||||
List<QuotaBalanceVO> trimmedRecords = new ArrayList<QuotaBalanceVO>();
|
||||
Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", false, 0L, 100L);
|
||||
// ASSUMPTION there will be less than 100 continuous credit
|
||||
// transactions
|
||||
QueryBuilder<QuotaBalanceVO> qb = QueryBuilder.create(QuotaBalanceVO.class);
|
||||
if (accountId != null) {
|
||||
qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId);
|
||||
}
|
||||
if (domainId != null) {
|
||||
qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId);
|
||||
}
|
||||
if ((pivotDate != null)) {
|
||||
qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.LTEQ, pivotDate);
|
||||
}
|
||||
quotaUsageRecords = search(qb.create(), filter);
|
||||
|
||||
// get records before startDate to find start balance
|
||||
for (QuotaBalanceVO entry : quotaUsageRecords) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("FindQuotaBalance Entry=" + entry);
|
||||
}
|
||||
if (entry.getCreditsId() > 0) {
|
||||
trimmedRecords.add(entry);
|
||||
} else {
|
||||
trimmedRecords.add(entry);
|
||||
break; // add only consecutive credit entries and last balance entry
|
||||
}
|
||||
}
|
||||
return trimmedRecords;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal lastQuotaBalance(final Long accountId, final Long domainId, Date startDate) {
|
||||
List<QuotaBalanceVO> quotaBalance = lastQuotaBalanceVO(accountId, domainId, startDate);
|
||||
BigDecimal finalBalance = new BigDecimal(0);
|
||||
if (quotaBalance.isEmpty()) {
|
||||
logger.info("There are no balance entries on or before the requested date.");
|
||||
return finalBalance;
|
||||
public BigDecimal getLastQuotaBalance(final Long accountId, final Long domainId) {
|
||||
QuotaBalanceVO quotaBalance = getLastQuotaBalanceEntry(accountId, domainId, null);
|
||||
BigDecimal finalBalance = BigDecimal.ZERO;
|
||||
Date startDate = DateUtils.addDays(new Date(), -1);
|
||||
if (quotaBalance == null) {
|
||||
logger.info("There are no balance entries for account [{}] and domain [{}]. Considering only new added credits.", accountId, domainId);
|
||||
} else {
|
||||
finalBalance = quotaBalance.getCreditBalance();
|
||||
startDate = quotaBalance.getUpdatedOn();
|
||||
}
|
||||
for (QuotaBalanceVO entry : quotaBalance) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("lastQuotaBalance Entry=" + entry);
|
||||
}
|
||||
finalBalance = finalBalance.add(entry.getCreditBalance());
|
||||
|
||||
List<QuotaBalanceVO> credits = findCreditBalances(accountId, domainId, startDate, new Date());
|
||||
|
||||
for (QuotaBalanceVO credit : credits) {
|
||||
finalBalance = finalBalance.add(credit.getCreditBalance());
|
||||
}
|
||||
|
||||
return finalBalance;
|
||||
}
|
||||
|
||||
private QueryBuilder<QuotaBalanceVO> getQuotaBalanceQueryBuilder(Long accountId, Long domainId) {
|
||||
QueryBuilder<QuotaBalanceVO> qb = QueryBuilder.create(QuotaBalanceVO.class);
|
||||
qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId);
|
||||
qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId);
|
||||
return qb;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
|
||||
// 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.
|
||||
package org.apache.cloudstack.quota.dao;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class QuotaBalanceDaoImplTest {
|
||||
QuotaBalanceDaoImpl quotaBalanceDaoImplSpy = Mockito.spy(QuotaBalanceDaoImpl.class);
|
||||
|
||||
@Mock
|
||||
QuotaBalanceVO quotaBalanceVoMock;
|
||||
|
||||
@Test
|
||||
public void getLastQuotaBalanceTestLastEntryIsNullAndNoCreditsReturnsZero() {
|
||||
Mockito.doReturn(null).when(quotaBalanceDaoImplSpy).getLastQuotaBalanceEntry(Mockito.anyLong(), Mockito.anyLong(), Mockito.any());
|
||||
Mockito.doReturn(new ArrayList<>()).when(quotaBalanceDaoImplSpy).findCreditBalances(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.any());
|
||||
|
||||
BigDecimal result = quotaBalanceDaoImplSpy.getLastQuotaBalance(1L, 2L);
|
||||
|
||||
Assert.assertEquals(BigDecimal.ZERO, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLastQuotaBalanceTestReturnsLastEntryAndNoCredits() {
|
||||
BigDecimal expected = BigDecimal.valueOf(-1542.46);
|
||||
Mockito.doReturn(quotaBalanceVoMock).when(quotaBalanceDaoImplSpy).getLastQuotaBalanceEntry(Mockito.anyLong(), Mockito.anyLong(), Mockito.any());
|
||||
Mockito.doReturn(expected).when(quotaBalanceVoMock).getCreditBalance();
|
||||
Mockito.doReturn(new ArrayList<>()).when(quotaBalanceDaoImplSpy).findCreditBalances(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.any());
|
||||
|
||||
BigDecimal result = quotaBalanceDaoImplSpy.getLastQuotaBalance(5L, 8L);
|
||||
|
||||
Assert.assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLastQuotaBalanceTestReturnsLastEntryPlusCredits() {
|
||||
BigDecimal balance = BigDecimal.valueOf(-1542.46);
|
||||
BigDecimal credit1 = new BigDecimal("150.14");
|
||||
BigDecimal credit2 = new BigDecimal("78.96");
|
||||
BigDecimal expected = balance.add(credit1).add(credit2);
|
||||
|
||||
Mockito.doReturn(quotaBalanceVoMock).when(quotaBalanceDaoImplSpy).getLastQuotaBalanceEntry(Mockito.anyLong(), Mockito.anyLong(), Mockito.any());
|
||||
Mockito.doReturn(balance, credit1, credit2).when(quotaBalanceVoMock).getCreditBalance();
|
||||
Mockito.doReturn(Arrays.asList(quotaBalanceVoMock, quotaBalanceVoMock)).when(quotaBalanceDaoImplSpy).findCreditBalances(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.any());
|
||||
|
||||
BigDecimal result = quotaBalanceDaoImplSpy.getLastQuotaBalance(5L, 8L);
|
||||
|
||||
Assert.assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLastQuotaBalanceTestReturnsLastEntryIsNullPlusCredits() {
|
||||
BigDecimal credit1 = new BigDecimal("150.14");
|
||||
BigDecimal credit2 = new BigDecimal("78.96");
|
||||
BigDecimal expected = credit1.add(credit2);
|
||||
|
||||
Mockito.doReturn(null).when(quotaBalanceDaoImplSpy).getLastQuotaBalanceEntry(Mockito.anyLong(), Mockito.anyLong(), Mockito.any());
|
||||
Mockito.doReturn(credit1, credit2).when(quotaBalanceVoMock).getCreditBalance();
|
||||
Mockito.doReturn(Arrays.asList(quotaBalanceVoMock, quotaBalanceVoMock)).when(quotaBalanceDaoImplSpy).findCreditBalances(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.any());
|
||||
|
||||
BigDecimal result = quotaBalanceDaoImplSpy.getLastQuotaBalance(5L, 8L);
|
||||
|
||||
Assert.assertEquals(expected, result);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
el8
|
||||
|
|
@ -0,0 +1 @@
|
|||
../el8/cloud-ipallocator.rc
|
||||
|
|
@ -0,0 +1 @@
|
|||
../el8/cloud.limits
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
../el8/cloudstack-agent.te
|
||||
|
|
@ -0,0 +1 @@
|
|||
../el8/cloudstack-sccs
|
||||
|
|
@ -0,0 +1 @@
|
|||
../el8/filelimit.conf
|
||||
|
|
@ -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/
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -226,12 +226,12 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
|
|||
} catch (AgentUnavailableException e) {
|
||||
logger.error("Unable to contact backend control plane to initiate backup for VM {}", vm.getInstanceName());
|
||||
backupVO.setStatus(Backup.Status.Failed);
|
||||
backupDao.remove(backupVO.getId());
|
||||
backupDao.update(backupVO.getId(), backupVO);
|
||||
throw new CloudRuntimeException("Unable to contact backend control plane to initiate backup");
|
||||
} catch (OperationTimedoutException e) {
|
||||
logger.error("Operation to initiate backup timed out for VM {}", vm.getInstanceName());
|
||||
backupVO.setStatus(Backup.Status.Failed);
|
||||
backupDao.remove(backupVO.getId());
|
||||
backupDao.update(backupVO.getId(), backupVO);
|
||||
throw new CloudRuntimeException("Operation to initiate backup timed out, please try again");
|
||||
}
|
||||
|
||||
|
|
@ -249,12 +249,12 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
|
|||
} else {
|
||||
logger.error("Failed to take backup for VM {}: {}", vm.getInstanceName(), answer != null ? answer.getDetails() : "No answer received");
|
||||
if (answer.getNeedsCleanup()) {
|
||||
logger.error("Backup cleanup failed for VM {}. Leaving the backup in Error state.", vm.getInstanceName());
|
||||
logger.error("Backup cleanup failed for VM {}. Leaving the backup in Error state. Backup should be manually deleted to free up the space", vm.getInstanceName());
|
||||
backupVO.setStatus(Backup.Status.Error);
|
||||
backupDao.update(backupVO.getId(), backupVO);
|
||||
} else {
|
||||
backupVO.setStatus(Backup.Status.Failed);
|
||||
backupDao.remove(backupVO.getId());
|
||||
backupDao.update(backupVO.getId(), backupVO);
|
||||
}
|
||||
return new Pair<>(false, null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,12 +17,9 @@
|
|||
package org.apache.cloudstack.api.command;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.cloud.user.Account;
|
||||
|
||||
import org.apache.cloudstack.api.ACL;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
|
|
@ -30,36 +27,41 @@ import org.apache.cloudstack.api.BaseCmd;
|
|||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.response.AccountResponse;
|
||||
import org.apache.cloudstack.api.response.DomainResponse;
|
||||
import org.apache.cloudstack.api.response.ProjectResponse;
|
||||
import org.apache.cloudstack.api.response.QuotaBalanceResponse;
|
||||
import org.apache.cloudstack.api.response.QuotaResponseBuilder;
|
||||
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
|
||||
import org.apache.cloudstack.api.response.QuotaStatementItemResponse;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
@APICommand(name = "quotaBalance", responseObject = QuotaStatementItemResponse.class, description = "Create a quota balance statement", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
|
||||
@APICommand(name = "quotaBalance", responseObject = QuotaBalanceResponse.class, description = "Create Quota balance statements for the provided Accounts or Projects.", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
|
||||
httpMethod = "GET")
|
||||
public class QuotaBalanceCmd extends BaseCmd {
|
||||
|
||||
|
||||
|
||||
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = true, description = "Account Id for which statement needs to be generated")
|
||||
@ACL
|
||||
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "Name of the Account for which balance statements will be generated. " +
|
||||
"Deprecated, please use 'accountid' instead.")
|
||||
private String accountName;
|
||||
|
||||
@ACL
|
||||
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class, description = "If domain Id is given and the caller is domain admin then the statement is generated for domain.")
|
||||
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "ID of the domain which the Account identified by 'account' belongs to." +
|
||||
"Deprecated, please use 'accountid' instead.")
|
||||
private Long domainId;
|
||||
|
||||
@Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "End of the period of the Quota balance." +
|
||||
ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS)
|
||||
@Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "Date of the last Quota balance to be returned. Must be informed together with the " +
|
||||
"startdate parameter, and can not be before startdate. " + ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS)
|
||||
private Date endDate;
|
||||
|
||||
@Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Start of the period of the Quota balance. " +
|
||||
@Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Date of the first Quota balance to be returned. Must be before today. " +
|
||||
ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS)
|
||||
private Date startDate;
|
||||
|
||||
@ACL
|
||||
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "List usage records for the specified Account")
|
||||
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "ID of the Account for which balance statements will be generated.")
|
||||
private Long accountId;
|
||||
|
||||
@ACL
|
||||
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "ID of the Project for which balance statements will be generated. Can not be specified with accountid.")
|
||||
private Long projectId;
|
||||
|
||||
@Inject
|
||||
QuotaResponseBuilder _responseBuilder;
|
||||
|
||||
|
|
@ -88,48 +90,32 @@ public class QuotaBalanceCmd extends BaseCmd {
|
|||
}
|
||||
|
||||
public Date getEndDate() {
|
||||
if (endDate == null){
|
||||
return null;
|
||||
}
|
||||
else{
|
||||
return _responseBuilder.startOfNextDay(new Date(endDate.getTime()));
|
||||
}
|
||||
return endDate;
|
||||
}
|
||||
|
||||
public void setEndDate(Date endDate) {
|
||||
this.endDate = endDate == null ? null : new Date(endDate.getTime());
|
||||
this.endDate = endDate;
|
||||
}
|
||||
|
||||
public Date getStartDate() {
|
||||
return startDate == null ? null : new Date(startDate.getTime());
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public void setStartDate(Date startDate) {
|
||||
this.startDate = startDate == null ? null : new Date(startDate.getTime());
|
||||
this.startDate = startDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
if (accountId != null) {
|
||||
return accountId;
|
||||
if (ObjectUtils.allNull(accountId, accountName, domainId, projectId)) {
|
||||
return -1;
|
||||
}
|
||||
Account account = _accountService.getActiveAccountByName(accountName, domainId);
|
||||
if (account != null) {
|
||||
return account.getAccountId();
|
||||
}
|
||||
return Account.ACCOUNT_ID_SYSTEM;
|
||||
return _accountService.finalizeAccountId(accountId, accountName, domainId, projectId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
List<QuotaBalanceVO> quotaUsage = _responseBuilder.getQuotaBalance(this);
|
||||
|
||||
QuotaBalanceResponse response;
|
||||
if (endDate == null) {
|
||||
response = _responseBuilder.createQuotaLastBalanceResponse(quotaUsage, getStartDate());
|
||||
} else {
|
||||
response = _responseBuilder.createQuotaBalanceResponse(quotaUsage, getStartDate(), new Date(endDate.getTime()));
|
||||
}
|
||||
QuotaBalanceResponse response = _responseBuilder.createQuotaBalanceResponse(this);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,137 +17,65 @@
|
|||
package org.apache.cloudstack.api.response;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
|
||||
|
||||
import com.cloud.serializer.Param;
|
||||
|
||||
public class QuotaBalanceResponse extends BaseResponse {
|
||||
|
||||
@SerializedName("accountid")
|
||||
@Param(description = "Account ID")
|
||||
private Long accountId;
|
||||
|
||||
@SerializedName("account")
|
||||
@Param(description = "Account name")
|
||||
private String accountName;
|
||||
|
||||
@SerializedName("domain")
|
||||
@Param(description = "Domain ID")
|
||||
private Long domainId;
|
||||
|
||||
@SerializedName("startquota")
|
||||
@Param(description = "Quota started with")
|
||||
private BigDecimal startQuota;
|
||||
|
||||
@SerializedName("endquota")
|
||||
@Param(description = "Quota by end of this period")
|
||||
private BigDecimal endQuota;
|
||||
|
||||
@SerializedName("credits")
|
||||
@Param(description = "List of credits made during this period")
|
||||
private List<QuotaCreditsResponse> credits = null;
|
||||
|
||||
@SerializedName("startdate")
|
||||
@Param(description = "Start date")
|
||||
private Date startDate = null;
|
||||
|
||||
@SerializedName("enddate")
|
||||
@Param(description = "End date")
|
||||
private Date endDate = null;
|
||||
|
||||
@SerializedName("currency")
|
||||
@Param(description = "Currency")
|
||||
@SerializedName(ApiConstants.CURRENCY)
|
||||
@Param(description = "Balance's currency.")
|
||||
private String currency;
|
||||
|
||||
@SerializedName(ApiConstants.DATE)
|
||||
@Param(description = "Balance's date.")
|
||||
private Date date;
|
||||
|
||||
@SerializedName(ApiConstants.BALANCE)
|
||||
@Param(description = "Balance's value.")
|
||||
private BigDecimal balance;
|
||||
|
||||
@SerializedName(ApiConstants.BALANCES)
|
||||
@Param(description = "List of balances in the period.")
|
||||
private List<QuotaBalanceResponse> balances;
|
||||
|
||||
public QuotaBalanceResponse() {
|
||||
super();
|
||||
credits = new ArrayList<QuotaCreditsResponse>();
|
||||
super("balance");
|
||||
}
|
||||
|
||||
public Long getAccountId() {
|
||||
return accountId;
|
||||
public QuotaBalanceResponse(Date date, BigDecimal balance) {
|
||||
super("balance");
|
||||
this.date = date;
|
||||
this.balance = balance;
|
||||
}
|
||||
|
||||
public void setAccountId(Long accountId) {
|
||||
this.accountId = accountId;
|
||||
public void setCurrency(String currency) {
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
public String getAccountName() {
|
||||
return accountName;
|
||||
}
|
||||
|
||||
public void setAccountName(String accountName) {
|
||||
this.accountName = accountName;
|
||||
}
|
||||
|
||||
public Long getDomainId() {
|
||||
return domainId;
|
||||
}
|
||||
|
||||
public void setDomainId(Long domainId) {
|
||||
this.domainId = domainId;
|
||||
}
|
||||
|
||||
public BigDecimal getStartQuota() {
|
||||
return startQuota;
|
||||
}
|
||||
|
||||
public void setStartQuota(BigDecimal startQuota) {
|
||||
this.startQuota = startQuota.setScale(2, RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
public BigDecimal getEndQuota() {
|
||||
return endQuota;
|
||||
}
|
||||
|
||||
public void setEndQuota(BigDecimal endQuota) {
|
||||
this.endQuota = endQuota.setScale(2, RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
public List<QuotaCreditsResponse> getCredits() {
|
||||
return credits;
|
||||
}
|
||||
|
||||
public void setCredits(List<QuotaCreditsResponse> credits) {
|
||||
this.credits = credits;
|
||||
}
|
||||
|
||||
public void addCredits(QuotaBalanceVO credit) {
|
||||
QuotaCreditsResponse cr = new QuotaCreditsResponse();
|
||||
cr.setCredit(credit.getCreditBalance());
|
||||
cr.setCreditedOn(credit.getUpdatedOn());
|
||||
credits.add(0, cr);
|
||||
}
|
||||
|
||||
public Date getStartDate() {
|
||||
return startDate == null ? null : new Date(startDate.getTime());
|
||||
}
|
||||
|
||||
public void setStartDate(Date startDate) {
|
||||
this.startDate = startDate == null ? null : new Date(startDate.getTime());
|
||||
}
|
||||
|
||||
public Date getEndDate() {
|
||||
return endDate == null ? null : new Date(endDate.getTime());
|
||||
}
|
||||
|
||||
public void setEndDate(Date endDate) {
|
||||
this.endDate = endDate == null ? null : new Date(endDate.getTime());
|
||||
public void setBalances(List<QuotaBalanceResponse> balances) {
|
||||
this.balances = balances;
|
||||
}
|
||||
|
||||
public String getCurrency() {
|
||||
return currency;
|
||||
}
|
||||
|
||||
public void setCurrency(String currency) {
|
||||
this.currency = currency;
|
||||
public List<QuotaBalanceResponse> getBalances() {
|
||||
return balances;
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public BigDecimal getBalance() {
|
||||
return balance;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import org.apache.cloudstack.api.command.QuotaTariffCreateCmd;
|
|||
import org.apache.cloudstack.api.command.QuotaTariffListCmd;
|
||||
import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd;
|
||||
import org.apache.cloudstack.api.command.QuotaValidateActivationRuleCmd;
|
||||
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
|
||||
import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO;
|
||||
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
|
||||
|
||||
|
|
@ -50,14 +49,10 @@ public interface QuotaResponseBuilder {
|
|||
|
||||
QuotaStatementResponse createQuotaStatementResponse(QuotaStatementCmd cmd);
|
||||
|
||||
QuotaBalanceResponse createQuotaBalanceResponse(List<QuotaBalanceVO> quotaUsage, Date startDate, Date endDate);
|
||||
QuotaBalanceResponse createQuotaBalanceResponse(QuotaBalanceCmd cmd);
|
||||
|
||||
Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(QuotaSummaryCmd cmd);
|
||||
|
||||
QuotaBalanceResponse createQuotaLastBalanceResponse(List<QuotaBalanceVO> quotaBalance, Date startDate);
|
||||
|
||||
List<QuotaBalanceVO> getQuotaBalance(QuotaBalanceCmd cmd);
|
||||
|
||||
QuotaCreditsResponse addQuotaCredits(Long accountId, Long domainId, Double amount, Long updatedBy, Boolean enforce);
|
||||
|
||||
List<QuotaEmailTemplateResponse> listQuotaEmailTemplates(QuotaEmailTemplateListCmd cmd);
|
||||
|
|
|
|||
|
|
@ -26,13 +26,10 @@ import java.time.ZoneId;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
|
@ -329,93 +326,18 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public QuotaBalanceResponse createQuotaBalanceResponse(List<QuotaBalanceVO> quotaBalance, Date startDate, Date endDate) {
|
||||
if (quotaBalance == null || quotaBalance.isEmpty()) {
|
||||
throw new InvalidParameterValueException("The request period does not contain balance entries.");
|
||||
}
|
||||
Collections.sort(quotaBalance, new Comparator<QuotaBalanceVO>() {
|
||||
@Override
|
||||
public int compare(QuotaBalanceVO o1, QuotaBalanceVO o2) {
|
||||
o1 = o1 == null ? new QuotaBalanceVO() : o1;
|
||||
o2 = o2 == null ? new QuotaBalanceVO() : o2;
|
||||
return o2.getUpdatedOn().compareTo(o1.getUpdatedOn()); // desc
|
||||
}
|
||||
});
|
||||
public QuotaBalanceResponse createQuotaBalanceResponse(QuotaBalanceCmd cmd) {
|
||||
List<QuotaBalanceVO> quotaBalances = _quotaService.listQuotaBalancesForAccount(cmd.getEntityOwnerId(), cmd.getStartDate(), cmd.getEndDate());
|
||||
|
||||
boolean have_balance_entries = false;
|
||||
//check that there is at least one balance entry
|
||||
for (Iterator<QuotaBalanceVO> it = quotaBalance.iterator(); it.hasNext();) {
|
||||
QuotaBalanceVO entry = it.next();
|
||||
if (entry.isBalanceEntry()) {
|
||||
have_balance_entries = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//if last entry is a credit deposit then remove that as that is already
|
||||
//accounted for in the starting balance after that entry, note the sort is desc
|
||||
if (have_balance_entries) {
|
||||
ListIterator<QuotaBalanceVO> li = quotaBalance.listIterator(quotaBalance.size());
|
||||
// Iterate in reverse.
|
||||
while (li.hasPrevious()) {
|
||||
QuotaBalanceVO entry = li.previous();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("createQuotaBalanceResponse: Entry=" + entry);
|
||||
}
|
||||
if (entry.getCreditsId() > 0) {
|
||||
li.remove();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
List<QuotaBalanceResponse> balances = quotaBalances.stream()
|
||||
.map(balance -> new QuotaBalanceResponse(balance.getUpdatedOn(), balance.getCreditBalance()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
int quota_activity = quotaBalance.size();
|
||||
QuotaBalanceResponse resp = new QuotaBalanceResponse();
|
||||
BigDecimal lastCredits = new BigDecimal(0);
|
||||
boolean consecutive = true;
|
||||
for (Iterator<QuotaBalanceVO> it = quotaBalance.iterator(); it.hasNext();) {
|
||||
QuotaBalanceVO entry = it.next();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("createQuotaBalanceResponse: All Credit Entry=" + entry);
|
||||
}
|
||||
if (entry.getCreditsId() > 0) {
|
||||
if (consecutive) {
|
||||
lastCredits = lastCredits.add(entry.getCreditBalance());
|
||||
}
|
||||
resp.addCredits(entry);
|
||||
it.remove();
|
||||
} else {
|
||||
consecutive = false;
|
||||
}
|
||||
}
|
||||
QuotaBalanceResponse response = new QuotaBalanceResponse();
|
||||
response.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
|
||||
response.setBalances(balances);
|
||||
|
||||
if (quota_activity > 0 && quotaBalance.size() > 0) {
|
||||
// order is desc last item is the start item
|
||||
QuotaBalanceVO startItem = quotaBalance.get(quotaBalance.size() - 1);
|
||||
QuotaBalanceVO endItem = quotaBalance.get(0);
|
||||
resp.setStartDate(startDate);
|
||||
resp.setStartQuota(startItem.getCreditBalance());
|
||||
resp.setEndDate(endDate);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("createQuotaBalanceResponse: Start Entry=" + startItem);
|
||||
logger.debug("createQuotaBalanceResponse: End Entry=" + endItem);
|
||||
}
|
||||
resp.setEndQuota(endItem.getCreditBalance().add(lastCredits));
|
||||
} else if (quota_activity > 0) {
|
||||
// order is desc last item is the start item
|
||||
resp.setStartDate(startDate);
|
||||
resp.setStartQuota(new BigDecimal(0));
|
||||
resp.setEndDate(endDate);
|
||||
resp.setEndQuota(new BigDecimal(0).add(lastCredits));
|
||||
} else {
|
||||
resp.setStartDate(startDate);
|
||||
resp.setEndDate(endDate);
|
||||
resp.setStartQuota(new BigDecimal(0));
|
||||
resp.setEndQuota(new BigDecimal(0));
|
||||
}
|
||||
resp.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
|
||||
resp.setObjectName("balance");
|
||||
return resp;
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -784,7 +706,7 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
|
|||
throw new InvalidParameterValueException("Account does not exist with account id " + accountId);
|
||||
}
|
||||
final boolean lockAccountEnforcement = "true".equalsIgnoreCase(QuotaConfig.QuotaEnableEnforcement.value());
|
||||
final BigDecimal currentAccountBalance = _quotaBalanceDao.lastQuotaBalance(accountId, domainId, startOfNextDay(new Date(depositedOn.getTime())));
|
||||
final BigDecimal currentAccountBalance = _quotaBalanceDao.getLastQuotaBalance(accountId, domainId);
|
||||
logger.debug("Depositing [{}] credits on adjusted date [{}]; current balance is [{}].", amount,
|
||||
DateUtil.displayDateInTimezone(QuotaManagerImpl.getUsageAggregationTimeZone(), depositedOn), currentAccountBalance);
|
||||
// update quota account with the balance
|
||||
|
|
@ -848,34 +770,6 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuotaBalanceResponse createQuotaLastBalanceResponse(List<QuotaBalanceVO> quotaBalance, Date startDate) {
|
||||
if (quotaBalance == null) {
|
||||
throw new InvalidParameterValueException("There are no balance entries on or before the requested date.");
|
||||
}
|
||||
if (startDate == null) {
|
||||
startDate = new Date();
|
||||
}
|
||||
QuotaBalanceResponse resp = new QuotaBalanceResponse();
|
||||
BigDecimal lastCredits = new BigDecimal(0);
|
||||
for (QuotaBalanceVO entry : quotaBalance) {
|
||||
logger.debug("createQuotaLastBalanceResponse Date={} balance={} credit={}",
|
||||
DateUtil.displayDateInTimezone(QuotaManagerImpl.getUsageAggregationTimeZone(), entry.getUpdatedOn()),
|
||||
entry.getCreditBalance(), entry.getCreditsId());
|
||||
|
||||
lastCredits = lastCredits.add(entry.getCreditBalance());
|
||||
}
|
||||
resp.setStartQuota(lastCredits);
|
||||
resp.setStartDate(startDate);
|
||||
resp.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
|
||||
resp.setObjectName("balance");
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QuotaBalanceVO> getQuotaBalance(QuotaBalanceCmd cmd) {
|
||||
return _quotaService.findQuotaBalanceVO(cmd.getAccountId(), cmd.getAccountName(), cmd.getDomainId(), cmd.getStartDate(), cmd.getEndDate());
|
||||
}
|
||||
@Override
|
||||
public Date startOfNextDay(Date date) {
|
||||
LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public interface QuotaService extends PluggableService {
|
|||
|
||||
List<QuotaUsageJoinVO> getQuotaUsage(Long accountId, String accountName, Long domainId, Integer usageType, Date startDate, Date endDate);
|
||||
|
||||
List<QuotaBalanceVO> findQuotaBalanceVO(Long accountId, String accountName, Long domainId, Date startDate, Date endDate);
|
||||
List<QuotaBalanceVO> listQuotaBalancesForAccount(Long accountId, Date startDate, Date endDate);
|
||||
|
||||
void setLockAccount(Long accountId, Boolean state);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import javax.naming.ConfigurationException;
|
|||
|
||||
import com.cloud.projects.ProjectManager;
|
||||
import com.cloud.user.AccountService;
|
||||
import com.cloud.utils.DateUtil;
|
||||
import org.apache.cloudstack.api.command.QuotaBalanceCmd;
|
||||
import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd;
|
||||
import org.apache.cloudstack.api.command.QuotaCreditsCmd;
|
||||
|
|
@ -58,17 +59,16 @@ import org.apache.cloudstack.quota.vo.QuotaAccountVO;
|
|||
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
|
||||
import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.configuration.Config;
|
||||
import com.cloud.domain.dao.DomainDao;
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.exception.PermissionDeniedException;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.user.AccountVO;
|
||||
import com.cloud.user.dao.AccountDao;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.db.Filter;
|
||||
|
||||
@Component
|
||||
public class QuotaServiceImpl extends ManagerBase implements QuotaService, Configurable, QuotaConfig {
|
||||
|
|
@ -152,63 +152,53 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<QuotaBalanceVO> findQuotaBalanceVO(Long accountId, String accountName, Long domainId, Date startDate, Date endDate) {
|
||||
if ((accountId == null) && (accountName != null) && (domainId != null)) {
|
||||
Account userAccount = null;
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
if (_domainDao.isChildDomain(caller.getDomainId(), domainId)) {
|
||||
Filter filter = new Filter(AccountVO.class, "id", Boolean.FALSE, null, null);
|
||||
List<AccountVO> accounts = _accountDao.listAccounts(accountName, domainId, filter);
|
||||
if (!accounts.isEmpty()) {
|
||||
userAccount = accounts.get(0);
|
||||
}
|
||||
if (userAccount != null) {
|
||||
accountId = userAccount.getId();
|
||||
} else {
|
||||
throw new InvalidParameterValueException("Unable to find account " + accountName + " in domain " + domainId);
|
||||
}
|
||||
} else {
|
||||
throw new PermissionDeniedException("Invalid Domain Id or Account");
|
||||
public List<QuotaBalanceVO> listQuotaBalancesForAccount(Long accountId, Date startDate, Date endDate) {
|
||||
validateStartDateAndEndDateForListQuotaBalancesForAccount(startDate, endDate);
|
||||
|
||||
if (accountId == -1) {
|
||||
accountId = CallContext.current().getCallingAccountId();
|
||||
}
|
||||
Account account = _accountDao.findByIdIncludingRemoved(accountId);
|
||||
Long domainId = account.getDomainId();
|
||||
|
||||
if (startDate == null && endDate == null) {
|
||||
logger.debug("Retrieving last quota balance for {}.", account);
|
||||
QuotaBalanceVO lastQuotaBalance = _quotaBalanceDao.getLastQuotaBalanceEntry(accountId, domainId, null);
|
||||
|
||||
if (lastQuotaBalance == null) {
|
||||
logger.debug("Did not found a quota balance entry for {}.", account);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return List.of(lastQuotaBalance);
|
||||
}
|
||||
|
||||
startDate = startDate == null ? new Date() : startDate;
|
||||
|
||||
if (endDate == null) {
|
||||
// adjust start date to end of day as there is no end date
|
||||
startDate = _respBldr.startOfNextDay(startDate);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("getQuotaBalance1: Getting quota balance records for account: " + accountId + ", domainId: " + domainId + ", on or before " + startDate);
|
||||
}
|
||||
List<QuotaBalanceVO> qbrecords = _quotaBalanceDao.lastQuotaBalanceVO(accountId, domainId, startDate);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Found records size=" + qbrecords.size());
|
||||
}
|
||||
if (qbrecords.isEmpty()) {
|
||||
logger.info("Incorrect Date there are no quota records before this date " + startDate);
|
||||
return qbrecords;
|
||||
} else {
|
||||
return qbrecords;
|
||||
}
|
||||
} else {
|
||||
if (startDate.before(endDate)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("getQuotaBalance2: Getting quota balance records for account: " + accountId + ", domainId: " + domainId + ", between " + startDate
|
||||
+ " and " + endDate);
|
||||
}
|
||||
List<QuotaBalanceVO> qbrecords = _quotaBalanceDao.findQuotaBalance(accountId, domainId, startDate, endDate);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("getQuotaBalance3: Found records size=" + qbrecords.size());
|
||||
}
|
||||
if (qbrecords.isEmpty()) {
|
||||
logger.info("There are no quota records between these dates start date " + startDate + " and end date:" + endDate);
|
||||
return qbrecords;
|
||||
} else {
|
||||
return qbrecords;
|
||||
}
|
||||
} else {
|
||||
throw new InvalidParameterValueException("Incorrect Date Range. Start date: " + startDate + " is after end date:" + endDate);
|
||||
}
|
||||
endDate = DateUtils.addDays(new Date(), -1);
|
||||
}
|
||||
|
||||
List<QuotaBalanceVO> quotaBalances = _quotaBalanceDao.listQuotaBalances(accountId, domainId, startDate, endDate);
|
||||
|
||||
if (quotaBalances.isEmpty()) {
|
||||
logger.info("There are no quota balances for {} between [{}] and [{}].", account,
|
||||
DateUtil.getOutputString(startDate), DateUtil.getOutputString(endDate));
|
||||
}
|
||||
|
||||
return quotaBalances;
|
||||
}
|
||||
|
||||
protected void validateStartDateAndEndDateForListQuotaBalancesForAccount(Date startDate, Date endDate) {
|
||||
if (startDate == null && endDate != null) {
|
||||
throw new InvalidParameterValueException("Parameter \"enddate\" must be informed together with parameter \"startdate\".");
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
if (startDate != null && startDate.after(now)) {
|
||||
throw new InvalidParameterValueException("The last balance can be at most from yesterday; therefore, the start date must be before today.");
|
||||
}
|
||||
|
||||
if (ObjectUtils.allNotNull(startDate, endDate) && startDate.after(endDate)) {
|
||||
throw new InvalidParameterValueException("The start date cannot be after the end date.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,18 +16,15 @@
|
|||
// under the License.
|
||||
package org.apache.cloudstack.api.command;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cloudstack.api.response.QuotaBalanceResponse;
|
||||
import org.apache.cloudstack.api.response.QuotaResponseBuilder;
|
||||
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
|
@ -36,31 +33,20 @@ import junit.framework.TestCase;
|
|||
public class QuotaBalanceCmdTest extends TestCase {
|
||||
|
||||
@Mock
|
||||
QuotaResponseBuilder responseBuilder;
|
||||
QuotaResponseBuilder quotaResponseBuilderMock;
|
||||
|
||||
@InjectMocks
|
||||
@Spy
|
||||
QuotaBalanceCmd quotaBalanceCmdSpy;
|
||||
|
||||
@Test
|
||||
public void testQuotaBalanceCmd() throws NoSuchFieldException, IllegalAccessException {
|
||||
QuotaBalanceCmd cmd = new QuotaBalanceCmd();
|
||||
Field rbField = QuotaBalanceCmd.class.getDeclaredField("_responseBuilder");
|
||||
rbField.setAccessible(true);
|
||||
rbField.set(cmd, responseBuilder);
|
||||
public void executeTestSetResponseObject() {
|
||||
QuotaBalanceResponse expected = new QuotaBalanceResponse();
|
||||
Mockito.doReturn(expected).when(quotaResponseBuilderMock).createQuotaBalanceResponse(Mockito.eq(quotaBalanceCmdSpy));
|
||||
|
||||
List<QuotaBalanceVO> quotaBalanceVOList = new ArrayList<QuotaBalanceVO>();
|
||||
Mockito.when(responseBuilder.getQuotaBalance(Mockito.any(cmd.getClass()))).thenReturn(quotaBalanceVOList);
|
||||
Mockito.when(responseBuilder.createQuotaLastBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class))).thenReturn(new QuotaBalanceResponse());
|
||||
Mockito.when(responseBuilder.createQuotaBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class), Mockito.any(Date.class))).thenReturn(new QuotaBalanceResponse());
|
||||
Mockito.lenient().when(responseBuilder.startOfNextDay(Mockito.any(Date.class))).thenReturn(new Date());
|
||||
quotaBalanceCmdSpy.execute();
|
||||
|
||||
// end date not specified
|
||||
cmd.setStartDate(new Date());
|
||||
cmd.setEndDate(null);
|
||||
cmd.execute();
|
||||
Mockito.verify(responseBuilder, Mockito.times(1)).createQuotaLastBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class));
|
||||
Mockito.verify(responseBuilder, Mockito.times(0)).createQuotaBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class), Mockito.any(Date.class));
|
||||
|
||||
// end date specified
|
||||
cmd.setEndDate(new Date());
|
||||
cmd.execute();
|
||||
Mockito.verify(responseBuilder, Mockito.times(1)).createQuotaBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class), Mockito.any(Date.class));
|
||||
Assert.assertEquals(expected, quotaBalanceCmdSpy.getResponseObject());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import com.cloud.user.UserVO;
|
|||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.command.QuotaBalanceCmd;
|
||||
import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd;
|
||||
import org.apache.cloudstack.api.command.QuotaCreditsListCmd;
|
||||
import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd;
|
||||
|
|
@ -249,7 +250,7 @@ public class QuotaResponseBuilderImplTest extends TestCase {
|
|||
credit.setCredit(new BigDecimal(amount));
|
||||
|
||||
Mockito.when(quotaCreditsDaoMock.saveCredits(Mockito.any(QuotaCreditsVO.class))).thenReturn(credit);
|
||||
Mockito.when(quotaBalanceDaoMock.lastQuotaBalance(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(Date.class))).thenReturn(new BigDecimal(111));
|
||||
Mockito.when(quotaBalanceDaoMock.getLastQuotaBalance(Mockito.anyLong(), Mockito.anyLong())).thenReturn(new BigDecimal(111));
|
||||
Mockito.doReturn(userVoMock).when(quotaResponseBuilderSpy).getCreditorForQuotaCredits(credit);
|
||||
|
||||
AccountVO account = new AccountVO();
|
||||
|
|
@ -295,33 +296,6 @@ public class QuotaResponseBuilderImplTest extends TestCase {
|
|||
assertTrue(quotaResponseBuilderSpy.updateQuotaEmailTemplate(cmd));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateQuotaLastBalanceResponse() {
|
||||
List<QuotaBalanceVO> quotaBalance = new ArrayList<>();
|
||||
// null balance test
|
||||
try {
|
||||
quotaResponseBuilderSpy.createQuotaLastBalanceResponse(null, new Date());
|
||||
} catch (InvalidParameterValueException e) {
|
||||
assertTrue(e.getMessage().equals("There are no balance entries on or before the requested date."));
|
||||
}
|
||||
|
||||
// empty balance test
|
||||
try {
|
||||
quotaResponseBuilderSpy.createQuotaLastBalanceResponse(quotaBalance, new Date());
|
||||
} catch (InvalidParameterValueException e) {
|
||||
assertTrue(e.getMessage().equals("There are no balance entries on or before the requested date."));
|
||||
}
|
||||
|
||||
// valid balance test
|
||||
QuotaBalanceVO entry = new QuotaBalanceVO();
|
||||
entry.setAccountId(2L);
|
||||
entry.setCreditBalance(new BigDecimal(100));
|
||||
quotaBalance.add(entry);
|
||||
quotaBalance.add(entry);
|
||||
QuotaBalanceResponse resp = quotaResponseBuilderSpy.createQuotaLastBalanceResponse(quotaBalance, null);
|
||||
assertTrue(resp.getStartQuota().compareTo(new BigDecimal(200)) == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartOfNextDayWithoutParameters() {
|
||||
Date nextDate = quotaResponseBuilderSpy.startOfNextDay();
|
||||
|
|
@ -919,6 +893,46 @@ public class QuotaResponseBuilderImplTest extends TestCase {
|
|||
Assert.assertTrue(formattedVariables.containsValue("zonename"));
|
||||
}
|
||||
|
||||
private List<QuotaBalanceVO> getQuotaBalancesForTest() {
|
||||
List<QuotaBalanceVO> balances = new ArrayList<>();
|
||||
|
||||
QuotaBalanceVO balance = new QuotaBalanceVO();
|
||||
balance.setUpdatedOn(new Date());
|
||||
balance.setCreditBalance(BigDecimal.valueOf(-10.42));
|
||||
balances.add(balance);
|
||||
|
||||
balance = new QuotaBalanceVO();
|
||||
balance.setUpdatedOn(new Date());
|
||||
balance.setCreditBalance(BigDecimal.valueOf(-18.94));
|
||||
balances.add(balance);
|
||||
|
||||
balance = new QuotaBalanceVO();
|
||||
balance.setUpdatedOn(new Date());
|
||||
balance.setCreditBalance(BigDecimal.valueOf(-29.37));
|
||||
balances.add(balance);
|
||||
|
||||
return balances;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createQuotaBalancesResponseTestCreateResponse() {
|
||||
List<QuotaBalanceVO> balances = getQuotaBalancesForTest();
|
||||
|
||||
QuotaBalanceResponse expected = new QuotaBalanceResponse();
|
||||
expected.setObjectName("balance");
|
||||
expected.setCurrency("$");
|
||||
|
||||
Mockito.doReturn(balances).when(quotaServiceMock).listQuotaBalancesForAccount(Mockito.any(), Mockito.any(), Mockito.any());
|
||||
QuotaBalanceResponse result = quotaResponseBuilderSpy.createQuotaBalanceResponse(new QuotaBalanceCmd());
|
||||
|
||||
Assert.assertEquals(expected.getCurrency(), result.getCurrency());
|
||||
|
||||
for (int i = 0; i < balances.size(); i++) {
|
||||
Assert.assertEquals(balances.get(i).getUpdatedOn(), result.getBalances().get(i).getDate());
|
||||
Assert.assertEquals(balances.get(i).getCreditBalance(), result.getBalances().get(i).getBalance());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformedTestUsageTypeDifferentFromNullDoNothing() {
|
||||
List<QuotaUsageJoinVO> listUsage = new ArrayList<>();
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package org.apache.cloudstack.quota;
|
|||
|
||||
import com.cloud.configuration.Config;
|
||||
import com.cloud.domain.dao.DomainDao;
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.user.AccountVO;
|
||||
import com.cloud.user.dao.AccountDao;
|
||||
import com.cloud.utils.db.TransactionLegacy;
|
||||
|
|
@ -31,7 +32,9 @@ import org.apache.cloudstack.quota.dao.QuotaUsageDao;
|
|||
import org.apache.cloudstack.quota.dao.QuotaUsageJoinDao;
|
||||
import org.apache.cloudstack.quota.vo.QuotaAccountVO;
|
||||
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
|
@ -43,7 +46,6 @@ import org.mockito.junit.MockitoJUnitRunner;
|
|||
|
||||
import javax.naming.ConfigurationException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
|
@ -51,6 +53,8 @@ import java.util.List;
|
|||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class QuotaServiceImplTest extends TestCase {
|
||||
|
||||
@Mock
|
||||
AccountVO accountVoMock;
|
||||
@Mock
|
||||
AccountDao accountDaoMock;
|
||||
@Mock
|
||||
|
|
@ -67,9 +71,6 @@ public class QuotaServiceImplTest extends TestCase {
|
|||
QuotaUsageJoinDao quotaUsageJoinDaoMock;
|
||||
@Mock
|
||||
QuotaResponseBuilder respBldr;
|
||||
@Mock
|
||||
private AccountVO accountVoMock;
|
||||
|
||||
@Spy
|
||||
@InjectMocks
|
||||
QuotaServiceImpl quotaServiceImplSpy;
|
||||
|
|
@ -112,30 +113,6 @@ public class QuotaServiceImplTest extends TestCase {
|
|||
quotaServiceImplSpy.configure("randomName", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindQuotaBalanceVO() {
|
||||
final long accountId = 2L;
|
||||
final String accountName = "admin123";
|
||||
final long domainId = 1L;
|
||||
final Date startDate = new DateTime().minusDays(2).toDate();
|
||||
final Date endDate = new Date();
|
||||
|
||||
List<QuotaBalanceVO> records = new ArrayList<>();
|
||||
QuotaBalanceVO qb = new QuotaBalanceVO();
|
||||
qb.setCreditBalance(new BigDecimal(100));
|
||||
qb.setAccountId(accountId);
|
||||
records.add(qb);
|
||||
|
||||
Mockito.when(respBldr.startOfNextDay(Mockito.any(Date.class))).thenReturn(startDate);
|
||||
Mockito.when(quotaBalanceDao.findQuotaBalance(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.any(Date.class), Mockito.any(Date.class))).thenReturn(records);
|
||||
Mockito.when(quotaBalanceDao.lastQuotaBalanceVO(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.any(Date.class))).thenReturn(records);
|
||||
|
||||
// with enddate
|
||||
assertTrue(quotaServiceImplSpy.findQuotaBalanceVO(accountId, accountName, domainId, startDate, endDate).get(0).equals(qb));
|
||||
// without enddate
|
||||
assertTrue(quotaServiceImplSpy.findQuotaBalanceVO(accountId, accountName, domainId, startDate, null).get(0).equals(qb));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetQuotaUsage() {
|
||||
final long accountId = 2L;
|
||||
|
|
@ -182,4 +159,66 @@ public class QuotaServiceImplTest extends TestCase {
|
|||
Mockito.verify(quotaAcc, Mockito.times(1)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class));
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void validateStartDateAndEndDateForListQuotaBalancesForAccountTestStartDateIsNullAndEndDateIsNotNullThrowsInvalidParameterException() {
|
||||
quotaServiceImplSpy.validateStartDateAndEndDateForListQuotaBalancesForAccount(null, new Date());
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void validateStartDateAndEndDateForListQuotaBalancesForAccountTestStartDateIsAfterNowThrowsInvalidParameterValueException() {
|
||||
Date startDate = DateUtils.addMinutes(new Date(), 1);
|
||||
quotaServiceImplSpy.validateStartDateAndEndDateForListQuotaBalancesForAccount(startDate, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateStartDateAndEndDateForListQuotaBalancesForAccountTestEndDateIsAfterNowDoesNothing() {
|
||||
Date startDate = DateUtils.addMinutes(new Date(), -1);
|
||||
Date endDate = DateUtils.addMinutes(new Date(), 1);
|
||||
quotaServiceImplSpy.validateStartDateAndEndDateForListQuotaBalancesForAccount(startDate, endDate);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void validateStartDateAndEndDateForListQuotaBalancesForAccountTestStartDateIsAfterEndDateThrowsInvalidParameterValueException() {
|
||||
Date startDate = DateUtils.addMinutes(new Date(), -10);
|
||||
Date endDate = DateUtils.addMinutes(new Date(), -15);
|
||||
quotaServiceImplSpy.validateStartDateAndEndDateForListQuotaBalancesForAccount(startDate, endDate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listQuotaBalancesForAccountTestLastQuotaBalanceIsNullReturnsEmptyList() {
|
||||
Mockito.doNothing().when(quotaServiceImplSpy).validateStartDateAndEndDateForListQuotaBalancesForAccount(Mockito.any(), Mockito.any());
|
||||
Mockito.doReturn(null).when(quotaBalanceDao).getLastQuotaBalanceEntry(Mockito.anyLong(), Mockito.anyLong(), Mockito.any());
|
||||
Mockito.doReturn(Mockito.mock(AccountVO.class)).when(accountDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
|
||||
|
||||
List<QuotaBalanceVO> result = quotaServiceImplSpy.listQuotaBalancesForAccount(1L, null, null);
|
||||
|
||||
Assert.assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listQuotaBalancesForAccountTestLastQuotaBalanceIsNotNullReturnsIt() {
|
||||
QuotaBalanceVO expected = new QuotaBalanceVO();
|
||||
|
||||
Mockito.doNothing().when(quotaServiceImplSpy).validateStartDateAndEndDateForListQuotaBalancesForAccount(Mockito.any(), Mockito.any());
|
||||
Mockito.doReturn(expected).when(quotaBalanceDao).getLastQuotaBalanceEntry(Mockito.anyLong(), Mockito.anyLong(), Mockito.any());
|
||||
Mockito.doReturn(Mockito.mock(AccountVO.class)).when(accountDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
|
||||
|
||||
List<QuotaBalanceVO> result = quotaServiceImplSpy.listQuotaBalancesForAccount(1L, null, null);
|
||||
|
||||
Assert.assertEquals(expected, result.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listQuotaBalancesForAccountTestReturnsQuotaBalances() {
|
||||
List<QuotaBalanceVO> expected = new ArrayList<>();
|
||||
|
||||
Mockito.doNothing().when(quotaServiceImplSpy).validateStartDateAndEndDateForListQuotaBalancesForAccount(Mockito.any(), Mockito.any());
|
||||
Mockito.doReturn(expected).when(quotaBalanceDao).listQuotaBalances(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.any());
|
||||
Mockito.doReturn(Mockito.mock(AccountVO.class)).when(accountDaoMock).findByIdIncludingRemoved(Mockito.anyLong());
|
||||
|
||||
List<QuotaBalanceVO> result = quotaServiceImplSpy.listQuotaBalancesForAccount(1L, new Date(), null);
|
||||
|
||||
Assert.assertEquals(expected, result);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -202,13 +202,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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -508,12 +508,33 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
|
|||
// if there is only one template-for property left for templates, the template isn't needed anymore
|
||||
// or if it isn't a template anyway, it will not have this Aux property
|
||||
// _cs-template-for- properties work like a ref-count.
|
||||
if (rd.getProps().keySet().stream()
|
||||
long remainingTemplateRefs = rd.getProps().keySet().stream()
|
||||
.filter(key -> key.startsWith("Aux/" + LinstorUtil.CS_TEMPLATE_FOR_PREFIX))
|
||||
.count() == expectedProps) {
|
||||
.count();
|
||||
if (remainingTemplateRefs == expectedProps) {
|
||||
// Surface the legacy case where a resource has zero `_cs-template-for-` aux
|
||||
// properties even though we never decremented one — that's a template predating
|
||||
// the ref-count convention, or a stale orphan. Logging before deletion lets
|
||||
// operators audit how many such orphans existed at upgrade time.
|
||||
if (expectedProps == 0) {
|
||||
logger.info("Linstor: deleting resource {} which has no _cs-template-for- aux properties " +
|
||||
"(legacy template predating the ref-count convention, or a stale orphan). " +
|
||||
"Resource group context: {}", rd.getName(), rscGrpName);
|
||||
}
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
@ -491,7 +543,26 @@ public class LinstorUtil {
|
|||
.filter(rscDfn -> rscDfn.getProps().containsKey(LinstorUtil.getTemplateForAuxPropKey(rscGrpName)))
|
||||
.findFirst();
|
||||
|
||||
return rd.orElseGet(() -> rdsStartingWith.get(0));
|
||||
if (rd.isPresent()) {
|
||||
return rd.get();
|
||||
}
|
||||
// Fallback: no resource has the exact "_cs-template-for-<rscGrpName>" property.
|
||||
// This happens when (a) the matched resource is a legacy template created before that
|
||||
// convention was introduced, or (b) the template was cached by a different resource
|
||||
// group and the operator hopes to share it. Log so the ambiguity is visible — silent
|
||||
// first-match fallback has previously routed clones to the wrong template when
|
||||
// multiple resource groups coexisted on the same controller.
|
||||
ResourceDefinition fallback = rdsStartingWith.get(0);
|
||||
LOGGER.warn("LINSTOR findResourceDefinition: no resource for '{}' has the expected " +
|
||||
"Aux property '{}' for resource group '{}'; falling back to first match '{}' " +
|
||||
"(present aux properties: {}). If this is wrong, set the property explicitly " +
|
||||
"or remove the unrelated resource definition.",
|
||||
rscName, getTemplateForAuxPropKey(rscGrpName), rscGrpName,
|
||||
fallback.getName(),
|
||||
fallback.getProps().keySet().stream()
|
||||
.filter(k -> k.startsWith("Aux/" + CS_TEMPLATE_FOR_PREFIX))
|
||||
.collect(Collectors.toList()));
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public static boolean isRscDiskless(ResourceWithVolumes rsc) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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
23
pom.xml
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1389,20 +1389,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);
|
||||
|
||||
|
|
@ -3848,6 +3847,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());
|
||||
}
|
||||
|
||||
|
|
@ -3918,7 +3922,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
|||
if (getActiveAccountById(accountId) != null) {
|
||||
return accountId;
|
||||
}
|
||||
throw new InvalidParameterValueException(String.format("Unable to find account with ID [%s].", accountId));
|
||||
throw new InvalidParameterValueException(String.format("Unable to find account with the specified ID."));
|
||||
}
|
||||
|
||||
if (accountName == null && domainId == null) {
|
||||
|
|
@ -3934,16 +3938,16 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
|||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Both %s and %s are needed if using either. Consider using %s instead.",
|
||||
ApiConstants.ACCOUNT, ApiConstants.DOMAIN_ID, ApiConstants.ACCOUNT_ID));
|
||||
}
|
||||
throw new InvalidParameterValueException(String.format("Unable to find account by name [%s] on domain [%s].", accountName, domainId));
|
||||
throw new InvalidParameterValueException(String.format("Unable to find account with name [%s] on the specified domain.", accountName));
|
||||
}
|
||||
|
||||
protected long getActiveProjectAccountByProjectId(long projectId) {
|
||||
Project project = _projectMgr.getProject(projectId);
|
||||
if (project == null) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Unable to find project with ID [%s].", projectId));
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find project with the specified ID.");
|
||||
}
|
||||
if (project.getState() != Project.State.Active) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Project with ID [%s] is not active.", projectId));
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Project is not active.");
|
||||
}
|
||||
return project.getProjectAccountId();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -156,7 +156,6 @@ import org.apache.commons.collections.CollectionUtils;
|
|||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang.math.NumberUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
|
@ -377,6 +376,7 @@ import com.cloud.utils.DateUtil;
|
|||
import com.cloud.utils.Journal;
|
||||
import com.cloud.utils.NumbersUtil;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.StringUtils;
|
||||
import com.cloud.utils.component.ComponentContext;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.concurrency.NamedThreadFactory;
|
||||
|
|
@ -890,10 +890,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
|
||||
private boolean resetVMPasswordInternal(Long vmId, String password) throws ResourceUnavailableException, InsufficientCapacityException {
|
||||
Long userId = CallContext.current().getCallingUserId();
|
||||
long userId = CallContext.current().getCallingUserId();
|
||||
VMInstanceVO vmInstance = _vmDao.findById(vmId);
|
||||
|
||||
if (password == null || password.equals("")) {
|
||||
if (StringUtils.isEmpty(password)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1078,7 +1078,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
|
||||
private boolean resetVMSSHKeyInternal(Long vmId, String sshPublicKeys, String keypairnames) throws ResourceUnavailableException, InsufficientCapacityException {
|
||||
Long userId = CallContext.current().getCallingUserId();
|
||||
long userId = CallContext.current().getCallingUserId();
|
||||
VMInstanceVO vmInstance = _vmDao.findById(vmId);
|
||||
|
||||
VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vmInstance.getTemplateId());
|
||||
|
|
@ -1250,7 +1250,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
if (vmInstance == null) {
|
||||
throw new InvalidParameterValueException("unable to find an Instance with id " + vmId);
|
||||
} else if (!(vmInstance.getState().equals(State.Stopped))) {
|
||||
throw new InvalidParameterValueException("Unable to upgrade Instance " + vmInstance.toString() + " " + " in state " + vmInstance.getState()
|
||||
throw new InvalidParameterValueException("Unable to upgrade Instance " + vmInstance + " " + " in state " + vmInstance.getState()
|
||||
+ "; make sure the Instance is stopped");
|
||||
}
|
||||
|
||||
|
|
@ -1743,7 +1743,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
oldNetworkOfferingId = oldDefaultNetwork.getNetworkOfferingId();
|
||||
}
|
||||
NicVO existingVO = _nicDao.findById(existing.id);
|
||||
Integer chosenID = nic.getDeviceId();
|
||||
int chosenID = nic.getDeviceId();
|
||||
Integer existingID = existing.getDeviceId();
|
||||
|
||||
Network newdefault = null;
|
||||
|
|
@ -2116,25 +2116,26 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
int newCpu = newServiceOffering.getCpu();
|
||||
int newMemory = newServiceOffering.getRamSize();
|
||||
int newSpeed = newServiceOffering.getSpeed();
|
||||
boolean cpuCapEnabledForTheNewOffering = newServiceOffering.getLimitCpuUse();
|
||||
int currentCpu = currentServiceOffering.getCpu();
|
||||
int currentMemory = currentServiceOffering.getRamSize();
|
||||
int currentSpeed = currentServiceOffering.getSpeed();
|
||||
boolean cpuCapEnabledForTheCurrentOffering = currentServiceOffering.getLimitCpuUse();
|
||||
int memoryDiff = newMemory - currentMemory;
|
||||
int cpuDiff = newCpu * newSpeed - currentCpu * currentSpeed;
|
||||
|
||||
// Don't allow to scale when (Any of the new values less than current values) OR (All current and new values are same)
|
||||
if ((newSpeed < currentSpeed || newMemory < currentMemory || newCpu < currentCpu) || (newSpeed == currentSpeed && newMemory == currentMemory && newCpu == currentCpu)) {
|
||||
String message = String.format("While the VM is running, only scalling up it is supported. New service offering {\"memory\": %s, \"speed\": %s, \"cpu\": %s} should"
|
||||
+ " have at least one value (ram, speed or cpu) greater than the current values {\"memory\": %s, \"speed\": %s, \"cpu\": %s}.", newMemory, newSpeed, newCpu,
|
||||
currentMemory, currentSpeed, currentCpu);
|
||||
|
||||
throw new InvalidParameterValueException(message);
|
||||
boolean scalingDown = newSpeed < currentSpeed || newMemory < currentMemory || newCpu < currentCpu;
|
||||
if (scalingDown) {
|
||||
throw new InvalidParameterValueException(String.format("Scaling down is not supported while the VM is running. The new service offering attributes " +
|
||||
"{\"memory\": %s, \"CPU speed\": %s, \"vCPUs\": %s} must not be lower than the current values {\"memory\": %s, \"CPU speed\": %s, \"vCPUs\": %s}.",
|
||||
newMemory, newSpeed, newCpu, currentMemory, currentSpeed, currentCpu));
|
||||
}
|
||||
|
||||
if (vmHypervisorType.equals(HypervisorType.KVM) && !currentServiceOffering.isDynamic()) {
|
||||
String message = String.format("Unable to live scale VM on KVM when current service offering is a \"Fixed Offering\". KVM needs the tag \"maxMemory\" to live scale and it is only configured when VM is deployed with a custom service offering and \"Dynamic Scalable\" is enabled.");
|
||||
logger.info(message);
|
||||
throw new InvalidParameterValueException(message);
|
||||
boolean sameAmountOfResourcesAsThePreviousOffering = newSpeed == currentSpeed && newMemory == currentMemory && newCpu == currentCpu;
|
||||
boolean cpuCapChange = cpuCapEnabledForTheCurrentOffering != cpuCapEnabledForTheNewOffering;
|
||||
if (sameAmountOfResourcesAsThePreviousOffering && (vmHypervisorType != HypervisorType.KVM || !cpuCapChange)) {
|
||||
throw new InvalidParameterValueException("While the VM is running, scaling to a service offering with the same attributes (memory, CPU speed and vCPUs) " +
|
||||
"is only allowed when the CPU cap is changed.");
|
||||
}
|
||||
|
||||
serviceOfferingDao.loadDetails(currentServiceOffering);
|
||||
|
|
@ -2171,14 +2172,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
|
||||
// Check vm flag
|
||||
if (!vmInstance.isDynamicallyScalable()) {
|
||||
throw new CloudRuntimeException(String.format("Unable to scale %s as it does not have tools to support dynamic scaling.", vmInstance.toString()));
|
||||
throw new CloudRuntimeException(String.format("Unable to scale %s as it does not have tools to support dynamic scaling.", vmInstance));
|
||||
}
|
||||
|
||||
// Check disable threshold for cluster is not crossed
|
||||
HostVO host = _hostDao.findById(vmInstance.getHostId());
|
||||
_hostDao.loadDetails(host);
|
||||
if (_capacityMgr.checkIfClusterCrossesThreshold(host.getClusterId(), cpuDiff, memoryDiff)) {
|
||||
throw new CloudRuntimeException(String.format("Unable to scale %s due to insufficient resources.", vmInstance.toString()));
|
||||
throw new CloudRuntimeException(String.format("Unable to scale %s due to insufficient resources.", vmInstance));
|
||||
}
|
||||
|
||||
while (retry-- != 0) { // It's != so that it can match -1.
|
||||
|
|
@ -2200,8 +2201,19 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
excludes.addHost(vmInstance.getHostId());
|
||||
}
|
||||
|
||||
boolean autoMigrateVmToASuitableHost = AutoMigrateVmOnLiveScaleInsufficientCapacity.valueIn(host.getClusterId());
|
||||
if (!existingHostHasCapacity && !autoMigrateVmToASuitableHost) {
|
||||
logger.error("Unable to scale the VM [{}] because the host [{}] in which it is currently allocated does not " +
|
||||
"have enough compute capacity to scale the instance and the VM should not be automatically migrated to another host " +
|
||||
"([{}] setting is [false]).", vmInstance.getInstanceName(), host.getName(), AutoMigrateVmOnLiveScaleInsufficientCapacity.key());
|
||||
return false;
|
||||
}
|
||||
|
||||
// #2 migrate the vm if host doesn't have capacity or is in avoid set
|
||||
if (!existingHostHasCapacity) {
|
||||
logger.info("Host [{}] does not have enough compute capacity to scale the instance [{}]. Since the [{}] setting is " +
|
||||
"[true], the VM will be migrated to a suitable host and, if succeeded, the VM will be live scaled to the requested " +
|
||||
"compute offering.", host.getName(), vmInstance.getInstanceName(), AutoMigrateVmOnLiveScaleInsufficientCapacity.key());
|
||||
_itMgr.findHostAndMigrate(vmInstance.getUuid(), newServiceOfferingId, customParameters, excludes);
|
||||
}
|
||||
|
||||
|
|
@ -2215,7 +2227,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
success = true;
|
||||
return success;
|
||||
} catch (InsufficientCapacityException | ResourceUnavailableException | ConcurrentOperationException e) {
|
||||
logger.error(String.format("Unable to scale %s due to [%s].", vmInstance.toString(), e.getMessage()), e);
|
||||
logger.error(String.format("Unable to scale %s due to [%s].", vmInstance, e.getMessage()), e);
|
||||
} finally {
|
||||
if (!success) {
|
||||
// Decrement CPU and Memory count accordingly.
|
||||
|
|
@ -4533,7 +4545,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
|
||||
NetworkOffering ntwkOffering = _networkOfferingDao.findById(network.getNetworkOfferingId());
|
||||
Long physicalNetworkId = _networkModel.findPhysicalNetworkId(zone.getId(), ntwkOffering.getTags(), ntwkOffering.getTrafficType());
|
||||
long physicalNetworkId = _networkModel.findPhysicalNetworkId(zone.getId(), ntwkOffering.getTags(), ntwkOffering.getTrafficType());
|
||||
|
||||
String provider = _ntwkSrvcDao.getProviderForServiceInNetwork(network.getId(), Service.Connectivity);
|
||||
if (!_networkModel.isProviderEnabledInPhysicalNetwork(physicalNetworkId, provider)) {
|
||||
|
|
@ -4751,7 +4763,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
|
||||
if (customParameters.containsKey(VmDetailConstants.ROOT_DISK_SIZE)) {
|
||||
Long rootDiskSize = NumbersUtil.parseLong(customParameters.get(VmDetailConstants.ROOT_DISK_SIZE), -1);
|
||||
long rootDiskSize = NumbersUtil.parseLong(customParameters.get(VmDetailConstants.ROOT_DISK_SIZE), -1);
|
||||
if (rootDiskSize <= 0) {
|
||||
throw new InvalidParameterValueException("Root disk size should be a positive number.");
|
||||
}
|
||||
|
|
@ -4870,7 +4882,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
final Map<String, Map<Integer, String>> extraDhcpOptionMap, final Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap,
|
||||
final Map<String, String> userVmOVFPropertiesMap, final VirtualMachine.PowerState powerState, final boolean dynamicScalingEnabled, String vmType, final Long rootDiskOfferingId, String sshkeypairs,
|
||||
List<VmDiskInfo> dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException {
|
||||
Long selectedGuestOsId = guestOsId != null ? guestOsId : template.getGuestOSId();
|
||||
long selectedGuestOsId = guestOsId != null ? guestOsId : template.getGuestOSId();
|
||||
UserVmVO vm = new UserVmVO(id, instanceName, displayName, template.getId(), hypervisorType, selectedGuestOsId, offering.isOfferHA(),
|
||||
offering.getLimitCpuUse(), owner.getDomainId(), owner.getId(), userId, offering.getId(), userData, userDataId, userDataDetails, hostName);
|
||||
vm.setUuid(uuidName);
|
||||
|
|
@ -5429,7 +5441,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
if (dc.getDns2() != null) {
|
||||
buf.append(" dns2=").append(dc.getDns2());
|
||||
}
|
||||
logger.info("cmdline details: "+ buf.toString());
|
||||
logger.info("cmdline details: "+ buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -5689,7 +5701,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
public UserVm stopVirtualMachine(long vmId, boolean forced) throws ConcurrentOperationException {
|
||||
// Input validation
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
Long userId = CallContext.current().getCallingUserId();
|
||||
long userId = CallContext.current().getCallingUserId();
|
||||
|
||||
// if account is removed, return error
|
||||
if (caller != null && caller.getRemoved() != null) {
|
||||
|
|
@ -6461,7 +6473,13 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
_accountMgr.checkAccess(caller, null, true, snapshot);
|
||||
VolumeInfo volumeOfSnapshot = getVolume(snapshot.getVolumeId(), templateId, true);
|
||||
templateId = volumeOfSnapshot.getTemplateId();
|
||||
if (volumeOfSnapshot != null) {
|
||||
templateId = volumeOfSnapshot.getTemplateId();
|
||||
} else if (templateId == null) {
|
||||
throw new InvalidParameterValueException(
|
||||
"Could not determine template from snapshot id=" + cmd.getSnapshotId() +
|
||||
"; the source volume no longer exists. Please specify a templateId.");
|
||||
}
|
||||
}
|
||||
|
||||
VirtualMachineTemplate template = null;
|
||||
|
|
@ -6743,7 +6761,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
private VolumeInfo getVolume(long id, Long templateId, boolean isSnapshot) {
|
||||
VolumeInfo volume = volFactory.getVolume(id);
|
||||
if (volume != null) {
|
||||
if (volume.getDataStore() == null || !ScopeType.ZONE.equals(volume.getDataStore().getScope().getScopeType())) {
|
||||
if (!isSnapshot
|
||||
&& (volume.getDataStore() == null
|
||||
|| !ScopeType.ZONE.equals(volume.getDataStore().getScope().getScopeType()))) {
|
||||
throw new InvalidParameterValueException("Deployment of virtual machine is supported only for Zone-wide storage pools");
|
||||
}
|
||||
checkIfVolumeTemplateIsTheSameAsTheProvided(volume, templateId);
|
||||
|
|
@ -6959,7 +6979,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
} else if (hypervisorType.equals(HypervisorType.VMware)) {
|
||||
persistExtraConfigVmware(decodedUrl, vm);
|
||||
} else {
|
||||
String msg = String.format("This hypervisor %s is not supported for use with this feature", hypervisorType.toString());
|
||||
String msg = String.format("This hypervisor %s is not supported for use with this feature", hypervisorType);
|
||||
throw new CloudRuntimeException(msg);
|
||||
}
|
||||
}
|
||||
|
|
@ -7305,7 +7325,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
vm.setLastHostId(null); // Last host does not have higher priority in vm migration
|
||||
final ServiceOfferingVO offering = serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId());
|
||||
final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, null, offering, null, null);
|
||||
final Long srcHostId = srcHost.getId();
|
||||
final long srcHostId = srcHost.getId();
|
||||
final Host host = _hostDao.findById(srcHostId);
|
||||
ExcludeList excludes = new ExcludeList();
|
||||
excludes.addHost(srcHostId);
|
||||
|
|
@ -7958,7 +7978,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
|
||||
Long domainId = cmd.getDomainId();
|
||||
Long projectId = cmd.getProjectId();
|
||||
Long oldAccountId = vm.getAccountId();
|
||||
long oldAccountId = vm.getAccountId();
|
||||
String newAccountName = cmd.getAccountName();
|
||||
final Account oldAccount = _accountService.getActiveAccountById(oldAccountId);
|
||||
final Account newAccount = _accountMgr.finalizeOwner(caller, newAccountName, domainId, projectId);
|
||||
|
|
@ -8587,7 +8607,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
protected void addDefaultSecurityGroupToSecurityGroupIdList(Account newAccount, List<Long> securityGroupIdList) {
|
||||
logger.debug("Adding default security group to security group list if not already in it.");
|
||||
|
||||
Long newAccountId = newAccount.getId();
|
||||
long newAccountId = newAccount.getId();
|
||||
SecurityGroup defaultGroup = _securityGroupMgr.getDefaultSecurityGroup(newAccountId);
|
||||
boolean defaultGroupPresent = false;
|
||||
|
||||
|
|
@ -8734,7 +8754,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
logger.trace("Creating an applicable network to create the VM.");
|
||||
|
||||
NetworkVO defaultNetwork;
|
||||
Long zoneId = zone.getId();
|
||||
long zoneId = zone.getId();
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
NetworkOfferingVO requiredOffering = getOfferingWithRequiredAvailabilityForNetworkCreation();
|
||||
String requiredOfferingTags = requiredOffering.getTags();
|
||||
|
|
@ -9458,7 +9478,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
VmIpFetchThreadPoolMax, VmIpFetchTaskWorkers, AllowDeployVmIfGivenHostFails, EnableAdditionalVmConfig, DisplayVMOVFProperties,
|
||||
KvmAdditionalConfigAllowList, XenServerAdditionalConfigAllowList, VmwareAdditionalConfigAllowList, DestroyRootVolumeOnVmDestruction,
|
||||
EnforceStrictResourceLimitHostTagCheck, StrictHostTags, AllowUserForceStopVm, VmDistinctHostNameScope,
|
||||
VmwareAdditionalDetailsFromOvaEnabled, VmwareAllowedAdditionalDetailsFromOva, AllowDifferentHostTagsOfferingsForVmScale};
|
||||
VmwareAdditionalDetailsFromOvaEnabled, VmwareAllowedAdditionalDetailsFromOva, AllowDifferentHostTagsOfferingsForVmScale, AutoMigrateVmOnLiveScaleInsufficientCapacity};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -9494,7 +9514,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
logger.debug("Found {} no. of volumes of type {} for vm with VM ID {}", listVolumes.size(), type, vm);
|
||||
for (VolumeVO volume : listVolumes) {
|
||||
Long volumeId = volume.getId();
|
||||
long volumeId = volume.getId();
|
||||
logger.debug("Checking status of snapshots for Volume: {}", volume);
|
||||
List<SnapshotVO> ongoingSnapshots = _snapshotDao.listByStatus(volumeId, Snapshot.State.Creating, Snapshot.State.CreatedOnPrimary, Snapshot.State.BackingUp);
|
||||
int ongoingSnapshotsCount = ongoingSnapshots.size();
|
||||
|
|
@ -9513,12 +9533,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
|
||||
for (VolumeVO volume : volumes) {
|
||||
if (volume.getInstanceId() == null || vmId != volume.getInstanceId() || volume.getVolumeType() != Volume.Type.DATADISK) {
|
||||
sb.append(volume.toString() + "; ");
|
||||
sb.append(volume + "; ");
|
||||
}
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(sb.toString())) {
|
||||
throw new InvalidParameterValueException("The following supplied volumes are not DATADISK attached to the VM: " + sb.toString());
|
||||
throw new InvalidParameterValueException("The following supplied volumes are not DATADISK attached to the VM: " + sb);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -9526,7 +9546,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
|
||||
for (VolumeVO volume : volumes) {
|
||||
if (!(volume.getVolumeType() == Volume.Type.ROOT || volume.getVolumeType() == Volume.Type.DATADISK)) {
|
||||
throw new InvalidParameterValueException("Please specify volume of type " + Volume.Type.DATADISK.toString() + " or " + Volume.Type.ROOT.toString());
|
||||
throw new InvalidParameterValueException("Please specify volume of type " + Volume.Type.DATADISK + " or " + Volume.Type.ROOT);
|
||||
}
|
||||
if (volume.isDeleteProtection()) {
|
||||
throw new InvalidParameterValueException(String.format(
|
||||
|
|
@ -9620,7 +9640,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
|
||||
final String uuidName = _uuidMgr.generateUuid(UserVm.class, null);
|
||||
final Host lastHost = powerState != VirtualMachine.PowerState.PowerOn ? host : null;
|
||||
final Boolean dynamicScalingEnabled = checkIfDynamicScalingCanBeEnabled(null, serviceOffering, template, zone.getId());
|
||||
final boolean dynamicScalingEnabled = checkIfDynamicScalingCanBeEnabled(null, serviceOffering, template, zone.getId());
|
||||
return commitUserVm(true, zone, host, lastHost, template, hostName, displayName, owner,
|
||||
null, null, userData, null, null, isDisplayVm, keyboard,
|
||||
accountId, userId, serviceOffering, template.getFormat().equals(ImageFormat.ISO), guestOsId, sshPublicKeys, networkNicMap,
|
||||
|
|
@ -9682,7 +9702,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
}
|
||||
|
||||
private void checkRootDiskSizeAgainstBackup(Long instanceVolumeSize,DiskOffering rootDiskOffering, Long backupVolumeSize) {
|
||||
Long instanceRootDiskSize = rootDiskOffering.isCustomized() ? instanceVolumeSize : rootDiskOffering.getDiskSize() / GiB_TO_BYTES;
|
||||
long instanceRootDiskSize = rootDiskOffering.isCustomized() ? instanceVolumeSize : rootDiskOffering.getDiskSize() / GiB_TO_BYTES;
|
||||
if (instanceRootDiskSize < backupVolumeSize) {
|
||||
throw new InvalidParameterValueException(
|
||||
String.format("Instance volume root disk size %d[GiB] cannot be less than the backed-up volume size %d[GiB].",
|
||||
|
|
@ -9759,7 +9779,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
Long size = cmd.getSize();
|
||||
|
||||
Long diskOfferingId = cmd.getDiskOfferingId();
|
||||
Boolean isIso = template.getFormat().equals(ImageFormat.ISO);
|
||||
boolean isIso = template.getFormat().equals(ImageFormat.ISO);
|
||||
if (diskOfferingId != null) {
|
||||
if (!isIso) {
|
||||
throw new InvalidParameterValueException(ApiConstants.DISK_OFFERING_ID + " parameter is supported for creating instance from backup only for ISO. For creating VMs with templates, please use the parameter " + ApiConstants.DATADISKS_DETAILS);
|
||||
|
|
|
|||
|
|
@ -576,12 +576,12 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, eventDescription = "assign VM to backup offering", async = true)
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, eventDescription = "assign Instance to Backup Offering", async = true)
|
||||
public boolean assignVMToBackupOffering(Long vmId, Long offeringId) {
|
||||
final VMInstanceVO vm = findVmById(vmId);
|
||||
|
||||
if (!Arrays.asList(VirtualMachine.State.Running, VirtualMachine.State.Stopped, VirtualMachine.State.Shutdown).contains(vm.getState())) {
|
||||
throw new CloudRuntimeException("VM is not in running or stopped state");
|
||||
throw new CloudRuntimeException("Instance is not in running or stopped state");
|
||||
}
|
||||
|
||||
validateBackupForZone(vm.getDataCenterId());
|
||||
|
|
@ -589,7 +589,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
|
||||
|
||||
if (vm.getBackupOfferingId() != null) {
|
||||
throw new CloudRuntimeException("VM already is assigned to a backup offering, please remove the VM from its previous offering");
|
||||
throw new CloudRuntimeException("Instance already is assigned to a backup offering, please remove the Instance from its previous offering");
|
||||
}
|
||||
|
||||
final BackupOfferingVO offering = backupOfferingDao.findById(offeringId);
|
||||
|
|
@ -621,12 +621,12 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
vm.setBackupVolumes(createVolumeInfoFromVolumes(new ArrayList<>(volumeDao.findByInstance(vmId))));
|
||||
|
||||
if (!backupProvider.assignVMToBackupOffering(vm, offering)) {
|
||||
throw new CloudRuntimeException("Failed to assign the VM to the backup offering, please try removing the assignment and try again.");
|
||||
throw new CloudRuntimeException("Failed to assign the Instance to the Backup Offering, please try removing the assignment and try again.");
|
||||
}
|
||||
|
||||
if (!vmInstanceDao.update(vmId, vm)) {
|
||||
backupProvider.removeVMFromBackupOffering(vm);
|
||||
throw new CloudRuntimeException("Failed to update VM assignment to the backup offering in the DB, please try again.");
|
||||
throw new CloudRuntimeException("Failed to update Instance assignment to the Backup Offering in the DB, please try again.");
|
||||
}
|
||||
|
||||
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, vm.getAccountId(), vm.getDataCenterId(), vmId,
|
||||
|
|
@ -635,7 +635,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
"uuid", "instanceName", "backupOfferingId", "backupVolumes"), ReflectionToStringBuilderUtils.reflectOnlySelectedFields(offering,
|
||||
"uuid", "name", "externalId", "provider")));
|
||||
} catch (Exception e) {
|
||||
String msg = String.format("Failed to assign VM [%s] to the Backup Offering [%s], using provider [name: %s, class: %s], due to: [%s].",
|
||||
String msg = String.format("Failed to assign Instance [%s] to the Backup Offering [%s], using provider [name: %s, class: %s], due to: [%s].",
|
||||
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(vm, "uuid", "instanceName", "backupOfferingId", "backupVolumes"),
|
||||
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(offering, "uuid", "name", "externalId", "provider"),
|
||||
backupProvider.getName(), backupProvider.getClass().getSimpleName(), e.getMessage());
|
||||
|
|
@ -649,11 +649,11 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, eventDescription = "remove VM from backup offering", async = true)
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, eventDescription = "remove Instance from Backup Offering", async = true)
|
||||
public boolean removeVMFromBackupOffering(final Long vmId, final boolean forced) {
|
||||
final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId);
|
||||
if (vm == null) {
|
||||
throw new CloudRuntimeException(String.format("Can't find any VM with ID: [%s].", vmId));
|
||||
throw new CloudRuntimeException(String.format("Can't find any Instance with ID: [%s].", vmId));
|
||||
}
|
||||
|
||||
validateBackupForZone(vm.getDataCenterId());
|
||||
|
|
@ -670,8 +670,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
}
|
||||
|
||||
if (!forced && backupProvider.willDeleteBackupsOnOfferingRemoval()) {
|
||||
String message = String.format("To remove VM [id: %s, name: %s] from Backup Offering [id: %s, name: %s] using the provider [%s], please specify the "
|
||||
+ "forced:true option to allow the deletion of all jobs and backups for this VM or remove the backups that this VM has with the backup "
|
||||
String message = String.format("To remove Instance [id: %s, name: %s] from Backup Offering [id: %s, name: %s] using the provider [%s], please specify the "
|
||||
+ "forced:true option to allow the deletion of all jobs and backups for this Instance or remove the backups that this Instance has with the backup "
|
||||
+ "offering.", vm.getUuid(), vm.getInstanceName(), offering.getUuid(), offering.getName(), backupProvider.getClass().getSimpleName());
|
||||
throw new CloudRuntimeException(message);
|
||||
}
|
||||
|
|
@ -709,7 +709,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_CONFIGURE, eventDescription = "configuring VM backup schedule")
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_CONFIGURE, eventDescription = "configuring Instance Backup Schedule")
|
||||
public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) {
|
||||
final Long vmId = cmd.getVmId();
|
||||
final DateUtil.IntervalType intervalType = cmd.getIntervalType();
|
||||
|
|
@ -725,7 +725,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
|
||||
|
||||
if (vm.getBackupOfferingId() == null) {
|
||||
throw new CloudRuntimeException("Cannot configure backup schedule for the VM without having any backup offering");
|
||||
throw new CloudRuntimeException("Cannot configure Backup Schedule for the Instance as it is not assigned any Backup Offering");
|
||||
}
|
||||
|
||||
final BackupOffering offering = backupOfferingDao.findById(vm.getBackupOfferingId());
|
||||
|
|
@ -859,7 +859,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE, eventDescription = "deleting VM backup schedule")
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE, eventDescription = "deleting Instance Backup Schedule")
|
||||
public boolean deleteBackupSchedule(DeleteBackupScheduleCmd cmd) {
|
||||
Long vmId = cmd.getVmId();
|
||||
Long id = cmd.getId();
|
||||
|
|
@ -910,7 +910,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_CREATE, eventDescription = "creating VM backup", async = true)
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_CREATE, eventDescription = "creating Instance Backup", async = true)
|
||||
public boolean createBackup(CreateBackupCmd cmd, Object job) throws ResourceAllocationException {
|
||||
Long vmId = cmd.getVmId();
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
|
|
@ -920,17 +920,17 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
accountManager.checkAccess(caller, null, true, vm);
|
||||
|
||||
if (vm.getBackupOfferingId() == null) {
|
||||
throw new CloudRuntimeException("VM has not backup offering configured, cannot create backup before assigning it to a backup offering");
|
||||
throw new CloudRuntimeException("Cannot create backup as the Instance doesn't have a Backup Offering assigned");
|
||||
}
|
||||
|
||||
final BackupOffering offering = backupOfferingDao.findById(vm.getBackupOfferingId());
|
||||
if (offering == null) {
|
||||
throw new CloudRuntimeException("VM backup offering not found");
|
||||
throw new CloudRuntimeException("Instance Backup Offering not found");
|
||||
}
|
||||
|
||||
final BackupProvider backupProvider = getBackupProvider(offering.getProvider());
|
||||
if (backupProvider == null) {
|
||||
throw new CloudRuntimeException("VM backup provider not found for the offering");
|
||||
throw new CloudRuntimeException("Instance backup provider not found for the Offering");
|
||||
}
|
||||
|
||||
if (!offering.isUserDrivenBackupAllowed()) {
|
||||
|
|
@ -970,14 +970,9 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
CheckedReservation backupStorageReservation = new CheckedReservation(owner,
|
||||
Resource.ResourceType.backup_storage, backupSize, reservationDao, resourceLimitMgr)) {
|
||||
|
||||
ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(),
|
||||
EventTypes.EVENT_VM_BACKUP_CREATE, "creating backup for VM ID:" + vm.getUuid(),
|
||||
vmId, ApiCommandResourceType.VirtualMachine.toString(),
|
||||
true, 0);
|
||||
|
||||
Pair<Boolean, Backup> result = backupProvider.takeBackup(vm, cmd.getQuiesceVM());
|
||||
if (!result.first()) {
|
||||
throw new CloudRuntimeException("Failed to create VM backup");
|
||||
throw new CloudRuntimeException("Failed to create Instance Backup");
|
||||
}
|
||||
Backup backup = result.second();
|
||||
if (backup != null) {
|
||||
|
|
@ -1079,7 +1074,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
for (int i = 0; i < amountOfBackupsToDelete; i++) {
|
||||
BackupVO backup = backups.get(i);
|
||||
if (deleteBackup(backup.getId(), false)) {
|
||||
String eventDescription = String.format("Successfully deleted backup for VM [ID: %s], suiting the retention specified in the backup schedule [ID: %s]", backup.getVmId(), backupScheduleId);
|
||||
String eventDescription = String.format("Successfully deleted Backup for Instance [ID: %s], suiting the retention specified in the Backup Schedule [ID: %s]", backup.getVmId(), backupScheduleId);
|
||||
logger.info(eventDescription);
|
||||
ActionEventUtils.onCompletedActionEvent(
|
||||
User.UID_SYSTEM, backup.getAccountId(), EventVO.LEVEL_INFO,
|
||||
|
|
@ -1176,9 +1171,9 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
logger.error(String.format("Failed to import VM [vmInternalName: %s] from backup restoration [%s] with hypervisor [type: %s] due to: [%s].", vmInternalName,
|
||||
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(backup, "id", "uuid", "vmId", "externalId", "backupType"), hypervisorType, e.getMessage()), e);
|
||||
ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE,
|
||||
String.format("Failed to import VM %s from backup %s with hypervisor [type: %s]", vmInternalName, backup.getUuid(), hypervisorType),
|
||||
String.format("Failed to import Instance %s from Backup %s with hypervisor [type: %s]", vmInternalName, backup.getUuid(), hypervisorType),
|
||||
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0);
|
||||
throw new CloudRuntimeException("Error during vm backup restoration and import: " + e.getMessage());
|
||||
throw new CloudRuntimeException("Error during Instance Backup restoration and import: " + e.getMessage());
|
||||
}
|
||||
if (vm == null) {
|
||||
String message = String.format("Failed to import restored VM %s with hypervisor type %s using backup of VM ID %s",
|
||||
|
|
@ -1188,14 +1183,14 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
message, vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0);
|
||||
} else {
|
||||
ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_INFO, EventTypes.EVENT_VM_BACKUP_RESTORE,
|
||||
String.format("Restored VM %s from backup %s", vm.getUuid(), backup.getUuid()),
|
||||
String.format("Restored Instance %s from Backup %s", vm.getUuid(), backup.getUuid()),
|
||||
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0);
|
||||
}
|
||||
return vm != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true)
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring Instance from Backup", async = true)
|
||||
public boolean restoreBackup(final Long backupId) {
|
||||
final BackupVO backup = backupDao.findById(backupId);
|
||||
if (backup == null) {
|
||||
|
|
@ -1214,7 +1209,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
|
||||
if (vm.getRemoved() == null && !vm.getState().equals(VirtualMachine.State.Stopped) &&
|
||||
!vm.getState().equals(VirtualMachine.State.Destroyed)) {
|
||||
throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup");
|
||||
throw new CloudRuntimeException("Existing Instance should be stopped before being restored from Backup");
|
||||
}
|
||||
|
||||
// This is done to handle historic backups if any with Veeam / Networker plugins
|
||||
|
|
@ -1222,7 +1217,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
vm.getBackupVolumeList() : backup.getBackedUpVolumes();
|
||||
List<VolumeVO> vmVolumes = volumeDao.findByInstance(vm.getId());
|
||||
if (vmVolumes.size() != backupVolumes.size()) {
|
||||
throw new CloudRuntimeException("Unable to restore VM with the current backup as the backup has different number of disks as the VM");
|
||||
throw new CloudRuntimeException("Unable to restore Instance with the current Backup as the Backup has different number of disks to the Instance");
|
||||
}
|
||||
|
||||
BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(vm.getBackupOfferingId());
|
||||
|
|
@ -1256,16 +1251,16 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
updateVmState(vm, VirtualMachine.Event.RestoringRequested, VirtualMachine.State.Restoring);
|
||||
updateVolumeState(vm, Volume.Event.RestoreRequested, Volume.State.Restoring);
|
||||
ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventTypes.EVENT_VM_BACKUP_RESTORE,
|
||||
String.format("Restoring VM %s from backup %s", vm.getUuid(), backup.getUuid()),
|
||||
String.format("Restoring Instance %s from Backup %s", vm.getUuid(), backup.getUuid()),
|
||||
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),
|
||||
true, 0);
|
||||
|
||||
final BackupProvider backupProvider = getBackupProvider(offering.getProvider());
|
||||
if (!backupProvider.restoreVMFromBackup(vm, backup)) {
|
||||
ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE,
|
||||
String.format("Failed to restore VM %s from backup %s", vm.getInstanceName(), backup.getUuid()),
|
||||
String.format("Failed to restore Instance %s from Backup %s", vm.getInstanceName(), backup.getUuid()),
|
||||
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0);
|
||||
throw new CloudRuntimeException("Error restoring VM from backup with uuid " + backup.getUuid());
|
||||
throw new CloudRuntimeException("Error restoring Instance from Backup with uuid " + backup.getUuid());
|
||||
}
|
||||
// The restore process is executed by a backup provider outside of ACS, I am using the catch-all (Exception) to
|
||||
// ensure that no provider-side exception is missed. Therefore, we have a proper handling of exceptions, and rollbacks if needed.
|
||||
|
|
@ -1273,7 +1268,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
logger.error(String.format("Failed to restore backup [%s] due to: [%s].", backupDetailsInMessage, e.getMessage()), e);
|
||||
updateVolumeState(vm, Volume.Event.RestoreFailed, Volume.State.Ready);
|
||||
updateVmState(vm, VirtualMachine.Event.RestoringFailed, VirtualMachine.State.Stopped);
|
||||
throw new CloudRuntimeException(String.format("Error restoring VM from backup [%s].", backupDetailsInMessage));
|
||||
throw new CloudRuntimeException(String.format("Error restoring Instance from Backup [%s].", backupDetailsInMessage));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1288,10 +1283,10 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
Transaction.execute(TransactionLegacy.CLOUD_DB, (TransactionCallback<VMInstanceVO>) status -> {
|
||||
try {
|
||||
if (!virtualMachineManager.stateTransitTo(vm, event, vm.getHostId())) {
|
||||
throw new CloudRuntimeException(String.format("Unable to change state of VM [%s] to [%s].", vm, next));
|
||||
throw new CloudRuntimeException(String.format("Unable to change state of Instance [%s] to [%s].", vm, next));
|
||||
}
|
||||
} catch (NoTransitionException e) {
|
||||
String errMsg = String.format("Failed to update state of VM [%s] with event [%s] due to [%s].", vm, event, e.getMessage());
|
||||
String errMsg = String.format("Failed to update state of Instance [%s] with event [%s] due to [%s].", vm, event, e.getMessage());
|
||||
logger.error(errMsg, e);
|
||||
throw new RuntimeException(errMsg);
|
||||
}
|
||||
|
|
@ -1503,7 +1498,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
throw new CloudRuntimeException("Instance with ID " + backup.getVmId() + " couldn't be found.");
|
||||
}
|
||||
if (!vm.getState().equals(VirtualMachine.State.Stopped)) {
|
||||
throw new CloudRuntimeException("The VM should be in stopped state");
|
||||
throw new CloudRuntimeException("The Instance should be in stopped state");
|
||||
}
|
||||
|
||||
List<Backup.VolumeInfo> backupVolumes = backup.getBackedUpVolumes();
|
||||
|
|
@ -1567,7 +1562,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true)
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE_VOLUME_TO_VM, eventDescription = "restoring Volume from Backup to Instance", async = true)
|
||||
public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long backupId, final Long vmId) throws Exception {
|
||||
if (StringUtils.isEmpty(backedUpVolumeUuid)) {
|
||||
throw new CloudRuntimeException("Invalid volume ID passed");
|
||||
|
|
@ -1585,7 +1580,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
|
||||
|
||||
if (vm.getBackupOfferingId() != null && !BackupEnableAttachDetachVolumes.value()) {
|
||||
throw new CloudRuntimeException("The selected VM has backups, cannot restore and attach volume to the VM.");
|
||||
throw new CloudRuntimeException("The selected Instance has backups, cannot restore and attach Volume to the Instance.");
|
||||
}
|
||||
|
||||
if (backup.getZoneId() != vm.getDataCenterId()) {
|
||||
|
|
@ -1596,7 +1591,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
if (volumeInfoList == null) {
|
||||
final VMInstanceVO vmFromBackup = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId());
|
||||
if (vmFromBackup == null) {
|
||||
throw new CloudRuntimeException("VM reference for the provided VM backup not found");
|
||||
throw new CloudRuntimeException("Instance reference for the provided Instance backup not found");
|
||||
} else if (vmFromBackup == null || vmFromBackup.getBackupVolumeList() == null) {
|
||||
throw new CloudRuntimeException("Volumes metadata not found in the backup");
|
||||
}
|
||||
|
|
@ -1610,7 +1605,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm);
|
||||
final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(backup.getBackupOfferingId());
|
||||
if (offering == null) {
|
||||
throw new CloudRuntimeException("Failed to find VM backup offering");
|
||||
throw new CloudRuntimeException("Failed to find Instance Backup Offering");
|
||||
}
|
||||
|
||||
BackupProvider backupProvider = getBackupProvider(offering.getProvider());
|
||||
|
|
@ -1636,12 +1631,12 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
Pair<Boolean, String> result = restoreBackedUpVolume(backupVolumeInfo, backup, backupProvider, hostPossibleValues, datastoresPossibleValues, vm);
|
||||
|
||||
if (BooleanUtils.isFalse(result.first())) {
|
||||
throw new CloudRuntimeException(String.format("Error restoring volume [%s] of VM [%s] to host [%s] using backup provider [%s] due to: [%s].",
|
||||
throw new CloudRuntimeException(String.format("Error restoring Volume [%s] of Instance [%s] to host [%s] using backup provider [%s] due to: [%s].",
|
||||
backedUpVolumeUuid, vm.getUuid(), host.getUuid(), backupProvider.getName(), result.second()));
|
||||
}
|
||||
if (!attachVolumeToVM(vm.getDataCenterId(), result.second(), backupVolumeInfo,
|
||||
backedUpVolumeUuid, vm, datastore.getUuid(), backup)) {
|
||||
throw new CloudRuntimeException(String.format("Error attaching volume [%s] to VM [%s].", backedUpVolumeUuid, vm.getUuid()));
|
||||
throw new CloudRuntimeException(String.format("Error attaching Volume [%s] to Instance [%s].", backedUpVolumeUuid, vm.getUuid()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1670,7 +1665,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_DELETE, eventDescription = "deleting VM backup", async = true)
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_DELETE, eventDescription = "deleting Instance backup", async = true)
|
||||
public boolean deleteBackup(final Long backupId, final Boolean forced) throws ResourceAllocationException {
|
||||
final BackupVO backup = backupDao.findByIdIncludingRemoved(backupId);
|
||||
if (backup == null) {
|
||||
|
|
@ -1776,12 +1771,12 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
HypervisorGuru guru = hypervisorGuruManager.getGuru(vm.getHypervisorType());
|
||||
backupVolumeInfo.setType(Volume.Type.DATADISK);
|
||||
|
||||
logger.info("Attaching the restored volume {} to VM {}.", () -> ReflectionToStringBuilder.toString(backupVolumeInfo, ToStringStyle.JSON_STYLE), () -> vm);
|
||||
logger.info("Attaching the restored Volume {} to Instance {}.", () -> ReflectionToStringBuilder.toString(backupVolumeInfo, ToStringStyle.JSON_STYLE), () -> vm);
|
||||
StoragePoolVO pool = primaryDataStoreDao.findByUuid(datastoreUuid);
|
||||
try {
|
||||
return guru.attachRestoredVolumeToVirtualMachine(zoneId, restoredVolumeLocation, backupVolumeInfo, vm, pool.getId(), backup);
|
||||
} catch (Exception e) {
|
||||
throw new CloudRuntimeException("Error attach restored volume to VM " + vm.getUuid() + " due to: " + e.getMessage());
|
||||
throw new CloudRuntimeException("Error attach restored Volume to Instance " + vm.getUuid() + " due to: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1964,7 +1959,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
case FAILED:
|
||||
final Date nextDateTime = scheduleNextBackupJob(backupSchedule);
|
||||
final String nextScheduledTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, nextDateTime);
|
||||
logger.debug("Next backup scheduled time for VM ID " + backupSchedule.getVmId() + " is " + nextScheduledTime);
|
||||
logger.debug("Next backup scheduled time for Instance ID " + backupSchedule.getVmId() + " is " + nextScheduledTime);
|
||||
break;
|
||||
default:
|
||||
logger.debug("Found async backup job [id: {}, uuid: {}, vmId: {}] with " +
|
||||
|
|
@ -2004,7 +1999,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
|
||||
final Account backupAccount = accountService.getAccount(vm.getAccountId());
|
||||
if (backupAccount == null || backupAccount.getState() == Account.State.DISABLED) {
|
||||
logger.debug("Skip backup for VM ({}) since its account has been removed or disabled.", vm);
|
||||
logger.debug("Skip backup for Instance ({}) since its account has been removed or disabled.", vm);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -2021,7 +2016,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
tmpBackupScheduleVO = backupScheduleDao.acquireInLockTable(backupScheduleId);
|
||||
|
||||
final Long eventId = ActionEventUtils.onScheduledActionEvent(User.UID_SYSTEM, vm.getAccountId(),
|
||||
EventTypes.EVENT_VM_BACKUP_CREATE, "creating backup for VM ID:" + vm.getUuid(),
|
||||
EventTypes.EVENT_VM_BACKUP_CREATE, "creating Backup for Instance ID:" + vm.getUuid(),
|
||||
vmId, ApiCommandResourceType.VirtualMachine.toString(),
|
||||
true, 0);
|
||||
final Map<String, String> params = new HashMap<String, String>();
|
||||
|
|
@ -2085,7 +2080,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
private VMInstanceVO findVmById(final Long vmId) {
|
||||
final VMInstanceVO vm = vmInstanceDao.findById(vmId);
|
||||
if (vm == null) {
|
||||
throw new CloudRuntimeException(String.format("Can't find any VM with ID: [%s].", vmId));
|
||||
throw new CloudRuntimeException(String.format("Can't find any Instance with ID: [%s].", vmId));
|
||||
}
|
||||
return vm;
|
||||
}
|
||||
|
|
@ -2269,7 +2264,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
|
|||
backup.getBackupOfferingId(), backup.getAccountId(), backup.getDomainId(), backup.getZoneId()));
|
||||
|
||||
ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_INFO, EventTypes.EVENT_VM_BACKUP_CREATE,
|
||||
String.format("Created backup %s for VM ID: %s", backup.getUuid(), vm.getUuid()),
|
||||
String.format("Created Backup %s for Instance ID: %s", backup.getUuid(), vm.getUuid()),
|
||||
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,14 @@ import java.math.BigInteger;
|
|||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
|
@ -39,6 +41,21 @@ import javax.inject.Inject;
|
|||
import javax.naming.ConfigurationException;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import com.trilead.ssh2.Connection;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.utils.PasswordGenerator;
|
||||
import com.cloud.utils.ssh.SSHCmdHelper;
|
||||
import com.cloud.vm.VMInstanceVO;
|
||||
import com.cloud.vm.dao.VMInstanceDao;
|
||||
import org.apache.cloudstack.utils.security.KeyStoreUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
|
|
@ -60,6 +77,7 @@ import org.joda.time.DateTime;
|
|||
import org.joda.time.DateTimeZone;
|
||||
|
||||
import com.cloud.agent.AgentManager;
|
||||
import com.cloud.agent.api.routing.NetworkElementCommand;
|
||||
import com.cloud.alert.AlertManager;
|
||||
import com.cloud.certificate.CrlVO;
|
||||
import com.cloud.certificate.dao.CrlDao;
|
||||
|
|
@ -81,6 +99,12 @@ public class CAManagerImpl extends ManagerBase implements CAManager {
|
|||
@Inject
|
||||
private HostDao hostDao;
|
||||
@Inject
|
||||
private VMInstanceDao vmInstanceDao;
|
||||
@Inject
|
||||
private NetworkOrchestrationService networkOrchestrationService;
|
||||
@Inject
|
||||
private ConfigurationDao configDao;
|
||||
@Inject
|
||||
private AgentManager agentManager;
|
||||
@Inject
|
||||
private BackgroundPollManager backgroundPollManager;
|
||||
|
|
@ -177,12 +201,17 @@ public class CAManagerImpl extends ManagerBase implements CAManager {
|
|||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_CA_CERTIFICATE_PROVISION, eventDescription = "provisioning certificate for host", async = true)
|
||||
public boolean provisionCertificate(final Host host, final Boolean reconnect, final String caProvider) {
|
||||
public boolean provisionCertificate(final Host host, final Boolean reconnect, final String caProvider, final boolean forced) {
|
||||
if (host == null) {
|
||||
throw new CloudRuntimeException("Unable to find valid host to renew certificate for");
|
||||
}
|
||||
CallContext.current().setEventDetails("Host ID: " + host.getUuid());
|
||||
CallContext.current().putContextParameter(Host.class, host.getUuid());
|
||||
|
||||
if (forced) {
|
||||
return provisionCertificateForced(host, reconnect, caProvider);
|
||||
}
|
||||
|
||||
String csr = null;
|
||||
|
||||
try {
|
||||
|
|
@ -200,6 +229,141 @@ public class CAManagerImpl extends ManagerBase implements CAManager {
|
|||
}
|
||||
}
|
||||
|
||||
protected boolean provisionCertificateForced(Host host, Boolean reconnect, String caProvider) {
|
||||
if (host.getType() == Host.Type.Routing && host.getHypervisorType() == com.cloud.hypervisor.Hypervisor.HypervisorType.KVM) {
|
||||
return provisionKvmHostViaSsh(host, caProvider);
|
||||
} else if (host.getType() == Host.Type.ConsoleProxy || host.getType() == Host.Type.SecondaryStorageVM) {
|
||||
return provisionSystemVmViaSsh(host, reconnect, caProvider);
|
||||
}
|
||||
throw new CloudRuntimeException("Forced certificate provisioning is only supported for KVM hosts and SystemVMs.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provisionCertificateViaSsh(final Connection sshConnection, final String agentIp, final String agentHostname, final String caProvider) {
|
||||
Integer validityPeriod = CAManager.CertValidityPeriod.value();
|
||||
if (validityPeriod < 1) {
|
||||
validityPeriod = 1;
|
||||
}
|
||||
|
||||
String keystorePassword = PasswordGenerator.generateRandomPassword(16);
|
||||
|
||||
// 1. Setup Keystore and Generate CSR
|
||||
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 and generate CSR via SSH on host: " + agentIp);
|
||||
}
|
||||
|
||||
// 2. Issue Certificate based on returned CSR
|
||||
final String csr = keystoreSetupResult.getStdOut();
|
||||
final Certificate certificate = issueCertificate(csr, Arrays.asList(agentHostname, agentIp),
|
||||
Collections.singletonList(agentIp), null, caProvider);
|
||||
|
||||
if (certificate == null || certificate.getClientCertificate() == null) {
|
||||
throw new CloudRuntimeException("Failed to issue certificates for host: " + agentIp);
|
||||
}
|
||||
|
||||
// 3. Import Certificate into agent keystore
|
||||
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.isSuccess()) {
|
||||
throw new CloudRuntimeException("Failed to import certificates into agent keystore via SSH on host: " + agentIp);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean provisionKvmHostViaSsh(Host host, String caProvider) {
|
||||
final HostVO hostVO = (HostVO) host;
|
||||
hostDao.loadDetails(hostVO);
|
||||
String username = hostVO.getDetail(ApiConstants.USERNAME);
|
||||
String password = hostVO.getDetail(ApiConstants.PASSWORD);
|
||||
String hostIp = host.getPrivateIpAddress();
|
||||
|
||||
int port = AgentManager.KVMHostDiscoverySshPort.valueIn(host.getClusterId());
|
||||
if (hostVO.getDetail(Host.HOST_SSH_PORT) != null) {
|
||||
port = NumberUtils.toInt(hostVO.getDetail(Host.HOST_SSH_PORT), port);
|
||||
}
|
||||
|
||||
Connection sshConnection = null;
|
||||
try {
|
||||
sshConnection = new Connection(hostIp, port);
|
||||
sshConnection.connect(null, 60000, 60000);
|
||||
|
||||
String privateKey = configDao.getValue("ssh.privatekey");
|
||||
if (!SSHCmdHelper.acquireAuthorizedConnectionWithPublicKey(sshConnection, username, privateKey)) {
|
||||
if (StringUtils.isEmpty(password) || !sshConnection.authenticateWithPassword(username, password)) {
|
||||
throw new CloudRuntimeException("Failed to authenticate to host via SSH for forced provisioning: " + hostIp);
|
||||
}
|
||||
}
|
||||
|
||||
provisionCertificateViaSsh(sshConnection, hostIp, host.getName(), caProvider);
|
||||
|
||||
String sudoPrefix = "root".equals(username) ? "" : "sudo ";
|
||||
SSHCmdHelper.sshExecuteCmd(sshConnection, sudoPrefix + "systemctl restart libvirtd");
|
||||
SSHCmdHelper.sshExecuteCmd(sshConnection, sudoPrefix + "systemctl restart cloudstack-agent");
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during forced SSH provisioning for KVM host " + host.getUuid(), e);
|
||||
return false;
|
||||
} finally {
|
||||
if (sshConnection != null) {
|
||||
sshConnection.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean provisionSystemVmViaSsh(Host host, Boolean reconnect, String caProvider) {
|
||||
VMInstanceVO vm = vmInstanceDao.findVMByInstanceName(host.getName());
|
||||
if (vm == null) {
|
||||
throw new CloudRuntimeException("Cannot find underlying VM for host: " + host.getName());
|
||||
}
|
||||
|
||||
final Map<String, String> sshAccessDetails = networkOrchestrationService.getSystemVMAccessDetails(vm);
|
||||
final Map<String, String> ipAddressDetails = new HashMap<>(sshAccessDetails);
|
||||
ipAddressDetails.remove(NetworkElementCommand.ROUTER_NAME);
|
||||
|
||||
try {
|
||||
final Host hypervisorHost = hostDao.findById(vm.getHostId());
|
||||
if (hypervisorHost == null) {
|
||||
throw new CloudRuntimeException("Cannot find hypervisor host for system VM: " + host.getName());
|
||||
}
|
||||
|
||||
final Certificate certificate = issueCertificate(null, Arrays.asList(vm.getHostName(), vm.getInstanceName()),
|
||||
new ArrayList<>(ipAddressDetails.values()), CertValidityPeriod.value(), caProvider);
|
||||
return deployCertificate(hypervisorHost, certificate, reconnect, sshAccessDetails);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to provision system VM " + host.getName() + " via hypervisor SSH proxy. Ensure the hypervisor host is connected.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateKeyStoreAndCsr(final Host host, final Map<String, String> sshAccessDetails) throws AgentUnavailableException, OperationTimedoutException {
|
||||
final SetupKeyStoreCommand cmd = new SetupKeyStoreCommand(CertValidityPeriod.value());
|
||||
|
|
@ -211,11 +375,6 @@ public class CAManagerImpl extends ManagerBase implements CAManager {
|
|||
return answer.getCsr();
|
||||
}
|
||||
|
||||
private boolean isValidSystemVMType(Host.Type type) {
|
||||
return Host.Type.SecondaryStorageVM.equals(type) ||
|
||||
Host.Type.ConsoleProxy.equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deployCertificate(final Host host, final Certificate certificate, final Boolean reconnect, final Map<String, String> sshAccessDetails)
|
||||
throws AgentUnavailableException, OperationTimedoutException {
|
||||
|
|
@ -340,7 +499,7 @@ public class CAManagerImpl extends ManagerBase implements CAManager {
|
|||
if (AutomaticCertRenewal.valueIn(host.getClusterId())) {
|
||||
try {
|
||||
logger.debug("Attempting certificate auto-renewal for " + hostDescription, e);
|
||||
boolean result = caManager.provisionCertificate(host, false, null);
|
||||
boolean result = caManager.provisionCertificate(host, false, null, false);
|
||||
if (result) {
|
||||
logger.debug("Succeeded in auto-renewing certificate for " + hostDescription, e);
|
||||
} else {
|
||||
|
|
@ -400,9 +559,57 @@ public class CAManagerImpl extends ManagerBase implements CAManager {
|
|||
logger.error("Failed to find valid configured CA provider, please check!");
|
||||
return false;
|
||||
}
|
||||
if (CaInjectDefaultTruststore.value()) {
|
||||
injectCaCertIntoDefaultTruststore();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void injectCaCertIntoDefaultTruststore() {
|
||||
try {
|
||||
final List<X509Certificate> caCerts = configuredCaProvider.getCaCertificate();
|
||||
if (caCerts == null || caCerts.isEmpty()) {
|
||||
logger.debug("No CA certificates found from the configured provider, skipping JVM truststore injection");
|
||||
return;
|
||||
}
|
||||
|
||||
final KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
trustStore.load(null, null);
|
||||
|
||||
// Copy existing default trusted certs
|
||||
final TrustManagerFactory defaultTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
defaultTmf.init((KeyStore) null);
|
||||
int aliasIndex = 0;
|
||||
for (final TrustManager tm : defaultTmf.getTrustManagers()) {
|
||||
if (tm instanceof X509TrustManager) {
|
||||
for (final X509Certificate cert : ((X509TrustManager) tm).getAcceptedIssuers()) {
|
||||
trustStore.setCertificateEntry("default-ca-" + aliasIndex++, cert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add CA provider's certificates
|
||||
int count = 0;
|
||||
for (final X509Certificate caCert : caCerts) {
|
||||
final String alias = "cloudstack-ca-" + count;
|
||||
trustStore.setCertificateEntry(alias, caCert);
|
||||
count++;
|
||||
logger.info("Injected CA certificate into JVM default truststore: subject={}, alias={}",
|
||||
caCert.getSubjectX500Principal().getName(), alias);
|
||||
}
|
||||
|
||||
// Reinitialize default SSLContext with the updated truststore
|
||||
final TrustManagerFactory updatedTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
updatedTmf.init(trustStore);
|
||||
final SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, updatedTmf.getTrustManagers(), new SecureRandom());
|
||||
SSLContext.setDefault(sslContext);
|
||||
logger.info("Successfully injected {} CA certificate(s) into JVM default truststore", count);
|
||||
} catch (final GeneralSecurityException | IOException e) {
|
||||
logger.error("Failed to inject CA certificate into JVM default truststore", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
|
||||
backgroundPollManager.submitTask(new CABackgroundTask(this, hostDao));
|
||||
|
|
@ -433,7 +640,7 @@ public class CAManagerImpl extends ManagerBase implements CAManager {
|
|||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[] {CAProviderPlugin, CertKeySize, CertSignatureAlgorithm, CertValidityPeriod,
|
||||
AutomaticCertRenewal, AllowHostIPInSysVMAgentCert, CABackgroundJobDelay, CertExpiryAlertPeriod,
|
||||
CertManagementCustomSubjectAlternativeName
|
||||
CertManagementCustomSubjectAlternativeName, CaInjectDefaultTruststore
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue