mirror of https://github.com/apache/cloudstack.git
Merge remote-tracking branch 'origin/main' into quota-ui
This commit is contained in:
commit
54ce551db6
21
.asf.yaml
21
.asf.yaml
|
|
@ -16,7 +16,6 @@
|
|||
# under the License.
|
||||
|
||||
# https://cwiki.apache.org/confluence/display/INFRA/git+-+.asf.yaml+features
|
||||
---
|
||||
github:
|
||||
description: "Apache CloudStack is an opensource Infrastructure as a Service (IaaS) cloud computing platform"
|
||||
homepage: https://cloudstack.apache.org/
|
||||
|
|
@ -57,8 +56,26 @@ github:
|
|||
- Damans227
|
||||
- jmsperu
|
||||
- GaOrtiga
|
||||
- bhouse-nexthop
|
||||
|
||||
protected_branches: ~
|
||||
protected_branches:
|
||||
|
||||
rulesets:
|
||||
- name: "Default Branch Protection"
|
||||
type: branch
|
||||
branches:
|
||||
includes:
|
||||
- "~DEFAULT_BRANCH"
|
||||
excludes: []
|
||||
bypass_teams:
|
||||
- root
|
||||
restrict_deletion: true
|
||||
restrict_force_push: true
|
||||
|
||||
copilot_code_review:
|
||||
enabled: true
|
||||
review_drafts: true
|
||||
review_on_push: true
|
||||
|
||||
notifications:
|
||||
commits: commits@cloudstack.apache.org
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
**Important**: If no action is needed after completing your analysis, you **MUST** call the `noop` safe-output tool with a brief explanation. Failing to call any safe-output tool is the most common cause of safe-output workflow failures.
|
||||
|
||||
```json
|
||||
{"noop": {"message": "No action needed: [brief explanation of what was analyzed and why]"}}
|
||||
```
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
---
|
||||
# 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)
|
||||
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
if: github.repository == 'apache/cloudstack'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
|
@ -251,7 +251,7 @@ jobs:
|
|||
smoke/test_list_storage_pools
|
||||
smoke/test_list_volumes"]
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
|
|
|||
|
|
@ -35,16 +35,16 @@ jobs:
|
|||
language: ["actions"]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||
with:
|
||||
category: "Security"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"d4b8988df7c60cd416200769cc6bc7f1aab5bb3128df0b9a83a35e061b4da111","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot"}
|
||||
# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"919fb17c7928e5e96d9c0a2854670a42f9c5f6cfc2059b46009bb3c23640d0ca","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot"}
|
||||
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"46d564922b082d0db93244972e8005ea6904ee5f","version":"v0.76.1"}],"containers":[{"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"},{"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"},{"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"},{"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"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4","digest":"sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]}
|
||||
# ___ _ _
|
||||
# / _ \ | | (_)
|
||||
|
|
@ -16,19 +16,17 @@
|
|||
#
|
||||
# This file was automatically generated by gh-aw (v0.76.1). DO NOT EDIT.
|
||||
#
|
||||
# To update this file, edit github/gh-aw/.github/workflows/issue-triage-agent.md@359795d49ada21681ab616bd4cbcb144a7387115 and run:
|
||||
# To update this file, edit githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9 and run:
|
||||
# gh aw compile
|
||||
# Not all edits will cause changes to this file.
|
||||
#
|
||||
# For more information: https://github.github.com/gh-aw/introduction/overview/
|
||||
#
|
||||
# Scheduled daily triage that processes untriaged CloudStack issues in batches.
|
||||
# Detects duplicates, filters spam, and assigns CloudStack-specific labels
|
||||
# (type:*, component:*, Severity:*, status:*), then posts a structured triage report.
|
||||
#
|
||||
# Source: github/gh-aw/.github/workflows/issue-triage-agent.md@359795d49ada21681ab616bd4cbcb144a7387115
|
||||
#
|
||||
# Resolved workflow manifest:
|
||||
# Imports:
|
||||
# - github/gh-aw/.github/workflows/shared/noop-reminder.md@359795d49ada21681ab616bd4cbcb144a7387115
|
||||
# - github/gh-aw/.github/workflows/shared/reporting.md@359795d49ada21681ab616bd4cbcb144a7387115
|
||||
# Source: githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9
|
||||
#
|
||||
# Secrets used:
|
||||
# - COPILOT_GITHUB_TOKEN
|
||||
|
|
@ -37,13 +35,12 @@
|
|||
# - GITHUB_TOKEN
|
||||
#
|
||||
# Custom actions used:
|
||||
# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
|
||||
# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
# - github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1
|
||||
# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8
|
||||
#
|
||||
# Container images used:
|
||||
# - ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731
|
||||
|
|
@ -53,10 +50,10 @@
|
|||
# - ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4
|
||||
# - node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14
|
||||
|
||||
name: "Issue Triage Agent"
|
||||
name: "Daily Issue Triage"
|
||||
on:
|
||||
schedule:
|
||||
- cron: "49 14 * * 1-5"
|
||||
- cron: "26 13 * * 1-5"
|
||||
# Friendly format: daily around 14:00 on weekdays (scattered)
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
|
|
@ -71,7 +68,7 @@ permissions: {}
|
|||
concurrency:
|
||||
group: "gh-aw-${{ github.workflow }}"
|
||||
|
||||
run-name: "Issue Triage Agent"
|
||||
run-name: "Daily Issue Triage"
|
||||
|
||||
jobs:
|
||||
activation:
|
||||
|
|
@ -93,13 +90,13 @@ jobs:
|
|||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1
|
||||
uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
env:
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage-agent.lock.yml@${{ github.ref }}
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Daily Issue Triage"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-issue-triage.lock.yml@${{ github.ref }}
|
||||
GH_AW_INFO_VERSION: "1.0.52"
|
||||
GH_AW_INFO_AWF_VERSION: "v0.25.55"
|
||||
GH_AW_INFO_BODY_MODIFIED: "false"
|
||||
|
|
@ -113,7 +110,7 @@ jobs:
|
|||
GH_AW_INFO_VERSION: "1.0.52"
|
||||
GH_AW_INFO_AGENT_VERSION: "1.0.52"
|
||||
GH_AW_INFO_CLI_VERSION: "v0.76.1"
|
||||
GH_AW_INFO_WORKFLOW_NAME: "Issue Triage Agent"
|
||||
GH_AW_INFO_WORKFLOW_NAME: "Daily Issue Triage"
|
||||
GH_AW_INFO_EXPERIMENTAL: "false"
|
||||
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
|
||||
GH_AW_INFO_STAGED: "false"
|
||||
|
|
@ -122,9 +119,8 @@ jobs:
|
|||
GH_AW_INFO_AWF_VERSION: "v0.25.55"
|
||||
GH_AW_INFO_AWMG_VERSION: ""
|
||||
GH_AW_INFO_FIREWALL_TYPE: "squid"
|
||||
GH_AW_INFO_FRONTMATTER_SOURCE: "github/gh-aw/.github/workflows/issue-triage-agent.md@359795d49ada21681ab616bd4cbcb144a7387115"
|
||||
GH_AW_INFO_FRONTMATTER_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9"
|
||||
GH_AW_INFO_BODY_MODIFIED: "false"
|
||||
GH_AW_INFO_FRONTMATTER_EMOJI: "🔧"
|
||||
GH_AW_COMPILED_STRICT: "true"
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
|
|
@ -139,7 +135,7 @@ jobs:
|
|||
env:
|
||||
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
||||
- name: Checkout .github and .agents folders
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
sparse-checkout: |
|
||||
|
|
@ -164,7 +160,7 @@ jobs:
|
|||
id: check-lock-file
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_WORKFLOW_FILE: "issue-triage-agent.lock.yml"
|
||||
GH_AW_WORKFLOW_FILE: "daily-issue-triage.lock.yml"
|
||||
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
|
||||
with:
|
||||
script: |
|
||||
|
|
@ -198,20 +194,20 @@ jobs:
|
|||
run: |
|
||||
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
|
||||
{
|
||||
cat << 'GH_AW_PROMPT_e560c36b9148ef78_EOF'
|
||||
cat << 'GH_AW_PROMPT_7c51e8f15cc7af75_EOF'
|
||||
<system>
|
||||
GH_AW_PROMPT_e560c36b9148ef78_EOF
|
||||
GH_AW_PROMPT_7c51e8f15cc7af75_EOF
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
|
||||
cat << 'GH_AW_PROMPT_e560c36b9148ef78_EOF'
|
||||
cat << 'GH_AW_PROMPT_7c51e8f15cc7af75_EOF'
|
||||
<safe-output-tools>
|
||||
Tools: add_comment, add_labels, missing_tool, missing_data, noop
|
||||
Tools: add_comment(max:10), add_labels(max:10), missing_tool, missing_data, noop
|
||||
</safe-output-tools>
|
||||
GH_AW_PROMPT_e560c36b9148ef78_EOF
|
||||
GH_AW_PROMPT_7c51e8f15cc7af75_EOF
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
|
||||
cat << 'GH_AW_PROMPT_e560c36b9148ef78_EOF'
|
||||
cat << 'GH_AW_PROMPT_7c51e8f15cc7af75_EOF'
|
||||
<github-context>
|
||||
The following GitHub context information is available for this workflow:
|
||||
{{#if github.actor}}
|
||||
|
|
@ -240,14 +236,12 @@ jobs:
|
|||
{{/if}}
|
||||
</github-context>
|
||||
|
||||
GH_AW_PROMPT_e560c36b9148ef78_EOF
|
||||
GH_AW_PROMPT_7c51e8f15cc7af75_EOF
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
|
||||
cat << 'GH_AW_PROMPT_e560c36b9148ef78_EOF'
|
||||
cat << 'GH_AW_PROMPT_7c51e8f15cc7af75_EOF'
|
||||
</system>
|
||||
{{#runtime-import .github/aw/imports/github/gh-aw/359795d49ada21681ab616bd4cbcb144a7387115/.github_workflows_shared_reporting.md}}
|
||||
{{#runtime-import .github/aw/imports/github/gh-aw/359795d49ada21681ab616bd4cbcb144a7387115/.github_workflows_shared_noop-reminder.md}}
|
||||
{{#runtime-import .github/workflows/issue-triage-agent.md}}
|
||||
GH_AW_PROMPT_e560c36b9148ef78_EOF
|
||||
{{#runtime-import .github/workflows/daily-issue-triage.md}}
|
||||
GH_AW_PROMPT_7c51e8f15cc7af75_EOF
|
||||
} > "$GH_AW_PROMPT"
|
||||
- name: Interpolate variables and render templates
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
|
|
@ -327,8 +321,7 @@ jobs:
|
|||
agent:
|
||||
needs: activation
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: read
|
||||
permissions: read-all
|
||||
concurrency:
|
||||
group: "gh-aw-copilot-${{ github.workflow }}"
|
||||
env:
|
||||
|
|
@ -337,9 +330,10 @@ jobs:
|
|||
GH_AW_ASSETS_BRANCH: ""
|
||||
GH_AW_ASSETS_MAX_SIZE_KB: 0
|
||||
GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
|
||||
GH_AW_WORKFLOW_ID_SANITIZED: issuetriageagent
|
||||
GH_AW_WORKFLOW_ID_SANITIZED: dailyissuetriage
|
||||
outputs:
|
||||
agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }}
|
||||
checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
|
||||
effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }}
|
||||
effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }}
|
||||
has_patch: ${{ steps.collect_output.outputs.has_patch }}
|
||||
|
|
@ -355,15 +349,15 @@ jobs:
|
|||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1
|
||||
uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
trace-id: ${{ needs.activation.outputs.setup-trace-id }}
|
||||
parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
|
||||
env:
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage-agent.lock.yml@${{ github.ref }}
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Daily Issue Triage"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-issue-triage.lock.yml@${{ github.ref }}
|
||||
GH_AW_INFO_VERSION: "1.0.52"
|
||||
GH_AW_INFO_AWF_VERSION: "v0.25.55"
|
||||
GH_AW_INFO_BODY_MODIFIED: "false"
|
||||
|
|
@ -377,7 +371,7 @@ jobs:
|
|||
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Create gh-aw temp directory
|
||||
|
|
@ -399,27 +393,44 @@ jobs:
|
|||
SERVER_URL_STRIPPED="${SERVER_URL#https://}"
|
||||
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
|
||||
echo "Git configured with standard GitHub Actions identity"
|
||||
- name: Checkout PR branch
|
||||
id: checkout-pr
|
||||
if: |
|
||||
github.event.pull_request || github.event.issue.pull_request
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
|
||||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||||
const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
|
||||
await main();
|
||||
- name: Install GitHub Copilot CLI
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.52
|
||||
env:
|
||||
GH_HOST: github.com
|
||||
- name: Install AWF binary
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.55
|
||||
- name: Determine automatic lockdown mode for GitHub MCP Server
|
||||
id: determine-automatic-lockdown
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
|
||||
- name: Parse integrity filter lists
|
||||
id: parse-guard-vars
|
||||
env:
|
||||
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
|
||||
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
|
||||
with:
|
||||
script: |
|
||||
const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
|
||||
await determineAutomaticLockdown(github, context, core);
|
||||
GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }}
|
||||
GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }}
|
||||
GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }}
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh"
|
||||
- name: Download activation artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: activation
|
||||
path: /tmp/gh-aw
|
||||
- name: Restore agent config folders from base branch
|
||||
if: steps.checkout-pr.outcome == 'success'
|
||||
env:
|
||||
GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi"
|
||||
GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc"
|
||||
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
|
||||
- name: Restore inline sub-agents from activation artifact
|
||||
env:
|
||||
GH_AW_SUB_AGENT_DIR: ".github/agents"
|
||||
|
|
@ -436,16 +447,16 @@ jobs:
|
|||
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
|
||||
mkdir -p /tmp/gh-aw/safeoutputs
|
||||
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
|
||||
cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_c8eaf0ada4607ff7_EOF'
|
||||
{"add_comment":{"max":1},"add_labels":{"allowed":["bug","feature","enhancement","documentation","question","help-wanted","good-first-issue"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
|
||||
GH_AW_SAFE_OUTPUTS_CONFIG_c8eaf0ada4607ff7_EOF
|
||||
cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_09fd9551c3cd7278_EOF'
|
||||
{"add_comment":{"max":10,"target":"*"},"add_labels":{"max":10,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
|
||||
GH_AW_SAFE_OUTPUTS_CONFIG_09fd9551c3cd7278_EOF
|
||||
- name: Generate Safe Outputs Tools
|
||||
env:
|
||||
GH_AW_TOOLS_META_JSON: |
|
||||
{
|
||||
"description_suffixes": {
|
||||
"add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Supports reply_to_id for discussion threading.",
|
||||
"add_labels": " CONSTRAINTS: Only these labels are allowed: [\"bug\" \"feature\" \"enhancement\" \"documentation\" \"question\" \"help-wanted\" \"good-first-issue\"]."
|
||||
"add_comment": " CONSTRAINTS: Maximum 10 comment(s) can be added. Target: *. Supports reply_to_id for discussion threading.",
|
||||
"add_labels": " CONSTRAINTS: Maximum 10 label(s) can be added. Target: *."
|
||||
},
|
||||
"repo_params": {},
|
||||
"dynamic_tools": []
|
||||
|
|
@ -620,8 +631,6 @@ jobs:
|
|||
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
|
||||
GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }}
|
||||
GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }}
|
||||
GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }}
|
||||
GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }}
|
||||
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -eo pipefail
|
||||
|
|
@ -640,8 +649,6 @@ jobs:
|
|||
export DEBUG="*"
|
||||
|
||||
export GH_AW_ENGINE="copilot"
|
||||
export GH_AW_MCP_CLI_SERVERS='["safeoutputs"]'
|
||||
echo GH_AW_MCP_CLI_SERVERS='["safeoutputs"]' >> "$GITHUB_ENV"
|
||||
MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
|
||||
MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
|
||||
case "${DOCKER_HOST:-}" in
|
||||
|
|
@ -654,7 +661,7 @@ jobs:
|
|||
|
||||
mkdir -p /home/runner/.copilot
|
||||
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
|
||||
cat << GH_AW_MCP_CONFIG_88b8311e90d25032_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
|
||||
cat << GH_AW_MCP_CONFIG_37cac1d5ee0c175c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
|
|
@ -668,8 +675,11 @@ jobs:
|
|||
},
|
||||
"guard-policies": {
|
||||
"allow-only": {
|
||||
"min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY",
|
||||
"repos": "$GITHUB_MCP_GUARD_REPOS"
|
||||
"approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }},
|
||||
"blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }},
|
||||
"min-integrity": "none",
|
||||
"repos": "all",
|
||||
"trusted-users": ${{ steps.parse-guard-vars.outputs.trusted_users }}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -695,7 +705,7 @@ jobs:
|
|||
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
|
||||
}
|
||||
}
|
||||
GH_AW_MCP_CONFIG_88b8311e90d25032_EOF
|
||||
GH_AW_MCP_CONFIG_37cac1d5ee0c175c_EOF
|
||||
- name: Mount MCP servers as CLIs
|
||||
id: mount-mcp-clis
|
||||
continue-on-error: true
|
||||
|
|
@ -720,7 +730,7 @@ jobs:
|
|||
- name: Execute GitHub Copilot CLI
|
||||
id: agentic_execution
|
||||
# Copilot CLI tool arguments (sorted):
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 60
|
||||
run: |
|
||||
set -o pipefail
|
||||
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
|
||||
|
|
@ -907,6 +917,8 @@ jobs:
|
|||
/tmp/gh-aw/sandbox/agent/logs/
|
||||
/tmp/gh-aw/redacted-urls.log
|
||||
/tmp/gh-aw/mcp-logs/
|
||||
/tmp/gh-aw/proxy-logs/
|
||||
!/tmp/gh-aw/proxy-logs/proxy-tls/
|
||||
/tmp/gh-aw/agent_usage.json
|
||||
/tmp/gh-aw/agent-stdio.log
|
||||
/tmp/gh-aw/pre-agent-audit.txt
|
||||
|
|
@ -938,7 +950,7 @@ jobs:
|
|||
issues: write
|
||||
pull-requests: write
|
||||
concurrency:
|
||||
group: "gh-aw-conclusion-issue-triage-agent"
|
||||
group: "gh-aw-conclusion-daily-issue-triage"
|
||||
cancel-in-progress: false
|
||||
queue: max
|
||||
outputs:
|
||||
|
|
@ -949,15 +961,15 @@ jobs:
|
|||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1
|
||||
uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
trace-id: ${{ needs.activation.outputs.setup-trace-id }}
|
||||
parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
|
||||
env:
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage-agent.lock.yml@${{ github.ref }}
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Daily Issue Triage"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-issue-triage.lock.yml@${{ github.ref }}
|
||||
GH_AW_INFO_VERSION: "1.0.52"
|
||||
GH_AW_INFO_AWF_VERSION: "v0.25.55"
|
||||
GH_AW_INFO_BODY_MODIFIED: "false"
|
||||
|
|
@ -982,9 +994,9 @@ jobs:
|
|||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_NOOP_MAX: "1"
|
||||
GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
|
||||
GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/issue-triage-agent.md@359795d49ada21681ab616bd4cbcb144a7387115"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/blob/359795d49ada21681ab616bd4cbcb144a7387115/.github/workflows/issue-triage-agent.md"
|
||||
GH_AW_WORKFLOW_NAME: "Daily Issue Triage"
|
||||
GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/d7c1dc4b72b00607a67caaffdcc216cb64379cf9/workflows/daily-issue-triage.md"
|
||||
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
|
||||
GH_AW_NOOP_REPORT_AS_ISSUE: "true"
|
||||
|
|
@ -1000,9 +1012,9 @@ jobs:
|
|||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
|
||||
GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/issue-triage-agent.md@359795d49ada21681ab616bd4cbcb144a7387115"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/blob/359795d49ada21681ab616bd4cbcb144a7387115/.github/workflows/issue-triage-agent.md"
|
||||
GH_AW_WORKFLOW_NAME: "Daily Issue Triage"
|
||||
GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/d7c1dc4b72b00607a67caaffdcc216cb64379cf9/workflows/daily-issue-triage.md"
|
||||
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }}
|
||||
GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }}
|
||||
|
|
@ -1019,9 +1031,9 @@ jobs:
|
|||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_MISSING_TOOL_CREATE_ISSUE: "true"
|
||||
GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
|
||||
GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/issue-triage-agent.md@359795d49ada21681ab616bd4cbcb144a7387115"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/blob/359795d49ada21681ab616bd4cbcb144a7387115/.github/workflows/issue-triage-agent.md"
|
||||
GH_AW_WORKFLOW_NAME: "Daily Issue Triage"
|
||||
GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/d7c1dc4b72b00607a67caaffdcc216cb64379cf9/workflows/daily-issue-triage.md"
|
||||
with:
|
||||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
|
@ -1035,9 +1047,9 @@ jobs:
|
|||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true"
|
||||
GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
|
||||
GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/issue-triage-agent.md@359795d49ada21681ab616bd4cbcb144a7387115"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/blob/359795d49ada21681ab616bd4cbcb144a7387115/.github/workflows/issue-triage-agent.md"
|
||||
GH_AW_WORKFLOW_NAME: "Daily Issue Triage"
|
||||
GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/d7c1dc4b72b00607a67caaffdcc216cb64379cf9/workflows/daily-issue-triage.md"
|
||||
with:
|
||||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
|
@ -1051,15 +1063,16 @@ jobs:
|
|||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||||
GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
|
||||
GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/issue-triage-agent.md@359795d49ada21681ab616bd4cbcb144a7387115"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/blob/359795d49ada21681ab616bd4cbcb144a7387115/.github/workflows/issue-triage-agent.md"
|
||||
GH_AW_WORKFLOW_NAME: "Daily Issue Triage"
|
||||
GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/d7c1dc4b72b00607a67caaffdcc216cb64379cf9/workflows/daily-issue-triage.md"
|
||||
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
|
||||
GH_AW_WORKFLOW_ID: "issue-triage-agent"
|
||||
GH_AW_WORKFLOW_ID: "daily-issue-triage"
|
||||
GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168"
|
||||
GH_AW_ENGINE_ID: "copilot"
|
||||
GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }}
|
||||
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
|
||||
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
|
||||
GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.effective_tokens_rate_limit_error || 'false' }}
|
||||
GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }}
|
||||
|
|
@ -1073,7 +1086,7 @@ jobs:
|
|||
GH_AW_FAILURE_REPORT_AS_ISSUE: "true"
|
||||
GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true"
|
||||
GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true"
|
||||
GH_AW_TIMEOUT_MINUTES: "5"
|
||||
GH_AW_TIMEOUT_MINUTES: "60"
|
||||
GH_AW_MAX_EFFECTIVE_TOKENS: "25000000"
|
||||
with:
|
||||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
|
|
@ -1099,15 +1112,15 @@ jobs:
|
|||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1
|
||||
uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
trace-id: ${{ needs.activation.outputs.setup-trace-id }}
|
||||
parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
|
||||
env:
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage-agent.lock.yml@${{ github.ref }}
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Daily Issue Triage"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-issue-triage.lock.yml@${{ github.ref }}
|
||||
GH_AW_INFO_VERSION: "1.0.52"
|
||||
GH_AW_INFO_AWF_VERSION: "v0.25.55"
|
||||
GH_AW_INFO_BODY_MODIFIED: "false"
|
||||
|
|
@ -1128,7 +1141,7 @@ jobs:
|
|||
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
|
||||
- name: Checkout repository for patch context
|
||||
if: needs.agent.outputs.has_patch == 'true'
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
# --- Threat Detection ---
|
||||
|
|
@ -1176,8 +1189,8 @@ jobs:
|
|||
if: always() && steps.detection_guard.outputs.run_detection == 'true'
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
WORKFLOW_NAME: "Issue Triage Agent"
|
||||
WORKFLOW_DESCRIPTION: "No description provided"
|
||||
WORKFLOW_NAME: "Daily Issue Triage"
|
||||
WORKFLOW_DESCRIPTION: "Scheduled daily triage that processes untriaged CloudStack issues in batches.\nDetects duplicates, filters spam, and assigns CloudStack-specific labels\n(type:*, component:*, Severity:*, status:*), then posts a structured triage report."
|
||||
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
|
||||
with:
|
||||
script: |
|
||||
|
|
@ -1300,18 +1313,17 @@ jobs:
|
|||
pull-requests: write
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/issue-triage-agent"
|
||||
GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/daily-issue-triage"
|
||||
GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }}
|
||||
GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }}
|
||||
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
|
||||
GH_AW_ENGINE_ID: "copilot"
|
||||
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
|
||||
GH_AW_ENGINE_VERSION: "1.0.52"
|
||||
GH_AW_WORKFLOW_EMOJI: "🔧"
|
||||
GH_AW_WORKFLOW_ID: "issue-triage-agent"
|
||||
GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
|
||||
GH_AW_WORKFLOW_SOURCE: "github/gh-aw/.github/workflows/issue-triage-agent.md@359795d49ada21681ab616bd4cbcb144a7387115"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/github/gh-aw/blob/359795d49ada21681ab616bd4cbcb144a7387115/.github/workflows/issue-triage-agent.md"
|
||||
GH_AW_WORKFLOW_ID: "daily-issue-triage"
|
||||
GH_AW_WORKFLOW_NAME: "Daily Issue Triage"
|
||||
GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9"
|
||||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/d7c1dc4b72b00607a67caaffdcc216cb64379cf9/workflows/daily-issue-triage.md"
|
||||
outputs:
|
||||
code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }}
|
||||
code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}
|
||||
|
|
@ -1324,15 +1336,15 @@ jobs:
|
|||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1
|
||||
uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
trace-id: ${{ needs.activation.outputs.setup-trace-id }}
|
||||
parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
|
||||
env:
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage-agent.lock.yml@${{ github.ref }}
|
||||
GH_AW_SETUP_WORKFLOW_NAME: "Daily Issue Triage"
|
||||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-issue-triage.lock.yml@${{ github.ref }}
|
||||
GH_AW_INFO_VERSION: "1.0.52"
|
||||
GH_AW_INFO_AWF_VERSION: "v0.25.55"
|
||||
GH_AW_INFO_BODY_MODIFIED: "false"
|
||||
|
|
@ -1369,7 +1381,7 @@ jobs:
|
|||
GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
|
||||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||||
GITHUB_API_URL: ${{ github.api_url }}
|
||||
GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"add_labels\":{\"allowed\":[\"bug\",\"feature\",\"enhancement\",\"documentation\",\"question\",\"help-wanted\",\"good-first-issue\"]},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
|
||||
GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}"
|
||||
with:
|
||||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
---
|
||||
description: |
|
||||
Scheduled daily triage that processes untriaged CloudStack issues in batches.
|
||||
Detects duplicates, filters spam, and assigns CloudStack-specific labels
|
||||
(type:*, component:*, Severity:*, status:*), then posts a structured triage report.
|
||||
|
||||
name: Daily Issue Triage
|
||||
|
||||
on:
|
||||
schedule: daily around 14:00 on weekdays
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: read-all
|
||||
|
||||
network: defaults
|
||||
|
||||
safe-outputs:
|
||||
add-labels:
|
||||
target: "*"
|
||||
max: 10
|
||||
add-comment:
|
||||
target: "*"
|
||||
max: 10
|
||||
|
||||
tools:
|
||||
web-fetch:
|
||||
github:
|
||||
toolsets: [issues, labels]
|
||||
min-integrity: none
|
||||
|
||||
source: githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9
|
||||
timeout-minutes: 60
|
||||
---
|
||||
|
||||
# Daily Issue Triage
|
||||
|
||||
<!-- Note - this file can be customized to your needs. Replace this section directly, or add further instructions here. After editing run 'gh aw compile' -->
|
||||
|
||||
You are a batch triage assistant for GitHub issues in **${{ github.repository }}** (Apache CloudStack). Your task is to find untriaged issues and triage them one by one. Your triage comments are written for maintainers reviewing the triage, not for the issue author.
|
||||
|
||||
Do not make assumptions beyond what the issue content supports. Do not invent missing context.
|
||||
|
||||
## Step 1: Find untriaged issues
|
||||
|
||||
Use the `search_issues` tool to find open issues that need triage. An issue is considered untriaged if it has **no labels applied**.
|
||||
|
||||
Query: `repo:${{ github.repository }} is:issue is:open no:label`
|
||||
|
||||
Paginate through all results to find untriaged issues. Do not stop at the first page.
|
||||
|
||||
From the results, filter out:
|
||||
- Issues that already have a triage comment (look for "🎯 Triage report" in comments). **Never retriage an issue that has already been triaged.**
|
||||
- Issues created by bots (unless they look like real user issues).
|
||||
- Issues that have any labels already applied (even if they weren't applied by this workflow).
|
||||
|
||||
Process the **oldest untriaged issues first**. Note: this workflow is capped at 10 label-sets and 10 comments per run, so the backlog will drain over several daily runs — that is intentional.
|
||||
|
||||
## Step 2: Fetch labels (once)
|
||||
|
||||
Before triaging any issues, fetch the list of labels available in this repository using the `list_labels` tool. Use this live list for all issues in the batch — only apply labels that actually exist in the repository.
|
||||
|
||||
CloudStack uses a prefixed label taxonomy. Choose from these families:
|
||||
|
||||
- **Type** (pick the single best one): `type:bug`, `type:new-feature`, `type:enhancement`, `type:improvement`, `type:regression`, `type:security`, `type:question`, `type:config`, `type:cleanup`
|
||||
- **Component** (apply when clearly identifiable; more than one is allowed): e.g. `component:kvm`, `component:vmware`, `component:XenServer`, `component:api`, `component:UI`, `component:networking`, `component:virtual-router`, `component:management-server`, `component:primary-storage`, `component:secondary-storage`, `component:kubernetes`, `component:database`, and others — use the full list returned by `list_labels`.
|
||||
- **Severity** (bugs only, when assessable): `Severity:BLOCKER`, `Severity:Critical`, `Severity:Major`, `Severity:Minor`, `Severity:Trivial`
|
||||
- **Duplicate / invalid**: `status:duplicate`, `status:invalid`
|
||||
- **Help wanted / newcomer-friendly**: `status:Help-wanted`
|
||||
|
||||
## Step 3: Triage each issue
|
||||
|
||||
For each untriaged issue, perform the following steps:
|
||||
|
||||
### 3a: Gather context
|
||||
|
||||
1. Retrieve the full issue content using the `get_issue` tool.
|
||||
2. Fetch any comments on the issue using the `get_issue_comments` tool.
|
||||
3. Search for similar issues using the `search_issues` tool.
|
||||
|
||||
### 3b: Spam and quality check
|
||||
|
||||
**Spam and invalid issues:** If the issue is obviously spam, bot-generated, gibberish, or a test issue:
|
||||
- Apply the `status:invalid` label.
|
||||
- **Do not close the issue** — closing is a human decision. Note in the report that it looks like spam/invalid so a maintainer can act.
|
||||
- Move to the next issue.
|
||||
|
||||
**Incomplete issues:** If the issue lacks enough detail for meaningful triage, add a comment that politely asks the author to provide the missing information:
|
||||
- For bugs: steps to reproduce, expected vs actual behavior, logs/errors, environment details (CloudStack version, hypervisor, etc.).
|
||||
- For other issue types: equivalent details that would make the report actionable.
|
||||
- Apply a `type:question` label if appropriate.
|
||||
- Be specific about what is missing and why it is needed.
|
||||
- Move to the next issue.
|
||||
|
||||
### 3c: Select labels
|
||||
|
||||
- Be cautious with labels; they can trigger automation.
|
||||
- Choose a single `type:*` label that best reflects the issue's nature.
|
||||
- Add `component:*` label(s) when the affected area is clear from the content.
|
||||
- Add a `Severity:*` label for bugs when severity can be reasonably assessed.
|
||||
- Do not apply labels that do not exist in the repository.
|
||||
- It is better to under-label than to speculatively add labels.
|
||||
|
||||
### 3d: Detect duplicates and related issues
|
||||
|
||||
- Review the similar issues found in Step 3a.
|
||||
- Classify matches as:
|
||||
- **Duplicate** (high confidence): the issue describes the same problem as an existing open issue. Include up to 3.
|
||||
- **Related**: similar domain or adjacent problem, but not a duplicate. Include up to 3.
|
||||
- If a high-confidence duplicate is found, apply the `status:duplicate` label.
|
||||
- If no similar issues are found, state that explicitly in your report.
|
||||
|
||||
### 3e: Assess coding agent suitability
|
||||
|
||||
Assess whether the issue is suitable for automated coding agent assignment:
|
||||
- **Suitable**: clear requirements, sufficient context, well-defined success criteria, self-contained scope.
|
||||
- **Needs more info**: potentially suitable but missing details needed to start.
|
||||
- **Not suitable**: requires investigation, design decisions, extensive coordination, or policy/architectural choices.
|
||||
|
||||
### 3f: Additional analysis
|
||||
|
||||
- Search the web for relevant documentation, error messages, or known solutions if applicable.
|
||||
- Write notes, debugging strategies, and/or reproduction steps relevant to the issue.
|
||||
- Suggest resources or links that might help resolve the issue.
|
||||
|
||||
### 3g: Apply results and post comment
|
||||
|
||||
Apply all triage results for this issue:
|
||||
- Use `update_issue` to apply the chosen labels.
|
||||
- Add an issue comment with the triage report using the format below.
|
||||
|
||||
Then move to the next issue.
|
||||
|
||||
## Processing order
|
||||
|
||||
1. Fetch available labels (Step 2, once at the start).
|
||||
2. Find untriaged issues (Step 1).
|
||||
3. For each issue (oldest first), run Step 3 (gather, check, label, detect duplicates, comment).
|
||||
|
||||
## Comment format
|
||||
|
||||
Use this structure for each triage comment. Use collapsed sections to keep it tidy.
|
||||
|
||||
```markdown
|
||||
## 🎯 Triage report
|
||||
|
||||
{2-3 sentence summary to help a maintainer quickly grasp the issue.}
|
||||
|
||||
### 📊 Assessment
|
||||
|
||||
| Dimension | Value | Reasoning |
|
||||
|---|---|---|
|
||||
| **Type** | [type:* label or "none"] | [brief] |
|
||||
| **Component** | [component:* label(s) or "none"] | [brief] |
|
||||
| **Severity** | [Severity:* label or "n/a"] | [brief] |
|
||||
| **Labels** | [all labels applied or "none"] | [brief] |
|
||||
| **Coding agent** | [Suitable / Needs more info / Not suitable] | [brief] |
|
||||
|
||||
### 🔗 Similar issues
|
||||
|
||||
- issue-url (duplicate/related) — [brief explanation]
|
||||
|
||||
<details><summary>💡 Notes and suggestions</summary>
|
||||
|
||||
{Debugging strategies, reproduction steps, resource links, sub-task checklists, nudges for the team.}
|
||||
|
||||
</details>
|
||||
```
|
||||
|
||||
If no similar issues were found, omit the "Similar issues" section. If there are no notes to add, omit the collapsed section.
|
||||
|
||||
**Important**: Never close issues. Only apply labels and post comments.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"ae44897dc32d20c421588b31a279653abf436964ad86c58885e0edbfbb2f0416","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot"}
|
||||
# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"bcecce6f1d9f8df2b3eca9eb7bb1fdbac13c396c240a2dc802a96546f435b969","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot","agent_model":"claude-haiku-4.5"}
|
||||
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"46d564922b082d0db93244972e8005ea6904ee5f","version":"v0.76.1"}],"containers":[{"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"},{"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"},{"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"},{"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"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4","digest":"sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]}
|
||||
# ___ _ _
|
||||
# / _ \ | | (_)
|
||||
|
|
@ -36,12 +36,12 @@
|
|||
# - GITHUB_TOKEN
|
||||
#
|
||||
# Custom actions used:
|
||||
# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
# - github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1
|
||||
# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8
|
||||
#
|
||||
# Container images used:
|
||||
# - ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731
|
||||
|
|
@ -91,7 +91,7 @@ jobs:
|
|||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1
|
||||
uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
|
|
@ -107,7 +107,7 @@ jobs:
|
|||
env:
|
||||
GH_AW_INFO_ENGINE_ID: "copilot"
|
||||
GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
|
||||
GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }}
|
||||
GH_AW_INFO_MODEL: "claude-haiku-4.5"
|
||||
GH_AW_INFO_VERSION: "1.0.52"
|
||||
GH_AW_INFO_AGENT_VERSION: "1.0.52"
|
||||
GH_AW_INFO_CLI_VERSION: "v0.76.1"
|
||||
|
|
@ -136,7 +136,7 @@ jobs:
|
|||
env:
|
||||
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
||||
- name: Checkout .github and .agents folders
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
sparse-checkout: |
|
||||
|
|
@ -195,20 +195,20 @@ jobs:
|
|||
run: |
|
||||
bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
|
||||
{
|
||||
cat << 'GH_AW_PROMPT_603e0a48f86a9470_EOF'
|
||||
cat << 'GH_AW_PROMPT_eeb322738661ed58_EOF'
|
||||
<system>
|
||||
GH_AW_PROMPT_603e0a48f86a9470_EOF
|
||||
GH_AW_PROMPT_eeb322738661ed58_EOF
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
|
||||
cat << 'GH_AW_PROMPT_603e0a48f86a9470_EOF'
|
||||
cat << 'GH_AW_PROMPT_eeb322738661ed58_EOF'
|
||||
<safe-output-tools>
|
||||
Tools: create_issue, missing_tool, missing_data, noop
|
||||
</safe-output-tools>
|
||||
GH_AW_PROMPT_603e0a48f86a9470_EOF
|
||||
GH_AW_PROMPT_eeb322738661ed58_EOF
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
|
||||
cat << 'GH_AW_PROMPT_603e0a48f86a9470_EOF'
|
||||
cat << 'GH_AW_PROMPT_eeb322738661ed58_EOF'
|
||||
<github-context>
|
||||
The following GitHub context information is available for this workflow:
|
||||
{{#if github.actor}}
|
||||
|
|
@ -237,12 +237,12 @@ jobs:
|
|||
{{/if}}
|
||||
</github-context>
|
||||
|
||||
GH_AW_PROMPT_603e0a48f86a9470_EOF
|
||||
GH_AW_PROMPT_eeb322738661ed58_EOF
|
||||
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
|
||||
cat << 'GH_AW_PROMPT_603e0a48f86a9470_EOF'
|
||||
cat << 'GH_AW_PROMPT_eeb322738661ed58_EOF'
|
||||
</system>
|
||||
{{#runtime-import .github/workflows/daily-repo-status.md}}
|
||||
GH_AW_PROMPT_603e0a48f86a9470_EOF
|
||||
GH_AW_PROMPT_eeb322738661ed58_EOF
|
||||
} > "$GH_AW_PROMPT"
|
||||
- name: Interpolate variables and render templates
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
|
|
@ -352,7 +352,7 @@ jobs:
|
|||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1
|
||||
uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
|
|
@ -374,7 +374,7 @@ jobs:
|
|||
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Create gh-aw temp directory
|
||||
|
|
@ -450,9 +450,9 @@ jobs:
|
|||
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
|
||||
mkdir -p /tmp/gh-aw/safeoutputs
|
||||
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
|
||||
cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_9b6646f0d620a78f_EOF'
|
||||
cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_51571b44da85874d_EOF'
|
||||
{"create_issue":{"close_older_issues":true,"labels":["report","daily-status"],"max":1,"title_prefix":"[repo-status] "},"create_report_incomplete_issue":{},"mentions":{"enabled":false},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}}
|
||||
GH_AW_SAFE_OUTPUTS_CONFIG_9b6646f0d620a78f_EOF
|
||||
GH_AW_SAFE_OUTPUTS_CONFIG_51571b44da85874d_EOF
|
||||
- name: Generate Safe Outputs Tools
|
||||
env:
|
||||
GH_AW_TOOLS_META_JSON: |
|
||||
|
|
@ -658,7 +658,7 @@ jobs:
|
|||
|
||||
mkdir -p /home/runner/.copilot
|
||||
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
|
||||
cat << GH_AW_MCP_CONFIG_d075a7f45ab51044_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
|
||||
cat << GH_AW_MCP_CONFIG_4b3a7789a6eea081_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
|
|
@ -702,7 +702,7 @@ jobs:
|
|||
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
|
||||
}
|
||||
}
|
||||
GH_AW_MCP_CONFIG_d075a7f45ab51044_EOF
|
||||
GH_AW_MCP_CONFIG_4b3a7789a6eea081_EOF
|
||||
- name: Mount MCP servers as CLIs
|
||||
id: mount-mcp-clis
|
||||
continue-on-error: true
|
||||
|
|
@ -750,7 +750,7 @@ jobs:
|
|||
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
|
||||
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
|
||||
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
||||
COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }}
|
||||
COPILOT_MODEL: claude-haiku-4.5
|
||||
GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
|
||||
GH_AW_PHASE: agent
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
|
|
@ -957,7 +957,7 @@ jobs:
|
|||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1
|
||||
uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
|
|
@ -1108,7 +1108,7 @@ jobs:
|
|||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1
|
||||
uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
|
|
@ -1137,7 +1137,7 @@ jobs:
|
|||
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
|
||||
- name: Checkout repository for patch context
|
||||
if: needs.agent.outputs.has_patch == 'true'
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
# --- Threat Detection ---
|
||||
|
|
@ -1238,7 +1238,7 @@ jobs:
|
|||
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
|
||||
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
|
||||
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
||||
COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }}
|
||||
COPILOT_MODEL: claude-haiku-4.5
|
||||
GH_AW_PHASE: detection
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
GH_AW_VERSION: v0.76.1
|
||||
|
|
@ -1312,7 +1312,7 @@ jobs:
|
|||
GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }}
|
||||
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
|
||||
GH_AW_ENGINE_ID: "copilot"
|
||||
GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
|
||||
GH_AW_ENGINE_MODEL: "claude-haiku-4.5"
|
||||
GH_AW_ENGINE_VERSION: "1.0.52"
|
||||
GH_AW_WORKFLOW_ID: "daily-repo-status"
|
||||
GH_AW_WORKFLOW_NAME: "Repo Status"
|
||||
|
|
@ -1330,7 +1330,7 @@ jobs:
|
|||
steps:
|
||||
- name: Setup Scripts
|
||||
id: setup
|
||||
uses: github/gh-aw-actions/setup@46d564922b082d0db93244972e8005ea6904ee5f # v0.76.1
|
||||
uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8
|
||||
with:
|
||||
destination: ${{ runner.temp }}/gh-aw/actions
|
||||
job-name: ${{ github.job }}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ permissions:
|
|||
|
||||
network: defaults
|
||||
|
||||
engine:
|
||||
id: copilot
|
||||
model: claude-haiku-4.5
|
||||
|
||||
tools:
|
||||
github:
|
||||
# If in a public repo, setting `lockdown: false` allows
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ jobs:
|
|||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
registry: ${{ secrets.DOCKER_REGISTRY }}
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
|
|
@ -47,7 +47,7 @@ jobs:
|
|||
- name: Set Docker repository name
|
||||
run: echo "DOCKER_REPOSITORY=apache" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
---
|
||||
on:
|
||||
schedule: daily around 14:00 on weekdays
|
||||
workflow_dispatch: null
|
||||
permissions:
|
||||
issues: read
|
||||
imports:
|
||||
- github/gh-aw/.github/workflows/shared/reporting.md@359795d49ada21681ab616bd4cbcb144a7387115
|
||||
- github/gh-aw/.github/workflows/shared/noop-reminder.md@359795d49ada21681ab616bd4cbcb144a7387115
|
||||
safe-outputs:
|
||||
add-comment: {}
|
||||
add-labels:
|
||||
allowed:
|
||||
- bug
|
||||
- feature
|
||||
- enhancement
|
||||
- documentation
|
||||
- question
|
||||
- help-wanted
|
||||
- good-first-issue
|
||||
emoji: 🔧
|
||||
source: github/gh-aw/.github/workflows/issue-triage-agent.md@359795d49ada21681ab616bd4cbcb144a7387115
|
||||
strict: true
|
||||
timeout-minutes: 5
|
||||
tools:
|
||||
cli-proxy: true
|
||||
github:
|
||||
toolsets:
|
||||
- issues
|
||||
- labels
|
||||
---
|
||||
# Issue Triage Agent
|
||||
|
||||
List open issues in ${{ github.repository }} that have no labels. For each unlabeled issue, analyze the title and body, then add one of the allowed labels: `bug`, `feature`, `enhancement`, `documentation`, `question`, `help-wanted`, or `good-first-issue`.
|
||||
|
||||
Skip issues that:
|
||||
- Already have any of these labels
|
||||
- Have been assigned to any user (especially non-bot users)
|
||||
|
||||
After adding the label to an issue, mention the issue author in a comment using this format (follow shared/reporting.md guidelines):
|
||||
|
||||
**Comment Template**:
|
||||
```markdown
|
||||
### 🏷️ Issue Triaged
|
||||
|
||||
Hi @{author}! I've categorized this issue as **{label_name}** based on the following analysis:
|
||||
|
||||
**Reasoning**: {brief_explanation_of_why_this_label}
|
||||
|
||||
<details>
|
||||
<summary>View Triage Details</summary>
|
||||
|
||||
#### Analysis
|
||||
- **Keywords detected**: {list_of_keywords_that_matched}
|
||||
- **Issue type indicators**: {what_made_this_fit_the_category}
|
||||
- **Confidence**: {High/Medium/Low}
|
||||
|
||||
#### Recommended Next Steps
|
||||
- {context_specific_suggestion_1}
|
||||
- {context_specific_suggestion_2}
|
||||
|
||||
</details>
|
||||
|
||||
**References**: [Triage run §{run_id}](https://github.com/github/gh-aw/actions/runs/{run_id})
|
||||
```
|
||||
|
||||
**Key formatting requirements**:
|
||||
- Use h3 (###) for the main heading
|
||||
- Keep reasoning visible for quick understanding
|
||||
- Wrap detailed analysis in `<details>` tags
|
||||
- Include workflow run reference
|
||||
- Keep total comment concise (collapsed details prevent noise)
|
||||
|
||||
## Batch Comment Optimization
|
||||
|
||||
For efficiency, if multiple issues are triaged in a single run:
|
||||
1. Add individual labels to each issue
|
||||
2. Add a brief comment to each issue (using the template above)
|
||||
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.
|
||||
|
|
@ -31,7 +31,7 @@ jobs:
|
|||
name: Sonar JaCoCo Coverage
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
|
@ -63,3 +63,5 @@ jobs:
|
|||
verbose: true
|
||||
name: codecov
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Compute Coverage Grade
|
||||
run: bash scripts/coverage-grade.sh client/target/site/jacoco-aggregate/jacoco.xml
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Conflict Check
|
||||
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
|
||||
uses: eps1lon/actions-label-merge-conflict@0273be72a0bbd58fcd71d0d6c02c209b50d1e5e1 # v3.1.0
|
||||
with:
|
||||
repoToken: "${{ secrets.GITHUB_TOKEN }}"
|
||||
dirtyLabel: "status:has-conflicts"
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ jobs:
|
|||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check Out
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup Environment
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
name: Sonar JaCoCo Coverage
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
|
@ -65,3 +65,51 @@ jobs:
|
|||
verbose: true
|
||||
name: codecov
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Compute Coverage Grade
|
||||
id: grade
|
||||
run: bash scripts/coverage-grade.sh client/target/site/jacoco-aggregate/jacoco.xml
|
||||
- name: Post Coverage Grade Comment on PR
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const grade = '${{ steps.grade.outputs.coverage_grade }}';
|
||||
const label = '${{ steps.grade.outputs.coverage_grade_label }}';
|
||||
const linePct = '${{ steps.grade.outputs.line_coverage }}';
|
||||
const branchPct = '${{ steps.grade.outputs.branch_coverage }}';
|
||||
const emojiMap = { A: '🟢', B: '🟡', C: '🟠', D: '🔴', F: '⛔' };
|
||||
const emoji = emojiMap[grade] ?? '❓';
|
||||
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
|
||||
const branchRow = branchPct !== 'N/A'
|
||||
? `| Branch coverage | **${branchPct}%** |`
|
||||
: '';
|
||||
|
||||
const body = [
|
||||
`## ${emoji} Test Coverage Grade: \`${grade}\` — ${label}`,
|
||||
'',
|
||||
'| Metric | Value |',
|
||||
'|--------|-------|',
|
||||
`| Line coverage | **${linePct}%** |`,
|
||||
branchRow,
|
||||
'',
|
||||
'### Grade Scale',
|
||||
'| Grade | Line Coverage | Meaning |',
|
||||
'|-------|--------------|---------|',
|
||||
'| 🟢 A | ≥ 80% | Excellent - this code sleeps well at night 😴 |',
|
||||
'| 🟡 B | 60-79% | Good - almost there, don\'t stop now 😉 |',
|
||||
'| 🟠 C | 40-59% | Acceptable - your code is wearing a seatbelt, but no airbags 😬 |',
|
||||
'| 🔴 D | 20-39% | Marginal - boldly shipping where no test has gone before 🖖 |',
|
||||
'| ⛔ F | < 20% | Failing - tests? what tests? 🔥 |',
|
||||
'',
|
||||
'> Branch coverage is shown as a secondary signal. Grade is determined by **line coverage**.',
|
||||
`> [View full Actions run](${runUrl})`,
|
||||
].filter(l => l !== undefined).join('\n');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: body,
|
||||
});
|
||||
console.log('Posted coverage grade comment');
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ jobs:
|
|||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ import org.apache.cloudstack.storage.object.Bucket;
|
|||
import org.apache.cloudstack.storage.object.ObjectStore;
|
||||
import org.apache.cloudstack.storage.sharedfs.SharedFS;
|
||||
import org.apache.cloudstack.usage.Usage;
|
||||
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||
import org.apache.cloudstack.schedule.ResourceSchedule;
|
||||
|
||||
import com.cloud.dc.DataCenter;
|
||||
import com.cloud.dc.DataCenterGuestIpv6Prefix;
|
||||
|
|
@ -125,17 +125,18 @@ public class EventTypes {
|
|||
public static final String EVENT_VM_UNMANAGE = "VM.UNMANAGE";
|
||||
public static final String EVENT_VM_RECOVER = "VM.RECOVER";
|
||||
|
||||
// VM Schedule
|
||||
public static final String EVENT_VM_SCHEDULE_CREATE = "VM.SCHEDULE.CREATE";
|
||||
public static final String EVENT_VM_SCHEDULE_UPDATE = "VM.SCHEDULE.UPDATE";
|
||||
public static final String EVENT_VM_SCHEDULE_DELETE = "VM.SCHEDULE.DELETE";
|
||||
|
||||
// VM Schedule action-execution events (fired when a scheduled action runs).
|
||||
public static final String EVENT_VM_SCHEDULE_START = "VM.SCHEDULE.START";
|
||||
public static final String EVENT_VM_SCHEDULE_STOP = "VM.SCHEDULE.STOP";
|
||||
public static final String EVENT_VM_SCHEDULE_REBOOT = "VM.SCHEDULE.REBOOT";
|
||||
public static final String EVENT_VM_SCHEDULE_FORCE_STOP = "VM.SCHEDULE.FORCE.STOP";
|
||||
public static final String EVENT_VM_SCHEDULE_FORCE_REBOOT = "VM.SCHEDULE.FORCE.REBOOT";
|
||||
|
||||
// Generic Resource Schedule CRUD events (apply to all resource types).
|
||||
public static final String EVENT_SCHEDULE_CREATE = "SCHEDULE.CREATE";
|
||||
public static final String EVENT_SCHEDULE_UPDATE = "SCHEDULE.UPDATE";
|
||||
public static final String EVENT_SCHEDULE_DELETE = "SCHEDULE.DELETE";
|
||||
|
||||
// Domain Router
|
||||
public static final String EVENT_ROUTER_CREATE = "ROUTER.CREATE";
|
||||
public static final String EVENT_ROUTER_DESTROY = "ROUTER.DESTROY";
|
||||
|
|
@ -676,6 +677,7 @@ public class EventTypes {
|
|||
public static final String EVENT_AUTOSCALEVMGROUP_DISABLE = "AUTOSCALEVMGROUP.DISABLE";
|
||||
public static final String EVENT_AUTOSCALEVMGROUP_SCALEDOWN = "AUTOSCALEVMGROUP.SCALEDOWN";
|
||||
public static final String EVENT_AUTOSCALEVMGROUP_SCALEUP = "AUTOSCALEVMGROUP.SCALEUP";
|
||||
public static final String EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE = "AUTOSCALEVMGROUP.SCHEDULE.UPDATE";
|
||||
|
||||
public static final String EVENT_BAREMETAL_DHCP_SERVER_ADD = "PHYSICAL.DHCP.ADD";
|
||||
public static final String EVENT_BAREMETAL_DHCP_SERVER_DELETE = "PHYSICAL.DHCP.DELETE";
|
||||
|
|
@ -888,15 +890,18 @@ public class EventTypes {
|
|||
entityEventDetails.put(EVENT_VM_IMPORT, VirtualMachine.class);
|
||||
entityEventDetails.put(EVENT_VM_UNMANAGE, VirtualMachine.class);
|
||||
|
||||
// VMSchedule
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_CREATE, VMSchedule.class);
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_DELETE, VMSchedule.class);
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_UPDATE, VMSchedule.class);
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_START, VMSchedule.class);
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_STOP, VMSchedule.class);
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_REBOOT, VMSchedule.class);
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_STOP, VMSchedule.class);
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_REBOOT, VMSchedule.class);
|
||||
// VMSchedule action-execution events
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_START, ResourceSchedule.class);
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_STOP, ResourceSchedule.class);
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_REBOOT, ResourceSchedule.class);
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_STOP, ResourceSchedule.class);
|
||||
entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_REBOOT, ResourceSchedule.class);
|
||||
entityEventDetails.put(EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE, ResourceSchedule.class);
|
||||
|
||||
// Generic Resource Schedule
|
||||
entityEventDetails.put(EVENT_SCHEDULE_CREATE, ResourceSchedule.class);
|
||||
entityEventDetails.put(EVENT_SCHEDULE_UPDATE, ResourceSchedule.class);
|
||||
entityEventDetails.put(EVENT_SCHEDULE_DELETE, ResourceSchedule.class);
|
||||
|
||||
entityEventDetails.put(EVENT_ROUTER_CREATE, VirtualRouter.class);
|
||||
entityEventDetails.put(EVENT_ROUTER_DESTROY, VirtualRouter.class);
|
||||
|
|
|
|||
|
|
@ -63,6 +63,9 @@ public interface Host extends StateObject<Status>, Identity, Partition, HAResour
|
|||
String HOST_OVFTOOL_VERSION = "host.ovftool.version";
|
||||
String HOST_VIRTV2V_VERSION = "host.virtv2v.version";
|
||||
String HOST_SSH_PORT = "host.ssh.port";
|
||||
String HOST_CDROM_MAX_COUNT = "host.cdrom.max.count";
|
||||
String GUEST_OS_CATEGORY_ID = "guest.os.category.id";
|
||||
String GUEST_OS_RULE = "guest.os.rule";
|
||||
|
||||
int DEFAULT_SSH_PORT = 22;
|
||||
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ public interface NetworkService {
|
|||
/**
|
||||
* Requests an IP address for the guest NIC
|
||||
*/
|
||||
NicSecondaryIp allocateSecondaryGuestIP(long nicId, IpAddresses requestedIpPair) throws InsufficientAddressCapacityException;
|
||||
NicSecondaryIp allocateSecondaryGuestIP(long nicId, IpAddresses requestedIpPair, String description) throws InsufficientAddressCapacityException;
|
||||
|
||||
boolean releaseSecondaryIpFromNic(long ipAddressId);
|
||||
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ public class Storage {
|
|||
ISO(false, false, EncryptionSupport.Unsupported), // for iso image
|
||||
LVM(false, false, EncryptionSupport.Unsupported), // XenServer local LVM SR
|
||||
CLVM(true, false, EncryptionSupport.Unsupported),
|
||||
CLVM_NG(true, false, EncryptionSupport.Hypervisor),
|
||||
RBD(true, true, EncryptionSupport.Unsupported), // http://libvirt.org/storage.html#StorageBackendRBD
|
||||
SharedMountPoint(true, true, EncryptionSupport.Hypervisor),
|
||||
VMFS(true, true, EncryptionSupport.Unsupported), // VMware VMFS storage
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ public interface NicSecondaryIp extends ControlledEntity, Identity, InternalIden
|
|||
|
||||
String getIp6Address();
|
||||
|
||||
String getDescription();
|
||||
|
||||
long getNetworkId();
|
||||
|
||||
long getVmId();
|
||||
|
|
|
|||
|
|
@ -450,6 +450,7 @@ public class ApiConstants {
|
|||
public static final String MAX_VGPU_PER_PHYSICAL_GPU = "maxvgpuperphysicalgpu";
|
||||
public static final String GUEST_OS_LIST = "guestoslist";
|
||||
public static final String GUEST_OS_COUNT = "guestoscount";
|
||||
public static final String GUEST_OS_RULE = "guestosrule";
|
||||
public static final String OS_MAPPING_CHECK_ENABLED = "osmappingcheckenabled";
|
||||
public static final String OUTOFBANDMANAGEMENT_POWERSTATE = "outofbandmanagementpowerstate";
|
||||
public static final String OUTOFBANDMANAGEMENT_ENABLED = "outofbandmanagementenabled";
|
||||
|
|
@ -1343,8 +1344,10 @@ public class ApiConstants {
|
|||
public static final String VNF_CONFIGURE_MANAGEMENT = "vnfconfiguremanagement";
|
||||
public static final String VNF_CIDR_LIST = "vnfcidrlist";
|
||||
|
||||
public static final String AUTHORIZE_URL = "authorizeurl";
|
||||
public static final String CLIENT_ID = "clientid";
|
||||
public static final String REDIRECT_URI = "redirecturi";
|
||||
public static final String TOKEN_URL = "tokenurl";
|
||||
|
||||
public static final String IS_TAG_A_RULE = "istagarule";
|
||||
|
||||
|
|
|
|||
|
|
@ -49,9 +49,14 @@ public class UpdateHostCmd extends BaseCmd {
|
|||
@Parameter(name = ApiConstants.OS_CATEGORY_ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = GuestOSCategoryResponse.class,
|
||||
description = "The ID of OS category to update the host with")
|
||||
description = "the ID of OS category used to prioritize VMs with matching OS category during the allocation process. " +
|
||||
"It cannot be used alongside the 'guestosrule' parameter.")
|
||||
private Long osCategoryId;
|
||||
|
||||
@Parameter(name = ApiConstants.GUEST_OS_RULE, type = CommandType.STRING, description = "the guest OS rule written in JavaScript to match with the OS of the VM." +
|
||||
"It cannot be used alongside the 'oscategoryid' parameter.")
|
||||
private String guestOsRule;
|
||||
|
||||
@Parameter(name = ApiConstants.ALLOCATION_STATE,
|
||||
type = CommandType.STRING,
|
||||
description = "Change resource state of host, valid values are [Enable, Disable]. Operation may failed if host in states not allowing Enable/Disable")
|
||||
|
|
@ -96,6 +101,10 @@ public class UpdateHostCmd extends BaseCmd {
|
|||
return osCategoryId;
|
||||
}
|
||||
|
||||
public String getGuestOsRule() {
|
||||
return guestOsRule;
|
||||
}
|
||||
|
||||
public String getAllocationState() {
|
||||
return allocationState;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public class RegisterUserKeysCmd extends BaseAsyncCmd {
|
|||
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "API key pair name.")
|
||||
private String name;
|
||||
|
||||
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "API key pair description.")
|
||||
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "API key pair description.", length = 1024)
|
||||
private String description;
|
||||
|
||||
@Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Start date of the API key pair. " +
|
||||
|
|
@ -138,6 +138,9 @@ public class RegisterUserKeysCmd extends BaseAsyncCmd {
|
|||
|
||||
String description = detail.get(ApiConstants.DESCRIPTION);
|
||||
if (StringUtils.isNotEmpty(description)) {
|
||||
if (description.length() > 255) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Rule description cannot be longer than 255 characters.");
|
||||
}
|
||||
ruleDetails.put(ApiConstants.DESCRIPTION, description);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import org.apache.cloudstack.api.ResponseObject.ResponseView;
|
|||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.command.user.UserCmd;
|
||||
import org.apache.cloudstack.api.command.user.vm.DeployVMCmd;
|
||||
import org.apache.cloudstack.api.response.TemplateResponse;
|
||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||
|
||||
import com.cloud.event.EventTypes;
|
||||
|
|
@ -51,6 +52,10 @@ public class DetachIsoCmd extends BaseAsyncCmd implements UserCmd {
|
|||
description = "If true, ejects the ISO before detaching on VMware. Default: false", since = "4.15.1")
|
||||
protected Boolean forced;
|
||||
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = TemplateResponse.class,
|
||||
description = "The ID of the ISO to detach. Required when the Instance has more than one ISO attached.", since = "4.23.0")
|
||||
protected Long id;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -104,7 +109,7 @@ public class DetachIsoCmd extends BaseAsyncCmd implements UserCmd {
|
|||
|
||||
@Override
|
||||
public void execute() {
|
||||
boolean result = _templateService.detachIso(virtualMachineId, null, isForced());
|
||||
boolean result = _templateService.detachIso(virtualMachineId, id, isForced());
|
||||
if (result) {
|
||||
UserVm userVm = _entityMgr.findById(UserVm.class, virtualMachineId);
|
||||
UserVmResponse response = _responseGenerator.createUserVmResponse(getResponseView(), "virtualmachine", userVm).get(0);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
// 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.api.command.user.schedule;
|
||||
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
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.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleManager;
|
||||
import org.apache.commons.lang3.EnumUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
@APICommand(name = "createResourceSchedule", description = "Create Resource Schedule", responseObject = ResourceScheduleResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||
public class CreateResourceScheduleCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
ResourceScheduleManager resourceScheduleManager;
|
||||
|
||||
@Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true, description = "Type of the resource")
|
||||
private String resourceType;
|
||||
|
||||
@Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING, required = true, description = "ID of the resource for which schedule is to be defined")
|
||||
private String resourceId;
|
||||
|
||||
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = false, description = "Description of the schedule")
|
||||
private String description;
|
||||
|
||||
@Parameter(name = ApiConstants.SCHEDULE, type = CommandType.STRING, required = true, description = "Schedule for action on resource in cron format. e.g. '0 15 10 * *' for 'at 15:00 on 10th day of every month'")
|
||||
private String schedule;
|
||||
|
||||
@Parameter(name = ApiConstants.TIMEZONE, type = CommandType.STRING, required = true, description = "Specifies a timezone for this command. For more information on the timezone parameter, see TimeZone Format.")
|
||||
private String timeZone;
|
||||
|
||||
@Parameter(name = ApiConstants.ACTION, type = CommandType.STRING, required = true, description = "Action to take on the resource.")
|
||||
private String action;
|
||||
|
||||
@Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, required = false, description = "Start date from which the schedule becomes active. Defaults to current date plus 1 minute. (Format \"yyyy-MM-dd hh:mm:ss\")")
|
||||
private Date startDate;
|
||||
|
||||
@Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, required = false, description = "End date after which the schedule becomes inactive. (Format \"yyyy-MM-dd hh:mm:ss\")")
|
||||
private Date endDate;
|
||||
|
||||
@Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, required = false, description = "Enable schedule. Defaults to true")
|
||||
private Boolean enabled;
|
||||
|
||||
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = false, description = "Map of (key/value pairs) details for the schedule.")
|
||||
private Map details;
|
||||
|
||||
public ApiCommandResourceType getResourceType() {
|
||||
ApiCommandResourceType type = EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, resourceType);
|
||||
if (type == null) {
|
||||
throw new InvalidParameterValueException("Unknown resource type: " + resourceType);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getSchedule() {
|
||||
return schedule;
|
||||
}
|
||||
|
||||
public String getTimeZone() {
|
||||
return timeZone;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public Date getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public Date getEndDate() {
|
||||
return endDate;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
if (enabled == null) {
|
||||
enabled = true;
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return convertDetailsToMap(details);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
ResourceScheduleResponse response = resourceScheduleManager.createSchedule(getResourceType(), getResourceId(),
|
||||
getDescription(), getSchedule(), getTimeZone(), getAction(), getStartDate(), getEndDate(), getEnabled(), getDetails());
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return CallContext.current().getCallingAccount().getAccountId();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
// 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.api.command.user.schedule;
|
||||
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
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.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
|
||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleManager;
|
||||
import org.apache.commons.lang3.EnumUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
@APICommand(name = "deleteResourceSchedule", description = "Delete Resource Schedule", responseObject = SuccessResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||
public class DeleteResourceScheduleCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
ResourceScheduleManager resourceScheduleManager;
|
||||
|
||||
@Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true, description = "Type of the resource")
|
||||
private String resourceType;
|
||||
|
||||
@Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING, required = true, description = "ID of the resource for which schedules are to be deleted")
|
||||
private String resourceId;
|
||||
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "ID of the schedule to be deleted")
|
||||
private Long id;
|
||||
|
||||
@Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "comma separated list of schedule ids to be deleted")
|
||||
private List<Long> ids;
|
||||
|
||||
public ApiCommandResourceType getResourceType() {
|
||||
ApiCommandResourceType type = EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, resourceType);
|
||||
if (type == null) {
|
||||
throw new InvalidParameterValueException("Unknown resource type: " + resourceType);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public List<Long> getIds() {
|
||||
return ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
resourceScheduleManager.removeSchedule(getResourceType(), getResourceId(), getId(), getIds());
|
||||
SuccessResponse response = new SuccessResponse(getCommandName());
|
||||
setResponseObject(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return CallContext.current().getCallingAccount().getAccountId();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
// 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.api.command.user.schedule;
|
||||
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
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.BaseListCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleManager;
|
||||
import org.apache.commons.lang3.EnumUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
|
||||
@APICommand(name = "listResourceSchedule", description = "List Resource Schedules", responseObject = ResourceScheduleResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||
public class ListResourceScheduleCmd extends BaseListCmd {
|
||||
|
||||
@Inject
|
||||
ResourceScheduleManager resourceScheduleManager;
|
||||
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "ID of the schedule")
|
||||
private Long id;
|
||||
|
||||
@Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "comma separated list of schedule ids")
|
||||
private List<Long> ids;
|
||||
|
||||
@Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true, description = "Type of the resource.")
|
||||
private String resourceType;
|
||||
|
||||
@Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING, required = true, description = "ID of the resource for which schedules are to be listed.")
|
||||
private String resourceId;
|
||||
|
||||
@Parameter(name = ApiConstants.ACTION, type = CommandType.STRING, required = false, description = "Action to take on the resource.")
|
||||
private String action;
|
||||
|
||||
@Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, required = false, description = "Filter by enabled status.")
|
||||
private Boolean enabled;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public List<Long> getIds() {
|
||||
return ids;
|
||||
}
|
||||
|
||||
public ApiCommandResourceType getResourceType() {
|
||||
ApiCommandResourceType type = EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, resourceType);
|
||||
if (type == null) {
|
||||
throw new InvalidParameterValueException("Unknown resource type: " + resourceType);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
ListResponse<ResourceScheduleResponse> response = resourceScheduleManager.listSchedule(
|
||||
getId(), getIds(), getResourceType(), getResourceId(), getAction(), getEnabled(),
|
||||
getStartIndex(), getPageSizeVal()
|
||||
);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
// 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.api.command.user.schedule;
|
||||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
@APICommand(name = "updateResourceSchedule", description = "Update Resource Schedule", responseObject = ResourceScheduleResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||
public class UpdateResourceScheduleCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
ResourceScheduleManager resourceScheduleManager;
|
||||
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = true, description = "ID of the schedule to be updated")
|
||||
private Long id;
|
||||
|
||||
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = false, description = "Description of the schedule")
|
||||
private String description;
|
||||
|
||||
@Parameter(name = ApiConstants.SCHEDULE, type = CommandType.STRING, required = false, description = "Schedule for action on resource in cron format.")
|
||||
private String schedule;
|
||||
|
||||
@Parameter(name = ApiConstants.TIMEZONE, type = CommandType.STRING, required = false, description = "Specifies a timezone for this command.")
|
||||
private String timeZone;
|
||||
|
||||
@Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, required = false, description = "Start date from which the schedule becomes active.")
|
||||
private Date startDate;
|
||||
|
||||
@Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, required = false, description = "End date after which the schedule becomes inactive.")
|
||||
private Date endDate;
|
||||
|
||||
@Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, required = false, description = "Enable or disable the schedule.")
|
||||
private Boolean enabled;
|
||||
|
||||
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = false, description = "Map of (key/value pairs) details for the schedule.")
|
||||
private Map details;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getSchedule() {
|
||||
return schedule;
|
||||
}
|
||||
|
||||
public String getTimeZone() {
|
||||
return timeZone;
|
||||
}
|
||||
|
||||
public Date getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public Date getEndDate() {
|
||||
return endDate;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return convertDetailsToMap(details);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
ResourceScheduleResponse response = resourceScheduleManager.updateSchedule(getId(), getDescription(), getSchedule(),
|
||||
getTimeZone(), getStartDate(), getEndDate(), getEnabled(), getDetails());
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return CallContext.current().getCallingAccount().getAccountId();
|
||||
}
|
||||
}
|
||||
|
|
@ -56,6 +56,9 @@ public class AddIpToVmNicCmd extends BaseAsyncCreateCmd {
|
|||
@Parameter(name = ApiConstants.IP_ADDRESS, type = CommandType.STRING, required = false, description = "Secondary IP Address")
|
||||
private String ipAddr;
|
||||
|
||||
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = false, description = "Description of the secondary IP address", length = 2048)
|
||||
private String description;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -160,7 +163,7 @@ public class AddIpToVmNicCmd extends BaseAsyncCreateCmd {
|
|||
}
|
||||
|
||||
try {
|
||||
result = _networkService.allocateSecondaryGuestIP(getNicId(), requestedIpPair);
|
||||
result = _networkService.allocateSecondaryGuestIP(getNicId(), requestedIpPair, description);
|
||||
if (result != null) {
|
||||
setEntityId(result.getId());
|
||||
setEntityUuid(result.getUuid());
|
||||
|
|
|
|||
|
|
@ -22,23 +22,26 @@ import com.cloud.exception.InvalidParameterValueException;
|
|||
import com.cloud.vm.VirtualMachine;
|
||||
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.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
|
||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Date;
|
||||
|
||||
@Deprecated
|
||||
@APICommand(name = "createVMSchedule", description = "Create Instance Schedule", responseObject = VMScheduleResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||
public class CreateVMScheduleCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
VMScheduleManager vmScheduleManager;
|
||||
ResourceScheduleManager resourceScheduleManager;
|
||||
|
||||
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
|
||||
type = CommandType.UUID,
|
||||
|
|
@ -75,14 +78,14 @@ public class CreateVMScheduleCmd extends BaseCmd {
|
|||
type = CommandType.DATE,
|
||||
required = false,
|
||||
description = "Start date from which the schedule becomes active. Defaults to current date plus 1 minute."
|
||||
+ "Use format \"yyyy-MM-dd hh:mm:ss\")")
|
||||
+ "(Format \"yyyy-MM-dd hh:mm:ss\")")
|
||||
private Date startDate;
|
||||
|
||||
@Parameter(name = ApiConstants.END_DATE,
|
||||
type = CommandType.DATE,
|
||||
required = false,
|
||||
description = "End date after which the schedule becomes inactive"
|
||||
+ "Use format \"yyyy-MM-dd hh:mm:ss\")")
|
||||
+ "(Format \"yyyy-MM-dd hh:mm:ss\")")
|
||||
private Date endDate;
|
||||
|
||||
@Parameter(name = ApiConstants.ENABLED,
|
||||
|
|
@ -91,9 +94,9 @@ public class CreateVMScheduleCmd extends BaseCmd {
|
|||
description = "Enable Instance schedule. Defaults to true")
|
||||
private Boolean enabled;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
/// //////////////// Accessors ///////////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
|
||||
public Long getVmId() {
|
||||
return vmId;
|
||||
|
|
@ -130,13 +133,19 @@ public class CreateVMScheduleCmd extends BaseCmd {
|
|||
return enabled;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
/// //////////// API Implementation///////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
VMScheduleResponse response = vmScheduleManager.createSchedule(this);
|
||||
String resourceIdStr = getVmId() != null ? String.valueOf(getVmId()) : null;
|
||||
|
||||
ResourceScheduleResponse scheduleResponse = resourceScheduleManager.createSchedule(
|
||||
ApiCommandResourceType.VirtualMachine,
|
||||
resourceIdStr, getDescription(), getSchedule(), getTimeZone(), getAction(),
|
||||
getStartDate(), getEndDate(), getEnabled(), null);
|
||||
VMScheduleResponse response = new VMScheduleResponse(scheduleResponse);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.cloud.exception.InvalidParameterValueException;
|
|||
import com.cloud.vm.VirtualMachine;
|
||||
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;
|
||||
|
|
@ -30,19 +31,19 @@ import org.apache.cloudstack.api.ServerApiException;
|
|||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Deprecated
|
||||
@APICommand(name = "deleteVMSchedule", description = "Delete Instance Schedule.", responseObject = SuccessResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||
public class DeleteVMScheduleCmd extends BaseCmd {
|
||||
@Inject
|
||||
VMScheduleManager vmScheduleManager;
|
||||
ResourceScheduleManager resourceScheduleManager;
|
||||
|
||||
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
|
||||
type = CommandType.UUID,
|
||||
|
|
@ -50,12 +51,14 @@ public class DeleteVMScheduleCmd extends BaseCmd {
|
|||
required = true,
|
||||
description = "ID of Instance")
|
||||
private Long vmId;
|
||||
|
||||
@Parameter(name = ApiConstants.ID,
|
||||
type = CommandType.UUID,
|
||||
entityType = VMScheduleResponse.class,
|
||||
required = false,
|
||||
description = "ID of Instance schedule")
|
||||
private Long id;
|
||||
|
||||
@Parameter(name = ApiConstants.IDS,
|
||||
type = CommandType.LIST,
|
||||
collectionType = CommandType.UUID,
|
||||
|
|
@ -64,9 +67,9 @@ public class DeleteVMScheduleCmd extends BaseCmd {
|
|||
description = "IDs of Instance schedule")
|
||||
private List<Long> ids;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
/// //////////////// Accessors ///////////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
|
|
@ -83,18 +86,21 @@ public class DeleteVMScheduleCmd extends BaseCmd {
|
|||
return vmId;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
/// //////////// API Implementation///////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
long rowsRemoved = vmScheduleManager.removeSchedule(this);
|
||||
String resourceIdStr = getVmId() != null ? String.valueOf(getVmId()) : null;
|
||||
long rowsRemoved = resourceScheduleManager.removeSchedule(
|
||||
ApiCommandResourceType.VirtualMachine,
|
||||
resourceIdStr, getId(), getIds());
|
||||
|
||||
if (rowsRemoved > 0) {
|
||||
final SuccessResponse response = new SuccessResponse();
|
||||
response.setResponseName(getCommandName());
|
||||
response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
|
||||
response.setObjectName("vmschedule");
|
||||
setResponseObject(response);
|
||||
} else {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete Instance Schedules");
|
||||
|
|
|
|||
|
|
@ -20,23 +20,27 @@ package org.apache.cloudstack.api.command.user.vm;
|
|||
|
||||
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.BaseListCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
|
||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Deprecated
|
||||
@APICommand(name = "listVMSchedule", description = "List Instance Schedules.", responseObject = VMScheduleResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||
public class ListVMScheduleCmd extends BaseListCmd {
|
||||
@Inject
|
||||
VMScheduleManager vmScheduleManager;
|
||||
ResourceScheduleManager resourceScheduleManager;
|
||||
|
||||
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID,
|
||||
type = CommandType.UUID,
|
||||
|
|
@ -61,12 +65,12 @@ public class ListVMScheduleCmd extends BaseListCmd {
|
|||
@Parameter(name = ApiConstants.ENABLED,
|
||||
type = CommandType.BOOLEAN,
|
||||
required = false,
|
||||
description = "ID of Instance schedule")
|
||||
description = "Filter by enabled status")
|
||||
private Boolean enabled;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
/// //////////////// Accessors ///////////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
|
||||
public Long getVmId() {
|
||||
return vmId;
|
||||
|
|
@ -84,14 +88,26 @@ public class ListVMScheduleCmd extends BaseListCmd {
|
|||
return enabled;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
/// //////////// API Implementation///////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
@Override
|
||||
public void execute() {
|
||||
ListResponse<VMScheduleResponse> response = vmScheduleManager.listSchedule(this);
|
||||
String resourceIdStr = getVmId() != null ? String.valueOf(getVmId()) : null;
|
||||
|
||||
ListResponse<ResourceScheduleResponse> scheduleResponse = resourceScheduleManager.listSchedule(
|
||||
getId(), null, ApiCommandResourceType.VirtualMachine, resourceIdStr, getAction(), getEnabled(),
|
||||
getStartIndex(), getPageSizeVal()
|
||||
);
|
||||
|
||||
List<VMScheduleResponse> vmScheduleResponses = new ArrayList<>();
|
||||
for (ResourceScheduleResponse resourceScheduleResponse : scheduleResponse.getResponses()) {
|
||||
vmScheduleResponses.add(new VMScheduleResponse(resourceScheduleResponse));
|
||||
}
|
||||
ListResponse<VMScheduleResponse> response = new ListResponse<>();
|
||||
response.setResponses(vmScheduleResponses, scheduleResponse.getCount());
|
||||
response.setResponseName(getCommandName());
|
||||
response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
|
||||
response.setObjectName("vmschedule");
|
||||
setResponseObject(response);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,22 +22,25 @@ import com.cloud.exception.InvalidParameterValueException;
|
|||
import com.cloud.vm.VirtualMachine;
|
||||
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.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
|
||||
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||
import org.apache.cloudstack.schedule.ResourceSchedule;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Date;
|
||||
|
||||
@Deprecated
|
||||
@APICommand(name = "updateVMSchedule", description = "Update Instance Schedule.", responseObject = VMScheduleResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||
public class UpdateVMScheduleCmd extends BaseCmd {
|
||||
@Inject
|
||||
VMScheduleManager vmScheduleManager;
|
||||
ResourceScheduleManager resourceScheduleManager;
|
||||
|
||||
@Parameter(name = ApiConstants.ID,
|
||||
type = CommandType.UUID,
|
||||
|
|
@ -68,14 +71,14 @@ public class UpdateVMScheduleCmd extends BaseCmd {
|
|||
type = CommandType.DATE,
|
||||
required = false,
|
||||
description = "Start date from which the schedule becomes active"
|
||||
+ "Use format \"yyyy-MM-dd hh:mm:ss\")")
|
||||
+ "(Format \"yyyy-MM-dd hh:mm:ss\")")
|
||||
private Date startDate;
|
||||
|
||||
@Parameter(name = ApiConstants.END_DATE,
|
||||
type = CommandType.DATE,
|
||||
required = false,
|
||||
description = "End date after which the schedule becomes inactive"
|
||||
+ "Use format \"yyyy-MM-dd hh:mm:ss\")")
|
||||
+ "(Format \"yyyy-MM-dd hh:mm:ss\")")
|
||||
private Date endDate;
|
||||
|
||||
@Parameter(name = ApiConstants.ENABLED,
|
||||
|
|
@ -84,9 +87,9 @@ public class UpdateVMScheduleCmd extends BaseCmd {
|
|||
description = "Enable Instance schedule")
|
||||
private Boolean enabled;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
/// //////////////// Accessors ///////////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
|
|
@ -116,24 +119,29 @@ public class UpdateVMScheduleCmd extends BaseCmd {
|
|||
return enabled;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
/// //////////// API Implementation///////////////////
|
||||
/// //////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
VMScheduleResponse response = vmScheduleManager.updateSchedule(this);
|
||||
ResourceScheduleResponse scheduleResponse = resourceScheduleManager.updateSchedule(
|
||||
getId(), getDescription(), getSchedule(), getTimeZone(), getStartDate(), getEndDate(), getEnabled(), null);
|
||||
VMScheduleResponse response = new VMScheduleResponse(scheduleResponse);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
VMSchedule vmSchedule = _entityMgr.findById(VMSchedule.class, getId());
|
||||
if (vmSchedule == null) {
|
||||
throw new InvalidParameterValueException(String.format("Unable to find vmSchedule by id=%d", getId()));
|
||||
ResourceSchedule schedule = _entityMgr.findById(ResourceSchedule.class, getId());
|
||||
if (schedule == null || !ApiCommandResourceType.VirtualMachine.equals(schedule.getResourceType())) {
|
||||
throw new InvalidParameterValueException(String.format("Unable to find VM schedule by id=%d", getId()));
|
||||
}
|
||||
VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, schedule.getResourceId());
|
||||
if (vm == null) {
|
||||
throw new InvalidParameterValueException(String.format("Unable to find VM schedule by id=%d", getId()));
|
||||
}
|
||||
VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, vmSchedule.getVmId());
|
||||
return vm.getAccountId();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
// 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.api.response;
|
||||
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
|
||||
import com.cloud.serializer.Param;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class AttachedIsoResponse extends BaseResponse {
|
||||
|
||||
@SerializedName("id")
|
||||
@Param(description = "The ID of the attached ISO")
|
||||
private String id;
|
||||
|
||||
@SerializedName("name")
|
||||
@Param(description = "The name of the attached ISO")
|
||||
private String name;
|
||||
|
||||
@SerializedName("displaytext")
|
||||
@Param(description = "The display text of the attached ISO")
|
||||
private String displayText;
|
||||
|
||||
@SerializedName("deviceseq")
|
||||
@Param(description = "The cdrom slot that holds this ISO (3=hdc, 4=hdd, ...)")
|
||||
private Integer deviceSeq;
|
||||
|
||||
@SerializedName("bootable")
|
||||
@Param(description = "Whether this is the bootable ISO for the VM")
|
||||
private Boolean bootable;
|
||||
|
||||
public AttachedIsoResponse() {
|
||||
}
|
||||
|
||||
public AttachedIsoResponse(String id, String name, String displayText, Integer deviceSeq, boolean bootable) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.displayText = displayText;
|
||||
this.deviceSeq = deviceSeq;
|
||||
this.bootable = bootable;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDisplayText() {
|
||||
return displayText;
|
||||
}
|
||||
|
||||
public Integer getDeviceSeq() {
|
||||
return deviceSeq;
|
||||
}
|
||||
|
||||
public Boolean getBootable() {
|
||||
return bootable;
|
||||
}
|
||||
}
|
||||
|
|
@ -63,6 +63,10 @@ public class HostResponse extends BaseResponseWithAnnotations {
|
|||
@Param(description = "The OS category name of the host")
|
||||
private String osCategoryName;
|
||||
|
||||
@SerializedName(ApiConstants.GUEST_OS_RULE)
|
||||
@Param(description = "the guest OS rule")
|
||||
private String guestOsRule;
|
||||
|
||||
@SerializedName(ApiConstants.IP_ADDRESS)
|
||||
@Param(description = "The IP address of the host")
|
||||
private String ipAddress;
|
||||
|
|
@ -999,4 +1003,12 @@ public class HostResponse extends BaseResponseWithAnnotations {
|
|||
public String getExtensionName() {
|
||||
return extensionName;
|
||||
}
|
||||
|
||||
public String getGuestOsRule() {
|
||||
return guestOsRule;
|
||||
}
|
||||
|
||||
public void setGuestOsRule(String guestOsRule) {
|
||||
this.guestOsRule = guestOsRule;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,10 @@ public class NicSecondaryIpResponse extends BaseResponse {
|
|||
@Param(description = "The ID of the Instance")
|
||||
private String vmId;
|
||||
|
||||
@SerializedName(ApiConstants.DESCRIPTION)
|
||||
@Param(description = "Description of the secondary IP address")
|
||||
private String description;
|
||||
|
||||
@Override
|
||||
public String getObjectId() {
|
||||
return this.getId();
|
||||
|
|
@ -98,6 +102,14 @@ public class NicSecondaryIpResponse extends BaseResponse {
|
|||
this.id = id;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public List<NicSecondaryIpResponse> getSecondaryIpsList() {
|
||||
return secondaryIpsList;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* 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.api.response;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
import org.apache.cloudstack.api.EntityReference;
|
||||
import org.apache.cloudstack.schedule.ResourceSchedule;
|
||||
|
||||
import com.cloud.serializer.Param;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
@EntityReference(value = ResourceSchedule.class)
|
||||
public class ResourceScheduleResponse extends BaseResponse {
|
||||
@SerializedName(ApiConstants.ID)
|
||||
@Param(description = "The ID of resource schedule")
|
||||
private String id;
|
||||
|
||||
@SerializedName(ApiConstants.RESOURCE_TYPE)
|
||||
@Param(description = "Type of the resource")
|
||||
private ApiCommandResourceType resourceType;
|
||||
|
||||
@SerializedName(ApiConstants.RESOURCE_ID)
|
||||
@Param(description = "ID of the resource")
|
||||
private String resourceId;
|
||||
|
||||
@SerializedName(ApiConstants.DESCRIPTION)
|
||||
@Param(description = "Description of resource schedule")
|
||||
private String description;
|
||||
|
||||
@SerializedName(ApiConstants.SCHEDULE)
|
||||
@Param(description = "Cron formatted resource schedule")
|
||||
private String schedule;
|
||||
|
||||
@SerializedName(ApiConstants.TIMEZONE)
|
||||
@Param(description = "Timezone of the schedule")
|
||||
private String timeZone;
|
||||
|
||||
@SerializedName(ApiConstants.ACTION)
|
||||
@Param(description = "Action")
|
||||
private ResourceSchedule.Action action;
|
||||
|
||||
@SerializedName(ApiConstants.ENABLED)
|
||||
@Param(description = "Resource schedule is enabled")
|
||||
private boolean enabled;
|
||||
|
||||
@SerializedName(ApiConstants.START_DATE)
|
||||
@Param(description = "Date from which the schedule is active")
|
||||
private Date startDate;
|
||||
|
||||
@SerializedName(ApiConstants.END_DATE)
|
||||
@Param(description = "Date after which the schedule becomes inactive")
|
||||
private Date endDate;
|
||||
|
||||
@SerializedName(ApiConstants.DETAILS)
|
||||
@Param(description = "Schedule details")
|
||||
private Map<String, String> details;
|
||||
|
||||
@SerializedName(ApiConstants.CREATED)
|
||||
@Param(description = "Date when the schedule was created")
|
||||
private Date created;
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setResourceType(ApiCommandResourceType resourceType) {
|
||||
this.resourceType = resourceType;
|
||||
}
|
||||
|
||||
public ApiCommandResourceType getResourceType() {
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
public void setResourceId(String resourceId) {
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setSchedule(String schedule) {
|
||||
this.schedule = schedule;
|
||||
}
|
||||
|
||||
public String getSchedule() {
|
||||
return schedule;
|
||||
}
|
||||
|
||||
public void setTimeZone(String timeZone) {
|
||||
this.timeZone = timeZone;
|
||||
}
|
||||
|
||||
public String getTimeZone() {
|
||||
return timeZone;
|
||||
}
|
||||
|
||||
public void setAction(ResourceSchedule.Action action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public ResourceSchedule.Action getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setStartDate(Date startDate) {
|
||||
this.startDate = startDate;
|
||||
}
|
||||
|
||||
public Date getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public void setEndDate(Date endDate) {
|
||||
this.endDate = endDate;
|
||||
}
|
||||
|
||||
public Date getEndDate() {
|
||||
return endDate;
|
||||
}
|
||||
|
||||
public void setDetails(Map<String, String> details) {
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
}
|
||||
|
|
@ -166,6 +166,14 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
|
|||
@Param(description = "An alternate display text of the ISO attached to the Instance")
|
||||
private String isoDisplayText;
|
||||
|
||||
@SerializedName("isos")
|
||||
@Param(description = "All ISOs attached to the Instance, keyed by cdrom slot. The first entry mirrors isoid/isoname for back-compat.", responseObject = AttachedIsoResponse.class, since = "4.23.0")
|
||||
private List<AttachedIsoResponse> isos;
|
||||
|
||||
@SerializedName("isomaxcount")
|
||||
@Param(description = "Maximum number of ISOs that may be attached to this Instance, after applying the cluster-scoped vm.iso.max.count and the hypervisor's own cap.", since = "4.23.0")
|
||||
private Integer isoMaxCount;
|
||||
|
||||
@SerializedName(ApiConstants.SERVICE_OFFERING_ID)
|
||||
@Param(description = "The ID of the service offering of the Instance")
|
||||
private String serviceOfferingId;
|
||||
|
|
@ -871,6 +879,22 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co
|
|||
this.isoId = isoId;
|
||||
}
|
||||
|
||||
public void setIsos(List<AttachedIsoResponse> isos) {
|
||||
this.isos = isos;
|
||||
}
|
||||
|
||||
public List<AttachedIsoResponse> getIsos() {
|
||||
return isos;
|
||||
}
|
||||
|
||||
public void setIsoMaxCount(Integer isoMaxCount) {
|
||||
this.isoMaxCount = isoMaxCount;
|
||||
}
|
||||
|
||||
public Integer getIsoMaxCount() {
|
||||
return isoMaxCount;
|
||||
}
|
||||
|
||||
public void setIsoName(String isoName) {
|
||||
this.isoName = isoName;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,11 +23,11 @@ import com.google.gson.annotations.SerializedName;
|
|||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
import org.apache.cloudstack.api.EntityReference;
|
||||
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||
import org.apache.cloudstack.schedule.ResourceSchedule;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@EntityReference(value = VMSchedule.class)
|
||||
@EntityReference(value = ResourceSchedule.class)
|
||||
public class VMScheduleResponse extends BaseResponse {
|
||||
@SerializedName(ApiConstants.ID)
|
||||
@Param(description = "The ID of Instance schedule")
|
||||
|
|
@ -51,7 +51,7 @@ public class VMScheduleResponse extends BaseResponse {
|
|||
|
||||
@SerializedName(ApiConstants.ACTION)
|
||||
@Param(description = "Action")
|
||||
private VMSchedule.Action action;
|
||||
private ResourceSchedule.Action action;
|
||||
|
||||
@SerializedName(ApiConstants.ENABLED)
|
||||
@Param(description = "VM schedule is enabled")
|
||||
|
|
@ -69,6 +69,20 @@ public class VMScheduleResponse extends BaseResponse {
|
|||
@Param(description = "Date when the schedule was created")
|
||||
private Date created;
|
||||
|
||||
public VMScheduleResponse(ResourceScheduleResponse scheduleResponse) {
|
||||
super("vmschedule");
|
||||
this.id = scheduleResponse.getId();
|
||||
this.vmId = scheduleResponse.getResourceId();
|
||||
this.description = scheduleResponse.getDescription();
|
||||
this.schedule = scheduleResponse.getSchedule();
|
||||
this.timeZone = scheduleResponse.getTimeZone();
|
||||
this.action = scheduleResponse.getAction();
|
||||
this.enabled = scheduleResponse.getEnabled();
|
||||
this.startDate = scheduleResponse.getStartDate();
|
||||
this.endDate = scheduleResponse.getEndDate();
|
||||
this.created = scheduleResponse.getCreated();
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
|
@ -89,7 +103,7 @@ public class VMScheduleResponse extends BaseResponse {
|
|||
this.timeZone = timeZone;
|
||||
}
|
||||
|
||||
public void setAction(VMSchedule.Action action) {
|
||||
public void setAction(ResourceSchedule.Action action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
|
|
@ -105,5 +119,7 @@ public class VMScheduleResponse extends BaseResponse {
|
|||
this.endDate = endDate;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {this.created = created;}
|
||||
public void setCreated(Date created) {
|
||||
this.created = created;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,20 +16,31 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.cloudstack.vm.schedule;
|
||||
package org.apache.cloudstack.schedule;
|
||||
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.Identity;
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
|
||||
public interface VMSchedule extends Identity, InternalIdentity {
|
||||
enum Action {
|
||||
START, STOP, REBOOT, FORCE_STOP, FORCE_REBOOT
|
||||
public interface ResourceSchedule extends Identity, InternalIdentity {
|
||||
|
||||
/**
|
||||
* Common contract for scheduler actions. Each provider defines its own enum
|
||||
* implementing this interface so the generic machinery can call {@link #name()}
|
||||
* and {@link #getEventType()} without knowing the concrete type.
|
||||
*/
|
||||
interface Action {
|
||||
String name();
|
||||
|
||||
String getEventType();
|
||||
}
|
||||
|
||||
long getVmId();
|
||||
ApiCommandResourceType getResourceType();
|
||||
|
||||
long getResourceId();
|
||||
|
||||
String getDescription();
|
||||
|
||||
|
|
@ -37,7 +48,7 @@ public interface VMSchedule extends Identity, InternalIdentity {
|
|||
|
||||
String getTimeZone();
|
||||
|
||||
Action getAction();
|
||||
String getActionName();
|
||||
|
||||
boolean getEnabled();
|
||||
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.schedule;
|
||||
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface ResourceScheduleManager {
|
||||
|
||||
ResourceScheduleResponse createSchedule(ApiCommandResourceType resourceType, String resourceUuid,
|
||||
String description, String schedule, String timeZone, String action,
|
||||
Date startDate, Date endDate, boolean enabled, Map<String, String> details);
|
||||
|
||||
ResourceScheduleResponse updateSchedule(Long id, String description, String schedule, String timeZone,
|
||||
Date startDate, Date endDate, Boolean enabled, Map<String, String> details);
|
||||
|
||||
ListResponse<ResourceScheduleResponse> listSchedule(Long id, List<Long> ids, ApiCommandResourceType resourceType,
|
||||
String resourceUuid, String action, Boolean enabled,
|
||||
Long startIndex, Long pageSize);
|
||||
|
||||
Long removeSchedule(ApiCommandResourceType resourceType, String resourceUuid, Long id, List<Long> ids);
|
||||
|
||||
void removeSchedulesForResource(ApiCommandResourceType resourceType, long resourceId);
|
||||
}
|
||||
|
|
@ -16,23 +16,26 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.cloudstack.vm.schedule;
|
||||
package org.apache.cloudstack.schedule;
|
||||
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.Identity;
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public interface VMScheduledJob extends Identity, InternalIdentity {
|
||||
long getVmId();
|
||||
public interface ResourceScheduledJob extends Identity, InternalIdentity {
|
||||
ApiCommandResourceType getResourceType();
|
||||
|
||||
long getVmScheduleId();
|
||||
long getResourceId();
|
||||
|
||||
long getScheduleId();
|
||||
|
||||
Long getAsyncJobId();
|
||||
|
||||
void setAsyncJobId(long asyncJobId);
|
||||
|
||||
VMSchedule.Action getAction();
|
||||
String getActionName();
|
||||
|
||||
Date getScheduledTime();
|
||||
}
|
||||
|
|
@ -16,21 +16,16 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.cloudstack.vm.schedule.dao;
|
||||
package org.apache.cloudstack.schedule.autoscale;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.vm.schedule.VMScheduledJobVO;
|
||||
import com.cloud.event.EventTypes;
|
||||
import org.apache.cloudstack.schedule.ResourceSchedule;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public interface VMScheduledJobDao extends GenericDao<VMScheduledJobVO, Long> {
|
||||
|
||||
List<VMScheduledJobVO> listJobsToStart(Date currentTimestamp);
|
||||
|
||||
int expungeJobsForSchedules(List<Long> scheduleId, Date dateAfter);
|
||||
|
||||
int expungeJobsBefore(Date currentTimestamp);
|
||||
|
||||
VMScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp);
|
||||
public enum AutoScaleScheduleAction implements ResourceSchedule.Action {
|
||||
UPDATE {
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return EventTypes.EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.schedule.vm;
|
||||
|
||||
import com.cloud.event.EventTypes;
|
||||
import org.apache.cloudstack.schedule.ResourceSchedule;
|
||||
|
||||
public enum VMScheduleAction implements ResourceSchedule.Action {
|
||||
START {
|
||||
@Override
|
||||
public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_START; }
|
||||
},
|
||||
STOP {
|
||||
@Override
|
||||
public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_STOP; }
|
||||
},
|
||||
REBOOT {
|
||||
@Override
|
||||
public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_REBOOT; }
|
||||
},
|
||||
FORCE_STOP {
|
||||
@Override
|
||||
public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_FORCE_STOP; }
|
||||
},
|
||||
FORCE_REBOOT {
|
||||
@Override
|
||||
public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_FORCE_REBOOT; }
|
||||
};
|
||||
}
|
||||
|
|
@ -1,40 +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.
|
||||
*/
|
||||
package org.apache.cloudstack.vm.schedule;
|
||||
|
||||
import org.apache.cloudstack.api.command.user.vm.CreateVMScheduleCmd;
|
||||
import org.apache.cloudstack.api.command.user.vm.DeleteVMScheduleCmd;
|
||||
import org.apache.cloudstack.api.command.user.vm.ListVMScheduleCmd;
|
||||
import org.apache.cloudstack.api.command.user.vm.UpdateVMScheduleCmd;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||
|
||||
public interface VMScheduleManager {
|
||||
VMScheduleResponse createSchedule(CreateVMScheduleCmd createVMScheduleCmd);
|
||||
|
||||
VMScheduleResponse createResponse(VMSchedule vmSchedule);
|
||||
|
||||
ListResponse<VMScheduleResponse> listSchedule(ListVMScheduleCmd listVMScheduleCmd);
|
||||
|
||||
VMScheduleResponse updateSchedule(UpdateVMScheduleCmd updateVMScheduleCmd);
|
||||
|
||||
long removeScheduleByVmId(long vmId, boolean expunge);
|
||||
|
||||
Long removeSchedule(DeleteVMScheduleCmd deleteVMScheduleCmd);
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ public class AddIpToVmNicTest extends TestCase {
|
|||
NicSecondaryIp secIp = Mockito.mock(NicSecondaryIp.class);
|
||||
|
||||
Mockito.when(
|
||||
networkService.allocateSecondaryGuestIP(ArgumentMatchers.anyLong(), ArgumentMatchers.any()))
|
||||
networkService.allocateSecondaryGuestIP(ArgumentMatchers.anyLong(), ArgumentMatchers.any(), ArgumentMatchers.anyString()))
|
||||
.thenReturn(secIp);
|
||||
|
||||
ipTonicCmd._networkService = networkService;
|
||||
|
|
@ -79,7 +79,7 @@ public class AddIpToVmNicTest extends TestCase {
|
|||
AddIpToVmNicCmd ipTonicCmd = Mockito.mock(AddIpToVmNicCmd.class);
|
||||
|
||||
Mockito.when(
|
||||
networkService.allocateSecondaryGuestIP(ArgumentMatchers.anyLong(), ArgumentMatchers.any()))
|
||||
networkService.allocateSecondaryGuestIP(ArgumentMatchers.anyLong(), ArgumentMatchers.any(), ArgumentMatchers.anyString()))
|
||||
.thenReturn(null);
|
||||
|
||||
ipTonicCmd._networkService = networkService;
|
||||
|
|
|
|||
|
|
@ -21,8 +21,9 @@ package org.apache.cloudstack.api.command.user.vm;
|
|||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.utils.db.EntityManager;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
|
||||
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleManager;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
|
|
@ -34,9 +35,11 @@ import org.mockito.MockitoAnnotations;
|
|||
|
||||
public class CreateVMScheduleCmdTest {
|
||||
@Mock
|
||||
public VMScheduleManager vmScheduleManager;
|
||||
public ResourceScheduleManager resourceScheduleManager;
|
||||
|
||||
@Mock
|
||||
public EntityManager entityManager;
|
||||
|
||||
@InjectMocks
|
||||
private CreateVMScheduleCmd createVMScheduleCmd = new CreateVMScheduleCmd();
|
||||
|
||||
|
|
@ -59,10 +62,19 @@ public class CreateVMScheduleCmdTest {
|
|||
*/
|
||||
@Test
|
||||
public void testSuccessfulExecution() {
|
||||
VMScheduleResponse vmScheduleResponse = Mockito.mock(VMScheduleResponse.class);
|
||||
Mockito.when(vmScheduleManager.createSchedule(createVMScheduleCmd)).thenReturn(vmScheduleResponse);
|
||||
ResourceScheduleResponse scheduleResponse = new ResourceScheduleResponse();
|
||||
scheduleResponse.setId("schedule-uuid");
|
||||
scheduleResponse.setResourceId("vm-uuid");
|
||||
Mockito.when(resourceScheduleManager.createSchedule(
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
|
||||
Mockito.anyBoolean(), Mockito.any()
|
||||
)).thenReturn(scheduleResponse);
|
||||
createVMScheduleCmd.execute();
|
||||
Assert.assertEquals(vmScheduleResponse, createVMScheduleCmd.getResponseObject());
|
||||
VMScheduleResponse response = (VMScheduleResponse) createVMScheduleCmd.getResponseObject();
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertEquals("schedule-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "id"));
|
||||
Assert.assertEquals("vm-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "vmId"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -72,7 +84,11 @@ public class CreateVMScheduleCmdTest {
|
|||
*/
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testInvalidParameterValueException() {
|
||||
Mockito.when(vmScheduleManager.createSchedule(createVMScheduleCmd)).thenThrow(InvalidParameterValueException.class);
|
||||
Mockito.when(resourceScheduleManager.createSchedule(
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
|
||||
Mockito.anyBoolean(), Mockito.any()
|
||||
)).thenThrow(new InvalidParameterValueException("Invalid schedule"));
|
||||
createVMScheduleCmd.execute();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ import com.cloud.utils.db.EntityManager;
|
|||
import com.cloud.vm.VirtualMachine;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleManager;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
|
|
@ -36,7 +35,8 @@ import org.mockito.MockitoAnnotations;
|
|||
|
||||
public class DeleteVMScheduleCmdTest {
|
||||
@Mock
|
||||
public VMScheduleManager vmScheduleManager;
|
||||
public ResourceScheduleManager resourceScheduleManager;
|
||||
|
||||
@Mock
|
||||
public EntityManager entityManager;
|
||||
|
||||
|
|
@ -64,9 +64,11 @@ public class DeleteVMScheduleCmdTest {
|
|||
public void testSuccessfulExecution() {
|
||||
final SuccessResponse response = new SuccessResponse();
|
||||
response.setResponseName(deleteVMScheduleCmd.getCommandName());
|
||||
response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase());
|
||||
response.setObjectName("vmschedule");
|
||||
|
||||
Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenReturn(1L);
|
||||
Mockito.when(resourceScheduleManager.removeSchedule(
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
|
||||
)).thenReturn(1L);
|
||||
deleteVMScheduleCmd.execute();
|
||||
SuccessResponse actualResponse = (SuccessResponse) deleteVMScheduleCmd.getResponseObject();
|
||||
Assert.assertEquals(response.getResponseName(), actualResponse.getResponseName());
|
||||
|
|
@ -80,7 +82,9 @@ public class DeleteVMScheduleCmdTest {
|
|||
*/
|
||||
@Test(expected = ServerApiException.class)
|
||||
public void testServerApiException() {
|
||||
Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenReturn(0L);
|
||||
Mockito.when(resourceScheduleManager.removeSchedule(
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
|
||||
)).thenReturn(0L);
|
||||
deleteVMScheduleCmd.execute();
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +95,9 @@ public class DeleteVMScheduleCmdTest {
|
|||
*/
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testInvalidParameterValueException() {
|
||||
Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenThrow(InvalidParameterValueException.class);
|
||||
Mockito.when(resourceScheduleManager.removeSchedule(
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
|
||||
)).thenThrow(new InvalidParameterValueException("Invalid schedule"));
|
||||
deleteVMScheduleCmd.execute();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ package org.apache.cloudstack.api.command.user.vm;
|
|||
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
|
||||
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleManager;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
|
|
@ -36,7 +37,8 @@ import java.util.Collections;
|
|||
|
||||
public class ListVMScheduleCmdTest {
|
||||
@Mock
|
||||
public VMScheduleManager vmScheduleManager;
|
||||
public ResourceScheduleManager resourceScheduleManager;
|
||||
|
||||
@InjectMocks
|
||||
private ListVMScheduleCmd listVMScheduleCmd = new ListVMScheduleCmd();
|
||||
private AutoCloseable closeable;
|
||||
|
|
@ -58,12 +60,14 @@ public class ListVMScheduleCmdTest {
|
|||
*/
|
||||
@Test
|
||||
public void testEmptyResponse() {
|
||||
ListResponse<VMScheduleResponse> response = new ListResponse<VMScheduleResponse>();
|
||||
response.setResponses(new ArrayList<VMScheduleResponse>());
|
||||
Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenReturn(response);
|
||||
ListResponse<ResourceScheduleResponse> response = new ListResponse<ResourceScheduleResponse>();
|
||||
response.setResponses(new ArrayList<ResourceScheduleResponse>());
|
||||
Mockito.when(resourceScheduleManager.listSchedule(
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
|
||||
)).thenReturn(response);
|
||||
listVMScheduleCmd.execute();
|
||||
ListResponse<VMScheduleResponse> actualResponseObject = (ListResponse<VMScheduleResponse>) listVMScheduleCmd.getResponseObject();
|
||||
Assert.assertEquals(response, actualResponseObject);
|
||||
Assert.assertEquals(0L, actualResponseObject.getResponses().size());
|
||||
}
|
||||
|
||||
|
|
@ -74,15 +78,22 @@ public class ListVMScheduleCmdTest {
|
|||
*/
|
||||
@Test
|
||||
public void testNonEmptyResponse() {
|
||||
ListResponse<VMScheduleResponse> listResponse = new ListResponse<VMScheduleResponse>();
|
||||
VMScheduleResponse response = Mockito.mock(VMScheduleResponse.class);
|
||||
ListResponse<ResourceScheduleResponse> listResponse = new ListResponse<ResourceScheduleResponse>();
|
||||
ResourceScheduleResponse response = new ResourceScheduleResponse();
|
||||
response.setId("schedule-uuid");
|
||||
response.setResourceId("vm-uuid");
|
||||
listResponse.setResponses(Collections.singletonList(response));
|
||||
Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenReturn(listResponse);
|
||||
Mockito.when(resourceScheduleManager.listSchedule(
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
|
||||
)).thenReturn(listResponse);
|
||||
listVMScheduleCmd.execute();
|
||||
ListResponse<VMScheduleResponse> actualResponseObject = (ListResponse<VMScheduleResponse>) listVMScheduleCmd.getResponseObject();
|
||||
Assert.assertEquals(listResponse, actualResponseObject);
|
||||
Assert.assertEquals(1L, actualResponseObject.getResponses().size());
|
||||
Assert.assertEquals(response, actualResponseObject.getResponses().get(0));
|
||||
Assert.assertEquals("schedule-uuid",
|
||||
org.springframework.test.util.ReflectionTestUtils.getField(actualResponseObject.getResponses().get(0), "id"));
|
||||
Assert.assertEquals("vm-uuid",
|
||||
org.springframework.test.util.ReflectionTestUtils.getField(actualResponseObject.getResponses().get(0), "vmId"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -92,7 +103,10 @@ public class ListVMScheduleCmdTest {
|
|||
*/
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testInvalidParameterValueException() {
|
||||
Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenThrow(InvalidParameterValueException.class);
|
||||
Mockito.when(resourceScheduleManager.listSchedule(
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
|
||||
)).thenThrow(InvalidParameterValueException.class);
|
||||
listVMScheduleCmd.execute();
|
||||
ListResponse<VMScheduleResponse> actualResponseObject = (ListResponse<VMScheduleResponse>) listVMScheduleCmd.getResponseObject();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,11 @@ package org.apache.cloudstack.api.command.user.vm;
|
|||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.utils.db.EntityManager;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.response.ResourceScheduleResponse;
|
||||
import org.apache.cloudstack.api.response.VMScheduleResponse;
|
||||
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||
import org.apache.cloudstack.vm.schedule.VMScheduleManager;
|
||||
import org.apache.cloudstack.schedule.ResourceSchedule;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleManager;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
|
|
@ -35,9 +37,11 @@ import org.mockito.MockitoAnnotations;
|
|||
|
||||
public class UpdateVMScheduleCmdTest {
|
||||
@Mock
|
||||
public VMScheduleManager vmScheduleManager;
|
||||
public ResourceScheduleManager resourceScheduleManager;
|
||||
|
||||
@Mock
|
||||
public EntityManager entityManager;
|
||||
|
||||
@InjectMocks
|
||||
private UpdateVMScheduleCmd updateVMScheduleCmd = new UpdateVMScheduleCmd();
|
||||
|
||||
|
|
@ -60,10 +64,18 @@ public class UpdateVMScheduleCmdTest {
|
|||
*/
|
||||
@Test
|
||||
public void testSuccessfulExecution() {
|
||||
VMScheduleResponse vmScheduleResponse = Mockito.mock(VMScheduleResponse.class);
|
||||
Mockito.when(vmScheduleManager.updateSchedule(updateVMScheduleCmd)).thenReturn(vmScheduleResponse);
|
||||
ResourceScheduleResponse scheduleResponse = new ResourceScheduleResponse();
|
||||
scheduleResponse.setId("schedule-uuid");
|
||||
scheduleResponse.setResourceId("vm-uuid");
|
||||
Mockito.when(resourceScheduleManager.updateSchedule(
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
|
||||
)).thenReturn(scheduleResponse);
|
||||
updateVMScheduleCmd.execute();
|
||||
Assert.assertEquals(vmScheduleResponse, updateVMScheduleCmd.getResponseObject());
|
||||
VMScheduleResponse response = (VMScheduleResponse) updateVMScheduleCmd.getResponseObject();
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertEquals("schedule-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "id"));
|
||||
Assert.assertEquals("vm-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "vmId"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -73,7 +85,10 @@ public class UpdateVMScheduleCmdTest {
|
|||
*/
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testInvalidParameterValueException() {
|
||||
Mockito.when(vmScheduleManager.updateSchedule(updateVMScheduleCmd)).thenThrow(InvalidParameterValueException.class);
|
||||
Mockito.when(resourceScheduleManager.updateSchedule(
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(),
|
||||
Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()
|
||||
)).thenThrow(new InvalidParameterValueException("Invalid schedule"));
|
||||
updateVMScheduleCmd.execute();
|
||||
}
|
||||
|
||||
|
|
@ -84,11 +99,12 @@ public class UpdateVMScheduleCmdTest {
|
|||
*/
|
||||
@Test
|
||||
public void testSuccessfulGetEntityOwnerId() {
|
||||
VMSchedule vmSchedule = Mockito.mock(VMSchedule.class);
|
||||
ResourceSchedule schedule = Mockito.mock(ResourceSchedule.class);
|
||||
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
|
||||
|
||||
Mockito.when(entityManager.findById(VMSchedule.class, updateVMScheduleCmd.getId())).thenReturn(vmSchedule);
|
||||
Mockito.when(entityManager.findById(VirtualMachine.class, vmSchedule.getVmId())).thenReturn(vm);
|
||||
Mockito.when(schedule.getResourceType()).thenReturn(ApiCommandResourceType.VirtualMachine);
|
||||
Mockito.when(entityManager.findById(ResourceSchedule.class, updateVMScheduleCmd.getId())).thenReturn(schedule);
|
||||
Mockito.when(entityManager.findById(VirtualMachine.class, schedule.getResourceId())).thenReturn(vm);
|
||||
|
||||
long ownerId = updateVMScheduleCmd.getEntityOwnerId();
|
||||
Assert.assertEquals(vm.getAccountId(), ownerId);
|
||||
|
|
@ -101,8 +117,7 @@ public class UpdateVMScheduleCmdTest {
|
|||
*/
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testFailureGetEntityOwnerId() {
|
||||
VMSchedule vmSchedule = Mockito.mock(VMSchedule.class);
|
||||
Mockito.when(entityManager.findById(VMSchedule.class, updateVMScheduleCmd.getId())).thenReturn(null);
|
||||
long ownerId = updateVMScheduleCmd.getEntityOwnerId();
|
||||
Mockito.when(entityManager.findById(ResourceSchedule.class, updateVMScheduleCmd.getId())).thenReturn(null);
|
||||
updateVMScheduleCmd.getEntityOwnerId();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
// 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.api.response;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public final class AttachedIsoResponseTest {
|
||||
|
||||
@Test
|
||||
public void testFullConstructorPopulatesAllFields() {
|
||||
AttachedIsoResponse response = new AttachedIsoResponse("uuid-1", "alpine-iso", "Alpine boot", 3, true);
|
||||
Assert.assertEquals("uuid-1", response.getId());
|
||||
Assert.assertEquals("alpine-iso", response.getName());
|
||||
Assert.assertEquals("Alpine boot", response.getDisplayText());
|
||||
Assert.assertEquals(Integer.valueOf(3), response.getDeviceSeq());
|
||||
Assert.assertTrue(response.getBootable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoArgConstructorLeavesFieldsNull() {
|
||||
AttachedIsoResponse response = new AttachedIsoResponse();
|
||||
Assert.assertNull(response.getId());
|
||||
Assert.assertNull(response.getName());
|
||||
Assert.assertNull(response.getDisplayText());
|
||||
Assert.assertNull(response.getDeviceSeq());
|
||||
Assert.assertNull(response.getBootable());
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ import java.util.Map;
|
|||
|
||||
import com.cloud.agent.api.to.DpdkTO;
|
||||
import com.cloud.agent.api.to.VirtualMachineTO;
|
||||
import com.cloud.storage.Storage;
|
||||
|
||||
public class MigrateCommand extends Command {
|
||||
private String vmName;
|
||||
|
|
@ -42,6 +43,7 @@ public class MigrateCommand extends Command {
|
|||
private Map<String, DpdkTO> dpdkInterfaceMapping = new HashMap<>();
|
||||
|
||||
private int newVmCpuShares;
|
||||
private boolean clvmCrossPoolMigration;
|
||||
|
||||
Map<String, Boolean> vlanToPersistenceMap = new HashMap<>();
|
||||
|
||||
|
|
@ -149,6 +151,14 @@ public class MigrateCommand extends Command {
|
|||
this.newVmCpuShares = newVmCpuShares;
|
||||
}
|
||||
|
||||
public boolean isClvmCrossPoolMigration() {
|
||||
return clvmCrossPoolMigration;
|
||||
}
|
||||
|
||||
public void setClvmCrossPoolMigration(boolean clvmCrossPoolMigration) {
|
||||
this.clvmCrossPoolMigration = clvmCrossPoolMigration;
|
||||
}
|
||||
|
||||
public static class MigrateDiskInfo {
|
||||
public enum DiskType {
|
||||
FILE, BLOCK;
|
||||
|
|
@ -184,6 +194,8 @@ public class MigrateCommand extends Command {
|
|||
private final String sourceText;
|
||||
private final String backingStoreText;
|
||||
private boolean isSourceDiskOnStorageFileSystem;
|
||||
private Storage.StoragePoolType sourcePoolType;
|
||||
private Storage.StoragePoolType destPoolType;
|
||||
|
||||
public MigrateDiskInfo(final String serialNumber, final DiskType diskType, final DriverType driverType, final Source source, final String sourceText) {
|
||||
this.serialNumber = serialNumber;
|
||||
|
|
@ -232,6 +244,22 @@ public class MigrateCommand extends Command {
|
|||
public void setSourceDiskOnStorageFileSystem(boolean isDiskOnFileSystemStorage) {
|
||||
this.isSourceDiskOnStorageFileSystem = isDiskOnFileSystemStorage;
|
||||
}
|
||||
|
||||
public Storage.StoragePoolType getSourcePoolType() {
|
||||
return sourcePoolType;
|
||||
}
|
||||
|
||||
public void setSourcePoolType(Storage.StoragePoolType sourcePoolType) {
|
||||
this.sourcePoolType = sourcePoolType;
|
||||
}
|
||||
|
||||
public Storage.StoragePoolType getDestPoolType() {
|
||||
return destPoolType;
|
||||
}
|
||||
|
||||
public void setDestPoolType(Storage.StoragePoolType destPoolType) {
|
||||
this.destPoolType = destPoolType;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// 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 com.cloud.agent.api;
|
||||
|
||||
/**
|
||||
* Answer for PostMigrationCommand.
|
||||
* Indicates success or failure of post-migration operations on the destination host.
|
||||
*/
|
||||
public class PostMigrationAnswer extends Answer {
|
||||
|
||||
protected PostMigrationAnswer() {
|
||||
}
|
||||
|
||||
public PostMigrationAnswer(PostMigrationCommand cmd, String detail) {
|
||||
super(cmd, false, detail);
|
||||
}
|
||||
|
||||
public PostMigrationAnswer(PostMigrationCommand cmd, Exception ex) {
|
||||
super(cmd, ex);
|
||||
}
|
||||
|
||||
public PostMigrationAnswer(PostMigrationCommand cmd) {
|
||||
super(cmd, true, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// 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 com.cloud.agent.api;
|
||||
|
||||
import com.cloud.agent.api.to.VirtualMachineTO;
|
||||
|
||||
/**
|
||||
* PostMigrationCommand is sent to the destination host after a successful VM migration.
|
||||
* It performs post-migration tasks such as:
|
||||
* - Claiming exclusive locks on CLVM volumes (converting from shared to exclusive mode)
|
||||
* - Other post-migration cleanup operations
|
||||
*/
|
||||
public class PostMigrationCommand extends Command {
|
||||
private VirtualMachineTO vm;
|
||||
private String vmName;
|
||||
|
||||
protected PostMigrationCommand() {
|
||||
}
|
||||
|
||||
public PostMigrationCommand(VirtualMachineTO vm, String vmName) {
|
||||
this.vm = vm;
|
||||
this.vmName = vmName;
|
||||
}
|
||||
|
||||
public VirtualMachineTO getVirtualMachine() {
|
||||
return vm;
|
||||
}
|
||||
|
||||
public String getVmName() {
|
||||
return vmName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executeInSequence() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBypassHostMaintenance() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// 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 com.cloud.agent.api;
|
||||
|
||||
import com.cloud.agent.api.to.VirtualMachineTO;
|
||||
|
||||
/**
|
||||
* PreMigrationCommand is sent to the source host before VM migration starts.
|
||||
* It performs pre-migration tasks such as:
|
||||
* - Converting CLVM volume exclusive locks to shared mode so destination host can access them
|
||||
* - Other pre-migration preparation operations on the source host
|
||||
*
|
||||
* This command runs on the SOURCE host before PrepareForMigrationCommand runs on the DESTINATION host.
|
||||
*/
|
||||
public class PreMigrationCommand extends Command {
|
||||
private VirtualMachineTO vm;
|
||||
private String vmName;
|
||||
|
||||
protected PreMigrationCommand() {
|
||||
}
|
||||
|
||||
public PreMigrationCommand(VirtualMachineTO vm, String vmName) {
|
||||
this.vm = vm;
|
||||
this.vmName = vmName;
|
||||
}
|
||||
|
||||
public VirtualMachineTO getVirtualMachine() {
|
||||
return vm;
|
||||
}
|
||||
|
||||
public String getVmName() {
|
||||
return vmName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executeInSequence() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBypassHostMaintenance() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// 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.storage.clvm.command;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
|
||||
/**
|
||||
* Answer for ClvmLockTransferCommand, containing lock state information.
|
||||
* This answer includes the current lock holder information when querying lock state.
|
||||
*/
|
||||
public class ClvmLockTransferAnswer extends Answer {
|
||||
|
||||
private String currentLockHostname;
|
||||
private boolean isActive;
|
||||
private boolean isOpen;
|
||||
private String lvAttributes;
|
||||
|
||||
public ClvmLockTransferAnswer(ClvmLockTransferCommand cmd, boolean result, String details) {
|
||||
super(cmd, result, details);
|
||||
}
|
||||
|
||||
public ClvmLockTransferAnswer(ClvmLockTransferCommand cmd, boolean result, String details,
|
||||
String currentLockHostname, boolean isActive, boolean isOpen,
|
||||
String lvAttributes) {
|
||||
super(cmd, result, details);
|
||||
this.currentLockHostname = currentLockHostname;
|
||||
this.isActive = isActive;
|
||||
this.isOpen = isOpen;
|
||||
this.lvAttributes = lvAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hostname from lv_host. Retained for diagnostics only —
|
||||
* do NOT use this to determine lock holder identity.
|
||||
*/
|
||||
public String getCurrentLockHostname() {
|
||||
return currentLockHostname;
|
||||
}
|
||||
|
||||
public void setCurrentLockHostname(String currentLockHostname) {
|
||||
this.currentLockHostname = currentLockHostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the LV is locally active on the queried host (lv_attr[4]=='a').
|
||||
* This is the authoritative signal for lock holder discovery via fan-out.
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return isActive;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
isActive = active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a process has the device file open on the queried host (lv_attr[5]=='o').
|
||||
* true means a VM is actively doing I/O on this host right now — do NOT deactivate.
|
||||
*/
|
||||
public boolean isOpen() {
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
public void setOpen(boolean open) {
|
||||
isOpen = open;
|
||||
}
|
||||
|
||||
public String getLvAttributes() {
|
||||
return lvAttributes;
|
||||
}
|
||||
|
||||
public void setLvAttributes(String lvAttributes) {
|
||||
this.lvAttributes = lvAttributes;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
// 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.storage.clvm.command;
|
||||
|
||||
import com.cloud.agent.api.Command;
|
||||
|
||||
/**
|
||||
* Command to transfer CLVM (Clustered LVM) exclusive lock between hosts.
|
||||
* This enables lightweight volume migration for CLVM storage pools where volumes
|
||||
* reside in the same Volume Group (VG) but need to be accessed from different hosts.
|
||||
*
|
||||
* <p>Instead of copying volume data (traditional migration), this command simply
|
||||
* deactivates the LV on the source host and activates it exclusively on the destination host.
|
||||
*
|
||||
* <p>This is significantly faster (10-100x) than traditional migration and uses no network bandwidth.
|
||||
*/
|
||||
public class ClvmLockTransferCommand extends Command {
|
||||
|
||||
/**
|
||||
* Operation to perform on the CLVM volume.
|
||||
* Maps to lvchange flags for LVM operations.
|
||||
*/
|
||||
public enum Operation {
|
||||
/** Deactivate the volume on this host (-an) */
|
||||
DEACTIVATE("-an", "deactivate"),
|
||||
|
||||
/** Activate the volume exclusively on this host (-aey) */
|
||||
ACTIVATE_EXCLUSIVE("-aey", "activate exclusively"),
|
||||
|
||||
/** Activate the volume in shared mode on this host (-asy) */
|
||||
ACTIVATE_SHARED("-asy", "activate in shared mode"),
|
||||
|
||||
/** Query the current lock state (lvs -o lv_attr,lv_host) */
|
||||
QUERY_LOCK_STATE("query", "query lock state");
|
||||
|
||||
private final String lvchangeFlag;
|
||||
private final String description;
|
||||
|
||||
Operation(String lvchangeFlag, String description) {
|
||||
this.lvchangeFlag = lvchangeFlag;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getLvchangeFlag() {
|
||||
return lvchangeFlag;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
private String lvPath;
|
||||
private Operation operation;
|
||||
private String volumeUuid;
|
||||
|
||||
public ClvmLockTransferCommand() {
|
||||
// For serialization
|
||||
}
|
||||
|
||||
public ClvmLockTransferCommand(Operation operation, String lvPath, String volumeUuid) {
|
||||
this.operation = operation;
|
||||
this.lvPath = lvPath;
|
||||
this.volumeUuid = volumeUuid;
|
||||
setWait(65);
|
||||
}
|
||||
|
||||
public String getLvPath() {
|
||||
return lvPath;
|
||||
}
|
||||
|
||||
public Operation getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
public String getVolumeUuid() {
|
||||
return volumeUuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executeInSequence() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -103,4 +103,21 @@ public interface VolumeInfo extends DownloadableDataInfo, Volume {
|
|||
List<String> getCheckpointPaths();
|
||||
|
||||
Set<String> getCheckpointImageStoreUrls();
|
||||
|
||||
/**
|
||||
* Gets the destination host ID hint for CLVM volume creation.
|
||||
* This is used to route volume creation commands to the specific host where the VM will be deployed.
|
||||
* Only applicable for CLVM storage pools to avoid shared mode activation.
|
||||
*
|
||||
* @return The host ID where the volume should be created, or null if not set
|
||||
*/
|
||||
Long getDestinationHostId();
|
||||
|
||||
/**
|
||||
* Sets the destination host ID hint for CLVM volume creation.
|
||||
* This should be set before volume creation when the destination host is known.
|
||||
*
|
||||
* @param hostId The host ID where the volume should be created
|
||||
*/
|
||||
void setDestinationHostId(Long hostId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import com.cloud.exception.StorageAccessException;
|
|||
import com.cloud.host.Host;
|
||||
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||
import com.cloud.offering.DiskOffering;
|
||||
import com.cloud.storage.Storage.StoragePoolType;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.utils.Pair;
|
||||
|
|
@ -123,4 +124,71 @@ public interface VolumeService {
|
|||
void checkAndRepairVolumeBasedOnConfig(DataObject dataObject, Host host);
|
||||
|
||||
void validateChangeDiskOfferingEncryptionType(long existingDiskOfferingId, long newDiskOfferingId);
|
||||
|
||||
/**
|
||||
* Transfers exclusive lock for a volume on cluster-based storage (e.g., CLVM/CLVM_NG) from one host to another.
|
||||
* This is used for storage that requires host-level lock management for volumes on shared storage pools.
|
||||
* For non-CLVM pool types, this method returns false without taking action.
|
||||
*
|
||||
* @param volume The volume to transfer lock for
|
||||
* @param sourceHostId Host currently holding the exclusive lock
|
||||
* @param destHostId Host to receive the exclusive lock
|
||||
* @return true if lock transfer succeeded or was not needed, false if it failed
|
||||
*/
|
||||
boolean transferVolumeLock(VolumeInfo volume, Long sourceHostId, Long destHostId);
|
||||
|
||||
/**
|
||||
* Finds which host currently has the exclusive lock on a CLVM volume.
|
||||
* Checks in order: explicit lock tracking, attached VM's host, or first available cluster host.
|
||||
*
|
||||
* @param volume The CLVM volume
|
||||
* @return Host ID that has the exclusive lock, or null if cannot be determined
|
||||
*/
|
||||
Long findVolumeLockHost(VolumeInfo volume);
|
||||
|
||||
/**
|
||||
* Performs lightweight CLVM lock migration for a volume to a target host.
|
||||
* This transfers the LVM exclusive lock without copying data (CLVM volumes are on shared cluster storage).
|
||||
* If the volume already has the lock on the destination host, no action is taken.
|
||||
*
|
||||
* @param volume The volume to migrate lock for
|
||||
* @param destHostId Destination host ID
|
||||
* @return Updated VolumeInfo after lock migration
|
||||
*/
|
||||
VolumeInfo performLockMigration(VolumeInfo volume, Long destHostId);
|
||||
|
||||
/**
|
||||
* Checks if both storage pools are CLVM type (CLVM or CLVM_NG).
|
||||
*
|
||||
* @param volumePoolType Storage pool type for the volume
|
||||
* @param vmPoolType Storage pool type for the VM
|
||||
* @return true if both pools are CLVM type (CLVM or CLVM_NG)
|
||||
*/
|
||||
boolean areBothPoolsClvmType(StoragePoolType volumePoolType, StoragePoolType vmPoolType);
|
||||
|
||||
/**
|
||||
* Determines if CLVM lock transfer is required when a volume is already on the correct storage pool.
|
||||
*
|
||||
* @param volumeToAttach The volume being attached
|
||||
* @param volumePoolType Storage pool type for the volume
|
||||
* @param vmPoolType Storage pool type for the VM's existing volume
|
||||
* @param volumePoolId Storage pool ID for the volume
|
||||
* @param vmPoolId Storage pool ID for the VM's existing volume
|
||||
* @param vmHostId VM's current host ID (or last host ID if stopped)
|
||||
* @return true if CLVM lock transfer is needed
|
||||
*/
|
||||
boolean isLockTransferRequired(VolumeInfo volumeToAttach, StoragePoolType volumePoolType, StoragePoolType vmPoolType,
|
||||
Long volumePoolId, Long vmPoolId, Long vmHostId);
|
||||
|
||||
/**
|
||||
* Determines if lightweight CLVM migration is needed instead of full data copy.
|
||||
*
|
||||
* @param volumePoolType Storage pool type for the volume
|
||||
* @param vmPoolType Storage pool type for the VM
|
||||
* @param volumePoolPath Storage pool path for the volume
|
||||
* @param vmPoolPath Storage pool path for the VM
|
||||
* @return true if lightweight migration should be used
|
||||
*/
|
||||
boolean isLightweightMigrationNeeded(StoragePoolType volumePoolType, StoragePoolType vmPoolType,
|
||||
String volumePoolPath, String vmPoolPath);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,21 @@ public interface TemplateManager {
|
|||
true,
|
||||
ConfigKey.Scope.Global);
|
||||
|
||||
ConfigKey<Integer> VmIsoMaxCount = new ConfigKey<Integer>("Advanced",
|
||||
Integer.class,
|
||||
"vm.iso.max.count", "1",
|
||||
"Maximum number of ISOs that may be attached to a VM.",
|
||||
true,
|
||||
ConfigKey.Scope.Cluster);
|
||||
|
||||
// KVM/libvirt maps deviceSeq=3 to hdc (hda/hdb are taken by the root volume on i440fx/IDE).
|
||||
// user_vm.iso_id has always pointed at this slot; additional cdroms live in vm_iso_map.
|
||||
int CDROM_PRIMARY_DEVICE_SEQ = 3;
|
||||
|
||||
// Fallback per-VM cdrom cap when the placement host hasn't advertised host.cdrom.max.count
|
||||
// (older agent, never-deployed VM, etc.).
|
||||
int DEFAULT_CDROM_MAX_PER_VM = 1;
|
||||
|
||||
static final String VMWARE_TOOLS_ISO = "vmware-tools.iso";
|
||||
static final String XS_TOOLS_ISO = "xs-tools.iso";
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ import javax.inject.Inject;
|
|||
import javax.naming.ConfigurationException;
|
||||
import javax.persistence.EntityExistsException;
|
||||
|
||||
import com.cloud.agent.api.PostMigrationCommand;
|
||||
import com.cloud.storage.clvm.ClvmPoolManager;
|
||||
import com.cloud.hypervisor.KVMGuru;
|
||||
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
|
||||
import org.apache.cloudstack.annotation.AnnotationService;
|
||||
|
|
@ -136,6 +138,7 @@ import com.cloud.agent.api.PrepareExternalProvisioningAnswer;
|
|||
import com.cloud.agent.api.PrepareExternalProvisioningCommand;
|
||||
import com.cloud.agent.api.PrepareForMigrationAnswer;
|
||||
import com.cloud.agent.api.PrepareForMigrationCommand;
|
||||
import com.cloud.agent.api.PreMigrationCommand;
|
||||
import com.cloud.agent.api.RebootAnswer;
|
||||
import com.cloud.agent.api.RebootCommand;
|
||||
import com.cloud.agent.api.RecreateCheckpointsCommand;
|
||||
|
|
@ -267,6 +270,7 @@ import com.cloud.storage.dao.StoragePoolHostDao;
|
|||
import com.cloud.storage.dao.VMTemplateDao;
|
||||
import com.cloud.storage.dao.VMTemplateZoneDao;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.storage.dao.VolumeDetailsDao;
|
||||
import com.cloud.storage.snapshot.SnapshotManager;
|
||||
import com.cloud.template.VirtualMachineTemplate;
|
||||
import com.cloud.user.Account;
|
||||
|
|
@ -361,6 +365,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
|||
@Inject
|
||||
private VolumeDao _volsDao;
|
||||
@Inject
|
||||
private VolumeDetailsDao _volsDetailsDao;
|
||||
@Inject
|
||||
private HighAvailabilityManager _haMgr;
|
||||
@Inject
|
||||
private HostPodDao _podDao;
|
||||
|
|
@ -463,6 +469,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
|||
ExtensionsManager extensionsManager;
|
||||
@Inject
|
||||
ExtensionDetailsDao extensionDetailsDao;
|
||||
@Inject
|
||||
ClvmPoolManager clvmPoolManager;
|
||||
|
||||
|
||||
VmWorkJobHandlerProxy _jobHandlerProxy = new VmWorkJobHandlerProxy(this);
|
||||
|
|
@ -3150,6 +3158,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
|||
updateOverCommitRatioForVmProfile(profile, dest.getHost().getClusterId());
|
||||
|
||||
final VirtualMachineTO to = toVmTO(profile);
|
||||
|
||||
executePreMigrationCommand(vm, to, srcHostId);
|
||||
|
||||
final PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(to);
|
||||
setVmNetworkDetails(vm, to);
|
||||
|
||||
|
|
@ -3281,6 +3292,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
|||
logger.warn("Error while checking the vm {} on host {}", vm, dest.getHost(), e);
|
||||
}
|
||||
migrated = true;
|
||||
executePostMigrationCommand(vm, to, dstHostId);
|
||||
} finally {
|
||||
if (!migrated) {
|
||||
logger.info("Migration was unsuccessful. Cleaning up: {}", vm);
|
||||
|
|
@ -3320,6 +3332,30 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
|||
}
|
||||
}
|
||||
|
||||
private void executePostMigrationCommand(VMInstanceVO vm, VirtualMachineTO to, long dstHostId) {
|
||||
if (!(vm.getHypervisorType() == HypervisorType.KVM && hasClvmVolumes(vm.getId()))) {
|
||||
return;
|
||||
}
|
||||
final String dstHostUuid = _hostDao.findById(dstHostId).getUuid();
|
||||
try {
|
||||
logger.info("Executing post-migration tasks for VM {} with CLVM volumes on destination host {}", vm.getInstanceName(), dstHostUuid);
|
||||
final PostMigrationCommand postMigrationCommand = new PostMigrationCommand(to, vm.getInstanceName());
|
||||
final Answer postMigrationAnswer = _agentMgr.send(dstHostId, postMigrationCommand);
|
||||
|
||||
if (postMigrationAnswer == null || !postMigrationAnswer.getResult()) {
|
||||
final String details = postMigrationAnswer != null ? postMigrationAnswer.getDetails() : "null answer returned";
|
||||
logger.warn("Post-migration tasks failed for VM {} on destination host {}: {}. Migration completed but some cleanup may be needed.",
|
||||
vm.getInstanceName(), dstHostUuid, details);
|
||||
} else {
|
||||
logger.info("Successfully completed post-migration tasks for VM {} on destination host {}", vm.getInstanceName(), dstHostUuid);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Exception during post-migration tasks for VM {} on destination host {}: {}. Migration completed but some cleanup may be needed.",
|
||||
vm.getInstanceName(), dstHostUuid, e.getMessage(), e);
|
||||
}
|
||||
updateClvmLockHostForVmVolumes(vm.getId(), dstHostId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and set parameters for the {@link MigrateCommand} used in the migration and scaling of VMs.
|
||||
*/
|
||||
|
|
@ -3366,6 +3402,27 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
|||
_vmDao.persist(newVm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates CLVM_LOCK_HOST_ID for all CLVM volumes attached to a VM after VM migration.
|
||||
* This ensures that subsequent operations on CLVM volumes are routed to the correct host.
|
||||
*
|
||||
* @param vmId The ID of the VM that was migrated
|
||||
* @param destHostId The destination host ID where the VM now resides
|
||||
*/
|
||||
private void updateClvmLockHostForVmVolumes(long vmId, long destHostId) {
|
||||
List<VolumeVO> volumes = _volsDao.findByInstance(vmId);
|
||||
if (CollectionUtils.isEmpty(volumes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (VolumeVO volume : volumes) {
|
||||
StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId());
|
||||
if (pool != null && ClvmPoolManager.isClvmPoolType(pool.getPoolType())) {
|
||||
clvmPoolManager.setClvmLockHostId(volume.getId(), destHostId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We create the mapping of volumes and storage pool to migrate the VMs according to the information sent by the user.
|
||||
* If the user did not enter a complete mapping, the volumes that were left behind will be auto mapped using {@link #createStoragePoolMappingsForVolumes(VirtualMachineProfile, DataCenterDeployment, Map, List)}
|
||||
|
|
@ -4922,6 +4979,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
|||
volumeMgr.prepareForMigration(profile, dest);
|
||||
|
||||
final VirtualMachineTO to = toVmTO(profile);
|
||||
|
||||
// Step 1: Send PreMigrationCommand to source host to convert CLVM volumes to shared mode
|
||||
// This must happen BEFORE PrepareForMigrationCommand on destination to avoid lock conflicts
|
||||
executePreMigrationCommand(vm, to, srcHostId);
|
||||
|
||||
// Step 2: Send PrepareForMigrationCommand to destination host
|
||||
final PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(to);
|
||||
|
||||
ItWorkVO work = new ItWorkVO(UUID.randomUUID().toString(), _nodeId, State.Migrating, vm.getType(), vm.getId());
|
||||
|
|
@ -5006,6 +5069,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
|||
}
|
||||
|
||||
migrated = true;
|
||||
executePostMigrationCommand(vm, to, dstHostId);
|
||||
} finally {
|
||||
if (!migrated) {
|
||||
logger.info("Migration was unsuccessful. Cleaning up: {}", vm);
|
||||
|
|
@ -6441,6 +6505,37 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
|||
return findClusterAndHostIdForVm(vm, false);
|
||||
}
|
||||
|
||||
private boolean hasClvmVolumes(long vmId) {
|
||||
List<VolumeVO> volumes = _volsDao.findByInstance(vmId);
|
||||
return volumes.stream()
|
||||
.map(v -> _storagePoolDao.findById(v.getPoolId()))
|
||||
.anyMatch(pool -> pool != null && ClvmPoolManager.isClvmPoolType(pool.getPoolType()));
|
||||
}
|
||||
|
||||
private void executePreMigrationCommand(VMInstanceVO vm, VirtualMachineTO to, long srcHostId) {
|
||||
if (!(vm.getHypervisorType() == HypervisorType.KVM && hasClvmVolumes(vm.getId()))) {
|
||||
return;
|
||||
}
|
||||
final String vmInstanceName = vm.getInstanceName();
|
||||
final String srcHostUuid = _hostDao.findById(srcHostId).getUuid();
|
||||
logger.info("Sending PreMigrationCommand to source host {} for VM {} with CLVM volumes", srcHostUuid, vmInstanceName);
|
||||
final PreMigrationCommand preMigCmd = new PreMigrationCommand(to, vmInstanceName);
|
||||
Answer preMigAnswer = null;
|
||||
try {
|
||||
preMigAnswer = _agentMgr.send(srcHostId, preMigCmd);
|
||||
if (preMigAnswer == null || !preMigAnswer.getResult()) {
|
||||
final String details = preMigAnswer != null ? preMigAnswer.getDetails() : "null answer returned";
|
||||
final String msg = "Failed to prepare source host for migration: " + details;
|
||||
logger.error("Failed to prepare source host {} for migration of VM {}: {}", srcHostUuid, vmInstanceName, details);
|
||||
throw new CloudRuntimeException(msg);
|
||||
}
|
||||
logger.info("Successfully prepared source host {} for migration of VM {}", srcHostUuid, vmInstanceName);
|
||||
} catch (final AgentUnavailableException | OperationTimedoutException e) {
|
||||
logger.error("Failed to send PreMigrationCommand to source host {}: {}", srcHostUuid, e.getMessage(), e);
|
||||
throw new CloudRuntimeException("Failed to prepare source host for migration: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<Long, Long> findClusterAndHostIdForVm(long vmId) {
|
||||
VMInstanceVO vm = _vmDao.findById(vmId);
|
||||
|
|
|
|||
|
|
@ -38,8 +38,10 @@ import java.util.stream.Collectors;
|
|||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import com.cloud.agent.AgentManager;
|
||||
import com.cloud.deploy.DeploymentClusterPlanner;
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.storage.clvm.ClvmPoolManager;
|
||||
import com.cloud.resourcelimit.ReservationHelper;
|
||||
import com.cloud.storage.DiskOfferingVO;
|
||||
import com.cloud.storage.VMTemplateVO;
|
||||
|
|
@ -275,6 +277,10 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
|||
ConfigurationDao configurationDao;
|
||||
@Inject
|
||||
VMInstanceDao vmInstanceDao;
|
||||
@Inject
|
||||
ClvmPoolManager clvmPoolManager;
|
||||
@Inject
|
||||
AgentManager _agentMgr;
|
||||
|
||||
@Inject
|
||||
protected SnapshotHelper snapshotHelper;
|
||||
|
|
@ -747,6 +753,17 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
|||
logger.debug("Trying to create volume [{}] on storage pool [{}].",
|
||||
volumeToString, poolToString);
|
||||
DataStore store = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary);
|
||||
|
||||
// For CLVM pools, set the lock host hint so volume is created on the correct host
|
||||
// This avoids the need for shared mode activation and improves performance
|
||||
if (ClvmPoolManager.isClvmPoolType(pool.getPoolType()) && hostId != null) {
|
||||
logger.info("CLVM pool detected. Setting lock host {} for volume {} to route creation to correct host",
|
||||
hostId, volumeInfo.getUuid());
|
||||
volumeInfo.setDestinationHostId(hostId);
|
||||
|
||||
clvmPoolManager.setClvmLockHostId(volumeInfo.getId(), hostId);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
// retry one more time in case of template reload is required for Vmware case
|
||||
AsyncCallFuture<VolumeApiResult> future = null;
|
||||
|
|
@ -788,6 +805,122 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
|||
return String.format("uuid: %s, name: %s", volume.getUuid(), volume.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the CLVM_LOCK_HOST_ID for a migrated volume if applicable.
|
||||
* For CLVM volumes that are attached to a VM, this updates the lock host tracking
|
||||
* to point to the VM's current host after volume migration.
|
||||
*
|
||||
* @param migratedVolume The volume that was migrated
|
||||
* @param destPool The destination storage pool
|
||||
* @param operationType Description of the operation (e.g., "migrated", "live-migrated") for logging
|
||||
*/
|
||||
private void updateClvmLockHostAfterMigration(Volume migratedVolume, StoragePool destPool, String operationType) {
|
||||
if (migratedVolume == null || destPool == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
StoragePoolVO pool = _storagePoolDao.findById(destPool.getId());
|
||||
if (pool == null || !ClvmPoolManager.isClvmPoolType(pool.getPoolType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (migratedVolume.getInstanceId() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
VMInstanceVO vm = vmInstanceDao.findById(migratedVolume.getInstanceId());
|
||||
if (vm == null || vm.getHostId() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
clvmPoolManager.setClvmLockHostId(migratedVolume.getId(), vm.getHostId());
|
||||
logger.debug("Updated CLVM_LOCK_HOST_ID for {} volume {} to host {} where VM {} is running",
|
||||
operationType, migratedVolume.getUuid(), vm.getHostId(), vm.getInstanceName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the CLVM lock host ID from any existing volume of the specified VM.
|
||||
* This is useful when attaching a new volume to a stopped VM - we want to maintain
|
||||
* consistency by using the same host that manages the VM's other CLVM volumes.
|
||||
*
|
||||
* @param vmId The ID of the VM
|
||||
* @return The host ID if found, null otherwise
|
||||
*/
|
||||
private Long getClvmLockHostFromVmVolumes(Long vmId) {
|
||||
if (vmId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<VolumeVO> vmVolumes = _volsDao.findByInstance(vmId);
|
||||
if (vmVolumes == null || vmVolumes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (VolumeVO volume : vmVolumes) {
|
||||
if (volume.getPoolId() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId());
|
||||
if (pool == null || !ClvmPoolManager.isClvmPoolType(pool.getPoolType())) {
|
||||
continue;
|
||||
}
|
||||
Long lockHostId = clvmPoolManager.getClvmLockHostId(
|
||||
volume.getId(),
|
||||
volume.getUuid(),
|
||||
volume.getPath(),
|
||||
pool,
|
||||
true
|
||||
);
|
||||
if (lockHostId != null) {
|
||||
logger.debug("Found actual CLVM lock host {} from volume {} of VM {} via LVM query",
|
||||
lockHostId, volume.getUuid(), vmId);
|
||||
return lockHostId;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void transferClvmLocksForVmStart(List<VolumeVO> volumes, Long destHostId, VMInstanceVO vm) {
|
||||
if (volumes == null || volumes.isEmpty() || destHostId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (VolumeVO volume : volumes) {
|
||||
if (volume.getPoolId() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId());
|
||||
if (pool == null || !ClvmPoolManager.isClvmPoolType(pool.getPoolType())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Long currentLockHost = clvmPoolManager.getClvmLockHostId(
|
||||
volume.getId(),
|
||||
volume.getUuid(),
|
||||
volume.getPath(),
|
||||
pool,
|
||||
true
|
||||
);
|
||||
|
||||
if (currentLockHost == null) {
|
||||
clvmPoolManager.setClvmLockHostId(volume.getId(), destHostId);
|
||||
} else if (!currentLockHost.equals(destHostId)) {
|
||||
logger.info("CLVM volume {} is locked on host {} but VM {} starting on host {}. Transferring lock.",
|
||||
volume.getUuid(), currentLockHost, vm.getInstanceName(), destHostId);
|
||||
|
||||
if (!clvmPoolManager.transferClvmVolumeLock(volume.getUuid(), volume.getId(),
|
||||
volume.getPath(), pool, currentLockHost, destHostId)) {
|
||||
throw new CloudRuntimeException(
|
||||
String.format("Failed to transfer CLVM lock for volume %s from host %s to host %s",
|
||||
volume.getUuid(), currentLockHost, destHostId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getRandomVolumeName() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
|
@ -1206,10 +1339,22 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
|||
Long clusterId = storagePool.getClusterId();
|
||||
logger.trace("storage-pool {}/{} is associated with cluster {}",storagePool.getName(), storagePool.getUuid(), clusterId);
|
||||
Long hostId = vm.getHostId();
|
||||
if (hostId == null && storagePool.isLocal()) {
|
||||
List<StoragePoolHostVO> poolHosts = storagePoolHostDao.listByPoolId(storagePool.getId());
|
||||
if (poolHosts.size() > 0) {
|
||||
hostId = poolHosts.get(0).getHostId();
|
||||
if (hostId == null && (storagePool.isLocal() || ClvmPoolManager.isClvmPoolType(storagePool.getPoolType()))) {
|
||||
if (ClvmPoolManager.isClvmPoolType(storagePool.getPoolType())) {
|
||||
hostId = getClvmLockHostFromVmVolumes(vm.getId());
|
||||
if (hostId != null) {
|
||||
logger.debug("Using CLVM lock host {} from VM {}'s existing volumes for new volume creation",
|
||||
hostId, vm.getUuid());
|
||||
}
|
||||
}
|
||||
|
||||
if (hostId == null) {
|
||||
List<StoragePoolHostVO> poolHosts = storagePoolHostDao.listByPoolId(storagePool.getId());
|
||||
if (!poolHosts.isEmpty()) {
|
||||
hostId = poolHosts.get(0).getHostId();
|
||||
logger.debug("Selected host {} from storage pool {} for stopped VM {} volume creation",
|
||||
hostId, storagePool.getUuid(), vm.getUuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1454,6 +1599,9 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
|||
_snapshotDao.updateVolumeIds(vol.getId(), result.getVolume().getId());
|
||||
_snapshotDataStoreDao.updateVolumeIds(vol.getId(), result.getVolume().getId());
|
||||
}
|
||||
|
||||
// For CLVM volumes attached to a VM, update the CLVM_LOCK_HOST_ID after migration
|
||||
updateClvmLockHostAfterMigration(result.getVolume(), destPool, "migrated");
|
||||
}
|
||||
return result.getVolume();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
|
|
@ -1479,6 +1627,10 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
|||
logger.error("Volume [{}] migration failed due to [{}].", volToString, result.getResult());
|
||||
return null;
|
||||
}
|
||||
|
||||
// For CLVM volumes attached to a VM, update the CLVM_LOCK_HOST_ID after live migration
|
||||
updateClvmLockHostAfterMigration(result.getVolume(), destPool, "live-migrated");
|
||||
|
||||
return result.getVolume();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
logger.error("Volume [{}] migration failed due to [{}].", volToString, e.getMessage());
|
||||
|
|
@ -1521,6 +1673,22 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
|||
logger.error(msg);
|
||||
throw new CloudRuntimeException(msg);
|
||||
}
|
||||
for (Map.Entry<Volume, StoragePool> entry : volumeToPool.entrySet()) {
|
||||
Volume volume = entry.getKey();
|
||||
StoragePool destPool = entry.getValue();
|
||||
StoragePoolVO srcPool = _storagePoolDao.findById(volume.getPoolId());
|
||||
if (srcPool != null && srcPool.getId() == destPool.getId() &&
|
||||
ClvmPoolManager.isClvmPoolType(srcPool.getPoolType())) {
|
||||
if (!clvmPoolManager.transferClvmVolumeLock(volume.getUuid(), volume.getId(),
|
||||
volume.getPath(), srcPool, srcHost.getId(), destHost.getId())) {
|
||||
throw new CloudRuntimeException(String.format(
|
||||
"Failed to transfer CLVM lock for volume [%s] to destination host [%s].",
|
||||
volume.getUuid(), destHost.getId()));
|
||||
}
|
||||
} else {
|
||||
updateClvmLockHostAfterMigration(volume, destPool, "vm-migrated");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
logger.error("Failed to migrate VM [{}] along with its volumes due to [{}].", vm, e.getMessage());
|
||||
logger.debug("Exception: ", e);
|
||||
|
|
@ -1853,6 +2021,19 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
|||
|
||||
future = volService.createManagedStorageVolumeFromTemplateAsync(volume, destPool.getId(), templ, hostId);
|
||||
} else {
|
||||
// For CLVM pools, set the destination host hint so volume is created on the correct host
|
||||
// This avoids the need for shared mode activation and improves performance
|
||||
StoragePoolVO poolVO = _storagePoolDao.findById(destPool.getId());
|
||||
if (poolVO != null && ClvmPoolManager.isClvmPoolType(poolVO.getPoolType())) {
|
||||
Long hostId = vm.getVirtualMachine().getHostId();
|
||||
if (hostId != null) {
|
||||
volume.setDestinationHostId(hostId);
|
||||
clvmPoolManager.setClvmLockHostId(volume.getId(), hostId);
|
||||
logger.info("CLVM pool detected during volume creation from template. Setting lock host {} for volume {} (persisted to DB) to route creation to correct host",
|
||||
hostId, volume.getUuid());
|
||||
}
|
||||
}
|
||||
|
||||
future = volService.createVolumeFromTemplateAsync(volume, destPool.getId(), templ);
|
||||
}
|
||||
}
|
||||
|
|
@ -1976,13 +2157,18 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
|||
throw new CloudRuntimeException(msg);
|
||||
}
|
||||
|
||||
// don't allow to start vm that doesn't have a root volume
|
||||
if (_volsDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT).isEmpty()) {
|
||||
throw new CloudRuntimeException(String.format("ROOT volume is missing, unable to prepare volumes for the VM [%s].", vm.getVirtualMachine()));
|
||||
}
|
||||
|
||||
List<VolumeVO> vols = _volsDao.findUsableVolumesForInstance(vm.getId());
|
||||
|
||||
VirtualMachine vmInstance = vm.getVirtualMachine();
|
||||
VMInstanceVO vmInstanceVO = vmInstanceDao.findById(vmInstance.getId());
|
||||
if (vmInstance.getState() == State.Starting && dest.getHost() != null) {
|
||||
transferClvmLocksForVmStart(vols, dest.getHost().getId(), vmInstanceVO);
|
||||
}
|
||||
|
||||
List<VolumeTask> tasks = getTasks(vols, dest.getStorageForDisks(), vm);
|
||||
Volume vol = null;
|
||||
PrimaryDataStore store;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@
|
|||
value="#{storagePoolAllocatorsRegistry.registered}" />
|
||||
</bean>
|
||||
|
||||
<bean id="clvmPoolManager" class="com.cloud.storage.clvm.ClvmPoolManager" />
|
||||
|
||||
<bean id="storageOrchestrator"
|
||||
class="org.apache.cloudstack.engine.orchestration.StorageOrchestrator"/>
|
||||
<bean id="dataMigrationHelper"
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import static org.mockito.Mockito.verify;
|
|||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
|
@ -60,6 +61,7 @@ import com.cloud.ha.HighAvailabilityManager;
|
|||
import com.cloud.network.Network;
|
||||
import com.cloud.network.NetworkModel;
|
||||
import com.cloud.resource.ResourceManager;
|
||||
import com.cloud.storage.clvm.ClvmPoolManager;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
|
||||
|
|
@ -336,6 +338,19 @@ public class VirtualMachineManagerImplTest {
|
|||
}
|
||||
}
|
||||
|
||||
private ClvmPoolManager injectMockedClvmPoolManager() {
|
||||
ClvmPoolManager clvmPoolManagerMock = mock(ClvmPoolManager.class);
|
||||
ReflectionTestUtils.setField(virtualMachineManagerImpl, "clvmPoolManager", clvmPoolManagerMock);
|
||||
return clvmPoolManagerMock;
|
||||
}
|
||||
|
||||
private Method getUpdateClvmLockHostForVmVolumesMethod() throws NoSuchMethodException {
|
||||
Method method = VirtualMachineManagerImpl.class.getDeclaredMethod(
|
||||
"updateClvmLockHostForVmVolumes", long.class, long.class);
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testaddHostIpToCertDetailsIfConfigAllows() {
|
||||
Host vmHost = mock(Host.class);
|
||||
|
|
@ -1954,4 +1969,181 @@ public class VirtualMachineManagerImplTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateClvmLockHostForVmVolumes_WithClvmVolumes() throws Exception {
|
||||
long vmId = 100L;
|
||||
long destHostId = 2L;
|
||||
long poolId = 10L;
|
||||
|
||||
VolumeVO clvmVolume1 = mock(VolumeVO.class);
|
||||
VolumeVO clvmVolume2 = mock(VolumeVO.class);
|
||||
|
||||
when(clvmVolume1.getId()).thenReturn(1L);
|
||||
when(clvmVolume1.getPoolId()).thenReturn(poolId);
|
||||
when(clvmVolume2.getId()).thenReturn(2L);
|
||||
when(clvmVolume2.getPoolId()).thenReturn(poolId);
|
||||
|
||||
StoragePoolVO clvmPool = mock(StoragePoolVO.class);
|
||||
when(clvmPool.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
when(volumeDaoMock.findByInstance(vmId)).thenReturn(Arrays.asList(clvmVolume1, clvmVolume2));
|
||||
when(storagePoolDaoMock.findById(poolId)).thenReturn(clvmPool);
|
||||
|
||||
ClvmPoolManager clvmPoolManagerMock = injectMockedClvmPoolManager();
|
||||
|
||||
Method method = getUpdateClvmLockHostForVmVolumesMethod();
|
||||
method.invoke(virtualMachineManagerImpl, vmId, destHostId);
|
||||
|
||||
verify(clvmPoolManagerMock, times(1)).setClvmLockHostId(1L, destHostId);
|
||||
verify(clvmPoolManagerMock, times(1)).setClvmLockHostId(2L, destHostId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateClvmLockHostForVmVolumes_WithNonClvmVolumes() throws Exception {
|
||||
long vmId = 100L;
|
||||
long destHostId = 2L;
|
||||
long poolId = 10L;
|
||||
|
||||
VolumeVO nfsVolume = mock(VolumeVO.class);
|
||||
when(nfsVolume.getPoolId()).thenReturn(poolId);
|
||||
|
||||
StoragePoolVO nfsPool = mock(StoragePoolVO.class);
|
||||
when(nfsPool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
|
||||
|
||||
when(volumeDaoMock.findByInstance(vmId)).thenReturn(Arrays.asList(nfsVolume));
|
||||
when(storagePoolDaoMock.findById(poolId)).thenReturn(nfsPool);
|
||||
|
||||
ClvmPoolManager clvmPoolManagerMock = injectMockedClvmPoolManager();
|
||||
|
||||
Method method = getUpdateClvmLockHostForVmVolumesMethod();
|
||||
method.invoke(virtualMachineManagerImpl, vmId, destHostId);
|
||||
|
||||
verify(clvmPoolManagerMock, never()).setClvmLockHostId(anyLong(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateClvmLockHostForVmVolumes_WithMixedVolumes() throws Exception {
|
||||
long vmId = 100L;
|
||||
long destHostId = 2L;
|
||||
long clvmPoolId = 10L;
|
||||
long nfsPoolId = 20L;
|
||||
|
||||
VolumeVO clvmVolume = mock(VolumeVO.class);
|
||||
VolumeVO nfsVolume = mock(VolumeVO.class);
|
||||
|
||||
when(clvmVolume.getId()).thenReturn(1L);
|
||||
when(clvmVolume.getPoolId()).thenReturn(clvmPoolId);
|
||||
when(nfsVolume.getPoolId()).thenReturn(nfsPoolId);
|
||||
|
||||
StoragePoolVO clvmPool = mock(StoragePoolVO.class);
|
||||
when(clvmPool.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
StoragePoolVO nfsPool = mock(StoragePoolVO.class);
|
||||
when(nfsPool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
|
||||
|
||||
when(volumeDaoMock.findByInstance(vmId)).thenReturn(Arrays.asList(clvmVolume, nfsVolume));
|
||||
when(storagePoolDaoMock.findById(clvmPoolId)).thenReturn(clvmPool);
|
||||
when(storagePoolDaoMock.findById(nfsPoolId)).thenReturn(nfsPool);
|
||||
|
||||
ClvmPoolManager clvmPoolManagerMock = injectMockedClvmPoolManager();
|
||||
|
||||
Method method = getUpdateClvmLockHostForVmVolumesMethod();
|
||||
method.invoke(virtualMachineManagerImpl, vmId, destHostId);
|
||||
|
||||
verify(clvmPoolManagerMock, times(1)).setClvmLockHostId(1L, destHostId);
|
||||
verify(clvmPoolManagerMock, never()).setClvmLockHostId(2L, destHostId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateClvmLockHostForVmVolumes_WithNoVolumes() throws Exception {
|
||||
long vmId = 100L;
|
||||
long destHostId = 2L;
|
||||
|
||||
when(volumeDaoMock.findByInstance(vmId)).thenReturn(Collections.emptyList());
|
||||
|
||||
ClvmPoolManager clvmPoolManagerMock = injectMockedClvmPoolManager();
|
||||
|
||||
Method method = getUpdateClvmLockHostForVmVolumesMethod();
|
||||
method.invoke(virtualMachineManagerImpl, vmId, destHostId);
|
||||
|
||||
verify(clvmPoolManagerMock, never()).setClvmLockHostId(anyLong(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateClvmLockHostForVmVolumes_WithNullPoolId() throws Exception {
|
||||
long vmId = 100L;
|
||||
long destHostId = 2L;
|
||||
|
||||
VolumeVO volumeWithoutPool = mock(VolumeVO.class);
|
||||
when(volumeWithoutPool.getPoolId()).thenReturn(null);
|
||||
|
||||
when(volumeDaoMock.findByInstance(vmId)).thenReturn(Arrays.asList(volumeWithoutPool));
|
||||
|
||||
ClvmPoolManager clvmPoolManagerMock = injectMockedClvmPoolManager();
|
||||
|
||||
Method method = getUpdateClvmLockHostForVmVolumesMethod();
|
||||
method.invoke(virtualMachineManagerImpl, vmId, destHostId);
|
||||
|
||||
verify(storagePoolDaoMock, never()).findById(anyLong());
|
||||
verify(clvmPoolManagerMock, never()).setClvmLockHostId(anyLong(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateClvmLockHostForVmVolumes_WithNullPool() throws Exception {
|
||||
long vmId = 100L;
|
||||
long destHostId = 2L;
|
||||
long poolId = 10L;
|
||||
|
||||
VolumeVO volume = mock(VolumeVO.class);
|
||||
when(volume.getPoolId()).thenReturn(poolId);
|
||||
|
||||
when(volumeDaoMock.findByInstance(vmId)).thenReturn(Arrays.asList(volume));
|
||||
when(storagePoolDaoMock.findById(poolId)).thenReturn(null);
|
||||
|
||||
ClvmPoolManager clvmPoolManagerMock = injectMockedClvmPoolManager();
|
||||
|
||||
Method method = getUpdateClvmLockHostForVmVolumesMethod();
|
||||
method.invoke(virtualMachineManagerImpl, vmId, destHostId);
|
||||
|
||||
verify(clvmPoolManagerMock, never()).setClvmLockHostId(anyLong(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateClvmLockHostForVmVolumes_MultipleClvmPools() throws Exception {
|
||||
long vmId = 100L;
|
||||
long destHostId = 2L;
|
||||
long pool1Id = 10L;
|
||||
long pool2Id = 20L;
|
||||
|
||||
VolumeVO volume1 = mock(VolumeVO.class);
|
||||
VolumeVO volume2 = mock(VolumeVO.class);
|
||||
VolumeVO volume3 = mock(VolumeVO.class);
|
||||
|
||||
when(volume1.getId()).thenReturn(1L);
|
||||
when(volume1.getPoolId()).thenReturn(pool1Id);
|
||||
when(volume2.getId()).thenReturn(2L);
|
||||
when(volume2.getPoolId()).thenReturn(pool2Id);
|
||||
when(volume3.getId()).thenReturn(3L);
|
||||
when(volume3.getPoolId()).thenReturn(pool1Id);
|
||||
|
||||
StoragePoolVO clvmPool1 = mock(StoragePoolVO.class);
|
||||
when(clvmPool1.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
StoragePoolVO clvmPool2 = mock(StoragePoolVO.class);
|
||||
when(clvmPool2.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
when(volumeDaoMock.findByInstance(vmId)).thenReturn(Arrays.asList(volume1, volume2, volume3));
|
||||
when(storagePoolDaoMock.findById(pool1Id)).thenReturn(clvmPool1);
|
||||
when(storagePoolDaoMock.findById(pool2Id)).thenReturn(clvmPool2);
|
||||
|
||||
ClvmPoolManager clvmPoolManagerMock = injectMockedClvmPoolManager();
|
||||
|
||||
Method method = getUpdateClvmLockHostForVmVolumesMethod();
|
||||
method.invoke(virtualMachineManagerImpl, vmId, destHostId);
|
||||
|
||||
verify(clvmPoolManagerMock, times(1)).setClvmLockHostId(1L, destHostId);
|
||||
verify(clvmPoolManagerMock, times(1)).setClvmLockHostId(2L, destHostId);
|
||||
verify(clvmPoolManagerMock, times(1)).setClvmLockHostId(3L, destHostId);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
// under the License.
|
||||
package org.apache.cloudstack.engine.orchestration;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
|
@ -30,6 +32,7 @@ import com.cloud.host.Host;
|
|||
import com.cloud.host.HostVO;
|
||||
import com.cloud.hypervisor.Hypervisor;
|
||||
import com.cloud.offering.DiskOffering;
|
||||
import com.cloud.storage.clvm.ClvmPoolManager;
|
||||
import com.cloud.storage.ScopeType;
|
||||
import com.cloud.storage.DataStoreRole;
|
||||
import com.cloud.storage.Storage;
|
||||
|
|
@ -42,6 +45,7 @@ import com.cloud.user.ResourceLimitService;
|
|||
import com.cloud.uservm.UserVm;
|
||||
import com.cloud.utils.db.EntityManager;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.vm.VMInstanceVO;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.utils.Pair;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
|
||||
|
|
@ -67,6 +71,7 @@ import org.junit.Assert;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedConstruction;
|
||||
|
|
@ -640,4 +645,305 @@ public class VolumeOrchestratorTest {
|
|||
Assert.assertEquals(1, result.second().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransferClvmLocksForVmStart_WithClvmVolumes() throws Exception {
|
||||
Long destHostId = 2L;
|
||||
Long currentHostId = 1L;
|
||||
Long poolId = 10L;
|
||||
|
||||
VolumeVO clvmVolume1 = Mockito.mock(VolumeVO.class);
|
||||
VolumeVO clvmVolume2 = Mockito.mock(VolumeVO.class);
|
||||
|
||||
Mockito.when(clvmVolume1.getId()).thenReturn(101L);
|
||||
Mockito.when(clvmVolume1.getPoolId()).thenReturn(poolId);
|
||||
Mockito.when(clvmVolume1.getUuid()).thenReturn("vol-uuid-1");
|
||||
Mockito.when(clvmVolume1.getPath()).thenReturn("vol-path-1");
|
||||
|
||||
Mockito.when(clvmVolume2.getId()).thenReturn(102L);
|
||||
Mockito.when(clvmVolume2.getPoolId()).thenReturn(poolId);
|
||||
Mockito.when(clvmVolume2.getUuid()).thenReturn("vol-uuid-2");
|
||||
Mockito.when(clvmVolume2.getPath()).thenReturn("vol-path-2");
|
||||
|
||||
StoragePoolVO clvmPool = Mockito.mock(StoragePoolVO.class);
|
||||
Mockito.when(clvmPool.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(vmInstance.getInstanceName()).thenReturn(MOCK_VM_NAME);
|
||||
|
||||
ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class);
|
||||
Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(101L), Mockito.anyString(),
|
||||
Mockito.anyString(), Mockito.any(), Mockito.eq(true))).thenReturn(currentHostId);
|
||||
Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(102L), Mockito.anyString(),
|
||||
Mockito.anyString(), Mockito.any(), Mockito.eq(true))).thenReturn(currentHostId);
|
||||
Mockito.when(clvmPoolManager.transferClvmVolumeLock(Mockito.anyString(), Mockito.anyLong(),
|
||||
Mockito.anyString(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong())).thenReturn(true);
|
||||
|
||||
Mockito.when(storagePoolDao.findById(poolId)).thenReturn(clvmPool);
|
||||
|
||||
setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager);
|
||||
setField(volumeOrchestrator, "_storagePoolDao", storagePoolDao);
|
||||
|
||||
Method method = VolumeOrchestrator.class.getDeclaredMethod(
|
||||
"transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
method.invoke(volumeOrchestrator, List.of(clvmVolume1, clvmVolume2), destHostId, vmInstance);
|
||||
|
||||
Mockito.verify(clvmPoolManager, Mockito.times(1)).transferClvmVolumeLock(
|
||||
Mockito.eq("vol-uuid-1"), Mockito.eq(101L), Mockito.eq("vol-path-1"),
|
||||
Mockito.eq(clvmPool), Mockito.eq(currentHostId), Mockito.eq(destHostId));
|
||||
Mockito.verify(clvmPoolManager, Mockito.times(1)).transferClvmVolumeLock(
|
||||
Mockito.eq("vol-uuid-2"), Mockito.eq(102L), Mockito.eq("vol-path-2"),
|
||||
Mockito.eq(clvmPool), Mockito.eq(currentHostId), Mockito.eq(destHostId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransferClvmLocksForVmStart_WithNonClvmVolumes() throws Exception {
|
||||
Long destHostId = 2L;
|
||||
Long poolId = 10L;
|
||||
|
||||
VolumeVO nfsVolume = Mockito.mock(VolumeVO.class);
|
||||
Mockito.when(nfsVolume.getPoolId()).thenReturn(poolId);
|
||||
|
||||
StoragePoolVO nfsPool = Mockito.mock(StoragePoolVO.class);
|
||||
Mockito.when(nfsPool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
|
||||
|
||||
VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class);
|
||||
|
||||
ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class);
|
||||
|
||||
Mockito.when(storagePoolDao.findById(poolId)).thenReturn(nfsPool);
|
||||
|
||||
setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager);
|
||||
setField(volumeOrchestrator, "_storagePoolDao", storagePoolDao);
|
||||
|
||||
Method method = VolumeOrchestrator.class.getDeclaredMethod(
|
||||
"transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
method.invoke(volumeOrchestrator, List.of(nfsVolume), destHostId, vmInstance);
|
||||
|
||||
Mockito.verify(clvmPoolManager, Mockito.never()).transferClvmVolumeLock(
|
||||
Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(),
|
||||
Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransferClvmLocksForVmStart_NoLockTransferNeeded() throws Exception {
|
||||
Long destHostId = 2L;
|
||||
Long poolId = 10L;
|
||||
|
||||
VolumeVO clvmVolume = Mockito.mock(VolumeVO.class);
|
||||
Mockito.when(clvmVolume.getId()).thenReturn(101L);
|
||||
Mockito.when(clvmVolume.getPoolId()).thenReturn(poolId);
|
||||
|
||||
StoragePoolVO clvmPool = Mockito.mock(StoragePoolVO.class);
|
||||
Mockito.when(clvmPool.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class);
|
||||
|
||||
ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class);
|
||||
Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(101L), ArgumentMatchers.nullable(String.class),
|
||||
ArgumentMatchers.nullable(String.class), Mockito.any(), Mockito.eq(true))).thenReturn(destHostId);
|
||||
|
||||
Mockito.when(storagePoolDao.findById(poolId)).thenReturn(clvmPool);
|
||||
|
||||
setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager);
|
||||
setField(volumeOrchestrator, "_storagePoolDao", storagePoolDao);
|
||||
|
||||
java.lang.reflect.Method method = VolumeOrchestrator.class.getDeclaredMethod(
|
||||
"transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
method.invoke(volumeOrchestrator, List.of(clvmVolume), destHostId, vmInstance);
|
||||
|
||||
Mockito.verify(clvmPoolManager, Mockito.never()).transferClvmVolumeLock(
|
||||
Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(),
|
||||
Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransferClvmLocksForVmStart_EmptyVolumeList() throws Exception {
|
||||
Long destHostId = 2L;
|
||||
VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class);
|
||||
|
||||
ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class);
|
||||
setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager);
|
||||
|
||||
Method method = VolumeOrchestrator.class.getDeclaredMethod(
|
||||
"transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
method.invoke(volumeOrchestrator, new ArrayList<VolumeVO>(), destHostId, vmInstance);
|
||||
|
||||
Mockito.verify(clvmPoolManager, Mockito.never()).getClvmLockHostId(Mockito.anyLong(), Mockito.anyString(),
|
||||
Mockito.anyString(), Mockito.any(), Mockito.anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransferClvmLocksForVmStart_NullPoolId() throws Exception {
|
||||
Long destHostId = 2L;
|
||||
|
||||
VolumeVO volumeWithoutPool = Mockito.mock(VolumeVO.class);
|
||||
Mockito.when(volumeWithoutPool.getPoolId()).thenReturn(null);
|
||||
|
||||
VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class);
|
||||
|
||||
ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class);
|
||||
setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager);
|
||||
setField(volumeOrchestrator, "_storagePoolDao", storagePoolDao);
|
||||
|
||||
Method method = VolumeOrchestrator.class.getDeclaredMethod(
|
||||
"transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
method.invoke(volumeOrchestrator, List.of(volumeWithoutPool), destHostId, vmInstance);
|
||||
|
||||
Mockito.verify(storagePoolDao, Mockito.never()).findById(Mockito.anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransferClvmLocksForVmStart_SetInitialLockHost() throws Exception {
|
||||
Long destHostId = 2L;
|
||||
Long poolId = 10L;
|
||||
|
||||
VolumeVO clvmVolume = Mockito.mock(VolumeVO.class);
|
||||
Mockito.when(clvmVolume.getId()).thenReturn(101L);
|
||||
Mockito.when(clvmVolume.getPoolId()).thenReturn(poolId);
|
||||
|
||||
StoragePoolVO clvmPool = Mockito.mock(StoragePoolVO.class);
|
||||
Mockito.when(clvmPool.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class);
|
||||
|
||||
ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class);
|
||||
Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(101L), ArgumentMatchers.nullable(String.class),
|
||||
ArgumentMatchers.nullable(String.class), Mockito.any(), Mockito.eq(true))).thenReturn(null);
|
||||
|
||||
Mockito.when(storagePoolDao.findById(poolId)).thenReturn(clvmPool);
|
||||
|
||||
setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager);
|
||||
setField(volumeOrchestrator, "_storagePoolDao", storagePoolDao);
|
||||
|
||||
Method method = VolumeOrchestrator.class.getDeclaredMethod(
|
||||
"transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
method.invoke(volumeOrchestrator, List.of(clvmVolume), destHostId, vmInstance);
|
||||
|
||||
Mockito.verify(clvmPoolManager, Mockito.times(1)).setClvmLockHostId(101L, destHostId);
|
||||
Mockito.verify(clvmPoolManager, Mockito.never()).transferClvmVolumeLock(
|
||||
Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(),
|
||||
Mockito.any(), Mockito.anyLong(), Mockito.anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransferClvmLocksForVmStart_MixedVolumes() throws Exception {
|
||||
Long destHostId = 2L;
|
||||
Long currentHostId = 1L;
|
||||
Long clvmPoolId = 10L;
|
||||
Long nfsPoolId = 20L;
|
||||
|
||||
VolumeVO clvmVolume = Mockito.mock(VolumeVO.class);
|
||||
Mockito.when(clvmVolume.getId()).thenReturn(101L);
|
||||
Mockito.when(clvmVolume.getPoolId()).thenReturn(clvmPoolId);
|
||||
Mockito.when(clvmVolume.getUuid()).thenReturn("clvm-vol-uuid");
|
||||
Mockito.when(clvmVolume.getPath()).thenReturn("clvm-vol-path");
|
||||
|
||||
VolumeVO nfsVolume = Mockito.mock(VolumeVO.class);
|
||||
Mockito.when(nfsVolume.getPoolId()).thenReturn(nfsPoolId);
|
||||
|
||||
StoragePoolVO clvmPool = Mockito.mock(StoragePoolVO.class);
|
||||
Mockito.when(clvmPool.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
StoragePoolVO nfsPool = Mockito.mock(StoragePoolVO.class);
|
||||
Mockito.when(nfsPool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
|
||||
|
||||
VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(vmInstance.getInstanceName()).thenReturn(MOCK_VM_NAME);
|
||||
|
||||
ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class);
|
||||
Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(101L), Mockito.anyString(),
|
||||
Mockito.anyString(), Mockito.any(), Mockito.eq(true))).thenReturn(currentHostId);
|
||||
Mockito.when(clvmPoolManager.transferClvmVolumeLock(Mockito.anyString(), Mockito.anyLong(),
|
||||
Mockito.anyString(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong())).thenReturn(true);
|
||||
|
||||
Mockito.when(storagePoolDao.findById(clvmPoolId)).thenReturn(clvmPool);
|
||||
Mockito.when(storagePoolDao.findById(nfsPoolId)).thenReturn(nfsPool);
|
||||
|
||||
setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager);
|
||||
setField(volumeOrchestrator, "_storagePoolDao", storagePoolDao);
|
||||
|
||||
Method method = VolumeOrchestrator.class.getDeclaredMethod(
|
||||
"transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
method.invoke(volumeOrchestrator, List.of(clvmVolume, nfsVolume), destHostId, vmInstance);
|
||||
|
||||
Mockito.verify(clvmPoolManager, Mockito.times(1)).transferClvmVolumeLock(
|
||||
Mockito.eq("clvm-vol-uuid"), Mockito.eq(101L), Mockito.eq("clvm-vol-path"),
|
||||
Mockito.eq(clvmPool), Mockito.eq(currentHostId), Mockito.eq(destHostId));
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
public void testTransferClvmLocksForVmStart_TransferFails() throws Throwable {
|
||||
Long destHostId = 2L;
|
||||
Long currentHostId = 1L;
|
||||
Long poolId = 10L;
|
||||
|
||||
VolumeVO clvmVolume = Mockito.mock(VolumeVO.class);
|
||||
Mockito.when(clvmVolume.getId()).thenReturn(101L);
|
||||
Mockito.when(clvmVolume.getPoolId()).thenReturn(poolId);
|
||||
Mockito.when(clvmVolume.getUuid()).thenReturn("vol-uuid");
|
||||
Mockito.when(clvmVolume.getPath()).thenReturn("vol-path");
|
||||
|
||||
StoragePoolVO clvmPool = Mockito.mock(StoragePoolVO.class);
|
||||
Mockito.when(clvmPool.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
|
||||
VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(vmInstance.getInstanceName()).thenReturn(MOCK_VM_NAME);
|
||||
|
||||
ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class);
|
||||
Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(101L), Mockito.anyString(),
|
||||
Mockito.anyString(), Mockito.any(), Mockito.eq(true))).thenReturn(currentHostId);
|
||||
Mockito.when(clvmPoolManager.transferClvmVolumeLock(Mockito.anyString(), Mockito.anyLong(),
|
||||
Mockito.anyString(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong())).thenReturn(false);
|
||||
|
||||
Mockito.when(storagePoolDao.findById(poolId)).thenReturn(clvmPool);
|
||||
|
||||
setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager);
|
||||
setField(volumeOrchestrator, "_storagePoolDao", storagePoolDao);
|
||||
|
||||
Method method = VolumeOrchestrator.class.getDeclaredMethod(
|
||||
"transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
try {
|
||||
method.invoke(volumeOrchestrator, List.of(clvmVolume), destHostId, vmInstance);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
private void setField(Object target, String fieldName, Object value) throws Exception {
|
||||
Field field = findField(target.getClass(), fieldName);
|
||||
if (field == null) {
|
||||
throw new NoSuchFieldException("Field " + fieldName + " not found in " + target.getClass());
|
||||
}
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
|
||||
private Field findField(Class<?> clazz, String fieldName) {
|
||||
Class<?> current = clazz;
|
||||
while (current != null && current != Object.class) {
|
||||
try {
|
||||
return current.getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import java.util.Date;
|
|||
import java.util.List;
|
||||
|
||||
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.alert.AlertVO;
|
||||
|
|
@ -28,7 +29,7 @@ import com.cloud.utils.db.GenericDaoBase;
|
|||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.utils.db.SearchCriteria.Op;
|
||||
import com.cloud.utils.db.TransactionLegacy;
|
||||
import com.cloud.utils.db.UpdateBuilder;
|
||||
|
||||
@Component
|
||||
public class AlertDaoImpl extends GenericDaoBase<AlertVO, Long> implements AlertDao {
|
||||
|
|
@ -107,25 +108,20 @@ public class AlertDaoImpl extends GenericDaoBase<AlertVO, Long> implements Alert
|
|||
}
|
||||
sc.setParameters("archived", false);
|
||||
|
||||
boolean result = true;
|
||||
;
|
||||
List<AlertVO> alerts = listBy(sc);
|
||||
if (ids != null && alerts.size() < ids.size()) {
|
||||
result = false;
|
||||
return result;
|
||||
return false;
|
||||
}
|
||||
if (alerts != null && !alerts.isEmpty()) {
|
||||
TransactionLegacy txn = TransactionLegacy.currentTxn();
|
||||
txn.start();
|
||||
for (AlertVO alert : alerts) {
|
||||
alert = lockRow(alert.getId(), true);
|
||||
alert.setArchived(true);
|
||||
update(alert.getId(), alert);
|
||||
txn.commit();
|
||||
}
|
||||
txn.close();
|
||||
|
||||
if (CollectionUtils.isEmpty(alerts)) {
|
||||
return true;
|
||||
}
|
||||
return result;
|
||||
|
||||
AlertVO alertForUpdate = createForUpdate();
|
||||
alertForUpdate.setArchived(true);
|
||||
UpdateBuilder ub = getUpdateBuilder(alertForUpdate);
|
||||
update(ub, sc, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ package com.cloud.event.dao;
|
|||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.event.Event.State;
|
||||
|
|
@ -29,12 +31,13 @@ import com.cloud.utils.db.GenericDaoBase;
|
|||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.utils.db.SearchCriteria.Op;
|
||||
import com.cloud.utils.db.TransactionLegacy;
|
||||
import com.cloud.utils.db.UpdateBuilder;
|
||||
|
||||
@Component
|
||||
public class EventDaoImpl extends GenericDaoBase<EventVO, Long> implements EventDao {
|
||||
protected final SearchBuilder<EventVO> CompletedEventSearch;
|
||||
protected final SearchBuilder<EventVO> ToArchiveOrDeleteEventSearch;
|
||||
protected final SearchBuilder<EventVO> ArchiveByIdsSearch;
|
||||
|
||||
public EventDaoImpl() {
|
||||
CompletedEventSearch = createSearchBuilder();
|
||||
|
|
@ -51,6 +54,10 @@ public class EventDaoImpl extends GenericDaoBase<EventVO, Long> implements Event
|
|||
ToArchiveOrDeleteEventSearch.and("createdDateL", ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LTEQ);
|
||||
ToArchiveOrDeleteEventSearch.and("archived", ToArchiveOrDeleteEventSearch.entity().getArchived(), Op.EQ);
|
||||
ToArchiveOrDeleteEventSearch.done();
|
||||
|
||||
ArchiveByIdsSearch = createSearchBuilder();
|
||||
ArchiveByIdsSearch.and("id", ArchiveByIdsSearch.entity().getId(), Op.IN);
|
||||
ArchiveByIdsSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -100,16 +107,16 @@ public class EventDaoImpl extends GenericDaoBase<EventVO, Long> implements Event
|
|||
|
||||
@Override
|
||||
public void archiveEvents(List<EventVO> events) {
|
||||
if (events != null && !events.isEmpty()) {
|
||||
TransactionLegacy txn = TransactionLegacy.currentTxn();
|
||||
txn.start();
|
||||
for (EventVO event : events) {
|
||||
event = lockRow(event.getId(), true);
|
||||
event.setArchived(true);
|
||||
update(event.getId(), event);
|
||||
txn.commit();
|
||||
}
|
||||
txn.close();
|
||||
if (CollectionUtils.isEmpty(events)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Long> ids = events.stream().map(EventVO::getId).collect(Collectors.toList());
|
||||
SearchCriteria<EventVO> sc = ArchiveByIdsSearch.create();
|
||||
sc.setParameters("id", ids.toArray(new Object[ids.size()]));
|
||||
EventVO eventForUpdate = createForUpdate();
|
||||
eventForUpdate.setArchived(true);
|
||||
UpdateBuilder ub = getUpdateBuilder(eventForUpdate);
|
||||
update(ub, sc, null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ import javax.persistence.Transient;
|
|||
import com.cloud.cpu.CPU;
|
||||
import org.apache.cloudstack.util.CPUArchConverter;
|
||||
import org.apache.cloudstack.util.HypervisorTypeConverter;
|
||||
import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper;
|
||||
import org.apache.cloudstack.utils.jsinterpreter.GenericRuleHelper;
|
||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
|
|
@ -856,7 +856,8 @@ public class HostVO implements Host {
|
|||
}
|
||||
|
||||
if (BooleanUtils.isTrue(this.getIsTagARule())) {
|
||||
return TagAsRuleHelper.interpretTagAsRule(this.getHostTags().get(0), serviceOffering.getHostTag(), HostTagsDao.hostTagRuleExecutionTimeout.value());
|
||||
return GenericRuleHelper.interpretTagAsRule(this.getHostTags().get(0), serviceOffering.getHostTag(), HostTagsDao.hostTagRuleExecutionTimeout.value(),
|
||||
HostTagsDao.hostTagRuleExecutionTimeout.key());
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(serviceOffering.getHostTag())) {
|
||||
|
|
|
|||
|
|
@ -32,12 +32,17 @@ import com.cloud.utils.Pair;
|
|||
import com.cloud.utils.db.GenericDao;
|
||||
import com.cloud.utils.fsm.StateDao;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
|
||||
/**
|
||||
* Data Access Object for server
|
||||
*
|
||||
*/
|
||||
public interface HostDao extends GenericDao<HostVO, Long>, StateDao<Status, Status.Event, Host> {
|
||||
|
||||
ConfigKey<Long> guestOsRuleExecutionTimeout = new ConfigKey<>("Advanced", Long.class, "guest.os.rule.execution.timeout", "3000", "The maximum runtime, in milliseconds, " +
|
||||
"to execute a guest OS rule; if it is reached, a timeout will happen.", true);
|
||||
|
||||
long countBy(long clusterId, ResourceState... states);
|
||||
|
||||
Integer countAllByType(final Host.Type type);
|
||||
|
|
@ -220,6 +225,8 @@ public interface HostDao extends GenericDao<HostVO, Long>, StateDao<Status, Stat
|
|||
|
||||
List<HostVO> findHostsWithTagRuleThatMatchComputeOfferingTags(String computeOfferingTags);
|
||||
|
||||
List<HostVO> findHostsWithGuestOsRulesThatDidNotMatchOsOfGuestVm(String templateGuestOSName);
|
||||
|
||||
List<Long> findClustersThatMatchHostTagRule(String computeOfferingTags);
|
||||
|
||||
List<Long> listSsvmHostsWithPendingMigrateJobsOrderedByJobCount();
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ import javax.persistence.TableGenerator;
|
|||
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
|
||||
import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.utils.jsinterpreter.GenericRuleHelper;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
|
||||
import com.cloud.agent.api.VgpuTypesInfo;
|
||||
|
|
@ -84,7 +86,7 @@ import org.apache.commons.lang3.ObjectUtils;
|
|||
|
||||
@DB
|
||||
@TableGenerator(name = "host_req_sq", table = "op_host", pkColumnName = "id", valueColumnName = "sequence", allocationSize = 1)
|
||||
public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao { //FIXME: , ExternalIdDao {
|
||||
public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao, Configurable {
|
||||
|
||||
private static final String LIST_HOST_IDS_BY_HOST_TAGS = "SELECT filtered.host_id, COUNT(filtered.tag) AS tag_count "
|
||||
+ "FROM (SELECT host_id, tag, is_tag_a_rule FROM host_tags GROUP BY host_id,tag,is_tag_a_rule) AS filtered "
|
||||
|
|
@ -644,16 +646,22 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
|
|||
sc.setParameters("lastPinged", lastPingSecondsAfter);
|
||||
sc.setParameters("status", Status.Disconnected, Status.Down, Status.Alert);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
List<HostVO> hosts = lockRows(sc, null, true); // exclusive lock
|
||||
for (HostVO host : hosts) {
|
||||
host.setManagementServerId(null);
|
||||
update(host.getId(), host);
|
||||
sb.append(host.getId());
|
||||
sb.append(" ");
|
||||
// SELECT before bulk UPDATE to preserve per-host-ID trace logging — the bulk UPDATE
|
||||
// cannot return which rows it matched since the WHERE column is being set to NULL
|
||||
if (logger.isTraceEnabled()) {
|
||||
List<HostVO> hosts = listBy(sc);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (HostVO host : hosts) {
|
||||
sb.append(host.getId());
|
||||
sb.append(" ");
|
||||
}
|
||||
logger.trace("Following hosts will be reset: {}", sb);
|
||||
}
|
||||
|
||||
logger.trace("Following hosts got reset: {}", sb);
|
||||
HostVO host = createForUpdate();
|
||||
host.setManagementServerId(null);
|
||||
UpdateBuilder ub = getUpdateBuilder(host);
|
||||
update(ub, sc, null);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1520,11 +1528,13 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HostVO> findHostsWithTagRuleThatMatchComputeOfferingTags(String computeOfferingTags) {
|
||||
List<HostTagVO> hostTagVOList = _hostTagsDao.findHostRuleTags();
|
||||
List<HostVO> result = new ArrayList<>();
|
||||
for (HostTagVO rule: hostTagVOList) {
|
||||
if (TagAsRuleHelper.interpretTagAsRule(rule.getTag(), computeOfferingTags, HostTagsDao.hostTagRuleExecutionTimeout.value())) {
|
||||
if (GenericRuleHelper.interpretTagAsRule(rule.getTag(), computeOfferingTags, HostTagsDao.hostTagRuleExecutionTimeout.value(),
|
||||
HostTagsDao.hostTagRuleExecutionTimeout.key())) {
|
||||
result.add(findById(rule.getHostId()));
|
||||
}
|
||||
}
|
||||
|
|
@ -1532,6 +1542,23 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
|
|||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HostVO> findHostsWithGuestOsRulesThatDidNotMatchOsOfGuestVm(String templateGuestOSName) {
|
||||
List<DetailVO> hostIdsWithGuestOsRule = _detailsDao.findByName(Host.GUEST_OS_RULE);
|
||||
List<HostVO> hostsWithIncompatibleRules = new ArrayList<>();
|
||||
for (DetailVO guestOsRule : hostIdsWithGuestOsRule) {
|
||||
if (!GenericRuleHelper.interpretGuestOsRule(guestOsRule.getValue(), templateGuestOSName, HostDao.guestOsRuleExecutionTimeout.value(),
|
||||
HostDao.guestOsRuleExecutionTimeout.key())) {
|
||||
logger.trace("The guest OS rule [{}] of the host with ID [{}] is incompatible with the OS of the VM.",
|
||||
guestOsRule.getHostId(), guestOsRule.getValue());
|
||||
hostsWithIncompatibleRules.add(findById(guestOsRule.getHostId()));
|
||||
}
|
||||
}
|
||||
logger.trace("The hosts with the following IDs [{}] are incompatible with the VM considering their guest OS rule.",
|
||||
hostsWithIncompatibleRules);
|
||||
return hostsWithIncompatibleRules;
|
||||
}
|
||||
|
||||
public List<Long> findClustersThatMatchHostTagRule(String computeOfferingTags) {
|
||||
Set<Long> result = new HashSet<>();
|
||||
List<HostVO> hosts = findHostsWithTagRuleThatMatchComputeOfferingTags(computeOfferingTags);
|
||||
|
|
@ -1984,4 +2011,14 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
|
|||
|
||||
return customSearch(sc, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[] {guestOsRuleExecutionTimeout};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfigComponentName() {
|
||||
return HostDaoImpl.class.getSimpleName();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ public class SecurityGroupWorkDaoImpl extends GenericDaoBase<SecurityGroupWorkVO
|
|||
//ensure that there is no job in Processing state for the same VM
|
||||
processing = true;
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Security Group work take: found a job in Scheduled and Processing vmid=" + work.getInstanceId());
|
||||
logger.trace("Security Group work take: found a job in Scheduled and Processing vmid={}", work.getInstanceId());
|
||||
}
|
||||
}
|
||||
work.setServerId(serverId);
|
||||
|
|
@ -141,26 +141,16 @@ public class SecurityGroupWorkDaoImpl extends GenericDaoBase<SecurityGroupWorkVO
|
|||
}
|
||||
|
||||
@Override
|
||||
@DB
|
||||
public void updateStep(Long vmId, Long logSequenceNumber, Step step) {
|
||||
final TransactionLegacy txn = TransactionLegacy.currentTxn();
|
||||
txn.start();
|
||||
SearchCriteria<SecurityGroupWorkVO> sc = VmIdSeqNumSearch.create();
|
||||
sc.setParameters("vmId", vmId);
|
||||
sc.setParameters("seqno", logSequenceNumber);
|
||||
|
||||
final Filter filter = new Filter(SecurityGroupWorkVO.class, null, true, 0l, 1l);
|
||||
|
||||
final List<SecurityGroupWorkVO> vos = lockRows(sc, filter, true);
|
||||
if (vos.size() == 0) {
|
||||
txn.commit();
|
||||
return;
|
||||
}
|
||||
SecurityGroupWorkVO work = vos.get(0);
|
||||
work.setStep(step);
|
||||
update(work.getId(), work);
|
||||
|
||||
txn.commit();
|
||||
SecurityGroupWorkVO workForUpdate = createForUpdate();
|
||||
workForUpdate.setStep(step);
|
||||
// LIMIT 1 preserves the original single-row semantics: op_nwgrp_work has no
|
||||
// uniqueness on (instance_id, seq_no), so without it duplicate rows would all be updated.
|
||||
update(workForUpdate, sc, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -172,21 +162,10 @@ public class SecurityGroupWorkDaoImpl extends GenericDaoBase<SecurityGroupWorkVO
|
|||
}
|
||||
|
||||
@Override
|
||||
@DB
|
||||
public void updateStep(Long workId, Step step) {
|
||||
final TransactionLegacy txn = TransactionLegacy.currentTxn();
|
||||
txn.start();
|
||||
|
||||
SecurityGroupWorkVO work = lockRow(workId, true);
|
||||
if (work == null) {
|
||||
txn.commit();
|
||||
return;
|
||||
}
|
||||
work.setStep(step);
|
||||
update(work.getId(), work);
|
||||
|
||||
txn.commit();
|
||||
|
||||
SecurityGroupWorkVO workForUpdate = createForUpdate();
|
||||
workForUpdate.setStep(step);
|
||||
update(workId, workForUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
// 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 com.cloud.vm;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
|
||||
@Entity
|
||||
@Table(name = "vm_iso_map")
|
||||
public class VmIsoMapVO implements InternalIdentity {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
|
||||
@Column(name = "vm_id")
|
||||
private long vmId;
|
||||
|
||||
@Column(name = "iso_id")
|
||||
private long isoId;
|
||||
|
||||
@Column(name = "device_seq")
|
||||
private int deviceSeq;
|
||||
|
||||
@Column(name = "created")
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date created;
|
||||
|
||||
public VmIsoMapVO() {
|
||||
}
|
||||
|
||||
public VmIsoMapVO(long vmId, long isoId, int deviceSeq) {
|
||||
this.vmId = vmId;
|
||||
this.isoId = isoId;
|
||||
this.deviceSeq = deviceSeq;
|
||||
this.created = new Date();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public long getVmId() {
|
||||
return vmId;
|
||||
}
|
||||
|
||||
public long getIsoId() {
|
||||
return isoId;
|
||||
}
|
||||
|
||||
public int getDeviceSeq() {
|
||||
return deviceSeq;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ public class NicSecondaryIpVO implements NicSecondaryIp {
|
|||
this.networkId = networkId;
|
||||
}
|
||||
|
||||
public NicSecondaryIpVO(long nicId, String ip4Address, String ip6Address, long vmId, long accountId, long domainId, long networkId) {
|
||||
public NicSecondaryIpVO(long nicId, String ip4Address, String ip6Address, long vmId, long accountId, long domainId, long networkId, String description) {
|
||||
this.nicId = nicId;
|
||||
this.vmId = vmId;
|
||||
this.ip4Address = ip4Address;
|
||||
|
|
@ -51,6 +51,7 @@ public class NicSecondaryIpVO implements NicSecondaryIp {
|
|||
this.accountId = accountId;
|
||||
this.domainId = domainId;
|
||||
this.networkId = networkId;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
protected NicSecondaryIpVO() {
|
||||
|
|
@ -88,6 +89,18 @@ public class NicSecondaryIpVO implements NicSecondaryIp {
|
|||
@Column(name = "vmId")
|
||||
long vmId;
|
||||
|
||||
@Column(name = "description")
|
||||
String description;
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("NicSecondaryIp %s",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
// 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 com.cloud.vm.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import com.cloud.vm.VmIsoMapVO;
|
||||
|
||||
public interface VmIsoMapDao extends GenericDao<VmIsoMapVO, Long> {
|
||||
List<VmIsoMapVO> listByVmId(long vmId);
|
||||
|
||||
List<VmIsoMapVO> listByIsoId(long isoId);
|
||||
|
||||
VmIsoMapVO findByVmIdDeviceSeq(long vmId, int deviceSeq);
|
||||
|
||||
VmIsoMapVO findByVmIdIsoId(long vmId, long isoId);
|
||||
|
||||
int removeByVmId(long vmId);
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
// 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 com.cloud.vm.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.vm.VmIsoMapVO;
|
||||
|
||||
@Component
|
||||
public class VmIsoMapDaoImpl extends GenericDaoBase<VmIsoMapVO, Long> implements VmIsoMapDao {
|
||||
|
||||
private SearchBuilder<VmIsoMapVO> ListByVmId;
|
||||
private SearchBuilder<VmIsoMapVO> ListByIsoId;
|
||||
private SearchBuilder<VmIsoMapVO> ByVmIdDeviceSeq;
|
||||
private SearchBuilder<VmIsoMapVO> ByVmIdIsoId;
|
||||
|
||||
protected VmIsoMapDaoImpl() {
|
||||
ListByVmId = createSearchBuilder();
|
||||
ListByVmId.and("vmId", ListByVmId.entity().getVmId(), SearchCriteria.Op.EQ);
|
||||
ListByVmId.done();
|
||||
|
||||
ListByIsoId = createSearchBuilder();
|
||||
ListByIsoId.and("isoId", ListByIsoId.entity().getIsoId(), SearchCriteria.Op.EQ);
|
||||
ListByIsoId.done();
|
||||
|
||||
ByVmIdDeviceSeq = createSearchBuilder();
|
||||
ByVmIdDeviceSeq.and("vmId", ByVmIdDeviceSeq.entity().getVmId(), SearchCriteria.Op.EQ);
|
||||
ByVmIdDeviceSeq.and("deviceSeq", ByVmIdDeviceSeq.entity().getDeviceSeq(), SearchCriteria.Op.EQ);
|
||||
ByVmIdDeviceSeq.done();
|
||||
|
||||
ByVmIdIsoId = createSearchBuilder();
|
||||
ByVmIdIsoId.and("vmId", ByVmIdIsoId.entity().getVmId(), SearchCriteria.Op.EQ);
|
||||
ByVmIdIsoId.and("isoId", ByVmIdIsoId.entity().getIsoId(), SearchCriteria.Op.EQ);
|
||||
ByVmIdIsoId.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VmIsoMapVO> listByVmId(long vmId) {
|
||||
SearchCriteria<VmIsoMapVO> sc = ListByVmId.create();
|
||||
sc.setParameters("vmId", vmId);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VmIsoMapVO> listByIsoId(long isoId) {
|
||||
SearchCriteria<VmIsoMapVO> sc = ListByIsoId.create();
|
||||
sc.setParameters("isoId", isoId);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmIsoMapVO findByVmIdDeviceSeq(long vmId, int deviceSeq) {
|
||||
SearchCriteria<VmIsoMapVO> sc = ByVmIdDeviceSeq.create();
|
||||
sc.setParameters("vmId", vmId);
|
||||
sc.setParameters("deviceSeq", deviceSeq);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmIsoMapVO findByVmIdIsoId(long vmId, long isoId) {
|
||||
SearchCriteria<VmIsoMapVO> sc = ByVmIdIsoId.create();
|
||||
sc.setParameters("vmId", vmId);
|
||||
sc.setParameters("isoId", isoId);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int removeByVmId(long vmId) {
|
||||
SearchCriteria<VmIsoMapVO> sc = ListByVmId.create();
|
||||
sc.setParameters("vmId", vmId);
|
||||
return remove(sc);
|
||||
}
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ public class ApiKeyPairVO implements ApiKeyPair {
|
|||
@Temporal(value = TemporalType.TIMESTAMP)
|
||||
private Date created = Date.from(Instant.now());
|
||||
|
||||
@Column(name = "description")
|
||||
@Column(name = "description", length = 1024)
|
||||
private String description = "";
|
||||
|
||||
@Column(name = "api_key", nullable = false)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
// 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.schedule;
|
||||
|
||||
import org.apache.cloudstack.api.ResourceDetail;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "resource_schedule_details")
|
||||
public class ResourceScheduleDetailVO implements ResourceDetail {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private long id;
|
||||
|
||||
@Column(name = "schedule_id")
|
||||
private long resourceId;
|
||||
|
||||
@Column(name = "name")
|
||||
private String name;
|
||||
|
||||
@Column(name = "value", length = 1024)
|
||||
private String value;
|
||||
|
||||
@Column(name = "display")
|
||||
private boolean display = true;
|
||||
|
||||
public ResourceScheduleDetailVO() {
|
||||
}
|
||||
|
||||
public ResourceScheduleDetailVO(long scheduleId, String name, String value, boolean display) {
|
||||
this.resourceId = scheduleId;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.display = display;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplay() {
|
||||
return display;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,9 +16,10 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.cloudstack.vm.schedule;
|
||||
package org.apache.cloudstack.schedule;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||
|
||||
import javax.persistence.Column;
|
||||
|
|
@ -37,8 +38,8 @@ import java.util.TimeZone;
|
|||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "vm_schedule")
|
||||
public class VMScheduleVO implements VMSchedule {
|
||||
@Table(name = "resource_schedule")
|
||||
public class ResourceScheduleVO implements ResourceSchedule {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id", nullable = false)
|
||||
|
|
@ -50,8 +51,12 @@ public class VMScheduleVO implements VMSchedule {
|
|||
@Column(name = "description")
|
||||
String description;
|
||||
|
||||
@Column(name = "vm_id", nullable = false)
|
||||
long vmId;
|
||||
@Enumerated(value = EnumType.STRING)
|
||||
@Column(name = "resource_type", nullable = false)
|
||||
ApiCommandResourceType resourceType;
|
||||
|
||||
@Column(name = "resource_id", nullable = false)
|
||||
long resourceId;
|
||||
|
||||
@Column(name = "schedule", nullable = false)
|
||||
String schedule;
|
||||
|
|
@ -60,8 +65,7 @@ public class VMScheduleVO implements VMSchedule {
|
|||
String timeZone;
|
||||
|
||||
@Column(name = "action", nullable = false)
|
||||
@Enumerated(value = EnumType.STRING)
|
||||
Action action;
|
||||
String actionName;
|
||||
|
||||
@Column(name = "enabled", nullable = false)
|
||||
boolean enabled;
|
||||
|
|
@ -80,17 +84,19 @@ public class VMScheduleVO implements VMSchedule {
|
|||
@Column(name = GenericDao.REMOVED_COLUMN)
|
||||
Date removed;
|
||||
|
||||
public VMScheduleVO() {
|
||||
public ResourceScheduleVO() {
|
||||
uuid = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
public VMScheduleVO(long vmId, String description, String schedule, String timeZone, Action action, Date startDate, Date endDate, boolean enabled) {
|
||||
public ResourceScheduleVO(ApiCommandResourceType resourceType, long resourceId, String description, String schedule,
|
||||
String timeZone, String action, Date startDate, Date endDate, boolean enabled) {
|
||||
uuid = UUID.randomUUID().toString();
|
||||
this.vmId = vmId;
|
||||
this.resourceType = resourceType;
|
||||
this.resourceId = resourceId;
|
||||
this.description = description;
|
||||
this.schedule = schedule;
|
||||
this.timeZone = timeZone;
|
||||
this.action = action;
|
||||
this.actionName = action;
|
||||
this.startDate = startDate;
|
||||
this.endDate = endDate;
|
||||
this.enabled = enabled;
|
||||
|
|
@ -98,7 +104,8 @@ public class VMScheduleVO implements VMSchedule {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("VMSchedule %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "uuid", "action", "description"));
|
||||
return String.format("ResourceSchedule %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
|
||||
this, "id", "uuid", "resourceType", "actionName", "description", "enabled"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -111,14 +118,25 @@ public class VMScheduleVO implements VMSchedule {
|
|||
return id;
|
||||
}
|
||||
|
||||
public long getVmId() {
|
||||
return vmId;
|
||||
@Override
|
||||
public ApiCommandResourceType getResourceType() {
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
public void setVmId(long vmId) {
|
||||
this.vmId = vmId;
|
||||
public void setResourceType(ApiCommandResourceType resourceType) {
|
||||
this.resourceType = resourceType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public void setResourceId(long resourceId) {
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
|
@ -127,6 +145,7 @@ public class VMScheduleVO implements VMSchedule {
|
|||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSchedule() {
|
||||
return schedule.substring(2);
|
||||
}
|
||||
|
|
@ -144,14 +163,16 @@ public class VMScheduleVO implements VMSchedule {
|
|||
this.timeZone = timeZone;
|
||||
}
|
||||
|
||||
public Action getAction() {
|
||||
return action;
|
||||
@Override
|
||||
public String getActionName() {
|
||||
return actionName;
|
||||
}
|
||||
|
||||
public void setAction(Action action) {
|
||||
this.action = action;
|
||||
public void setActionName(String action) {
|
||||
this.actionName = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
|
@ -183,6 +204,7 @@ public class VMScheduleVO implements VMSchedule {
|
|||
return TimeZone.getTimeZone(getTimeZone()).toZoneId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
|
@ -16,8 +16,9 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.cloudstack.vm.schedule;
|
||||
package org.apache.cloudstack.schedule;
|
||||
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
|
||||
|
||||
import javax.persistence.Column;
|
||||
|
|
@ -34,8 +35,8 @@ import java.util.Date;
|
|||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "vm_scheduled_job")
|
||||
public class VMScheduledJobVO implements VMScheduledJob {
|
||||
@Table(name = "resource_scheduled_job")
|
||||
public class ResourceScheduledJobVO implements ResourceScheduledJob {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
|
|
@ -44,41 +45,44 @@ public class VMScheduledJobVO implements VMScheduledJob {
|
|||
@Column(name = "uuid", nullable = false)
|
||||
String uuid;
|
||||
|
||||
@Column(name = "vm_id", nullable = false)
|
||||
long vmId;
|
||||
@Enumerated(value = EnumType.STRING)
|
||||
@Column(name = "resource_type", nullable = false)
|
||||
ApiCommandResourceType resourceType;
|
||||
|
||||
@Column(name = "vm_schedule_id", nullable = false)
|
||||
long vmScheduleId;
|
||||
@Column(name = "resource_id", nullable = false)
|
||||
long resourceId;
|
||||
|
||||
@Column(name = "schedule_id", nullable = false)
|
||||
long scheduleId;
|
||||
|
||||
@Column(name = "async_job_id")
|
||||
Long asyncJobId;
|
||||
|
||||
@Column(name = "action", nullable = false)
|
||||
@Enumerated(value = EnumType.STRING)
|
||||
VMSchedule.Action action;
|
||||
String actionName;
|
||||
|
||||
@Column(name = "scheduled_timestamp")
|
||||
@Temporal(value = TemporalType.TIMESTAMP)
|
||||
Date scheduledTime;
|
||||
|
||||
public VMScheduledJobVO() {
|
||||
public ResourceScheduledJobVO() {
|
||||
uuid = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
public VMScheduledJobVO(long vmId, long vmScheduleId, VMSchedule.Action action, Date scheduledTime) {
|
||||
public ResourceScheduledJobVO(ApiCommandResourceType resourceType, long resourceId, long scheduleId, String action, Date scheduledTime) {
|
||||
uuid = UUID.randomUUID().toString();
|
||||
this.vmId = vmId;
|
||||
this.vmScheduleId = vmScheduleId;
|
||||
this.action = action;
|
||||
this.resourceType = resourceType;
|
||||
this.resourceId = resourceId;
|
||||
this.scheduleId = scheduleId;
|
||||
this.actionName = action;
|
||||
this.scheduledTime = scheduledTime;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("VMScheduledJob %s",
|
||||
return String.format("ResourceScheduledJob %s",
|
||||
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
|
||||
this, "id", "uuid", "action", "vmScheduleId", "vmId", "asyncJobId"));
|
||||
this, "id", "uuid", "resourceType", "actionName", "scheduleId", "resourceId", "asyncJobId"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -92,13 +96,18 @@ public class VMScheduledJobVO implements VMScheduledJob {
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getVmId() {
|
||||
return vmId;
|
||||
public ApiCommandResourceType getResourceType() {
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getVmScheduleId() {
|
||||
return vmScheduleId;
|
||||
public long getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getScheduleId() {
|
||||
return scheduleId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -112,8 +121,12 @@ public class VMScheduledJobVO implements VMScheduledJob {
|
|||
}
|
||||
|
||||
@Override
|
||||
public VMSchedule.Action getAction() {
|
||||
return action;
|
||||
public String getActionName() {
|
||||
return actionName;
|
||||
}
|
||||
|
||||
public void setActionName(String action) {
|
||||
this.actionName = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.schedule.dao;
|
||||
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ResourceScheduleDao extends GenericDao<ResourceScheduleVO, Long> {
|
||||
List<ResourceScheduleVO> listAllActiveSchedules(ApiCommandResourceType resourceType);
|
||||
|
||||
long removeSchedulesForResourceAndIds(ApiCommandResourceType resourceType, long resourceId, List<Long> ids);
|
||||
|
||||
long removeAllSchedulesForResource(ApiCommandResourceType resourceType, long resourceId);
|
||||
|
||||
Pair<List<ResourceScheduleVO>, Integer> searchAndCount(List<Long> ids, ApiCommandResourceType resourceType, Long resourceId,
|
||||
String action, Boolean enabled, Long offset, Long limit);
|
||||
|
||||
SearchCriteria<ResourceScheduleVO> getSearchCriteriaForResource(ApiCommandResourceType resourceType, long resourceId);
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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.schedule.dao;
|
||||
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.db.Filter;
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleVO;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class ResourceScheduleDaoImpl extends GenericDaoBase<ResourceScheduleVO, Long> implements ResourceScheduleDao {
|
||||
|
||||
private final SearchBuilder<ResourceScheduleVO> activeScheduleSearch;
|
||||
private final SearchBuilder<ResourceScheduleVO> allSearch;
|
||||
|
||||
static final String RESOURCE_TYPE = "resourceType";
|
||||
static final String RESOURCE_ID = "resourceId";
|
||||
|
||||
public ResourceScheduleDaoImpl() {
|
||||
super();
|
||||
|
||||
activeScheduleSearch = createSearchBuilder();
|
||||
activeScheduleSearch.and(RESOURCE_TYPE, activeScheduleSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
|
||||
activeScheduleSearch.and(ApiConstants.ENABLED, activeScheduleSearch.entity().getEnabled(), SearchCriteria.Op.EQ);
|
||||
activeScheduleSearch.done();
|
||||
|
||||
allSearch = createSearchBuilder();
|
||||
allSearch.and(ApiConstants.ID, allSearch.entity().getId(), SearchCriteria.Op.IN);
|
||||
allSearch.and(RESOURCE_TYPE, allSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
|
||||
allSearch.and(RESOURCE_ID, allSearch.entity().getResourceId(), SearchCriteria.Op.EQ);
|
||||
allSearch.and(ApiConstants.ACTION, allSearch.entity().getActionName(), SearchCriteria.Op.EQ);
|
||||
allSearch.and(ApiConstants.ENABLED, allSearch.entity().getEnabled(), SearchCriteria.Op.EQ);
|
||||
allSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ResourceScheduleVO> listAllActiveSchedules(ApiCommandResourceType resourceType) {
|
||||
SearchCriteria<ResourceScheduleVO> sc = activeScheduleSearch.create();
|
||||
sc.setParameters(RESOURCE_TYPE, resourceType);
|
||||
sc.setParameters(ApiConstants.ENABLED, true);
|
||||
return search(sc, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long removeSchedulesForResourceAndIds(ApiCommandResourceType resourceType, long resourceId, List<Long> ids) {
|
||||
SearchCriteria<ResourceScheduleVO> sc = allSearch.create();
|
||||
if (CollectionUtils.isNotEmpty(ids)) {
|
||||
sc.setParameters(ApiConstants.ID, ids.toArray());
|
||||
}
|
||||
sc.setParameters(RESOURCE_TYPE, resourceType);
|
||||
sc.setParameters(RESOURCE_ID, resourceId);
|
||||
return remove(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long removeAllSchedulesForResource(ApiCommandResourceType resourceType, long resourceId) {
|
||||
SearchCriteria<ResourceScheduleVO> sc = allSearch.create();
|
||||
sc.setParameters(RESOURCE_TYPE, resourceType);
|
||||
sc.setParameters(RESOURCE_ID, resourceId);
|
||||
return remove(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<List<ResourceScheduleVO>, Integer> searchAndCount(List<Long> ids, ApiCommandResourceType resourceType, Long resourceId,
|
||||
String action, Boolean enabled, Long offset, Long limit) {
|
||||
SearchCriteria<ResourceScheduleVO> sc = allSearch.create();
|
||||
if (CollectionUtils.isNotEmpty(ids)) {
|
||||
sc.setParameters(ApiConstants.ID, ids.toArray());
|
||||
}
|
||||
sc.setParametersIfNotNull(ApiConstants.ENABLED, enabled);
|
||||
sc.setParametersIfNotNull(ApiConstants.ACTION, action);
|
||||
sc.setParametersIfNotNull(RESOURCE_TYPE, resourceType);
|
||||
sc.setParametersIfNotNull(RESOURCE_ID, resourceId);
|
||||
Filter filter = new Filter(ResourceScheduleVO.class, ApiConstants.ID, false, offset, limit);
|
||||
return searchAndCount(sc, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchCriteria<ResourceScheduleVO> getSearchCriteriaForResource(ApiCommandResourceType resourceType, long resourceId) {
|
||||
SearchCriteria<ResourceScheduleVO> sc = allSearch.create();
|
||||
sc.setParameters(RESOURCE_TYPE, resourceType);
|
||||
sc.setParameters(RESOURCE_ID, resourceId);
|
||||
return sc;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// 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.schedule.dao;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import org.apache.cloudstack.resourcedetail.ResourceDetailsDao;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleDetailVO;
|
||||
|
||||
public interface ResourceScheduleDetailsDao extends GenericDao<ResourceScheduleDetailVO, Long>, ResourceDetailsDao<ResourceScheduleDetailVO> {
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// 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.schedule.dao;
|
||||
|
||||
import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduleDetailVO;
|
||||
|
||||
public class ResourceScheduleDetailsDaoImpl extends ResourceDetailsDaoBase<ResourceScheduleDetailVO> implements ResourceScheduleDetailsDao {
|
||||
|
||||
@Override
|
||||
public void addDetail(long resourceId, String key, String value, boolean display) {
|
||||
super.addDetail(new ResourceScheduleDetailVO(resourceId, key, value, display));
|
||||
}
|
||||
}
|
||||
|
|
@ -16,22 +16,21 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.cloudstack.vm.schedule.dao;
|
||||
package org.apache.cloudstack.schedule.dao;
|
||||
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||
import org.apache.cloudstack.vm.schedule.VMScheduleVO;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduledJobVO;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public interface VMScheduleDao extends GenericDao<VMScheduleVO, Long> {
|
||||
List<VMScheduleVO> listAllActiveSchedules();
|
||||
public interface ResourceScheduledJobDao extends GenericDao<ResourceScheduledJobVO, Long> {
|
||||
List<ResourceScheduledJobVO> listJobsToStart(ApiCommandResourceType resourceType, Date currentTimestamp);
|
||||
|
||||
long removeSchedulesForVmIdAndIds(Long vmId, List<Long> ids);
|
||||
int expungeJobsForSchedules(List<Long> scheduleIds, Date dateAfter);
|
||||
|
||||
Pair<List<VMScheduleVO>, Integer> searchAndCount(Long id, Long vmId, VMSchedule.Action action, Boolean enabled, Long offset, Long limit);
|
||||
int expungeJobsBefore(ApiCommandResourceType resourceType, Date currentTimestamp);
|
||||
|
||||
SearchCriteria<VMScheduleVO> getSearchCriteriaForVMId(Long vmId);
|
||||
ResourceScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp);
|
||||
}
|
||||
|
|
@ -16,13 +16,14 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.cloudstack.vm.schedule.dao;
|
||||
package org.apache.cloudstack.schedule.dao;
|
||||
|
||||
import com.cloud.utils.db.Filter;
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import org.apache.cloudstack.vm.schedule.VMScheduledJobVO;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.schedule.ResourceScheduledJobVO;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
|
@ -31,62 +32,59 @@ import java.util.Date;
|
|||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class VMScheduledJobDaoImpl extends GenericDaoBase<VMScheduledJobVO, Long> implements VMScheduledJobDao {
|
||||
public class ResourceScheduledJobDaoImpl extends GenericDaoBase<ResourceScheduledJobVO, Long> implements ResourceScheduledJobDao {
|
||||
|
||||
private final SearchBuilder<VMScheduledJobVO> jobsToStartSearch;
|
||||
private final SearchBuilder<ResourceScheduledJobVO> jobsToStartSearch;
|
||||
private final SearchBuilder<ResourceScheduledJobVO> expungeJobsBeforeSearch;
|
||||
private final SearchBuilder<ResourceScheduledJobVO> expungeJobForScheduleSearch;
|
||||
private final SearchBuilder<ResourceScheduledJobVO> scheduleAndTimestampSearch;
|
||||
|
||||
private final SearchBuilder<VMScheduledJobVO> expungeJobsBeforeSearch;
|
||||
static final String SCHEDULED_TIMESTAMP = "scheduledTimestamp";
|
||||
static final String SCHEDULE_ID = "scheduleId";
|
||||
static final String RESOURCE_TYPE = "resourceType";
|
||||
|
||||
private final SearchBuilder<VMScheduledJobVO> expungeJobForScheduleSearch;
|
||||
|
||||
private final SearchBuilder<VMScheduledJobVO> scheduleAndTimestampSearch;
|
||||
|
||||
static final String SCHEDULED_TIMESTAMP = "scheduled_timestamp";
|
||||
|
||||
static final String VM_SCHEDULE_ID = "vm_schedule_id";
|
||||
|
||||
public VMScheduledJobDaoImpl() {
|
||||
public ResourceScheduledJobDaoImpl() {
|
||||
super();
|
||||
|
||||
jobsToStartSearch = createSearchBuilder();
|
||||
jobsToStartSearch.and(RESOURCE_TYPE, jobsToStartSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
|
||||
jobsToStartSearch.and(SCHEDULED_TIMESTAMP, jobsToStartSearch.entity().getScheduledTime(), SearchCriteria.Op.EQ);
|
||||
jobsToStartSearch.and("async_job_id", jobsToStartSearch.entity().getAsyncJobId(), SearchCriteria.Op.NULL);
|
||||
jobsToStartSearch.done();
|
||||
|
||||
expungeJobsBeforeSearch = createSearchBuilder();
|
||||
expungeJobsBeforeSearch.and(RESOURCE_TYPE, expungeJobsBeforeSearch.entity().getResourceType(), SearchCriteria.Op.EQ);
|
||||
expungeJobsBeforeSearch.and(SCHEDULED_TIMESTAMP, expungeJobsBeforeSearch.entity().getScheduledTime(), SearchCriteria.Op.LT);
|
||||
expungeJobsBeforeSearch.done();
|
||||
|
||||
expungeJobForScheduleSearch = createSearchBuilder();
|
||||
expungeJobForScheduleSearch.and(VM_SCHEDULE_ID, expungeJobForScheduleSearch.entity().getVmScheduleId(), SearchCriteria.Op.IN);
|
||||
expungeJobForScheduleSearch.and(SCHEDULE_ID, expungeJobForScheduleSearch.entity().getScheduleId(), SearchCriteria.Op.IN);
|
||||
expungeJobForScheduleSearch.and(SCHEDULED_TIMESTAMP, expungeJobForScheduleSearch.entity().getScheduledTime(), SearchCriteria.Op.GTEQ);
|
||||
expungeJobForScheduleSearch.done();
|
||||
|
||||
scheduleAndTimestampSearch = createSearchBuilder();
|
||||
scheduleAndTimestampSearch.and(VM_SCHEDULE_ID, scheduleAndTimestampSearch.entity().getVmScheduleId(), SearchCriteria.Op.EQ);
|
||||
scheduleAndTimestampSearch.and(SCHEDULE_ID, scheduleAndTimestampSearch.entity().getScheduleId(), SearchCriteria.Op.EQ);
|
||||
scheduleAndTimestampSearch.and(SCHEDULED_TIMESTAMP, scheduleAndTimestampSearch.entity().getScheduledTime(), SearchCriteria.Op.EQ);
|
||||
scheduleAndTimestampSearch.done();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execution of job wouldn't be at exact seconds. So, we round off and then execute.
|
||||
*/
|
||||
@Override
|
||||
public List<VMScheduledJobVO> listJobsToStart(Date currentTimestamp) {
|
||||
public List<ResourceScheduledJobVO> listJobsToStart(ApiCommandResourceType resourceType, Date currentTimestamp) {
|
||||
if (currentTimestamp == null) {
|
||||
currentTimestamp = new Date();
|
||||
}
|
||||
Date truncatedTs = DateUtils.round(currentTimestamp, Calendar.MINUTE);
|
||||
|
||||
SearchCriteria<VMScheduledJobVO> sc = jobsToStartSearch.create();
|
||||
SearchCriteria<ResourceScheduledJobVO> sc = jobsToStartSearch.create();
|
||||
sc.setParameters(RESOURCE_TYPE, resourceType);
|
||||
sc.setParameters(SCHEDULED_TIMESTAMP, truncatedTs);
|
||||
Filter filter = new Filter(VMScheduledJobVO.class, "vmScheduleId", true, null, null);
|
||||
Filter filter = new Filter(ResourceScheduledJobVO.class, "scheduleId", true, null, null);
|
||||
return search(sc, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expungeJobsForSchedules(List<Long> vmScheduleIds, Date dateAfter) {
|
||||
SearchCriteria<VMScheduledJobVO> sc = expungeJobForScheduleSearch.create();
|
||||
sc.setParameters(VM_SCHEDULE_ID, vmScheduleIds.toArray());
|
||||
public int expungeJobsForSchedules(List<Long> scheduleIds, Date dateAfter) {
|
||||
SearchCriteria<ResourceScheduledJobVO> sc = expungeJobForScheduleSearch.create();
|
||||
sc.setParameters(SCHEDULE_ID, scheduleIds.toArray());
|
||||
if (dateAfter != null) {
|
||||
sc.setParameters(SCHEDULED_TIMESTAMP, dateAfter);
|
||||
}
|
||||
|
|
@ -94,16 +92,17 @@ public class VMScheduledJobDaoImpl extends GenericDaoBase<VMScheduledJobVO, Long
|
|||
}
|
||||
|
||||
@Override
|
||||
public int expungeJobsBefore(Date date) {
|
||||
SearchCriteria<VMScheduledJobVO> sc = expungeJobsBeforeSearch.create();
|
||||
public int expungeJobsBefore(ApiCommandResourceType resourceType, Date date) {
|
||||
SearchCriteria<ResourceScheduledJobVO> sc = expungeJobsBeforeSearch.create();
|
||||
sc.setParameters(RESOURCE_TYPE, resourceType);
|
||||
sc.setParameters(SCHEDULED_TIMESTAMP, date);
|
||||
return expunge(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VMScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp) {
|
||||
SearchCriteria<VMScheduledJobVO> sc = scheduleAndTimestampSearch.create();
|
||||
sc.setParameters(VM_SCHEDULE_ID, scheduleId);
|
||||
public ResourceScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp) {
|
||||
SearchCriteria<ResourceScheduledJobVO> sc = scheduleAndTimestampSearch.create();
|
||||
sc.setParameters(SCHEDULE_ID, scheduleId);
|
||||
sc.setParameters(SCHEDULED_TIMESTAMP, scheduledTimestamp);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
|
@ -1,108 +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.
|
||||
*/
|
||||
package org.apache.cloudstack.vm.schedule.dao;
|
||||
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.db.Filter;
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.vm.schedule.VMSchedule;
|
||||
import org.apache.cloudstack.vm.schedule.VMScheduleVO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class VMScheduleDaoImpl extends GenericDaoBase<VMScheduleVO, Long> implements VMScheduleDao {
|
||||
|
||||
private final SearchBuilder<VMScheduleVO> activeScheduleSearch;
|
||||
|
||||
private final SearchBuilder<VMScheduleVO> scheduleSearchByVmIdAndIds;
|
||||
|
||||
private final SearchBuilder<VMScheduleVO> scheduleSearch;
|
||||
|
||||
public VMScheduleDaoImpl() {
|
||||
super();
|
||||
activeScheduleSearch = createSearchBuilder();
|
||||
activeScheduleSearch.and(ApiConstants.ENABLED, activeScheduleSearch.entity().getEnabled(), SearchCriteria.Op.EQ);
|
||||
activeScheduleSearch.and().op(activeScheduleSearch.entity().getEndDate(), SearchCriteria.Op.NULL);
|
||||
activeScheduleSearch.or(ApiConstants.END_DATE, activeScheduleSearch.entity().getEndDate(), SearchCriteria.Op.GT);
|
||||
activeScheduleSearch.cp();
|
||||
activeScheduleSearch.done();
|
||||
|
||||
scheduleSearchByVmIdAndIds = createSearchBuilder();
|
||||
scheduleSearchByVmIdAndIds.and(ApiConstants.ID, scheduleSearchByVmIdAndIds.entity().getId(), SearchCriteria.Op.IN);
|
||||
scheduleSearchByVmIdAndIds.and(ApiConstants.VIRTUAL_MACHINE_ID, scheduleSearchByVmIdAndIds.entity().getVmId(), SearchCriteria.Op.EQ);
|
||||
scheduleSearchByVmIdAndIds.done();
|
||||
|
||||
scheduleSearch = createSearchBuilder();
|
||||
scheduleSearch.and(ApiConstants.ID, scheduleSearch.entity().getId(), SearchCriteria.Op.EQ);
|
||||
scheduleSearch.and(ApiConstants.VIRTUAL_MACHINE_ID, scheduleSearch.entity().getVmId(), SearchCriteria.Op.EQ);
|
||||
scheduleSearch.and(ApiConstants.ACTION, scheduleSearch.entity().getAction(), SearchCriteria.Op.EQ);
|
||||
scheduleSearch.and(ApiConstants.ENABLED, scheduleSearch.entity().getEnabled(), SearchCriteria.Op.EQ);
|
||||
scheduleSearch.done();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VMScheduleVO> listAllActiveSchedules() {
|
||||
// WHERE enabled = true AND (end_date IS NULL OR end_date > current_date)
|
||||
SearchCriteria<VMScheduleVO> sc = activeScheduleSearch.create();
|
||||
sc.setParameters(ApiConstants.ENABLED, true);
|
||||
sc.setParameters(ApiConstants.END_DATE, new Date());
|
||||
return search(sc, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long removeSchedulesForVmIdAndIds(Long vmId, List<Long> ids) {
|
||||
SearchCriteria<VMScheduleVO> sc = scheduleSearchByVmIdAndIds.create();
|
||||
sc.setParameters(ApiConstants.ID, ids.toArray());
|
||||
sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId);
|
||||
return remove(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<List<VMScheduleVO>, Integer> searchAndCount(Long id, Long vmId, VMSchedule.Action action, Boolean enabled, Long offset, Long limit) {
|
||||
SearchCriteria<VMScheduleVO> sc = scheduleSearch.create();
|
||||
|
||||
if (id != null) {
|
||||
sc.setParameters(ApiConstants.ID, id);
|
||||
}
|
||||
if (enabled != null) {
|
||||
sc.setParameters(ApiConstants.ENABLED, enabled);
|
||||
}
|
||||
if (action != null) {
|
||||
sc.setParameters(ApiConstants.ACTION, action);
|
||||
}
|
||||
sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId);
|
||||
|
||||
Filter filter = new Filter(VMScheduleVO.class, ApiConstants.ID, false, offset, limit);
|
||||
return searchAndCount(sc, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchCriteria<VMScheduleVO> getSearchCriteriaForVMId(Long vmId) {
|
||||
SearchCriteria<VMScheduleVO> sc = scheduleSearch.create();
|
||||
sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId);
|
||||
return sc;
|
||||
}
|
||||
}
|
||||
|
|
@ -108,6 +108,7 @@
|
|||
<bean id="instanceGroupJoinDaoImpl" class="com.cloud.api.query.dao.InstanceGroupJoinDaoImpl" />
|
||||
<bean id="managementServerJoinDaoImpl" class="com.cloud.api.query.dao.ManagementServerJoinDaoImpl" />
|
||||
<bean id="instanceGroupVMMapDaoImpl" class="com.cloud.vm.dao.InstanceGroupVMMapDaoImpl" />
|
||||
<bean id="vmIsoMapDaoImpl" class="com.cloud.vm.dao.VmIsoMapDaoImpl" />
|
||||
<bean id="itWorkDaoImpl" class="com.cloud.vm.ItWorkDaoImpl" />
|
||||
<bean id="lBHealthCheckPolicyDaoImpl" class="com.cloud.network.dao.LBHealthCheckPolicyDaoImpl" />
|
||||
<bean id="lBStickinessPolicyDaoImpl" class="com.cloud.network.dao.LBStickinessPolicyDaoImpl" />
|
||||
|
|
@ -284,8 +285,9 @@
|
|||
<bean id="secondaryStorageHeuristicDaoImpl" class="org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDaoImpl" />
|
||||
<bean id="heuristicRuleHelper" class="org.apache.cloudstack.storage.heuristics.HeuristicRuleHelper" />
|
||||
<bean id="publicIpQuarantineDaoImpl" class="com.cloud.network.dao.PublicIpQuarantineDaoImpl" />
|
||||
<bean id="VMScheduleDaoImpl" class="org.apache.cloudstack.vm.schedule.dao.VMScheduleDaoImpl" />
|
||||
<bean id="VMScheduledJobDaoImpl" class="org.apache.cloudstack.vm.schedule.dao.VMScheduledJobDaoImpl" />
|
||||
<bean id="ResourceScheduleDaoImpl" class="org.apache.cloudstack.schedule.dao.ResourceScheduleDaoImpl" />
|
||||
<bean id="ResourceScheduleDetailsDaoImpl" class="org.apache.cloudstack.schedule.dao.ResourceScheduleDetailsDaoImpl" />
|
||||
<bean id="ResourceScheduledJobDaoImpl" class="org.apache.cloudstack.schedule.dao.ResourceScheduledJobDaoImpl" />
|
||||
<bean id="VmwareDatacenterDaoImpl" class="com.cloud.hypervisor.vmware.dao.VmwareDatacenterDaoImpl" />
|
||||
<bean id="vnfTemplateDetailsDaoImpl" class="com.cloud.storage.dao.VnfTemplateDetailsDaoImpl" />
|
||||
<bean id="vnfTemplateNicDaoImpl" class="com.cloud.storage.dao.VnfTemplateNicDaoImpl" />
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`api_keypair` (
|
|||
`user_id` bigint(20) unsigned NOT NULL,
|
||||
`start_date` datetime,
|
||||
`end_date` datetime,
|
||||
`description` varchar(100),
|
||||
`description` varchar(1024),
|
||||
`api_key` varchar(255) NOT NULL,
|
||||
`secret_key` varchar(255) NOT NULL,
|
||||
`created` datetime NOT NULL,
|
||||
|
|
@ -107,17 +107,25 @@ WHERE user.api_key IS NOT NULL AND user.secret_key IS NOT NULL;
|
|||
-- Drop API keys from user table
|
||||
ALTER TABLE `cloud`.`user` DROP COLUMN api_key, DROP COLUMN secret_key;
|
||||
|
||||
-- Grant access to the "deleteUserKeys" API to the "User", "Domain Admin" and "Resource Admin" roles, similarly to the "registerUserKeys" API
|
||||
-- Grant access to the "deleteUserKeys" and "listUserKeyRules" APIs to the "User", "Domain Admin" and "Resource Admin" roles, similarly to the "registerUserKeys" API
|
||||
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('User', 'deleteUserKeys', 'ALLOW');
|
||||
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Domain Admin', 'deleteUserKeys', 'ALLOW');
|
||||
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Resource Admin', 'deleteUserKeys', 'ALLOW');
|
||||
|
||||
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('User', 'listUserKeyRules', 'ALLOW');
|
||||
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Domain Admin', 'listUserKeyRules', 'ALLOW');
|
||||
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Resource Admin', 'listUserKeyRules', 'ALLOW');
|
||||
|
||||
-- Add conserve mode for VPC offerings
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','conserve_mode', 'tinyint(1) unsigned NULL DEFAULT 0 COMMENT ''True if the VPC offering is IP conserve mode enabled, allowing public IP services to be used across multiple VPC tiers'' ');
|
||||
|
||||
--- Disable/enable NICs
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nics','enabled', 'TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''Indicates whether the NIC is enabled or not'' ');
|
||||
|
||||
--- Add URLs for OAuth provider
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.oauth_provider','authorize_url', 'VARCHAR(255) DEFAULT NULL COMMENT ''Authorize URL for OAuth initialization'' ');
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.oauth_provider','token_url', 'VARCHAR(255) DEFAULT NULL COMMENT ''Token URL for OAuth finalization'' ');
|
||||
|
||||
--- Quota tariff/usage mapping
|
||||
CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
|
|
@ -128,6 +136,20 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` (
|
|||
CONSTRAINT `fk_quota_tariff_usage__tariff_id` FOREIGN KEY (`tariff_id`) REFERENCES `cloud_usage`.`quota_tariff` (`id`),
|
||||
CONSTRAINT `fk_quota_tariff_usage__quota_usage_id` FOREIGN KEY (`quota_usage_id`) REFERENCES `cloud_usage`.`quota_usage` (`id`));
|
||||
|
||||
--- Per-VM ISO attachments. user_vm.iso_id remains as the primary/bootable ISO pointer.
|
||||
CREATE TABLE IF NOT EXISTS `cloud`.`vm_iso_map` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`vm_id` bigint(20) unsigned NOT NULL COMMENT 'foreign key to user_vm',
|
||||
`iso_id` bigint(20) unsigned NOT NULL COMMENT 'foreign key to vm_template (ISOs are templates of format ISO)',
|
||||
`device_seq` int(10) unsigned NOT NULL COMMENT 'cdrom slot index used to derive the libvirt device label (3=hdc, 4=hdd)',
|
||||
`created` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uc_vm_iso_map__vm_iso` (`vm_id`, `iso_id`),
|
||||
UNIQUE KEY `uc_vm_iso_map__vm_seq` (`vm_id`, `device_seq`),
|
||||
CONSTRAINT `fk_vm_iso_map__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `cloud`.`user_vm` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_vm_iso_map__iso_id` FOREIGN KEY (`iso_id`) REFERENCES `cloud`.`vm_template` (`id`)
|
||||
);
|
||||
|
||||
-- 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');
|
||||
|
|
@ -150,6 +172,61 @@ 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';
|
||||
|
||||
-- Generalise VM schedule tables into resource-agnostic resource_schedule / resource_scheduled_job.
|
||||
-- Step 1: rename vm_schedule → resource_schedule, rename vm_id → resource_id, add resource_type column.
|
||||
ALTER TABLE `cloud`.`vm_schedule`
|
||||
DROP FOREIGN KEY `fk_vm_schedule__vm_id`,
|
||||
DROP INDEX `i_vm_schedule__vm_id`,
|
||||
DROP INDEX `i_vm_schedule__enabled_end_date`,
|
||||
CHANGE COLUMN `vm_id` `resource_id` bigint unsigned NOT NULL COMMENT 'id of the scheduled resource',
|
||||
ADD COLUMN `resource_type` varchar(64) NOT NULL DEFAULT 'VirtualMachine' COMMENT 'type of the scheduled resource' AFTER `uuid`;
|
||||
|
||||
RENAME TABLE `cloud`.`vm_schedule` TO `cloud`.`resource_schedule`;
|
||||
|
||||
ALTER TABLE `cloud`.`resource_schedule`
|
||||
ADD INDEX `i_resource_schedule__resource` (`resource_type`, `resource_id`),
|
||||
ADD INDEX `i_resource_schedule__enabled_end_date` (`enabled`, `end_date`);
|
||||
|
||||
-- Step 2: rename vm_scheduled_job → resource_scheduled_job, rename columns.
|
||||
ALTER TABLE `cloud`.`vm_scheduled_job`
|
||||
DROP FOREIGN KEY `fk_vm_scheduled_job__vm_id`,
|
||||
DROP FOREIGN KEY `fk_vm_scheduled_job__vm_schedule_id`,
|
||||
DROP INDEX `i_vm_scheduled_job__vm_id`,
|
||||
DROP INDEX `i_vm_scheduled_job__scheduled_timestamp`,
|
||||
DROP INDEX `vm_schedule_id`,
|
||||
CHANGE COLUMN `vm_id` `resource_id` bigint unsigned NOT NULL COMMENT 'id of the scheduled resource',
|
||||
CHANGE COLUMN `vm_schedule_id` `schedule_id` bigint unsigned NOT NULL COMMENT 'id of the resource_schedule row',
|
||||
ADD COLUMN `resource_type` varchar(64) NOT NULL DEFAULT 'VirtualMachine' COMMENT 'type of the scheduled resource' AFTER `uuid`;
|
||||
|
||||
RENAME TABLE `cloud`.`vm_scheduled_job` TO `cloud`.`resource_scheduled_job`;
|
||||
|
||||
ALTER TABLE `cloud`.`resource_scheduled_job`
|
||||
ADD UNIQUE KEY `uc_resource_scheduled_job__schedule_timestamp` (`schedule_id`, `scheduled_timestamp`),
|
||||
ADD INDEX `i_resource_scheduled_job__resource` (`resource_type`, `resource_id`),
|
||||
ADD INDEX `i_resource_scheduled_job__scheduled_timestamp` (`scheduled_timestamp`),
|
||||
ADD CONSTRAINT `fk_resource_scheduled_job__schedule_id` FOREIGN KEY (`schedule_id`) REFERENCES `resource_schedule`(`id`) ON DELETE CASCADE;
|
||||
|
||||
-- Step 3: details table for action-specific parameters (used by the generic resource schedule API in Commit 2).
|
||||
CREATE TABLE IF NOT EXISTS `cloud`.`resource_schedule_details` (
|
||||
`id` bigint unsigned NOT NULL auto_increment,
|
||||
`schedule_id` bigint unsigned NOT NULL COMMENT 'id of the resource_schedule row',
|
||||
`name` varchar(255) NOT NULL,
|
||||
`value` varchar(1024) NOT NULL,
|
||||
`display` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'should this detail be visible to the end user',
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `i_resource_schedule_details__schedule_id` (`schedule_id`),
|
||||
CONSTRAINT `fk_resource_schedule_details__schedule_id` FOREIGN KEY (`schedule_id`) REFERENCES `resource_schedule`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Step 4: rename CRUD event types from VM.SCHEDULE.{CREATE,UPDATE,DELETE} to the new generic SCHEDULE.{CREATE,UPDATE,DELETE}.
|
||||
-- Action-execution events (VM.SCHEDULE.START, .STOP, .REBOOT, .FORCE_STOP, .FORCE_REBOOT) keep their existing names.
|
||||
UPDATE `cloud`.`event` SET `type` = 'SCHEDULE.CREATE' WHERE `type` = 'VM.SCHEDULE.CREATE';
|
||||
UPDATE `cloud`.`event` SET `type` = 'SCHEDULE.UPDATE' WHERE `type` = 'VM.SCHEDULE.UPDATE';
|
||||
UPDATE `cloud`.`event` SET `type` = 'SCHEDULE.DELETE' WHERE `type` = 'VM.SCHEDULE.DELETE';
|
||||
|
||||
-- Step 5: Rename the global configuration key for the scheduler
|
||||
UPDATE `cloud`.`configuration` SET name='scheduler.jobs.expire.interval' WHERE name='vmscheduler.jobs.expire.interval';
|
||||
|
||||
-- Remove stale realhostip.com default values; domain has been dead since ~2015.
|
||||
UPDATE `cloud`.`configuration`
|
||||
SET value = NULL
|
||||
|
|
@ -208,3 +285,6 @@ INSERT INTO cloud.role_permissions (uuid, role_id, rule, permission, sort_order)
|
|||
SELECT uuid(), role_id, 'quotaResourceStatement', permission, sort_order
|
||||
FROM cloud.role_permissions rp
|
||||
WHERE rule = 'quotaStatement' AND NOT EXISTS(SELECT 1 FROM cloud.role_permissions rp_ WHERE rp.role_id = rp_.role_id AND rp_.rule = 'quotaResourceStatement');
|
||||
|
||||
-- Add description for secondary IP addresses
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nic_secondary_ips', 'description', 'VARCHAR(2048) DEFAULT NULL');
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ SELECT
|
|||
guest_os_category.id guest_os_category_id,
|
||||
guest_os_category.uuid guest_os_category_uuid,
|
||||
guest_os_category.name guest_os_category_name,
|
||||
(SELECT `value` FROM `cloud`.`host_details` `hd` WHERE `hd`.`host_id` = `cloud`.`host`.`id` AND `hd`.`name` = 'guest.os.rule') AS `guest_os_rule`,
|
||||
mem_caps.used_capacity memory_used_capacity,
|
||||
mem_caps.reserved_capacity memory_reserved_capacity,
|
||||
cpu_caps.used_capacity cpu_used_capacity,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
// 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 com.cloud.vm;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class VmIsoMapVOTest {
|
||||
|
||||
@Test
|
||||
public void testFullConstructorPopulatesAllFields() {
|
||||
VmIsoMapVO row = new VmIsoMapVO(7L, 42L, 4);
|
||||
Assert.assertEquals(7L, row.getVmId());
|
||||
Assert.assertEquals(42L, row.getIsoId());
|
||||
Assert.assertEquals(4, row.getDeviceSeq());
|
||||
Assert.assertNotNull(row.getCreated());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoArgConstructorLeavesNonIdFieldsAtDefaults() {
|
||||
VmIsoMapVO row = new VmIsoMapVO();
|
||||
Assert.assertEquals(0L, row.getVmId());
|
||||
Assert.assertEquals(0L, row.getIsoId());
|
||||
Assert.assertEquals(0, row.getDeviceSeq());
|
||||
Assert.assertNull(row.getCreated());
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ import java.util.Objects;
|
|||
import javax.inject.Inject;
|
||||
|
||||
import com.cloud.agent.api.to.DiskTO;
|
||||
import com.cloud.storage.clvm.ClvmPoolManager;
|
||||
import com.cloud.storage.Storage;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
|
||||
|
|
@ -75,6 +76,7 @@ import com.cloud.storage.ScopeType;
|
|||
import com.cloud.storage.Snapshot.Type;
|
||||
import com.cloud.storage.SnapshotVO;
|
||||
import com.cloud.storage.StorageManager;
|
||||
import com.cloud.storage.Storage.ImageFormat;
|
||||
import com.cloud.storage.Storage.StoragePoolType;
|
||||
import com.cloud.storage.StoragePool;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
|
|
@ -108,6 +110,8 @@ public class AncientDataMotionStrategy implements DataMotionStrategy {
|
|||
StorageCacheManager cacheMgr;
|
||||
@Inject
|
||||
VolumeDataStoreDao volumeDataStoreDao;
|
||||
@Inject
|
||||
ClvmPoolManager clvmPoolManager;
|
||||
|
||||
@Inject
|
||||
StorageManager storageManager;
|
||||
|
|
@ -309,6 +313,8 @@ public class AncientDataMotionStrategy implements DataMotionStrategy {
|
|||
ep = selector.select(srcData, volObj);
|
||||
}
|
||||
|
||||
updateLockHostForVolume(ep, volObj);
|
||||
|
||||
CopyCommand cmd = new CopyCommand(srcData.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(volObj.getTO()), _createVolumeFromSnapshotWait, VirtualMachineManager.ExecuteInSequence.value());
|
||||
|
||||
Answer answer = null;
|
||||
|
|
@ -331,6 +337,29 @@ public class AncientDataMotionStrategy implements DataMotionStrategy {
|
|||
}
|
||||
}
|
||||
|
||||
private void updateLockHostForVolume(EndPoint ep, DataObject volObj) {
|
||||
if (ep == null || !(volObj instanceof VolumeInfo)) {
|
||||
return;
|
||||
}
|
||||
VolumeInfo volumeInfo = (VolumeInfo) volObj;
|
||||
StoragePool destPool = (StoragePool) volObj.getDataStore();
|
||||
if (destPool == null || !ClvmPoolManager.isClvmPoolType(destPool.getPoolType())) {
|
||||
return;
|
||||
}
|
||||
Long hostId = ep.getId();
|
||||
Long existingHostId = clvmPoolManager.getClvmLockHostId(
|
||||
volumeInfo.getId(),
|
||||
volumeInfo.getUuid(),
|
||||
volumeInfo.getPath(),
|
||||
destPool,
|
||||
true
|
||||
);
|
||||
if (existingHostId == null) {
|
||||
clvmPoolManager.setClvmLockHostId(volumeInfo.getId(), hostId);
|
||||
logger.debug("Set lock host ID {} for CLVM volume {} being created from snapshot", hostId, volumeInfo.getId());
|
||||
}
|
||||
}
|
||||
|
||||
protected Answer cloneVolume(DataObject template, DataObject volume) {
|
||||
CopyCommand cmd = new CopyCommand(template.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(volume.getTO()), 0, VirtualMachineManager.ExecuteInSequence.value());
|
||||
try {
|
||||
|
|
@ -581,6 +610,9 @@ public class AncientDataMotionStrategy implements DataMotionStrategy {
|
|||
volumeVo.setPoolId(destPool.getId());
|
||||
volumeVo.setPoolType(destPool.getPoolType());
|
||||
volumeVo.setLastPoolId(oldPoolId);
|
||||
if (destPool.getPoolType() == StoragePoolType.CLVM) {
|
||||
volumeVo.setFormat(ImageFormat.RAW);
|
||||
}
|
||||
// For SMB, pool credentials are also stored in the uri query string. We trim the query string
|
||||
// part here to make sure the credentials do not get stored in the db unencrypted.
|
||||
String folder = destPool.getPath();
|
||||
|
|
|
|||
|
|
@ -144,12 +144,16 @@ public class KvmNonManagedStorageDataMotionStrategy extends StorageSystemDataMot
|
|||
}
|
||||
|
||||
/**
|
||||
* Configures a {@link MigrateDiskInfo} object configured for migrating a File System volume and calls rootImageProvisioning.
|
||||
* Configures a {@link MigrateDiskInfo} object configured for migrating a File System volume.
|
||||
*/
|
||||
@Override
|
||||
protected MigrateCommand.MigrateDiskInfo configureMigrateDiskInfo(VolumeInfo srcVolumeInfo, String destPath, String backingPath) {
|
||||
return new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(), MigrateCommand.MigrateDiskInfo.DiskType.FILE, MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
|
||||
MigrateCommand.MigrateDiskInfo.Source.FILE, destPath, backingPath);
|
||||
return new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(),
|
||||
MigrateCommand.MigrateDiskInfo.DiskType.FILE,
|
||||
MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
|
||||
MigrateCommand.MigrateDiskInfo.Source.FILE,
|
||||
destPath,
|
||||
backingPath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -158,6 +162,17 @@ public class KvmNonManagedStorageDataMotionStrategy extends StorageSystemDataMot
|
|||
*/
|
||||
@Override
|
||||
protected String generateDestPath(Host destHost, StoragePoolVO destStoragePool, VolumeInfo destVolumeInfo) {
|
||||
if (destStoragePool.getPoolType() == StoragePoolType.CLVM || destStoragePool.getPoolType() == StoragePoolType.CLVM_NG) {
|
||||
String vgName = destStoragePool.getPath();
|
||||
if (StringUtils.isBlank(vgName)) {
|
||||
throw new CloudRuntimeException(String.format("CLVM/CLVM_NG destination pool [%s] has empty VG path", destStoragePool.getUuid()));
|
||||
}
|
||||
if (vgName.startsWith("/")) {
|
||||
vgName = vgName.substring(1);
|
||||
}
|
||||
return String.format("/dev/%s/%s", vgName, destVolumeInfo.getUuid());
|
||||
}
|
||||
|
||||
return new File(destStoragePool.getPath(), destVolumeInfo.getUuid()).getAbsolutePath();
|
||||
}
|
||||
|
||||
|
|
@ -285,6 +300,7 @@ public class KvmNonManagedStorageDataMotionStrategy extends StorageSystemDataMot
|
|||
}
|
||||
|
||||
protected Boolean supportStoragePoolType(StoragePoolType storagePoolType) {
|
||||
return super.supportStoragePoolType(storagePoolType, StoragePoolType.Filesystem);
|
||||
return super.supportStoragePoolType(storagePoolType, StoragePoolType.Filesystem,
|
||||
StoragePoolType.CLVM, StoragePoolType.CLVM_NG);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ import com.cloud.agent.api.CheckVirtualMachineAnswer;
|
|||
import com.cloud.agent.api.CheckVirtualMachineCommand;
|
||||
import com.cloud.agent.api.PrepareForMigrationAnswer;
|
||||
import com.cloud.resource.ResourceManager;
|
||||
import com.cloud.storage.clvm.ClvmPoolManager;
|
||||
import org.apache.cloudstack.storage.clvm.command.ClvmLockTransferCommand;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
|
||||
|
|
@ -87,6 +89,7 @@ import com.cloud.agent.api.MigrateCommand;
|
|||
import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo;
|
||||
import com.cloud.agent.api.ModifyTargetsAnswer;
|
||||
import com.cloud.agent.api.ModifyTargetsCommand;
|
||||
import com.cloud.agent.api.PreMigrationCommand;
|
||||
import com.cloud.agent.api.PrepareForMigrationCommand;
|
||||
import com.cloud.agent.api.storage.CopyVolumeAnswer;
|
||||
import com.cloud.agent.api.storage.CopyVolumeCommand;
|
||||
|
|
@ -206,6 +209,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
private VolumeDataFactory _volFactory;
|
||||
@Inject
|
||||
ResourceManager resourceManager;
|
||||
@Inject
|
||||
private ClvmPoolManager clvmPoolManager;
|
||||
|
||||
@Override
|
||||
public StrategyPriority canHandle(DataObject srcData, DataObject destData) {
|
||||
|
|
@ -2023,6 +2028,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
String errMsg = null;
|
||||
boolean success = false;
|
||||
Map<VolumeInfo, VolumeInfo> srcVolumeInfoToDestVolumeInfo = new HashMap<>();
|
||||
List<VolumeInfo> samePoolClvmVolumes = new ArrayList<>();
|
||||
|
||||
try {
|
||||
if (srcHost.getHypervisorType() != HypervisorType.KVM) {
|
||||
|
|
@ -2052,6 +2058,13 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (sourceStoragePool.getId() == destStoragePool.getId() &&
|
||||
ClvmPoolManager.isClvmPoolType(destStoragePool.getPoolType())) {
|
||||
logger.info("Same-pool CLVM migration for volume [{}]: skipping data copy.", srcVolumeInfo.getUuid());
|
||||
samePoolClvmVolumes.add(srcVolumeInfo);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!shouldMigrateVolume(sourceStoragePool, destHost, destStoragePool)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -2071,6 +2084,13 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
|
||||
setVolumeMigrationOptions(srcVolumeInfo, destVolumeInfo, vmTO, srcHost, destStoragePool, migrationType);
|
||||
|
||||
if (ClvmPoolManager.isClvmPoolType(destStoragePool.getPoolType())) {
|
||||
destVolumeInfo.setDestinationHostId(destHost.getId());
|
||||
clvmPoolManager.setClvmLockHostId(destVolume.getId(), destHost.getId());
|
||||
logger.info("Set CLVM lock host {} for volume {} during migration to ensure creation on destination host",
|
||||
destHost.getId(), destVolumeInfo.getUuid());
|
||||
}
|
||||
|
||||
// create a volume on the destination storage
|
||||
destDataStore.getDriver().createAsync(destDataStore, destVolumeInfo, null);
|
||||
|
||||
|
|
@ -2096,7 +2116,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
|
||||
MigrateCommand.MigrateDiskInfo migrateDiskInfo;
|
||||
|
||||
boolean isNonManagedToNfs = supportStoragePoolType(sourceStoragePool.getPoolType(), StoragePoolType.Filesystem) && destStoragePool.getPoolType() == StoragePoolType.NetworkFilesystem && !managedStorageDestination;
|
||||
boolean isNonManagedToNfs = supportStoragePoolType(sourceStoragePool.getPoolType(), StoragePoolType.Filesystem, StoragePoolType.CLVM, StoragePoolType.CLVM_NG) && destStoragePool.getPoolType() == StoragePoolType.NetworkFilesystem && !managedStorageDestination;
|
||||
if (isNonManagedToNfs) {
|
||||
migrateDiskInfo = new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(),
|
||||
MigrateCommand.MigrateDiskInfo.DiskType.FILE,
|
||||
|
|
@ -2106,9 +2126,12 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
} else {
|
||||
String backingPath = generateBackingPath(destStoragePool, destVolumeInfo);
|
||||
migrateDiskInfo = configureMigrateDiskInfo(srcVolumeInfo, destPath, backingPath);
|
||||
migrateDiskInfo = updateMigrateDiskInfoForBlockDevice(migrateDiskInfo, destStoragePool);
|
||||
migrateDiskInfo.setSourceDiskOnStorageFileSystem(isStoragePoolTypeOfFile(sourceStoragePool));
|
||||
migrateDiskInfoList.add(migrateDiskInfo);
|
||||
}
|
||||
migrateDiskInfo.setSourcePoolType(sourceStoragePool.getPoolType());
|
||||
migrateDiskInfo.setDestPoolType(destVolumeInfo.getStoragePoolType());
|
||||
prepareDiskWithSecretConsumerDetail(vmTO, srcVolumeInfo, destVolumeInfo.getPath());
|
||||
|
||||
migrateStorage.put(srcVolumeInfo.getPath(), migrateDiskInfo);
|
||||
|
|
@ -2116,6 +2139,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
srcVolumeInfoToDestVolumeInfo.put(srcVolumeInfo, destVolumeInfo);
|
||||
}
|
||||
|
||||
prepareDisksForMigrationForClvm(vmTO, volumeDataStoreMap, srcHost);
|
||||
|
||||
PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(vmTO);
|
||||
Answer pfma;
|
||||
|
||||
|
|
@ -2132,6 +2157,25 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
throw new AgentUnavailableException("Operation timed out", destHost.getId());
|
||||
}
|
||||
|
||||
for (VolumeInfo vol : samePoolClvmVolumes) {
|
||||
StoragePoolVO samePoolClvmPool = _storagePoolDao.findById(vol.getPoolId());
|
||||
String vgName = samePoolClvmPool.getPath();
|
||||
if (vgName.startsWith("/")) {
|
||||
vgName = vgName.substring(1);
|
||||
}
|
||||
String lvPath = String.format("/dev/%s/%s", vgName, vol.getPath());
|
||||
logger.info("Activating CLVM volume [{}] in shared mode on dest host [{}] for same-pool migration.",
|
||||
vol.getUuid(), destHost.getId());
|
||||
Answer activateAnswer = agentManager.send(destHost.getId(),
|
||||
new ClvmLockTransferCommand(ClvmLockTransferCommand.Operation.ACTIVATE_SHARED, lvPath, vol.getUuid()));
|
||||
if (activateAnswer == null || !activateAnswer.getResult()) {
|
||||
throw new CloudRuntimeException(String.format(
|
||||
"Failed to activate CLVM volume [%s] in shared mode on dest host [%s]: %s",
|
||||
vol.getUuid(), destHost.getId(),
|
||||
activateAnswer != null ? activateAnswer.getDetails() : "null answer"));
|
||||
}
|
||||
}
|
||||
|
||||
VMInstanceVO vm = _vmDao.findById(vmTO.getId());
|
||||
boolean isWindows = _guestOsCategoryDao.findById(_guestOsDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows");
|
||||
|
||||
|
|
@ -2141,6 +2185,9 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
migrateCommand.setMigrateDiskInfoList(migrateDiskInfoList);
|
||||
migrateCommand.setMigrateStorageManaged(managedStorageDestination);
|
||||
migrateCommand.setMigrateNonSharedInc(migrateNonSharedInc);
|
||||
boolean hasClvmCrossPoolVolume = migrateStorage.values().stream()
|
||||
.anyMatch(info -> ClvmPoolManager.isClvmPoolType(info.getSourcePoolType()));
|
||||
migrateCommand.setClvmCrossPoolMigration(hasClvmCrossPoolVolume);
|
||||
|
||||
Integer newVmCpuShares = ((PrepareForMigrationAnswer) pfma).getNewVmCpuShares();
|
||||
if (newVmCpuShares != null) {
|
||||
|
|
@ -2171,7 +2218,7 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
}
|
||||
}
|
||||
|
||||
handlePostMigration(success, srcVolumeInfoToDestVolumeInfo, vmTO, destHost);
|
||||
handlePostMigration(success, srcVolumeInfoToDestVolumeInfo, vmTO, srcHost, destHost);
|
||||
|
||||
if (!success) {
|
||||
if (migrateAnswer == null) {
|
||||
|
|
@ -2211,10 +2258,43 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
}
|
||||
}
|
||||
|
||||
private void prepareDisksForMigrationForClvm(VirtualMachineTO vmTO, Map<VolumeInfo, DataStore> volumeDataStoreMap, Host srcHost) {
|
||||
// For CLVM/CLVM_NG source pools, convert volumes from exclusive to shared mode
|
||||
// on the source host BEFORE PrepareForMigrationCommand on the destination.
|
||||
boolean hasClvmSource = volumeDataStoreMap.keySet().stream()
|
||||
.map(v -> _storagePoolDao.findById(v.getPoolId()))
|
||||
.anyMatch(p -> p != null && (p.getPoolType() == StoragePoolType.CLVM || p.getPoolType() == StoragePoolType.CLVM_NG));
|
||||
if (hasClvmSource && srcHost.getHypervisorType() == HypervisorType.KVM) {
|
||||
logger.info("CLVM/CLVM_NG source pool detected for VM [{}], sending PreMigrationCommand to source host [{}] to convert volumes to shared mode.", vmTO.getName(), srcHost.getId());
|
||||
PreMigrationCommand preMigCmd = new PreMigrationCommand(vmTO, vmTO.getName());
|
||||
try {
|
||||
Answer preMigAnswer = agentManager.send(srcHost.getId(), preMigCmd);
|
||||
if (preMigAnswer == null || !preMigAnswer.getResult()) {
|
||||
String details = preMigAnswer != null ? preMigAnswer.getDetails() : "null answer returned";
|
||||
logger.warn("PreMigrationCommand failed for CLVM/CLVM_NG VM [{}] on source host [{}]: {}. Migration will continue but may fail if volumes are exclusively locked.", vmTO.getName(), srcHost.getId(), details);
|
||||
} else {
|
||||
logger.info("Successfully converted CLVM/CLVM_NG volumes to shared mode on source host [{}] for VM [{}].", srcHost.getId(), vmTO.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to send PreMigrationCommand to source host [{}] for VM [{}]: {}. Migration will continue but may fail if volumes are exclusively locked.", srcHost.getId(), vmTO.getName(), e.getMessage());
|
||||
}
|
||||
} else if (hasClvmSource) {
|
||||
logger.debug("Skipping PreMigrationCommand for non-KVM hypervisor type: {} on host [{}]", srcHost.getHypervisorType(), srcHost.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private MigrationOptions.Type decideMigrationTypeAndCopyTemplateIfNeeded(Host destHost, VMInstanceVO vmInstance, VolumeInfo srcVolumeInfo, StoragePoolVO sourceStoragePool, StoragePoolVO destStoragePool, DataStore destDataStore) {
|
||||
VMTemplateVO vmTemplate = _vmTemplateDao.findById(vmInstance.getTemplateId());
|
||||
String srcVolumeBackingFile = getVolumeBackingFile(srcVolumeInfo);
|
||||
|
||||
// Check if source is CLVM/CLVM_NG (block device storage)
|
||||
// LinkedClone (VIR_MIGRATE_NON_SHARED_INC) only works for file → file migrations
|
||||
// For block device sources, use FullClone (VIR_MIGRATE_NON_SHARED_DISK)
|
||||
boolean sourceIsBlockDevice = sourceStoragePool.getPoolType() == StoragePoolType.CLVM ||
|
||||
sourceStoragePool.getPoolType() == StoragePoolType.CLVM_NG;
|
||||
|
||||
if (StringUtils.isNotBlank(srcVolumeBackingFile) && supportStoragePoolType(destStoragePool.getPoolType(), StoragePoolType.Filesystem) &&
|
||||
!sourceIsBlockDevice &&
|
||||
srcVolumeInfo.getTemplateId() != null &&
|
||||
Objects.nonNull(vmTemplate) &&
|
||||
!Arrays.asList(KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME, VM_IMPORT_DEFAULT_TEMPLATE_NAME).contains(vmTemplate.getName())) {
|
||||
|
|
@ -2222,8 +2302,12 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
copyTemplateToTargetFilesystemStorageIfNeeded(srcVolumeInfo, sourceStoragePool, destDataStore, destStoragePool, destHost);
|
||||
return MigrationOptions.Type.LinkedClone;
|
||||
}
|
||||
logger.debug(String.format("Skipping copy template from source storage pool [%s] to target storage pool [%s] before migration due to volume [%s] does not have a " +
|
||||
"template or we are doing full clone migration.", sourceStoragePool.getId(), destStoragePool.getId(), srcVolumeInfo.getId()));
|
||||
|
||||
if (sourceIsBlockDevice) {
|
||||
logger.debug(String.format("Source storage pool [%s] is block device (CLVM/CLVM_NG). Using FullClone migration for volume [%s] to target storage pool [%s]. Template copy skipped as entire volume will be copied.", sourceStoragePool.getId(), srcVolumeInfo.getId(), destStoragePool.getId()));
|
||||
} else {
|
||||
logger.debug(String.format("Skipping copy template from source storage pool [%s] to target storage pool [%s] before migration due to volume [%s] does not have a template or we are doing full clone migration.", sourceStoragePool.getId(), destStoragePool.getId(), srcVolumeInfo.getId()));
|
||||
}
|
||||
return MigrationOptions.Type.FullClone;
|
||||
}
|
||||
|
||||
|
|
@ -2289,6 +2373,39 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
MigrateCommand.MigrateDiskInfo.Source.DEV, destPath, backingPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdatesMigrateDiskInfo for CLVM/CLVM_NG block devices by returning a new instance with corrected disk type, driver type, and source.
|
||||
* For CLVM/CLVM_NG destinations, returns a new MigrateDiskInfo with BLOCK disk type, DEV source, and appropriate driver type (QCOW2 for CLVM_NG, RAW for CLVM).
|
||||
* For other storage types, returns the original MigrateDiskInfo unchanged.
|
||||
*
|
||||
* @param migrateDiskInfo The original MigrateDiskInfo object
|
||||
* @param destStoragePool The destination storage pool
|
||||
* @return A new MigrateDiskInfo with updated values for CLVM/CLVM_NG, or the original for other storage types
|
||||
*/
|
||||
protected MigrateCommand.MigrateDiskInfo updateMigrateDiskInfoForBlockDevice(MigrateCommand.MigrateDiskInfo migrateDiskInfo,
|
||||
StoragePoolVO destStoragePool) {
|
||||
if (ClvmPoolManager.isClvmPoolType(destStoragePool.getPoolType())) {
|
||||
|
||||
MigrateCommand.MigrateDiskInfo.DriverType driverType =
|
||||
(destStoragePool.getPoolType() == StoragePoolType.CLVM_NG) ?
|
||||
MigrateCommand.MigrateDiskInfo.DriverType.QCOW2 :
|
||||
MigrateCommand.MigrateDiskInfo.DriverType.RAW;
|
||||
|
||||
logger.debug("Updating MigrateDiskInfo for {} destination: setting BLOCK disk type, DEV source, and {} driver type",
|
||||
destStoragePool.getPoolType(), driverType);
|
||||
|
||||
return new MigrateCommand.MigrateDiskInfo(
|
||||
migrateDiskInfo.getSerialNumber(),
|
||||
MigrateCommand.MigrateDiskInfo.DiskType.BLOCK,
|
||||
driverType,
|
||||
MigrateCommand.MigrateDiskInfo.Source.DEV,
|
||||
migrateDiskInfo.getSourceText(),
|
||||
migrateDiskInfo.getBackingStoreText());
|
||||
}
|
||||
|
||||
return migrateDiskInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the volume path as the iScsi name in case of a configured iScsi.
|
||||
*/
|
||||
|
|
@ -2320,7 +2437,26 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
return null;
|
||||
}
|
||||
|
||||
private void handlePostMigration(boolean success, Map<VolumeInfo, VolumeInfo> srcVolumeInfoToDestVolumeInfo, VirtualMachineTO vmTO, Host destHost) {
|
||||
private void sendClvmLockCommand(long hostId, StoragePoolVO pool, VolumeInfo volumeInfo,
|
||||
ClvmLockTransferCommand.Operation operation) {
|
||||
String vgName = pool.getPath();
|
||||
if (vgName.startsWith("/")) {
|
||||
vgName = vgName.substring(1);
|
||||
}
|
||||
String lvPath = String.format("/dev/%s/%s", vgName, volumeInfo.getPath());
|
||||
try {
|
||||
Answer answer = agentManager.send(hostId,
|
||||
new ClvmLockTransferCommand(operation, lvPath, volumeInfo.getUuid()));
|
||||
if (answer == null || !answer.getResult()) {
|
||||
String details = answer != null ? answer.getDetails() : "null answer";
|
||||
logger.warn("CLVM lock command [{}] failed for LV [{}] on host [{}]: {}", operation, lvPath, hostId, details);
|
||||
}
|
||||
} catch (AgentUnavailableException | OperationTimedoutException e) {
|
||||
logger.warn("Exception sending CLVM lock command [{}] for LV [{}] on host [{}]: {}", operation, lvPath, hostId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePostMigration(boolean success, Map<VolumeInfo, VolumeInfo> srcVolumeInfoToDestVolumeInfo, VirtualMachineTO vmTO, Host srcHost, Host destHost) {
|
||||
if (!success) {
|
||||
try {
|
||||
PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(vmTO);
|
||||
|
|
@ -2339,6 +2475,17 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
catch (Exception e) {
|
||||
logger.debug("Failed to disconnect one or more (original) dest volumes", e);
|
||||
}
|
||||
|
||||
if (srcHost != null && srcHost.getHypervisorType() == HypervisorType.KVM) {
|
||||
for (VolumeInfo srcVolumeInfo : srcVolumeInfoToDestVolumeInfo.keySet()) {
|
||||
StoragePoolVO srcPool = _storagePoolDao.findById(srcVolumeInfo.getPoolId());
|
||||
if (srcPool == null || !ClvmPoolManager.isClvmPoolType(srcPool.getPoolType())) {
|
||||
continue;
|
||||
}
|
||||
sendClvmLockCommand(srcHost.getId(), srcPool, srcVolumeInfo,
|
||||
ClvmLockTransferCommand.Operation.ACTIVATE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<VolumeInfo, VolumeInfo> entry : srcVolumeInfoToDestVolumeInfo.entrySet()) {
|
||||
|
|
@ -2349,12 +2496,13 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
|
||||
if (success) {
|
||||
VolumeVO volumeVO = _volumeDao.findById(destVolumeInfo.getId());
|
||||
volumeVO.setFormat(ImageFormat.QCOW2);
|
||||
StoragePoolVO srcPoolVO = _storagePoolDao.findById(srcVolumeInfo.getPoolId());
|
||||
StoragePoolVO destPoolVO = _storagePoolDao.findById(destVolumeInfo.getPoolId());
|
||||
volumeVO.setFormat(destPoolVO != null && destPoolVO.getPoolType() == StoragePoolType.CLVM
|
||||
? ImageFormat.RAW : ImageFormat.QCOW2);
|
||||
volumeVO.setLastId(srcVolumeInfo.getId());
|
||||
|
||||
if (Objects.equals(srcVolumeInfo.getDiskOfferingId(), destVolumeInfo.getDiskOfferingId())) {
|
||||
StoragePoolVO srcPoolVO = _storagePoolDao.findById(srcVolumeInfo.getPoolId());
|
||||
StoragePoolVO destPoolVO = _storagePoolDao.findById(destVolumeInfo.getPoolId());
|
||||
if (srcPoolVO != null && destPoolVO != null &&
|
||||
((srcPoolVO.isShared() && destPoolVO.isLocal()) || (srcPoolVO.isLocal() && destPoolVO.isShared()))) {
|
||||
Long offeringId = getSuitableDiskOfferingForVolumeOnPool(volumeVO, destPoolVO);
|
||||
|
|
@ -2365,6 +2513,12 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy {
|
|||
}
|
||||
|
||||
_volumeDao.update(volumeVO.getId(), volumeVO);
|
||||
if (destPoolVO != null && ClvmPoolManager.isClvmPoolType(destPoolVO.getPoolType())
|
||||
&& (srcPoolVO == null || srcPoolVO.getId() != destPoolVO.getId())) {
|
||||
sendClvmLockCommand(destHost.getId(), destPoolVO, destVolumeInfo,
|
||||
ClvmLockTransferCommand.Operation.ACTIVATE_EXCLUSIVE);
|
||||
clvmPoolManager.setClvmLockHostId(destVolumeInfo.getId(), destHost.getId());
|
||||
}
|
||||
|
||||
_volumeService.copyPoliciesBetweenVolumesAndDestroySourceVolumeAfterMigration(Event.OperationSucceeded, null, srcVolumeInfo, destVolumeInfo, false);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,14 +28,18 @@ import static org.mockito.Mockito.verify;
|
|||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.any;
|
||||
|
||||
import com.cloud.storage.clvm.ClvmPoolManager;
|
||||
import com.cloud.storage.Storage;
|
||||
import com.cloud.storage.StorageManager;
|
||||
import com.cloud.storage.StoragePool;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.StorageCacheManager;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
|
|
@ -95,6 +99,14 @@ public class AncientDataMotionStrategyTest {
|
|||
f.set(configKey, value);
|
||||
}
|
||||
|
||||
private ClvmPoolManager injectMockedClvmPoolManager() throws Exception {
|
||||
ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class);
|
||||
Field clvmPoolManagerField = AncientDataMotionStrategy.class.getDeclaredField("clvmPoolManager");
|
||||
clvmPoolManagerField.setAccessible(true);
|
||||
clvmPoolManagerField.set(strategy, clvmPoolManager);
|
||||
return clvmPoolManager;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddFullCloneFlagOnVMwareDest(){
|
||||
strategy.addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(dataTO);
|
||||
|
|
@ -288,4 +300,185 @@ public class AncientDataMotionStrategyTest {
|
|||
canBypassSecondaryStorage = (boolean) method.invoke(strategy, destVolumeInfo, srcVolumeInfo);
|
||||
Assert.assertTrue(canBypassSecondaryStorage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateLockHostForVolume_CLVMPool_SetsLockHost() throws Exception {
|
||||
Method method = AncientDataMotionStrategy.class.getDeclaredMethod(
|
||||
"updateLockHostForVolume",
|
||||
EndPoint.class,
|
||||
DataObject.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
EndPoint endPoint = Mockito.mock(EndPoint.class);
|
||||
VolumeInfo volumeInfo = Mockito.mock(VolumeInfo.class);
|
||||
DataStore dataStore = Mockito.mock(DataStore.class, Mockito.withSettings().extraInterfaces(StoragePool.class));
|
||||
ClvmPoolManager clvmPoolManager = injectMockedClvmPoolManager();
|
||||
|
||||
Long hostId = 123L;
|
||||
Long volumeId = 456L;
|
||||
String volumeUuid = "test-volume-uuid";
|
||||
|
||||
Mockito.when(endPoint.getId()).thenReturn(hostId);
|
||||
Mockito.when(volumeInfo.getDataStore()).thenReturn(dataStore);
|
||||
Mockito.when(volumeInfo.getId()).thenReturn(volumeId);
|
||||
Mockito.when(volumeInfo.getUuid()).thenReturn(volumeUuid);
|
||||
Mockito.when(volumeInfo.getPath()).thenReturn("test-volume-path");
|
||||
Mockito.when(((StoragePool) dataStore).getPoolType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(volumeId), Mockito.eq(volumeUuid),
|
||||
Mockito.anyString(), Mockito.any(StoragePool.class), Mockito.eq(true))).thenReturn(null);
|
||||
|
||||
method.invoke(strategy, endPoint, volumeInfo);
|
||||
|
||||
Mockito.verify(clvmPoolManager).setClvmLockHostId(volumeId, hostId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateLockHostForVolume_CLVM_NG_Pool_SetsLockHost() throws Exception {
|
||||
Method method = AncientDataMotionStrategy.class.getDeclaredMethod(
|
||||
"updateLockHostForVolume",
|
||||
EndPoint.class,
|
||||
DataObject.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
EndPoint endPoint = Mockito.mock(EndPoint.class);
|
||||
VolumeInfo volumeInfo = Mockito.mock(VolumeInfo.class);
|
||||
DataStore dataStore = Mockito.mock(DataStore.class, Mockito.withSettings().extraInterfaces(StoragePool.class));
|
||||
ClvmPoolManager clvmPoolManager = injectMockedClvmPoolManager();
|
||||
|
||||
Long hostId = 789L;
|
||||
Long volumeId = 101L;
|
||||
String volumeUuid = "test-clvm-ng-volume-uuid";
|
||||
|
||||
Mockito.when(endPoint.getId()).thenReturn(hostId);
|
||||
Mockito.when(volumeInfo.getDataStore()).thenReturn(dataStore);
|
||||
Mockito.when(volumeInfo.getId()).thenReturn(volumeId);
|
||||
Mockito.when(volumeInfo.getUuid()).thenReturn(volumeUuid);
|
||||
Mockito.when(volumeInfo.getPath()).thenReturn("test-clvm-ng-volume-path");
|
||||
Mockito.when(((StoragePool) dataStore).getPoolType()).thenReturn(Storage.StoragePoolType.CLVM_NG);
|
||||
Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(volumeId), Mockito.eq(volumeUuid),
|
||||
Mockito.anyString(), Mockito.any(StoragePool.class), Mockito.eq(true))).thenReturn(null);
|
||||
|
||||
try {
|
||||
method.invoke(strategy, endPoint, volumeInfo);
|
||||
} catch (InvocationTargetException e) {
|
||||
e.getCause().printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
|
||||
Mockito.verify(clvmPoolManager).setClvmLockHostId(volumeId, hostId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateLockHostForVolume_NonCLVMPool_DoesNotSetLockHost() throws Exception {
|
||||
Method method = AncientDataMotionStrategy.class.getDeclaredMethod(
|
||||
"updateLockHostForVolume",
|
||||
EndPoint.class,
|
||||
DataObject.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
EndPoint endPoint = Mockito.mock(EndPoint.class);
|
||||
VolumeInfo volumeInfo = Mockito.mock(VolumeInfo.class);
|
||||
// Create mock that implements both DataStore and StoragePool interfaces
|
||||
DataStore dataStore = Mockito.mock(DataStore.class, Mockito.withSettings().extraInterfaces(StoragePool.class));
|
||||
ClvmPoolManager clvmPoolManager = injectMockedClvmPoolManager();
|
||||
|
||||
Mockito.when(volumeInfo.getDataStore()).thenReturn(dataStore);
|
||||
Mockito.when(((StoragePool) dataStore).getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
|
||||
|
||||
method.invoke(strategy, endPoint, volumeInfo);
|
||||
|
||||
Mockito.verify(clvmPoolManager, never()).setClvmLockHostId(any(Long.class), any(Long.class));
|
||||
Mockito.verify(clvmPoolManager, never()).getClvmLockHostId(any(Long.class), any(String.class),
|
||||
any(String.class), any(StoragePool.class), Mockito.anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateLockHostForVolume_ExistingLockHost_DoesNotOverwrite() throws Exception {
|
||||
Method method = AncientDataMotionStrategy.class.getDeclaredMethod(
|
||||
"updateLockHostForVolume",
|
||||
EndPoint.class,
|
||||
DataObject.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
EndPoint endPoint = Mockito.mock(EndPoint.class);
|
||||
VolumeInfo volumeInfo = Mockito.mock(VolumeInfo.class);
|
||||
DataStore dataStore = Mockito.mock(DataStore.class, Mockito.withSettings().extraInterfaces(StoragePool.class));
|
||||
ClvmPoolManager clvmPoolManager = injectMockedClvmPoolManager();
|
||||
|
||||
Long hostId = 555L;
|
||||
Long existingHostId = 666L;
|
||||
Long volumeId = 777L;
|
||||
String volumeUuid = "existing-lock-volume-uuid";
|
||||
|
||||
Mockito.when(endPoint.getId()).thenReturn(hostId);
|
||||
Mockito.when(volumeInfo.getDataStore()).thenReturn(dataStore);
|
||||
Mockito.when(volumeInfo.getId()).thenReturn(volumeId);
|
||||
Mockito.when(volumeInfo.getUuid()).thenReturn(volumeUuid);
|
||||
Mockito.when(volumeInfo.getPath()).thenReturn("existing-lock-volume-path");
|
||||
Mockito.when(((StoragePool) dataStore).getPoolType()).thenReturn(Storage.StoragePoolType.CLVM);
|
||||
Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(volumeId), Mockito.eq(volumeUuid),
|
||||
Mockito.anyString(), Mockito.any(StoragePool.class), Mockito.eq(true))).thenReturn(existingHostId);
|
||||
|
||||
method.invoke(strategy, endPoint, volumeInfo);
|
||||
|
||||
Mockito.verify(clvmPoolManager, never()).setClvmLockHostId(any(Long.class), any(Long.class));
|
||||
Mockito.verify(clvmPoolManager).getClvmLockHostId(Mockito.eq(volumeId), Mockito.eq(volumeUuid),
|
||||
Mockito.anyString(), Mockito.any(StoragePool.class), Mockito.eq(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateLockHostForVolume_NullEndPoint_DoesNotSetLockHost() throws Exception {
|
||||
Method method = AncientDataMotionStrategy.class.getDeclaredMethod(
|
||||
"updateLockHostForVolume",
|
||||
EndPoint.class,
|
||||
DataObject.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
VolumeInfo volumeInfo = Mockito.mock(VolumeInfo.class);
|
||||
ClvmPoolManager clvmPoolManager = injectMockedClvmPoolManager();
|
||||
|
||||
method.invoke(strategy, null, volumeInfo);
|
||||
|
||||
Mockito.verify(clvmPoolManager, never()).setClvmLockHostId(any(Long.class), any(Long.class));
|
||||
Mockito.verify(clvmPoolManager, never()).getClvmLockHostId(any(Long.class), any(String.class),
|
||||
any(String.class), any(StoragePool.class), Mockito.anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateLockHostForVolume_NonVolumeDataObject_DoesNotSetLockHost() throws Exception {
|
||||
Method method = AncientDataMotionStrategy.class.getDeclaredMethod(
|
||||
"updateLockHostForVolume",
|
||||
EndPoint.class,
|
||||
DataObject.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
EndPoint endPoint = Mockito.mock(EndPoint.class);
|
||||
SnapshotInfo snapshotInfo = Mockito.mock(SnapshotInfo.class);
|
||||
ClvmPoolManager clvmPoolManager = injectMockedClvmPoolManager();
|
||||
|
||||
method.invoke(strategy, endPoint, snapshotInfo);
|
||||
|
||||
Mockito.verify(clvmPoolManager, never()).setClvmLockHostId(any(Long.class), any(Long.class));
|
||||
Mockito.verify(clvmPoolManager, never()).getClvmLockHostId(any(Long.class), any(String.class),
|
||||
any(String.class), any(StoragePool.class), Mockito.anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateLockHostForVolume_NullPool_DoesNotSetLockHost() throws Exception {
|
||||
Method method = AncientDataMotionStrategy.class.getDeclaredMethod(
|
||||
"updateLockHostForVolume",
|
||||
EndPoint.class,
|
||||
DataObject.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
EndPoint endPoint = Mockito.mock(EndPoint.class);
|
||||
VolumeInfo volumeInfo = Mockito.mock(VolumeInfo.class);
|
||||
ClvmPoolManager clvmPoolManager = injectMockedClvmPoolManager();
|
||||
|
||||
method.invoke(strategy, endPoint, volumeInfo);
|
||||
|
||||
Mockito.verify(clvmPoolManager, never()).setClvmLockHostId(any(Long.class), any(Long.class));
|
||||
Mockito.verify(clvmPoolManager, never()).getClvmLockHostId(any(Long.class), any(String.class),
|
||||
any(String.class), any(StoragePool.class), Mockito.anyBoolean());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,6 +168,8 @@ public class KvmNonManagedStorageSystemDataMotionTest {
|
|||
supportedTypes.add(StoragePoolType.Filesystem);
|
||||
supportedTypes.add(StoragePoolType.NetworkFilesystem);
|
||||
supportedTypes.add(StoragePoolType.SharedMountPoint);
|
||||
supportedTypes.add(StoragePoolType.CLVM);
|
||||
supportedTypes.add(StoragePoolType.CLVM_NG);
|
||||
|
||||
return supportedTypes.contains(storagePoolType);
|
||||
}
|
||||
|
|
@ -505,6 +507,8 @@ public class KvmNonManagedStorageSystemDataMotionTest {
|
|||
supportedTypes.add(StoragePoolType.Filesystem);
|
||||
supportedTypes.add(StoragePoolType.NetworkFilesystem);
|
||||
supportedTypes.add(StoragePoolType.SharedMountPoint);
|
||||
supportedTypes.add(StoragePoolType.CLVM);
|
||||
supportedTypes.add(StoragePoolType.CLVM_NG);
|
||||
|
||||
for (StoragePoolType poolType : StoragePoolType.values()) {
|
||||
boolean isSupported = kvmNonManagedStorageDataMotionStrategy.supportStoragePoolType(poolType);
|
||||
|
|
|
|||
|
|
@ -369,4 +369,286 @@ public class StorageSystemDataMotionStrategyTest {
|
|||
|
||||
assertFalse(strategy.isStoragePoolTypeInList(StoragePoolType.SharedMountPoint, listTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updateMigrateDiskInfoForBlockDevice with CLVM destination pool
|
||||
* Should set driver type to RAW for CLVM
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateMigrateDiskInfoForBlockDevice_ClvmDestination() {
|
||||
MigrateCommand.MigrateDiskInfo originalDiskInfo = new MigrateCommand.MigrateDiskInfo(
|
||||
"serial123",
|
||||
MigrateCommand.MigrateDiskInfo.DiskType.FILE,
|
||||
MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
|
||||
MigrateCommand.MigrateDiskInfo.Source.FILE,
|
||||
"/source/path",
|
||||
null
|
||||
);
|
||||
|
||||
StoragePoolVO destStoragePool = new StoragePoolVO();
|
||||
destStoragePool.setPoolType(StoragePoolType.CLVM);
|
||||
|
||||
MigrateCommand.MigrateDiskInfo updatedDiskInfo = strategy.updateMigrateDiskInfoForBlockDevice(
|
||||
originalDiskInfo, destStoragePool);
|
||||
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, updatedDiskInfo.getDiskType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.RAW, updatedDiskInfo.getDriverType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.DEV, updatedDiskInfo.getSource());
|
||||
Assert.assertEquals("serial123", updatedDiskInfo.getSerialNumber());
|
||||
Assert.assertEquals("/source/path", updatedDiskInfo.getSourceText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updateMigrateDiskInfoForBlockDevice with CLVM_NG destination pool
|
||||
* Should set driver type to QCOW2 for CLVM_NG
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateMigrateDiskInfoForBlockDevice_ClvmNgDestination() {
|
||||
MigrateCommand.MigrateDiskInfo originalDiskInfo = new MigrateCommand.MigrateDiskInfo(
|
||||
"serial456",
|
||||
MigrateCommand.MigrateDiskInfo.DiskType.FILE,
|
||||
MigrateCommand.MigrateDiskInfo.DriverType.RAW,
|
||||
MigrateCommand.MigrateDiskInfo.Source.FILE,
|
||||
"/source/path",
|
||||
"/backing/path"
|
||||
);
|
||||
|
||||
StoragePoolVO destStoragePool = new StoragePoolVO();
|
||||
destStoragePool.setPoolType(StoragePoolType.CLVM_NG);
|
||||
|
||||
MigrateCommand.MigrateDiskInfo updatedDiskInfo = strategy.updateMigrateDiskInfoForBlockDevice(
|
||||
originalDiskInfo, destStoragePool);
|
||||
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, updatedDiskInfo.getDiskType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.QCOW2, updatedDiskInfo.getDriverType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.DEV, updatedDiskInfo.getSource());
|
||||
Assert.assertEquals("serial456", updatedDiskInfo.getSerialNumber());
|
||||
Assert.assertEquals("/source/path", updatedDiskInfo.getSourceText());
|
||||
Assert.assertEquals("/backing/path", updatedDiskInfo.getBackingStoreText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updateMigrateDiskInfoForBlockDevice with non-CLVM destination pool
|
||||
* Should return original DiskInfo unchanged
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateMigrateDiskInfoForBlockDevice_NonClvmDestination() {
|
||||
MigrateCommand.MigrateDiskInfo originalDiskInfo = new MigrateCommand.MigrateDiskInfo(
|
||||
"serial789",
|
||||
MigrateCommand.MigrateDiskInfo.DiskType.FILE,
|
||||
MigrateCommand.MigrateDiskInfo.DriverType.QCOW2,
|
||||
MigrateCommand.MigrateDiskInfo.Source.FILE,
|
||||
"/source/path",
|
||||
null
|
||||
);
|
||||
|
||||
StoragePoolVO destStoragePool = new StoragePoolVO();
|
||||
destStoragePool.setPoolType(StoragePoolType.NetworkFilesystem);
|
||||
|
||||
MigrateCommand.MigrateDiskInfo updatedDiskInfo = strategy.updateMigrateDiskInfoForBlockDevice(
|
||||
originalDiskInfo, destStoragePool);
|
||||
|
||||
Assert.assertSame(originalDiskInfo, updatedDiskInfo);
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.FILE, updatedDiskInfo.getDiskType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.QCOW2, updatedDiskInfo.getDriverType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.FILE, updatedDiskInfo.getSource());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test supportStoragePoolType with CLVM and CLVM_NG types
|
||||
*/
|
||||
@Test
|
||||
public void testSupportStoragePoolType_ClvmTypes() {
|
||||
assertTrue(strategy.supportStoragePoolType(StoragePoolType.CLVM, StoragePoolType.CLVM, StoragePoolType.CLVM_NG));
|
||||
assertTrue(strategy.supportStoragePoolType(StoragePoolType.CLVM_NG, StoragePoolType.CLVM, StoragePoolType.CLVM_NG));
|
||||
|
||||
assertFalse(strategy.supportStoragePoolType(StoragePoolType.CLVM));
|
||||
assertFalse(strategy.supportStoragePoolType(StoragePoolType.CLVM_NG));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test configureMigrateDiskInfo with CLVM destination
|
||||
*/
|
||||
@Test
|
||||
public void testConfigureMigrateDiskInfo_ForClvm() {
|
||||
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
|
||||
Mockito.doReturn("/dev/vg/volume-path").when(srcVolumeInfo).getPath();
|
||||
|
||||
MigrateCommand.MigrateDiskInfo migrateDiskInfo = strategy.configureMigrateDiskInfo(
|
||||
srcVolumeInfo, "/dev/vg/dest-path", null);
|
||||
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, migrateDiskInfo.getDiskType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.RAW, migrateDiskInfo.getDriverType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.DEV, migrateDiskInfo.getSource());
|
||||
Assert.assertEquals("/dev/vg/dest-path", migrateDiskInfo.getSourceText());
|
||||
Assert.assertEquals("/dev/vg/volume-path", migrateDiskInfo.getSerialNumber());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test configureMigrateDiskInfo with CLVM_NG destination and backing file
|
||||
*/
|
||||
@Test
|
||||
public void testConfigureMigrateDiskInfo_ForClvmNgWithBacking() {
|
||||
VolumeObject srcVolumeInfo = Mockito.spy(new VolumeObject());
|
||||
Mockito.doReturn("/dev/vg/volume-path").when(srcVolumeInfo).getPath();
|
||||
|
||||
MigrateCommand.MigrateDiskInfo migrateDiskInfo = strategy.configureMigrateDiskInfo(
|
||||
srcVolumeInfo, "/dev/vg/dest-path", "/dev/vg/backing-template");
|
||||
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DiskType.BLOCK, migrateDiskInfo.getDiskType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.DriverType.RAW, migrateDiskInfo.getDriverType());
|
||||
Assert.assertEquals(MigrateCommand.MigrateDiskInfo.Source.DEV, migrateDiskInfo.getSource());
|
||||
Assert.assertEquals("/dev/vg/dest-path", migrateDiskInfo.getSourceText());
|
||||
Assert.assertEquals("/dev/vg/backing-template", migrateDiskInfo.getBackingStoreText());
|
||||
Assert.assertEquals("/dev/vg/volume-path", migrateDiskInfo.getSerialNumber());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test isStoragePoolTypeInList with CLVM types
|
||||
*/
|
||||
@Test
|
||||
public void testIsStoragePoolTypeInList_WithClvmTypes() {
|
||||
StoragePoolType[] clvmTypes = new StoragePoolType[] {
|
||||
StoragePoolType.CLVM,
|
||||
StoragePoolType.CLVM_NG,
|
||||
StoragePoolType.Filesystem
|
||||
};
|
||||
|
||||
assertTrue(strategy.isStoragePoolTypeInList(StoragePoolType.CLVM, clvmTypes));
|
||||
assertTrue(strategy.isStoragePoolTypeInList(StoragePoolType.CLVM_NG, clvmTypes));
|
||||
assertTrue(strategy.isStoragePoolTypeInList(StoragePoolType.Filesystem, clvmTypes));
|
||||
assertFalse(strategy.isStoragePoolTypeInList(StoragePoolType.NetworkFilesystem, clvmTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test supportStoragePoolType with mixed CLVM and NFS types
|
||||
*/
|
||||
@Test
|
||||
public void testSupportStoragePoolType_MixedClvmAndNfs() {
|
||||
assertTrue(strategy.supportStoragePoolType(
|
||||
StoragePoolType.CLVM,
|
||||
StoragePoolType.CLVM,
|
||||
StoragePoolType.CLVM_NG,
|
||||
StoragePoolType.NetworkFilesystem
|
||||
));
|
||||
|
||||
assertTrue(strategy.supportStoragePoolType(
|
||||
StoragePoolType.CLVM_NG,
|
||||
StoragePoolType.CLVM,
|
||||
StoragePoolType.CLVM_NG,
|
||||
StoragePoolType.NetworkFilesystem
|
||||
));
|
||||
|
||||
assertTrue(strategy.supportStoragePoolType(
|
||||
StoragePoolType.NetworkFilesystem,
|
||||
StoragePoolType.CLVM,
|
||||
StoragePoolType.CLVM_NG
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test internalCanHandle with CLVM source and managed destination
|
||||
*/
|
||||
@Test
|
||||
public void testInternalCanHandle_ClvmSourceManagedDestination() {
|
||||
VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
|
||||
Mockito.doReturn(0L).when(volumeInfo).getPoolId();
|
||||
|
||||
DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
|
||||
|
||||
Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
|
||||
volumeMap.put(volumeInfo, ds);
|
||||
|
||||
StoragePoolVO sourcePool = Mockito.spy(new StoragePoolVO());
|
||||
Mockito.lenient().doReturn(StoragePoolType.CLVM).when(sourcePool).getPoolType();
|
||||
Mockito.doReturn(true).when(sourcePool).isManaged();
|
||||
|
||||
Mockito.doReturn(sourcePool).when(primaryDataStoreDao).findById(0L);
|
||||
|
||||
StrategyPriority result = strategy.internalCanHandle(
|
||||
volumeMap, new HostVO("srcHostUuid"), new HostVO("destHostUuid"));
|
||||
|
||||
Assert.assertEquals(StrategyPriority.HIGHEST, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test internalCanHandle with CLVM_NG source and managed destination
|
||||
*/
|
||||
@Test
|
||||
public void testInternalCanHandle_ClvmNgSourceManagedDestination() {
|
||||
VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
|
||||
Mockito.doReturn(0L).when(volumeInfo).getPoolId();
|
||||
|
||||
DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
|
||||
|
||||
Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
|
||||
volumeMap.put(volumeInfo, ds);
|
||||
|
||||
StoragePoolVO sourcePool = Mockito.spy(new StoragePoolVO());
|
||||
Mockito.lenient().doReturn(StoragePoolType.CLVM_NG).when(sourcePool).getPoolType();
|
||||
Mockito.doReturn(true).when(sourcePool).isManaged();
|
||||
|
||||
Mockito.doReturn(sourcePool).when(primaryDataStoreDao).findById(0L);
|
||||
|
||||
StrategyPriority result = strategy.internalCanHandle(
|
||||
volumeMap, new HostVO("srcHostUuid"), new HostVO("destHostUuid"));
|
||||
|
||||
Assert.assertEquals(StrategyPriority.HIGHEST, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test internalCanHandle with both CLVM source and CLVM_NG destination
|
||||
*/
|
||||
@Test
|
||||
public void testInternalCanHandle_ClvmToClvmNg() {
|
||||
VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
|
||||
Mockito.doReturn(0L).when(volumeInfo).getPoolId();
|
||||
|
||||
DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
|
||||
|
||||
Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
|
||||
volumeMap.put(volumeInfo, ds);
|
||||
|
||||
StoragePoolVO sourcePool = Mockito.spy(new StoragePoolVO());
|
||||
Mockito.lenient().doReturn(StoragePoolType.CLVM).when(sourcePool).getPoolType();
|
||||
Mockito.doReturn(true).when(sourcePool).isManaged();
|
||||
|
||||
StoragePoolVO destPool = Mockito.spy(new StoragePoolVO());
|
||||
Mockito.lenient().doReturn(StoragePoolType.CLVM_NG).when(destPool).getPoolType();
|
||||
|
||||
Mockito.doReturn(sourcePool).when(primaryDataStoreDao).findById(0L);
|
||||
|
||||
StrategyPriority result = strategy.internalCanHandle(
|
||||
volumeMap, new HostVO("srcHostUuid"), new HostVO("destHostUuid"));
|
||||
|
||||
Assert.assertEquals(StrategyPriority.HIGHEST, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test internalCanHandle with CLVM_NG to CLVM migration
|
||||
*/
|
||||
@Test
|
||||
public void testInternalCanHandle_ClvmNgToClvm() {
|
||||
VolumeObject volumeInfo = Mockito.spy(new VolumeObject());
|
||||
Mockito.doReturn(0L).when(volumeInfo).getPoolId();
|
||||
|
||||
DataStore ds = Mockito.spy(new PrimaryDataStoreImpl());
|
||||
|
||||
Map<VolumeInfo, DataStore> volumeMap = new HashMap<>();
|
||||
volumeMap.put(volumeInfo, ds);
|
||||
|
||||
StoragePoolVO sourcePool = Mockito.spy(new StoragePoolVO());
|
||||
Mockito.lenient().doReturn(StoragePoolType.CLVM_NG).when(sourcePool).getPoolType();
|
||||
Mockito.doReturn(true).when(sourcePool).isManaged();
|
||||
|
||||
StoragePoolVO destPool = Mockito.spy(new StoragePoolVO());
|
||||
Mockito.lenient().doReturn(StoragePoolType.CLVM).when(destPool).getPoolType();
|
||||
|
||||
Mockito.doReturn(sourcePool).when(primaryDataStoreDao).findById(0L);
|
||||
|
||||
StrategyPriority result = strategy.internalCanHandle(
|
||||
volumeMap, new HostVO("srcHostUuid"), new HostVO("destHostUuid"));
|
||||
|
||||
Assert.assertEquals(StrategyPriority.HIGHEST, result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ import com.cloud.event.UsageEventUtils;
|
|||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.hypervisor.Hypervisor;
|
||||
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
||||
import com.cloud.storage.clvm.ClvmPoolManager;
|
||||
import com.cloud.storage.CreateSnapshotPayload;
|
||||
import com.cloud.storage.DataStoreRole;
|
||||
import com.cloud.storage.Snapshot;
|
||||
|
|
@ -643,6 +644,10 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
|
|||
return StrategyPriority.DEFAULT;
|
||||
}
|
||||
|
||||
if (isSnapshotStoredOnSecondaryForCLVMVolume(snapshot, volumeVO)) {
|
||||
return StrategyPriority.DEFAULT;
|
||||
}
|
||||
|
||||
return StrategyPriority.CANT_HANDLE;
|
||||
}
|
||||
if (zoneId != null && SnapshotOperation.DELETE.equals(op)) {
|
||||
|
|
@ -691,4 +696,32 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
|
|||
dataStoreMgr.getStoreZoneId(s.getDataStoreId(), s.getRole()), volumeVO.getDataCenterId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a CLVM volume snapshot is stored on secondary storage in the same zone.
|
||||
* CLVM snapshots are backed up to secondary storage and removed from primary storage.
|
||||
*/
|
||||
protected boolean isSnapshotStoredOnSecondaryForCLVMVolume(Snapshot snapshot, VolumeVO volumeVO) {
|
||||
if (volumeVO == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Long poolId = volumeVO.getPoolId();
|
||||
if (poolId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StoragePool pool = (StoragePool) dataStoreMgr.getDataStore(poolId, DataStoreRole.Primary);
|
||||
if (pool == null || !ClvmPoolManager.isClvmPoolType(pool.getPoolType())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<SnapshotDataStoreVO> snapshotStores = snapshotStoreDao.listReadyBySnapshot(snapshot.getId(), DataStoreRole.Image);
|
||||
if (CollectionUtils.isEmpty(snapshotStores)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return snapshotStores.stream().anyMatch(s -> Objects.equals(
|
||||
dataStoreMgr.getStoreZoneId(s.getDataStoreId(), s.getRole()), volumeVO.getDataCenterId()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue