Skip to content

Add support to clone existing offerings and update them#12357

Draft
Pearl1594 wants to merge 24 commits intomainfrom
clone-edit-existing-offerings
Draft

Add support to clone existing offerings and update them#12357
Pearl1594 wants to merge 24 commits intomainfrom
clone-edit-existing-offerings

Conversation

@Pearl1594
Copy link
Contributor

Description

This PR adds APIs to clone existing offerings - service, system, network, VPC, backup and update certain parameters of the existing offering.

Types of changes

  • Breaking change (fix or feature that would cause existing functionality to change)
  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • Build/CI
  • Test (unit or integration test code)

Feature/Enhancement Scale or Bug Severity

Feature/Enhancement Scale

  • Major
  • Minor

Bug Severity

  • BLOCKER
  • Critical
  • Major
  • Minor
  • Trivial

Screenshots (if appropriate):

How Has This Been Tested?

How did you try to break this feature and the system with this change?

@codecov
Copy link

codecov bot commented Dec 31, 2025

Codecov Report

❌ Patch coverage is 49.60904% with 580 lines in your changes missing coverage. Please review.
✅ Project coverage is 17.97%. Comparing base (a5b6bc3) to head (6ea054c).

Files with missing lines Patch % Lines
...ain/java/com/cloud/network/vpc/VpcManagerImpl.java 0.48% 205 Missing and 2 partials ⚠️
.../cloud/configuration/ConfigurationManagerImpl.java 70.46% 102 Missing and 64 partials ⚠️
.../command/admin/network/NetworkOfferingBaseCmd.java 29.64% 129 Missing and 11 partials ⚠️
...rg/apache/cloudstack/backup/BackupManagerImpl.java 48.88% 11 Missing and 12 partials ⚠️
...i/command/admin/backup/CloneBackupOfferingCmd.java 66.00% 17 Missing ⚠️
...i/command/admin/offering/CloneDiskOfferingCmd.java 0.00% 13 Missing ⚠️
...ack/api/command/admin/vpc/CloneVPCOfferingCmd.java 60.00% 10 Missing ⚠️
...in/java/com/cloud/server/ManagementServerImpl.java 0.00% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main   #12357      +/-   ##
============================================
+ Coverage     17.89%   17.97%   +0.07%     
- Complexity    16092    16284     +192     
============================================
  Files          5936     5950      +14     
  Lines        532734   534978    +2244     
  Branches      65165    65703     +538     
============================================
+ Hits          95347    96157     +810     
- Misses       426711   427999    +1288     
- Partials      10676    10822     +146     
Flag Coverage Δ
uitests 3.56% <ø> (-0.13%) ⬇️
unittests 19.12% <49.60%> (+0.12%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Pearl1594 Pearl1594 force-pushed the clone-edit-existing-offerings branch from 62368f3 to 3787aaa Compare January 6, 2026 22:39
@DaanHoogland DaanHoogland added this to the 4.23.0 milestone Jan 7, 2026
@Pearl1594 Pearl1594 force-pushed the clone-edit-existing-offerings branch from a5a9eb4 to bb2e90d Compare January 8, 2026 21:49
@Pearl1594 Pearl1594 force-pushed the clone-edit-existing-offerings branch from bb2e90d to 7fd4567 Compare January 8, 2026 21:52
@boring-cyborg boring-cyborg bot added component:marvin Python Warning... Python code Ahead! labels Jan 9, 2026
@github-actions
Copy link

This pull request has merge conflicts. Dear author, please fix the conflicts and sync your branch with the base branch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you make the new if and for blocks separte methods (with clear names please?

@Pearl1594 Pearl1594 force-pushed the clone-edit-existing-offerings branch from a7266b2 to cc4ada0 Compare January 21, 2026 15:26
@Pearl1594 Pearl1594 force-pushed the clone-edit-existing-offerings branch from 5f5147d to 3511525 Compare January 21, 2026 15:44
@Pearl1594 Pearl1594 force-pushed the clone-edit-existing-offerings branch from 1f5796d to 10333df Compare January 21, 2026 22:02
@Pearl1594 Pearl1594 requested a review from ustcweizhou February 2, 2026 13:26
@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 2, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
36.9% Coverage on New Code (required ≥ 40%)

See analysis details on SonarQube Cloud

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces “clone offering” functionality across the CloudStack API and UI, enabling users to clone existing offerings (compute/service, disk, network, VPC, backup) while overriding selected parameters. It also refactors network/VPC service-capability param building into shared UI helpers and wires the new actions into the offerings UI.

Changes:

  • Added new API commands and server implementations for cloning multiple offering types (plus event types and service interfaces).
  • Added new UI clone views (compute/disk/backup shown here) and integrated clone actions into the offerings section config.
  • Refactored network/VPC service-capability parameter construction into a shared composable and extracted disk offering form into a reusable component.

Reviewed changes

Copilot reviewed 41 out of 41 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
ui/src/views/offering/CloneDiskOffering.vue New UI flow to clone disk offerings using the shared DiskOfferingForm.
ui/src/views/offering/CloneComputeOffering.vue New UI flow to clone compute/service offerings (includes prefill + submit logic).
ui/src/views/offering/CloneBackupOffering.vue New UI flow to clone backup offerings (zone/provider offering selection + async job polling).
ui/src/views/offering/AddVpcOffering.vue Refactors VPC service capability param building into a shared helper.
ui/src/views/offering/AddNetworkOffering.vue Refactors network service capability param building into a shared helper.
ui/src/views/offering/AddDiskOffering.vue Refactors disk offering creation to use the new shared DiskOfferingForm component.
ui/src/config/section/offering.js Adds new “Clone * Offering” actions in the UI offerings sections.
ui/src/composables/useServiceCapabilityParams.js New shared helper(s) to build serviceCapability/serviceProvider param sets for network/VPC offerings.
ui/src/components/offering/DiskOfferingForm.vue New reusable disk offering form component extracted from the previous view.
ui/src/components/CheckBoxSelectPair.vue Adds support for setting a default select value.
ui/public/locales/en.json Adds i18n strings for clone offering actions and messages.
server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java Adds tests asserting cloned backup offering domain handling.
server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java Updates test mock to support new clone APIs and new base cmd type for network offering creation.
server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java Adds tests around reflection helpers and some clone-related scaffolding.
server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java Implements backup offering clone behavior and registers the command.
server/src/main/java/com/cloud/server/ManagementServerImpl.java Registers new clone commands in management server command list.
server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java Implements VPC offering clone logic, including service/provider/capability reconstruction.
api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CloneVpcOfferingCmdTest.java Adds unit tests for the new clone VPC offering command.
api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CloneServiceOfferingCmdTest.java Adds unit tests for the new clone service offering command.
api/src/test/java/org/apache/cloudstack/api/command/admin/network/CloneNetworkOfferingCmdTest.java Adds unit tests for the new clone network offering command.
api/src/test/java/org/apache/cloudstack/api/command/admin/backup/CloneBackupOfferingCmdTest.java Adds unit tests for the new clone backup offering command.
api/src/main/java/org/apache/cloudstack/backup/BackupManager.java Adds cloneBackupOffering to the backup manager interface.
api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java Adjusts supported-services behavior for external providers (validation moved server-side).
api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CloneVPCOfferingCmd.java New API command for cloning VPC offerings.
api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CloneServiceOfferingCmd.java New API command for cloning service offerings.
api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CloneDiskOfferingCmd.java New API command for cloning disk offerings.
api/src/main/java/org/apache/cloudstack/api/command/admin/network/NetworkOfferingBaseCmd.java New base command consolidating shared network-offering params/logic.
api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java Refactors create network offering cmd to extend NetworkOfferingBaseCmd.
api/src/main/java/org/apache/cloudstack/api/command/admin/network/CloneNetworkOfferingCmd.java New API command for cloning network offerings.
api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java Makes backupManager protected and annotates domainIds param with since.
api/src/main/java/org/apache/cloudstack/api/command/admin/backup/CloneBackupOfferingCmd.java New API command for cloning backup offerings (domainIds resolver support).
api/src/main/java/org/apache/cloudstack/api/ApiConstants.java Adds SOURCE_OFFERING_ID constant.
api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java Adds cloneVPCOffering to VPC provisioning service interface.
api/src/main/java/com/cloud/event/EventTypes.java Adds new event types for offering/backup offering clone.
api/src/main/java/com/cloud/configuration/ConfigurationService.java Adds clone methods and generalizes createNetworkOffering to accept the new base cmd.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


@APICommand(name = "cloneBackupOffering",
description = "Clones a backup offering from an existing offering",
responseObject = BackupOfferingResponse.class, since = "4.14.0",
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API annotation says since = "4.14.0", but this clone API is introduced alongside the other clone APIs marked since = "4.23.0" and even has a parameter marked since = "4.23.0". Consider aligning the command-level since value to the actual release where this API is added.

Suggested change
responseObject = BackupOfferingResponse.class, since = "4.14.0",
responseObject = BackupOfferingResponse.class, since = "4.23.0",

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +73
export default {
name: 'CreateComputeOffering',
mixins: [mixinForm],
components: {
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The component is registered with name 'CreateComputeOffering', but this view is for cloning. This breaks devtools/component identification and may affect caching/keep-alive behavior if other components rely on the name. Rename it to 'CloneComputeOffering' (or another clone-specific name).

Copilot uses AI. Check for mistakes.
Comment on lines +636 to +641
}

params.sourceofferingid = this.resource.id

postAPI('cloneServiceOffering', params).then(json => {
const message = this.isSystem
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.loading is never set to true before calling postAPI('cloneServiceOffering', ...), so the submit-guard (if (this.loading) return) does not prevent double submits and the UI spinner never activates. Set this.loading = true immediately before the API call (and keep the existing finally that resets it).

Copilot uses AI. Check for mistakes.
required = true, description = "The ID of the source backup offering to clone from")
private Long sourceOfferingId;

@Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, required = false,
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name is marked required = false, but the manager uses cmd.getName() for uniqueness checks and to persist the cloned offering. If name is omitted, this can lead to NPEs/invalid DB rows. Make the parameter required (or implement a safe default like <sourceName>-clone).

Suggested change
@Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, required = false,
@Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, required = true,

Copilot uses AI. Check for mistakes.
}
normalizedDropServices.add(service.getName());
}
finalServices.removeAll(dropServices);
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normalizedDropServices is computed to canonicalize service names, but the code removes dropServices (raw input) from finalServices instead of removing normalizedDropServices. This can fail to drop services when callers provide non-canonical casing/names, and it leaves normalizedDropServices unused. Use finalServices.removeAll(normalizedDropServices).

Suggested change
finalServices.removeAll(dropServices);
finalServices.removeAll(normalizedDropServices);

Copilot uses AI. Check for mistakes.
Comment on lines +404 to +408
}).then(json => {
this.diskOfferings = json.listdiskofferingsresponse.diskoffering || []
if (this.selectedDiskOfferingId === '') {
this.selectedDiskOfferingId = this.diskOfferings[0].id || ''
}
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.diskOfferings[0].id will throw if listDiskOfferings returns an empty list. Guard with a length check (or use optional chaining) before reading the first element.

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +85
<a-switch v-model:checked="form.encryptdisk" :checked="encryptdisk" @change="val => { encryptdisk = val }" />
</a-form-item>
<a-form-item name="disksizestrictness" ref="disksizestrictness">
<template #label>
<tooltip-label :title="$t('label.disksizestrictness')" :tooltip="apiParams.disksizestrictness.description" />
</template>
<a-switch v-model:checked="form.disksizestrictness" :checked="disksizestrictness" @change="val => { disksizestrictness = val }" />
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The switch uses both v-model:checked and an explicit :checked bound to a different data property (encryptdisk). This can desynchronize the UI from form.encryptdisk (especially when initialValues sets a non-default). Prefer binding only via v-model:checked and remove the separate :checked/@change state mirror.

Suggested change
<a-switch v-model:checked="form.encryptdisk" :checked="encryptdisk" @change="val => { encryptdisk = val }" />
</a-form-item>
<a-form-item name="disksizestrictness" ref="disksizestrictness">
<template #label>
<tooltip-label :title="$t('label.disksizestrictness')" :tooltip="apiParams.disksizestrictness.description" />
</template>
<a-switch v-model:checked="form.disksizestrictness" :checked="disksizestrictness" @change="val => { disksizestrictness = val }" />
<a-switch v-model:checked="form.encryptdisk" />
</a-form-item>
<a-form-item name="disksizestrictness" ref="disksizestrictness">
<template #label>
<tooltip-label :title="$t('label.disksizestrictness')" :tooltip="apiParams.disksizestrictness.description" />
</template>
<a-switch v-model:checked="form.disksizestrictness" />

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +85
<a-switch v-model:checked="form.encryptdisk" :checked="encryptdisk" @change="val => { encryptdisk = val }" />
</a-form-item>
<a-form-item name="disksizestrictness" ref="disksizestrictness">
<template #label>
<tooltip-label :title="$t('label.disksizestrictness')" :tooltip="apiParams.disksizestrictness.description" />
</template>
<a-switch v-model:checked="form.disksizestrictness" :checked="disksizestrictness" @change="val => { disksizestrictness = val }" />
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as encrypt switch: v-model:checked plus explicit :checked backed by disksizestrictness can cause the displayed value to diverge from form.disksizestrictness / initialValues. Bind the switch to a single source of truth (the form model).

Suggested change
<a-switch v-model:checked="form.encryptdisk" :checked="encryptdisk" @change="val => { encryptdisk = val }" />
</a-form-item>
<a-form-item name="disksizestrictness" ref="disksizestrictness">
<template #label>
<tooltip-label :title="$t('label.disksizestrictness')" :tooltip="apiParams.disksizestrictness.description" />
</template>
<a-switch v-model:checked="form.disksizestrictness" :checked="disksizestrictness" @change="val => { disksizestrictness = val }" />
<a-switch v-model:checked="form.encryptdisk" />
</a-form-item>
<a-form-item name="disksizestrictness" ref="disksizestrictness">
<template #label>
<tooltip-label :title="$t('label.disksizestrictness')" :tooltip="apiParams.disksizestrictness.description" />
</template>
<a-switch v-model:checked="form.disksizestrictness" />

Copilot uses AI. Check for mistakes.
}
},
handleWriteCacheTypeChange (val) {
this.form.writeCacheType = val
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleWriteCacheTypeChange writes to form.writeCacheType, but the form field is writecachetype (lowercase). As written, this handler doesn't update the value that gets validated/submitted. Either remove the handler (since v-model already updates form.writecachetype) or update the correct property name.

Suggested change
this.form.writeCacheType = val
this.form.writecachetype = val

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants