diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 00000000000..3c632f8ba53 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,20 @@ +# 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. + +[codespell] +ignore-words = .github/linters/codespell.txt +skip = systemvm/agent/noVNC/*,ui/package.json,ui/package-lock.json,ui/public/js/less.min.js,ui/public/locales/*.json,server/src/test/java/org/apache/cloudstack/network/ssl/CertServiceTest.java,test/integration/smoke/test_ssl_offloading.py diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 41b307863fc..cef74aafb4b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -22,8 +22,19 @@ version: 2 updates: - - package-ecosystem: "maven" # See documentation for possible values - directory: "/" # Location of package manifests + - package-ecosystem: "github-actions" + directory: "/" + open-pull-requests-limit: 2 + schedule: + interval: "weekly" + groups: + github-actions-dependencies: + patterns: + - "*" + cooldown: + default-days: 7 + - package-ecosystem: "maven" + directory: "/" schedule: interval: "daily" cooldown: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 84020f4a6b0..4c33a131343 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 17 uses: actions/setup-java@v5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6957d3f5446..df60179ceb5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -217,7 +217,7 @@ jobs: smoke/test_list_volumes"] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index fbd944a758f..88b10ac9178 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -32,7 +32,7 @@ jobs: name: codecov runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 74e59aa821d..31a8746b85a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,7 +35,7 @@ jobs: language: ["actions"] steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: diff --git a/.github/workflows/docker-cloudstack-simulator.yml b/.github/workflows/docker-cloudstack-simulator.yml index af6cbf49f5e..8d23ac449dd 100644 --- a/.github/workflows/docker-cloudstack-simulator.yml +++ b/.github/workflows/docker-cloudstack-simulator.yml @@ -47,7 +47,7 @@ jobs: - name: Set Docker repository name run: echo "DOCKER_REPOSITORY=apache" >> $GITHUB_ENV - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set ACS version run: echo "ACS_VERSION=$(grep '' pom.xml | head -2 | tail -1 | cut -d'>' -f2 |cut -d'<' -f1)" >> $GITHUB_ENV diff --git a/.github/workflows/main-sonar-check.yml b/.github/workflows/main-sonar-check.yml index 224ea2cde80..7ccd6600ab9 100644 --- a/.github/workflows/main-sonar-check.yml +++ b/.github/workflows/main-sonar-check.yml @@ -32,7 +32,7 @@ jobs: name: Main Sonar JaCoCo Build runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 11fe5c06881..895a597659d 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Check Out - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install run: | python -m pip install --upgrade pip diff --git a/.github/workflows/rat.yml b/.github/workflows/rat.yml index d71f4b0852d..21b8e197d82 100644 --- a/.github/workflows/rat.yml +++ b/.github/workflows/rat.yml @@ -30,7 +30,7 @@ jobs: build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up JDK 17 uses: actions/setup-java@v5 with: diff --git a/.github/workflows/sonar-check.yml b/.github/workflows/sonar-check.yml index 31fb671cc58..9f5c3a194bc 100644 --- a/.github/workflows/sonar-check.yml +++ b/.github/workflows/sonar-check.yml @@ -33,7 +33,7 @@ jobs: name: Sonar JaCoCo Coverage runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: ref: "refs/pull/${{ github.event.number }}/merge" fetch-depth: 0 diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml index 56b04a6f9c9..4580b6bbd5d 100644 --- a/.github/workflows/ui.yml +++ b/.github/workflows/ui.yml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Node uses: actions/setup-node@v5 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cf6f8d39027..755ae125edf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -162,17 +162,15 @@ repos: - id: forbid-submodules - id: mixed-line-ending - id: trailing-whitespace - files: ^(LICENSE|NOTICE)$|\.(bat|cfg|cs|css|gitignore|header|in|install|java|md|properties|py|rb|rc|sh|sql|te|template|txt|ucls|vue|xml|xsl|yaml|yml)$|^cloud-cli/bindir/cloud-tool$|^debian/changelog$ + files: ^(LICENSE|NOTICE)$|README$|\.(bat|cfg|config|cs|css|erb|gitignore|header|in|install|java|md|properties|py|rb|rc|sh|sql|svg|te|template|txt|ucls|vue|xml|xsl|yaml|yml)$|^cloud-cli/bindir/cloud-tool$|^debian/changelog$ args: [--markdown-linebreak-ext=md] exclude: ^services/console-proxy/rdpconsole/src/test/doc/freerdp-debug-log\.txt$ - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 + rev: v2.4.2 hooks: - id: codespell name: run codespell description: Check spelling with codespell - args: [--ignore-words=.github/linters/codespell.txt] - exclude: ^systemvm/agent/noVNC/|^ui/package\.json$|^ui/package-lock\.json$|^ui/public/js/less\.min\.js$|^ui/public/locales/.*[^n].*\.json$|^server/src/test/java/org/apache/cloudstack/network/ssl/CertServiceTest.java$|^test/integration/smoke/test_ssl_offloading.py$ - repo: https://github.com/pycqa/flake8 rev: 7.0.0 hooks: @@ -186,7 +184,7 @@ repos: description: check Markdown files with markdownlint args: [--config=.github/linters/.markdown-lint.yml] types: [markdown] - files: \.(md|mdown|markdown)$ + files: \.md$ - repo: https://github.com/adrienverge/yamllint rev: v1.37.1 hooks: diff --git a/api/src/main/java/com/cloud/agent/api/to/NicTO.java b/api/src/main/java/com/cloud/agent/api/to/NicTO.java index ca95fcfd679..2ed7d9f9a20 100644 --- a/api/src/main/java/com/cloud/agent/api/to/NicTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/NicTO.java @@ -33,6 +33,7 @@ public class NicTO extends NetworkTO { boolean dpdkEnabled; Integer mtu; Long networkId; + boolean enabled; String networkSegmentName; @@ -154,4 +155,12 @@ public class NicTO extends NetworkTO { public void setNetworkSegmentName(String networkSegmentName) { this.networkSegmentName = networkSegmentName; } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } } diff --git a/api/src/main/java/com/cloud/user/AccountService.java b/api/src/main/java/com/cloud/user/AccountService.java index 30919e5b782..4145e2b89eb 100644 --- a/api/src/main/java/com/cloud/user/AccountService.java +++ b/api/src/main/java/com/cloud/user/AccountService.java @@ -138,6 +138,8 @@ public interface AccountService { Long finalizeAccountId(String accountName, Long domainId, Long projectId, boolean enabledOnly); + Long finalizeAccountId(Long accountId, String accountName, Long domainId, Long projectId); + /** * returns the user account object for a given user id * @param userId user id diff --git a/api/src/main/java/com/cloud/vm/Nic.java b/api/src/main/java/com/cloud/vm/Nic.java index 9143fe053b7..3722e5769c9 100644 --- a/api/src/main/java/com/cloud/vm/Nic.java +++ b/api/src/main/java/com/cloud/vm/Nic.java @@ -167,4 +167,6 @@ public interface Nic extends Identity, InternalIdentity { String getIPv6Address(); Integer getMtu(); + + boolean isEnabled(); } diff --git a/api/src/main/java/com/cloud/vm/NicProfile.java b/api/src/main/java/com/cloud/vm/NicProfile.java index a0c80ceb1bf..d3ed7b4b87d 100644 --- a/api/src/main/java/com/cloud/vm/NicProfile.java +++ b/api/src/main/java/com/cloud/vm/NicProfile.java @@ -52,6 +52,7 @@ public class NicProfile implements InternalIdentity, Serializable { boolean defaultNic; Integer networkRate; boolean isSecurityGroupEnabled; + boolean enabled; Integer orderIndex; @@ -87,6 +88,7 @@ public class NicProfile implements InternalIdentity, Serializable { broadcastType = network.getBroadcastDomainType(); trafficType = network.getTrafficType(); format = nic.getAddressFormat(); + enabled = nic.isEnabled(); iPv4Address = nic.getIPv4Address(); iPv4Netmask = nic.getIPv4Netmask(); @@ -414,6 +416,14 @@ public class NicProfile implements InternalIdentity, Serializable { this.ipv4AllocationRaceCheck = ipv4AllocationRaceCheck; } + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + // // OTHER METHODS // diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java b/api/src/main/java/com/cloud/vm/UserVmService.java index 01f11b73cd4..67aa0534a5f 100644 --- a/api/src/main/java/com/cloud/vm/UserVmService.java +++ b/api/src/main/java/com/cloud/vm/UserVmService.java @@ -40,6 +40,7 @@ import org.apache.cloudstack.api.command.user.vm.ScaleVMCmd; import org.apache.cloudstack.api.command.user.vm.StartVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateDefaultNicForVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; +import org.apache.cloudstack.api.command.user.vm.UpdateVmNicCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVmNicIpCmd; import org.apache.cloudstack.api.command.user.vm.UpgradeVMCmd; import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; @@ -152,6 +153,8 @@ public interface UserVmService { */ UserVm updateNicIpForVirtualMachine(UpdateVmNicIpCmd cmd); + UserVm updateVirtualMachineNic(UpdateVmNicCmd cmd); + UserVm recoverVirtualMachine(RecoverVMCmd cmd) throws ResourceAllocationException; /** @@ -524,6 +527,7 @@ public interface UserVmService { * @param userId user ID * @param serviceOffering service offering for the imported VM * @param sshPublicKey ssh key for the imported VM + * @param guestOsId guest OS ID for the imported VM (if not passed, then the guest OS of the template will be used) * @param hostName the name for the imported VM * @param hypervisorType hypervisor type for the imported VM * @param customParameters details for the imported VM @@ -533,7 +537,7 @@ public interface UserVmService { * @throws InsufficientCapacityException in case of errors */ UserVm importVM(final DataCenter zone, final Host host, final VirtualMachineTemplate template, final String instanceNameInternal, final String displayName, final Account owner, final String userData, final Account caller, final Boolean isDisplayVm, final String keyboard, - final long accountId, final long userId, final ServiceOffering serviceOffering, final String sshPublicKey, + final long accountId, final long userId, final ServiceOffering serviceOffering, final String sshPublicKey, final Long guestOsId, final String hostName, final HypervisorType hypervisorType, final Map customParameters, final VirtualMachine.PowerState powerState, final LinkedHashMap> networkNicMap) throws InsufficientCapacityException; diff --git a/api/src/main/java/com/cloud/vm/VirtualMachine.java b/api/src/main/java/com/cloud/vm/VirtualMachine.java index d244de7115e..41c9a864c9d 100644 --- a/api/src/main/java/com/cloud/vm/VirtualMachine.java +++ b/api/src/main/java/com/cloud/vm/VirtualMachine.java @@ -124,6 +124,9 @@ public interface VirtualMachine extends RunningOn, ControlledEntity, Partition, s_fsm.addTransition(new Transition(State.Stopping, VirtualMachine.Event.StopRequested, State.Stopping, null)); s_fsm.addTransition(new Transition(State.Stopping, VirtualMachine.Event.AgentReportShutdowned, State.Stopped, Arrays.asList(new Impact[]{Impact.USAGE}))); s_fsm.addTransition(new Transition(State.Expunging, VirtualMachine.Event.OperationFailed, State.Expunging,null)); + // Note: In addition to the Stopped -> Error transition for failed VM creation, + // a VM can also transition from Expunging to Error on OperationFailedToError. + s_fsm.addTransition(new Transition(State.Expunging, VirtualMachine.Event.OperationFailedToError, State.Error, null)); s_fsm.addTransition(new Transition(State.Expunging, VirtualMachine.Event.ExpungeOperation, State.Expunging,null)); s_fsm.addTransition(new Transition(State.Error, VirtualMachine.Event.DestroyRequested, State.Expunging, null)); s_fsm.addTransition(new Transition(State.Error, VirtualMachine.Event.ExpungeOperation, State.Expunging, null)); diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 4a4e8fb2877..afc7407c150 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -20,6 +20,7 @@ public class ApiConstants { public static final String ACCOUNT = "account"; public static final String ACCOUNTS = "accounts"; public static final String ACCOUNT_NAME = "accountname"; + public static final String ACCOUNT_STATE_TO_SHOW = "accountstatetoshow"; public static final String ACCOUNT_TYPE = "accounttype"; public static final String ACCOUNT_ID = "accountid"; public static final String ACCOUNT_IDS = "accountids"; diff --git a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java index 338c1e738df..b0738cf78e1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -343,6 +343,8 @@ public interface ResponseGenerator { UserVm findUserVmById(Long vmId); + UserVm findUserVmByNicId(Long nicId); + Volume findVolumeById(Long volumeId); Account findAccountByNameDomain(String accountName, Long domainId); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java index f7940460d6c..50ccfbd69c5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java @@ -30,6 +30,7 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GuestOSResponse; import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.NetworkResponse; import org.apache.cloudstack.api.response.StoragePoolResponse; @@ -171,6 +172,13 @@ public class ImportVmCmd extends ImportUnmanagedInstanceCmd { description = "(only for importing VMs from VMware to KVM) optional - if true, forces virt-v2v conversions to write directly on the provided storage pool (avoid using temporary conversion pool).") private Boolean forceConvertToPool; + @Parameter(name = ApiConstants.OS_ID, + type = CommandType.UUID, + entityType = GuestOSResponse.class, + since = "4.22.1", + description = "(only for importing VMs from VMware to KVM) optional - the ID of the guest OS for the imported VM.") + private Long guestOsId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -268,6 +276,10 @@ public class ImportVmCmd extends ImportUnmanagedInstanceCmd { return BooleanUtils.toBooleanDefaultIfNull(forceConvertToPool, false); } + public Long getGuestOsId() { + return guestOsId; + } + @Override public String getEventDescription() { String vmName = getName(); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCmd.java index 4ff2ebf0a66..d558e4c4f24 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCmd.java @@ -45,6 +45,11 @@ public class ListGuestOsCmd extends BaseListCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSResponse.class, description = "List by OS type ID") private Long id; + @Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, + entityType = GuestOSResponse.class, since = "4.22.1", + description = "Comma separated list of OS types") + private List ids; + @Parameter(name = ApiConstants.OS_CATEGORY_ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, description = "List by OS Category ID") private Long osCategoryId; @@ -63,6 +68,10 @@ public class ListGuestOsCmd extends BaseListCmd { return id; } + public List getIds() { + return ids; + } + public Long getOsCategoryId() { return osCategoryId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVmNicCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVmNicCmd.java new file mode 100644 index 00000000000..363273a4670 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVmNicCmd.java @@ -0,0 +1,95 @@ +// 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.vm; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.NicResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; +import com.cloud.user.Account; +import com.cloud.uservm.UserVm; + +import java.util.ArrayList; +import java.util.EnumSet; + +@APICommand(name = "updateVmNic", description = "Updates the specified VM NIC", responseObject = NicResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + authorized = { RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User }) +public class UpdateVmNicCmd extends BaseAsyncCmd { + + @ACL + @Parameter(name = ApiConstants.NIC_ID, type = CommandType.UUID, entityType = NicResponse.class, required = true, description = "NIC ID") + private Long nicId; + + @Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, description = "If true, sets the NIC state to UP; otherwise, sets the NIC state to DOWN") + private Boolean enabled; + + public Long getNicId() { + return nicId; + } + + public Boolean isEnabled() { + return enabled; + } + + @Override + public String getEventType() { + return EventTypes.EVENT_NIC_UPDATE; + } + + @Override + public String getEventDescription() { + return String.format("Updating NIC %s.", getResourceUuid(ApiConstants.NIC_ID)); + } + + @Override + public long getEntityOwnerId() { + UserVm vm = _responseGenerator.findUserVmByNicId(nicId); + if (vm == null) { + return Account.ACCOUNT_ID_SYSTEM; + } + return vm.getAccountId(); + } + + @Override + public void execute() { + CallContext.current().setEventDetails(String.format("NIC ID: %s", getResourceUuid(ApiConstants.NIC_ID))); + + UserVm result = _userVmService.updateVirtualMachineNic(this); + + ArrayList dc = new ArrayList<>(); + dc.add(ApiConstants.VMDetails.valueOf("nics")); + EnumSet details = EnumSet.copyOf(dc); + + if (result != null){ + UserVmResponse response = _responseGenerator.createUserVmResponse(ResponseObject.ResponseView.Restricted, "virtualmachine", details, result).get(0); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update NIC from VM."); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java index 11930bcbab6..9b5d25aa0dd 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java @@ -46,7 +46,6 @@ public class CreateStaticRouteCmd extends BaseAsyncCreateCmd { @Parameter(name = ApiConstants.GATEWAY_ID, type = CommandType.UUID, entityType = PrivateGatewayResponse.class, - required = true, description = "The gateway ID we are creating static route for. Mutually exclusive with the nexthop parameter") private Long gatewayId; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java index 95b5fb401d2..3f6451d1c66 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java @@ -146,6 +146,10 @@ public class NicResponse extends BaseResponse { @Param(description = "Public IP address associated with this NIC via Static NAT rule") private String publicIp; + @SerializedName(ApiConstants.ENABLED) + @Param(description = "whether the NIC is enabled or not") + private Boolean isEnabled; + @SerializedName("dnsrecordurl") @Param(description = "Public IP address associated with this NIC via Static NAT rule") private String dnsRecordUrl; @@ -421,6 +425,14 @@ public class NicResponse extends BaseResponse { this.publicIp = publicIp; } + public Boolean getEnabled() { + return isEnabled; + } + + public void setEnabled(Boolean enabled) { + isEnabled = enabled; + } + public void setDnsRecordUrl(String dnsRecordUrl) { this.dnsRecordUrl = dnsRecordUrl; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UnmanagedInstanceResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UnmanagedInstanceResponse.java index 195323b741d..3f2dc045e87 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UnmanagedInstanceResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UnmanagedInstanceResponse.java @@ -51,6 +51,14 @@ public class UnmanagedInstanceResponse extends BaseResponse { @Param(description = "The name of the host to which Instance belongs") private String hostName; + @SerializedName(ApiConstants.HYPERVISOR) + @Param(description = "The hypervisor to which Instance belongs") + private String hypervisor; + + @SerializedName(ApiConstants.HYPERVISOR_VERSION) + @Param(description = "The hypervisor version of the host to which Instance belongs") + private String hypervisorVersion; + @SerializedName(ApiConstants.POWER_STATE) @Param(description = "The power state of the Instance") private String powerState; @@ -140,6 +148,22 @@ public class UnmanagedInstanceResponse extends BaseResponse { this.hostName = hostName; } + public String getHypervisor() { + return hypervisor; + } + + public void setHypervisor(String hypervisor) { + this.hypervisor = hypervisor; + } + + public String getHypervisorVersion() { + return hypervisorVersion; + } + + public void setHypervisorVersion(String hypervisorVersion) { + this.hypervisorVersion = hypervisorVersion; + } + public String getPowerState() { return powerState; } diff --git a/api/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageService.java b/api/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageService.java index 5f69f3e46e7..78c598272e0 100644 --- a/api/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageService.java +++ b/api/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageService.java @@ -25,11 +25,13 @@ import org.apache.cloudstack.api.command.admin.volume.ImportVolumeCmd; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.VolumeForImportResponse; import org.apache.cloudstack.api.response.VolumeResponse; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; import java.util.Arrays; import java.util.List; -public interface VolumeImportUnmanageService extends PluggableService { +public interface VolumeImportUnmanageService extends PluggableService, Configurable { List SUPPORTED_HYPERVISORS = Arrays.asList(Hypervisor.HypervisorType.KVM, Hypervisor.HypervisorType.VMware); @@ -37,6 +39,15 @@ public interface VolumeImportUnmanageService extends PluggableService { List SUPPORTED_STORAGE_POOL_TYPES_FOR_KVM = Arrays.asList(Storage.StoragePoolType.NetworkFilesystem, Storage.StoragePoolType.Filesystem, Storage.StoragePoolType.RBD); + ConfigKey AllowImportVolumeWithBackingFile = new ConfigKey<>(Boolean.class, + "allow.import.volume.with.backing.file", + "Advanced", + "false", + "If enabled, allows QCOW2 volumes with backing files to be imported or unmanaged", + true, + ConfigKey.Scope.Global, + null); + ListResponse listVolumesForImport(ListVolumesForImportCmd cmd); VolumeResponse importVolume(ImportVolumeCmd cmd); diff --git a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java index bba97dff71c..cbb7c4de698 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java +++ b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java @@ -55,6 +55,9 @@ public class UnmanagedInstanceTO { private String hostName; + private String hypervisorType; + private String hostHypervisorVersion; + private List disks; private List nics; @@ -168,6 +171,22 @@ public class UnmanagedInstanceTO { this.hostName = hostName; } + public String getHypervisorType() { + return hypervisorType; + } + + public void setHypervisorType(String hypervisorType) { + this.hypervisorType = hypervisorType; + } + + public String getHostHypervisorVersion() { + return hostHypervisorVersion; + } + + public void setHostHypervisorVersion(String hostHypervisorVersion) { + this.hostHypervisorVersion = hostHypervisorVersion; + } + public List getDisks() { return disks; } diff --git a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManager.java b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManager.java index b6233b9c270..e1963313eb6 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManager.java +++ b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManager.java @@ -26,6 +26,8 @@ import static com.cloud.hypervisor.Hypervisor.HypervisorType.VMware; public interface UnmanagedVMsManager extends VmImportService, UnmanageVMService, PluggableService, Configurable { + String VM_IMPORT_DEFAULT_TEMPLATE_NAME = "system-default-vm-import-dummy-template.iso"; + String KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME = "kvm-default-vm-import-dummy-template"; ConfigKey UnmanageVMPreserveNic = new ConfigKey<>("Advanced", Boolean.class, "unmanage.vm.preserve.nics", "false", "If set to true, do not remove VM nics (and its MAC addresses) when unmanaging a VM, leaving them allocated but not reserved. " + "If set to false, nics are removed and MAC addresses can be reassigned", true, ConfigKey.Scope.Zone); diff --git a/core/src/main/java/com/cloud/agent/api/UpdateVmNicAnswer.java b/core/src/main/java/com/cloud/agent/api/UpdateVmNicAnswer.java new file mode 100644 index 00000000000..0af6d7fa3a3 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/UpdateVmNicAnswer.java @@ -0,0 +1,27 @@ +// 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; + +public class UpdateVmNicAnswer extends Answer { + public UpdateVmNicAnswer() { + } + + public UpdateVmNicAnswer(UpdateVmNicCommand cmd, boolean success, String result) { + super(cmd, success, result); + } +} diff --git a/core/src/main/java/com/cloud/agent/api/UpdateVmNicCommand.java b/core/src/main/java/com/cloud/agent/api/UpdateVmNicCommand.java new file mode 100644 index 00000000000..a5c5faf32fe --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/UpdateVmNicCommand.java @@ -0,0 +1,51 @@ +// 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; + +public class UpdateVmNicCommand extends Command { + + String nicMacAddress; + String instanceName; + Boolean enabled; + + @Override + public boolean executeInSequence() { + return true; + } + + protected UpdateVmNicCommand() { + } + + public UpdateVmNicCommand(String nicMacAdderss, String instanceName, Boolean enabled) { + this.nicMacAddress = nicMacAdderss; + this.instanceName = instanceName; + this.enabled = enabled; + } + + public String getNicMacAddress() { + return nicMacAddress; + } + + public String getVmName() { + return instanceName; + } + + public Boolean isEnabled() { + return enabled; + } +} diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java index 9390efb28e6..2ef19a5ea98 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -231,6 +231,8 @@ public interface VirtualMachineManager extends Manager { Boolean updateDefaultNicForVM(VirtualMachine vm, Nic nic, Nic defaultNic); + boolean updateVmNic(VirtualMachine vm, Nic nic, Boolean enabled) throws ResourceUnavailableException; + /** * @param vm * @param network diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index e8796fb0252..8fe4c19a7e1 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -153,6 +153,8 @@ import com.cloud.agent.api.UnPlugNicAnswer; import com.cloud.agent.api.UnPlugNicCommand; import com.cloud.agent.api.UnmanageInstanceCommand; import com.cloud.agent.api.UnregisterVMCommand; +import com.cloud.agent.api.UpdateVmNicAnswer; +import com.cloud.agent.api.UpdateVmNicCommand; import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmNetworkStatsEntry; import com.cloud.agent.api.VmStatsEntry; @@ -6270,6 +6272,80 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac _jobMgr.marshallResultObject(result)); } + @Override + public boolean updateVmNic(VirtualMachine vm, Nic nic, Boolean enabled) { + Outcome outcome = updateVmNicThroughJobQueue(vm, nic, enabled); + + retrieveVmFromJobOutcome(outcome, vm.getUuid(), "updateVmNic"); + + try { + Object jobResult = retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + if (jobResult instanceof Boolean) { + return BooleanUtils.isTrue((Boolean) jobResult); + } + } catch (ResourceUnavailableException | InsufficientCapacityException ex) { + throw new CloudRuntimeException(String.format("Exception while updating VM [%s] NIC. Check the logs for more information.", vm.getUuid())); + } + throw new CloudRuntimeException("Unexpected job execution result."); + } + + private boolean orchestrateUpdateVmNic(final VirtualMachine vm, final Nic nic, final Boolean enabled) throws ResourceUnavailableException { + if (vm.getState() == State.Running) { + try { + UpdateVmNicCommand updateVmNicCmd = new UpdateVmNicCommand(nic.getMacAddress(), vm.getName(), enabled); + Commands cmds = new Commands(Command.OnError.Stop); + cmds.addCommand("updatevmnic", updateVmNicCmd); + + _agentMgr.send(vm.getHostId(), cmds); + + UpdateVmNicAnswer updateVmNicAnswer = cmds.getAnswer(UpdateVmNicAnswer.class); + if (updateVmNicAnswer == null || !updateVmNicAnswer.getResult()) { + logger.warn("Unable to update VM %s NIC [{}].", vm.getName(), nic.getUuid()); + return false; + } + } catch (final OperationTimedoutException e) { + throw new AgentUnavailableException(String.format("Unable to update NIC %s for VM %s.", nic.getUuid(), vm.getUuid()), vm.getHostId(), e); + } + } + + NicVO nicVo = _nicsDao.findById(nic.getId()); + nicVo.setEnabled(enabled); + _nicsDao.persist(nicVo); + + return true; + } + + public Outcome updateVmNicThroughJobQueue(final VirtualMachine vm, final Nic nic, final Boolean isNicEnabled) { + Long vmId = vm.getId(); + String commandName = VmWorkUpdateNic.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkUpdateNic workInfo = new VmWorkUpdateNic(newVmWorkJobAndInfo.second(), nic.getId(), isNicEnabled); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmJobVirtualMachineOutcome(workJob, vmId); + } + + @ReflectionUse + private Pair orchestrateUpdateVmNic(final VmWorkUpdateNic work) throws Exception { + VMInstanceVO vm = findVmById(work.getVmId()); + final NicVO nic = _entityMgr.findById(NicVO.class, work.getNicId()); + if (nic == null) { + throw new CloudRuntimeException(String.format("Unable to find NIC with ID %s.", work.getNicId())); + } + final boolean result = orchestrateUpdateVmNic(vm, nic, work.isEnabled()); + return new Pair<>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(result)); + } + private Pair findClusterAndHostIdForVmFromVolumes(long vmId) { Long clusterId = null; Long hostId = null; diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkUpdateNic.java b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkUpdateNic.java new file mode 100644 index 00000000000..1c63cf34a19 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkUpdateNic.java @@ -0,0 +1,38 @@ +// 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; + +public class VmWorkUpdateNic extends VmWork { + private static final long serialVersionUID = -8957066627929113278L; + + Long nicId; + Boolean enabled; + + public VmWorkUpdateNic(VmWork vmWork, Long nicId, Boolean enabled) { + super(vmWork); + this.nicId = nicId; + this.enabled = enabled; + } + + public Long getNicId() { + return nicId; + } + + public Boolean isEnabled() { + return enabled; + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index b6f31414655..77ea965e50a 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -2326,7 +2326,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra for (final NetworkElement element : networkElements) { if (providersToImplement.contains(element.getProvider())) { if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { - throw new CloudRuntimeException(String.format("Service provider %s either doesn't exist or is not enabled in physical network: %s", element.getProvider().getName(), _physicalNetworkDao.findById(network.getPhysicalNetworkId()))); + throw new CloudRuntimeException(String.format("Service provider %s either doesn't exist or is not enabled in physical network: %s", + element.getProvider().getName(), _physicalNetworkDao.findById(network.getPhysicalNetworkId()))); } if (element instanceof NetworkMigrationResponder) { if (!((NetworkMigrationResponder) element).prepareMigration(profile, network, vm, dest, context)) { @@ -2633,6 +2634,10 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); guru.deallocate(network, profile, vm); + if (nic.getReservationStrategy() == Nic.ReservationStrategy.Create) { + applyProfileToNicForRelease(nic, profile); + _nicDao.update(nic.getId(), nic); + } if (BooleanUtils.isNotTrue(preserveNics)) { _nicDao.remove(nic.getId()); } diff --git a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java index 56d971bbe01..1afa0d22dcc 100644 --- a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java @@ -262,7 +262,7 @@ public class DomainDaoImpl extends GenericDaoBase implements Dom SearchCriteria sc = DomainPairSearch.create(); sc.setParameters("id", parentId, childId); - List domainPair = listBy(sc); + List domainPair = listIncludingRemovedBy(sc); if ((domainPair != null) && (domainPair.size() == 2)) { DomainVO d1 = domainPair.get(0); diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDao.java index 1a2b098c40a..f24f3f3b67c 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDao.java @@ -35,7 +35,7 @@ public interface GuestOSDao extends GenericDao { List listByDisplayName(String displayName); - Pair, Integer> listGuestOSByCriteria(Long startIndex, Long pageSize, Long id, Long osCategoryId, String description, String keyword, Boolean forDisplay); + Pair, Integer> listGuestOSByCriteria(Long startIndex, Long pageSize, List ids, Long osCategoryId, String description, String keyword, Boolean forDisplay); List listIdsByCategoryId(final long categoryId); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDaoImpl.java index 881be207c1a..09f8772fb59 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDaoImpl.java @@ -125,12 +125,12 @@ public class GuestOSDaoImpl extends GenericDaoBase implements G return listBy(sc); } - public Pair, Integer> listGuestOSByCriteria(Long startIndex, Long pageSize, Long id, Long osCategoryId, String description, String keyword, Boolean forDisplay) { + public Pair, Integer> listGuestOSByCriteria(Long startIndex, Long pageSize, List ids, Long osCategoryId, String description, String keyword, Boolean forDisplay) { final Filter searchFilter = new Filter(GuestOSVO.class, "displayName", true, startIndex, pageSize); final SearchCriteria sc = createSearchCriteria(); - if (id != null) { - sc.addAnd("id", SearchCriteria.Op.EQ, id); + if (CollectionUtils.isNotEmpty(ids)) { + sc.addAnd("id", SearchCriteria.Op.IN, ids.toArray()); } if (osCategoryId != null) { diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java index a03b94faa79..717e3e782f2 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java @@ -166,4 +166,12 @@ public interface VolumeDao extends GenericDao, StateDao implements Vol private final SearchBuilder storeAndInstallPathSearch; private final SearchBuilder volumeIdSearch; protected GenericSearchBuilder CountByAccount; + protected final SearchBuilder ExternalUuidSearch; protected GenericSearchBuilder primaryStorageSearch; protected GenericSearchBuilder primaryStorageSearch2; protected GenericSearchBuilder secondaryStorageSearch; @@ -459,6 +460,10 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol CountByAccount.and("idNIN", CountByAccount.entity().getId(), Op.NIN); CountByAccount.done(); + ExternalUuidSearch = createSearchBuilder(); + ExternalUuidSearch.and("externalUuid", ExternalUuidSearch.entity().getExternalUuid(), Op.EQ); + ExternalUuidSearch.done(); + primaryStorageSearch = createSearchBuilder(SumCount.class); primaryStorageSearch.select("sum", Func.SUM, primaryStorageSearch.entity().getSize()); primaryStorageSearch.and("accountId", primaryStorageSearch.entity().getAccountId(), Op.EQ); @@ -934,4 +939,11 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol sc.and(sc.entity().getState(), SearchCriteria.Op.IN, (Object[]) states); return sc.find(); } + + @Override + public VolumeVO findByExternalUuid(String externalUuid) { + SearchCriteria sc = ExternalUuidSearch.create(); + sc.setParameters("externalUuid", externalUuid); + return findOneBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42200to42210.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42200to42210.java index c9610f7b9ff..813351d7534 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42200to42210.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42200to42210.java @@ -16,6 +16,14 @@ // under the License. package com.cloud.upgrade.dao; +import org.apache.cloudstack.vm.UnmanagedVMsManager; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.List; + public class Upgrade42200to42210 extends DbUpgradeAbstractImpl implements DbUpgrade, DbUpgradeSystemVmTemplate { @Override @@ -27,4 +35,47 @@ public class Upgrade42200to42210 extends DbUpgradeAbstractImpl implements DbUpgr public String getUpgradedVersion() { return "4.22.1.0"; } + + @Override + public void performDataMigration(Connection conn) { + removeDuplicateKVMImportTemplates(conn); + } + + private void removeDuplicateKVMImportTemplates(Connection conn) { + List templateIds = new ArrayList<>(); + try (PreparedStatement selectStmt = conn.prepareStatement(String.format("SELECT id FROM cloud.vm_template WHERE name='%s' ORDER BY id ASC", UnmanagedVMsManager.KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME))) { + ResultSet rs = selectStmt.executeQuery(); + while (rs.next()) { + templateIds.add(rs.getLong(1)); + } + + if (templateIds.size() <= 1) { + return; + } + + logger.info("Removing duplicate template " + UnmanagedVMsManager.KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME + " entries"); + Long firstTemplateId = templateIds.get(0); + + String updateTemplateSql = "UPDATE cloud.vm_instance SET vm_template_id = ? WHERE vm_template_id = ?"; + String deleteTemplateSql = "DELETE FROM cloud.vm_template WHERE id = ?"; + + try (PreparedStatement updateTemplateStmt = conn.prepareStatement(updateTemplateSql); + PreparedStatement deleteTemplateStmt = conn.prepareStatement(deleteTemplateSql)) { + for (int i = 1; i < templateIds.size(); i++) { + Long duplicateTemplateId = templateIds.get(i); + + // Update VM references + updateTemplateStmt.setLong(1, firstTemplateId); + updateTemplateStmt.setLong(2, duplicateTemplateId); + updateTemplateStmt.executeUpdate(); + + // Delete duplicate dummy template + deleteTemplateStmt.setLong(1, duplicateTemplateId); + deleteTemplateStmt.executeUpdate(); + } + } + } catch (Exception e) { + logger.warn("Failed to remove duplicate template " + UnmanagedVMsManager.KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME + " entries", e); + } + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/NicVO.java b/engine/schema/src/main/java/com/cloud/vm/NicVO.java index 6c569e22dd9..65946b8d821 100644 --- a/engine/schema/src/main/java/com/cloud/vm/NicVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/NicVO.java @@ -131,6 +131,9 @@ public class NicVO implements Nic { @Column(name = "mtu") Integer mtu; + @Column(name = "enabled") + boolean enabled; + @Transient transient String nsxLogicalSwitchUuid; @@ -143,6 +146,7 @@ public class NicVO implements Nic { this.networkId = configurationId; this.state = State.Allocated; this.vmType = vmType; + this.enabled = true; } @Override @@ -397,6 +401,14 @@ public class NicVO implements Nic { this.nsxLogicalSwitchPortUuid = nsxLogicalSwitchPortUuid; } + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + @Override public int hashCode() { return new HashCodeBuilder(17, 31).append(id).toHashCode(); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java index cdf903407c1..bbb2b4f3a88 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java @@ -67,7 +67,7 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase searchFilteringStoreIdEqStoreRoleEqStateNeqRefCntNeq; protected SearchBuilder searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq; private SearchBuilder stateSearch; - private SearchBuilder idStateNeqSearch; + private SearchBuilder idStateNinSearch; protected SearchBuilder snapshotVOSearch; private SearchBuilder snapshotCreatedSearch; private SearchBuilder dataStoreAndInstallPathSearch; @@ -146,10 +146,10 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase findBySnapshotIdWithNonDestroyedState(long snapshotId) { - SearchCriteria sc = idStateNeqSearch.create(); + SearchCriteria sc = idStateNinSearch.create(); sc.setParameters(SNAPSHOT_ID, snapshotId); sc.setParameters(STATE, State.Destroyed.name()); return listBy(sc); @@ -488,7 +488,7 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase findBySnapshotIdAndNotInDestroyedHiddenState(long snapshotId) { - SearchCriteria sc = idStateNeqSearch.create(); + SearchCriteria sc = idStateNinSearch.create(); sc.setParameters(SNAPSHOT_ID, snapshotId); sc.setParameters(STATE, State.Destroyed.name(), State.Hidden.name()); return listBy(sc); diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42200to42210-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-42200to42210-cleanup.sql index 54baf226ac4..505c8ef5715 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42200to42210-cleanup.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42200to42210-cleanup.sql @@ -18,3 +18,5 @@ --; -- Schema upgrade cleanup from 4.22.0.0 to 4.22.1.0 --; + +DROP VIEW IF EXISTS `cloud`.`account_netstats_view`; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42200to42210.sql b/engine/schema/src/main/resources/META-INF/db/schema-42200to42210.sql index a8a3d3f7bd4..2326a855f32 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42200to42210.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42200to42210.sql @@ -33,3 +33,5 @@ UPDATE `cloud`.`alert` SET type = 34 WHERE name = 'ALERT.VR.PRIVATE.IFACE.MTU'; -- Update configuration 'kvm.ssh.to.agent' description and is_dynamic fields UPDATE `cloud`.`configuration` SET description = 'True if the management server will restart the agent service via SSH into the KVM hosts after or during maintenance operations', is_dynamic = 1 WHERE name = 'kvm.ssh.to.agent'; + +UPDATE `cloud`.`vm_template` SET guest_os_id = 99 WHERE name = 'kvm-default-vm-import-dummy-template'; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql index c3605538ed7..433c8a891ae 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -115,6 +115,9 @@ CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Resource Admin', 'deleteUserKey -- 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'' '); + -- ====================================================================== -- DNS Framework Schema diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.account_netstats_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.account_netstats_view.sql deleted file mode 100644 index 11193c465fd..00000000000 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.account_netstats_view.sql +++ /dev/null @@ -1,31 +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. - --- cloud.account_netstats_view source - - -DROP VIEW IF EXISTS `cloud`.`account_netstats_view`; - -CREATE VIEW `cloud`.`account_netstats_view` AS -select - `user_statistics`.`account_id` AS `account_id`, - (sum(`user_statistics`.`net_bytes_received`) + sum(`user_statistics`.`current_bytes_received`)) AS `bytesReceived`, - (sum(`user_statistics`.`net_bytes_sent`) + sum(`user_statistics`.`current_bytes_sent`)) AS `bytesSent` -from - `user_statistics` -group by - `user_statistics`.`account_id`; diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.account_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.account_view.sql index edc164c40cb..327c6c627e2 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.account_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.account_view.sql @@ -39,8 +39,8 @@ select `data_center`.`id` AS `data_center_id`, `data_center`.`uuid` AS `data_center_uuid`, `data_center`.`name` AS `data_center_name`, - `account_netstats_view`.`bytesReceived` AS `bytesReceived`, - `account_netstats_view`.`bytesSent` AS `bytesSent`, + `account_netstats`.`bytesReceived` AS `bytesReceived`, + `account_netstats`.`bytesSent` AS `bytesSent`, `vmlimit`.`max` AS `vmLimit`, `vmcount`.`count` AS `vmTotal`, `runningvm`.`vmcount` AS `runningVms`, @@ -89,8 +89,15 @@ from `cloud`.`domain` ON account.domain_id = domain.id left join `cloud`.`data_center` ON account.default_zone_id = data_center.id - left join - `cloud`.`account_netstats_view` ON account.id = account_netstats_view.account_id + left join lateral ( + select + coalesce(sum(`user_statistics`.`net_bytes_received` + `user_statistics`.`current_bytes_received`), 0) AS `bytesReceived`, + coalesce(sum(`user_statistics`.`net_bytes_sent` + `user_statistics`.`current_bytes_sent`), 0) AS `bytesSent` + from + `cloud`.`user_statistics` + where + `user_statistics`.`account_id` = `account`.`id` + ) AS `account_netstats` ON TRUE left join `cloud`.`resource_limit` vmlimit ON account.id = vmlimit.account_id and vmlimit.type = 'user_vm' and vmlimit.tag IS NULL diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql index a12e02bcfdb..d5f17606cb4 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql @@ -76,6 +76,7 @@ select nics.broadcast_uri broadcast_uri, nics.isolation_uri isolation_uri, nics.mtu mtu, + nics.enabled is_nic_enabled, vpc.id vpc_id, vpc.uuid vpc_uuid, vpc.name vpc_name, diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql index ac2d007bd2b..5e1ff126002 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql @@ -141,6 +141,7 @@ SELECT `nics`.`mac_address` AS `mac_address`, `nics`.`broadcast_uri` AS `broadcast_uri`, `nics`.`isolation_uri` AS `isolation_uri`, + `nics`.`enabled` AS `is_nic_enabled`, `nic_details`.`value` AS `dns_record_url`, `vpc`.`id` AS `vpc_id`, `vpc`.`uuid` AS `vpc_uuid`, diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_summary_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_summary_view.sql new file mode 100644 index 00000000000..0646c3b6f91 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_summary_view.sql @@ -0,0 +1,48 @@ +-- 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. + +-- cloud_usage.quota_summary_view source + +-- Create view for quota summary +DROP VIEW IF EXISTS `cloud_usage`.`quota_summary_view`; +CREATE VIEW `cloud_usage`.`quota_summary_view` AS +SELECT + cloud_usage.quota_account.account_id AS account_id, + cloud_usage.quota_account.quota_balance AS quota_balance, + cloud_usage.quota_account.quota_balance_date AS quota_balance_date, + cloud_usage.quota_account.quota_enforce AS quota_enforce, + cloud_usage.quota_account.quota_min_balance AS quota_min_balance, + cloud_usage.quota_account.quota_alert_date AS quota_alert_date, + cloud_usage.quota_account.quota_alert_type AS quota_alert_type, + cloud_usage.quota_account.last_statement_date AS last_statement_date, + cloud.account.uuid AS account_uuid, + cloud.account.account_name AS account_name, + cloud.account.state AS account_state, + cloud.account.removed AS account_removed, + cloud.domain.id AS domain_id, + cloud.domain.uuid AS domain_uuid, + cloud.domain.name AS domain_name, + cloud.domain.path AS domain_path, + cloud.domain.removed AS domain_removed, + cloud.projects.uuid AS project_uuid, + cloud.projects.name AS project_name, + cloud.projects.removed AS project_removed +FROM + cloud_usage.quota_account + INNER JOIN cloud.account ON (cloud.account.id = cloud_usage.quota_account.account_id) + INNER JOIN cloud.domain ON (cloud.domain.id = cloud.account.domain_id) + LEFT JOIN cloud.projects ON (cloud.account.type = 5 AND cloud.account.id = cloud.projects.project_account_id); diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java index 275f41de43a..bcade3a371c 100644 --- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java @@ -148,8 +148,8 @@ import java.util.HashSet; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; -import static org.apache.cloudstack.vm.UnmanagedVMsManagerImpl.KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME; -import static org.apache.cloudstack.vm.UnmanagedVMsManagerImpl.VM_IMPORT_DEFAULT_TEMPLATE_NAME; +import static org.apache.cloudstack.vm.UnmanagedVMsManager.KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME; +import static org.apache.cloudstack.vm.UnmanagedVMsManager.VM_IMPORT_DEFAULT_TEMPLATE_NAME; public class StorageSystemDataMotionStrategy implements DataMotionStrategy { protected Logger logger = LogManager.getLogger(getClass()); diff --git a/framework/db/src/main/java/com/cloud/utils/db/SequenceFetcher.java b/framework/db/src/main/java/com/cloud/utils/db/SequenceFetcher.java index f902fda3bf1..a59b73e5cee 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/SequenceFetcher.java +++ b/framework/db/src/main/java/com/cloud/utils/db/SequenceFetcher.java @@ -64,7 +64,7 @@ public class SequenceFetcher { try { return future.get(); } catch (Exception e) { - logger.warn("Unable to get sequeunce for " + tg.table() + ":" + tg.pkColumnValue(), e); + logger.warn("Unable to get sequence for " + tg.table() + ":" + tg.pkColumnValue(), e); return null; } } diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAccountStateFilter.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAccountStateFilter.java new file mode 100644 index 00000000000..cc6ca203ef7 --- /dev/null +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAccountStateFilter.java @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.quota; + +import org.apache.commons.lang3.StringUtils; + +public enum QuotaAccountStateFilter { + ALL, ACTIVE, REMOVED; + + public static QuotaAccountStateFilter getValue(String value) { + if (StringUtils.isBlank(value)) { + return null; + } + for (QuotaAccountStateFilter state : values()) { + if (state.name().equalsIgnoreCase(value)) { + return state; + } + } + return null; + } +} diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaSummaryDao.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaSummaryDao.java new file mode 100644 index 00000000000..d8ee2607501 --- /dev/null +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaSummaryDao.java @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.quota.dao; + +import java.util.List; + +import org.apache.cloudstack.quota.QuotaAccountStateFilter; +import org.apache.cloudstack.quota.vo.QuotaSummaryVO; + +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; + +public interface QuotaSummaryDao extends GenericDao { + + Pair, Integer> listQuotaSummariesForAccountAndOrDomain(Long accountId, String accountName, Long domainId, String domainPath, + QuotaAccountStateFilter accountStateFilter, Long startIndex, Long pageSize); +} diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaSummaryDaoImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaSummaryDaoImpl.java new file mode 100644 index 00000000000..d90d5a75859 --- /dev/null +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaSummaryDaoImpl.java @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.quota.dao; + +import java.util.List; + +import org.apache.cloudstack.quota.QuotaAccountStateFilter; +import org.apache.cloudstack.quota.vo.QuotaSummaryVO; + +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 com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionLegacy; + +public class QuotaSummaryDaoImpl extends GenericDaoBase implements QuotaSummaryDao { + + @Override + public Pair, Integer> listQuotaSummariesForAccountAndOrDomain(Long accountId, String accountName, Long domainId, String domainPath, + QuotaAccountStateFilter accountStateFilter, Long startIndex, Long pageSize) { + SearchCriteria searchCriteria = createListQuotaSummariesSearchCriteria(accountId, accountName, domainId, domainPath, accountStateFilter); + Filter filter = new Filter(QuotaSummaryVO.class, "accountName", true, startIndex, pageSize); + + return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback, Integer>>) status -> searchAndCount(searchCriteria, filter)); + } + + protected SearchCriteria createListQuotaSummariesSearchCriteria(Long accountId, String accountName, Long domainId, String domainPath, + QuotaAccountStateFilter accountStateFilter) { + SearchCriteria searchCriteria = createListQuotaSummariesSearchBuilder(accountStateFilter).create(); + + searchCriteria.setParametersIfNotNull("accountId", accountId); + searchCriteria.setParametersIfNotNull("domainId", domainId); + + if (accountName != null) { + searchCriteria.setParameters("accountName", "%" + accountName + "%"); + } + + if (domainPath != null) { + searchCriteria.setParameters("domainPath", domainPath + "%"); + } + + return searchCriteria; + } + + protected SearchBuilder createListQuotaSummariesSearchBuilder(QuotaAccountStateFilter accountStateFilter) { + SearchBuilder searchBuilder = createSearchBuilder(); + + searchBuilder.and("accountId", searchBuilder.entity().getAccountId(), SearchCriteria.Op.EQ); + searchBuilder.and("accountName", searchBuilder.entity().getAccountName(), SearchCriteria.Op.LIKE); + searchBuilder.and("domainId", searchBuilder.entity().getDomainId(), SearchCriteria.Op.EQ); + searchBuilder.and("domainPath", searchBuilder.entity().getDomainPath(), SearchCriteria.Op.LIKE); + + if (QuotaAccountStateFilter.REMOVED.equals(accountStateFilter)) { + searchBuilder.and("accountRemoved", searchBuilder.entity().getAccountRemoved(), SearchCriteria.Op.NNULL); + } else if (QuotaAccountStateFilter.ACTIVE.equals(accountStateFilter)) { + searchBuilder.and("accountRemoved", searchBuilder.entity().getAccountRemoved(), SearchCriteria.Op.NULL); + } + + return searchBuilder; + } + +} diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaSummaryVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaSummaryVO.java new file mode 100644 index 00000000000..f9796497d57 --- /dev/null +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaSummaryVO.java @@ -0,0 +1,154 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.quota.vo; + +import java.math.BigDecimal; +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import com.cloud.user.Account; + +@Entity +@Table(name = "quota_summary_view") +public class QuotaSummaryVO { + + @Id + @Column(name = "account_id") + private Long accountId = null; + + @Column(name = "quota_enforce") + private Integer quotaEnforce = 0; + + @Column(name = "quota_balance") + private BigDecimal quotaBalance; + + @Column(name = "quota_balance_date") + @Temporal(value = TemporalType.TIMESTAMP) + private Date quotaBalanceDate = null; + + @Column(name = "quota_min_balance") + private BigDecimal quotaMinBalance; + + @Column(name = "quota_alert_type") + private Integer quotaAlertType = null; + + @Column(name = "quota_alert_date") + @Temporal(value = TemporalType.TIMESTAMP) + private Date quotaAlertDate = null; + + @Column(name = "last_statement_date") + @Temporal(value = TemporalType.TIMESTAMP) + private Date lastStatementDate = null; + + @Column(name = "account_uuid") + private String accountUuid; + + @Column(name = "account_name") + private String accountName; + + @Column(name = "account_state") + @Enumerated(EnumType.STRING) + private Account.State accountState; + + @Column(name = "account_removed") + private Date accountRemoved; + + @Column(name = "domain_id") + private Long domainId; + + @Column(name = "domain_uuid") + private String domainUuid; + + @Column(name = "domain_name") + private String domainName; + + @Column(name = "domain_path") + private String domainPath; + + @Column(name = "domain_removed") + private Date domainRemoved; + + @Column(name = "project_uuid") + private String projectUuid; + + @Column(name = "project_name") + private String projectName; + + @Column(name = "project_removed") + private Date projectRemoved; + + public Long getAccountId() { + return accountId; + } + + public BigDecimal getQuotaBalance() { + return quotaBalance; + } + + public String getAccountUuid() { + return accountUuid; + } + + public String getAccountName() { + return accountName; + } + + public Date getAccountRemoved() { + return accountRemoved; + } + + public Account.State getAccountState() { + return accountState; + } + + public Long getDomainId() { + return domainId; + } + + public String getDomainUuid() { + return domainUuid; + } + + public String getDomainPath() { + return domainPath; + } + + public Date getDomainRemoved() { + return domainRemoved; + } + + public String getProjectUuid() { + return projectUuid; + } + + public String getProjectName() { + return projectName; + } + + public Date getProjectRemoved() { + return projectRemoved; + } +} diff --git a/framework/quota/src/main/resources/META-INF/cloudstack/quota/spring-framework-quota-context.xml b/framework/quota/src/main/resources/META-INF/cloudstack/quota/spring-framework-quota-context.xml index 453355c8522..304b23b7220 100644 --- a/framework/quota/src/main/resources/META-INF/cloudstack/quota/spring-framework-quota-context.xml +++ b/framework/quota/src/main/resources/META-INF/cloudstack/quota/spring-framework-quota-context.xml @@ -19,7 +19,8 @@ - + + , Integer> responses; - if (caller.getType() == Account.Type.ADMIN) { - if (getAccountName() != null && getDomainId() != null) - responses = _responseBuilder.createQuotaSummaryResponse(getAccountName(), getDomainId()); - else - responses = _responseBuilder.createQuotaSummaryResponse(isListAll(), getKeyword(), getStartIndex(), getPageSizeVal()); - } else { - responses = _responseBuilder.createQuotaSummaryResponse(caller.getAccountName(), caller.getDomainId()); - } - final ListResponse response = new ListResponse(); + Pair, Integer> responses = quotaResponseBuilder.createQuotaSummaryResponse(this); + ListResponse response = new ListResponse<>(); response.setResponses(responses.first(), responses.second()); response.setResponseName(getCommandName()); setResponseObject(response); } + public Long getAccountId() { + return accountId; + } + + public void setAccountId(Long accountId) { + this.accountId = accountId; + } + public String getAccountName() { return accountName; } @@ -88,16 +107,31 @@ public class QuotaSummaryCmd extends BaseListCmd { } public Boolean isListAll() { - return listAll == null ? false: listAll; + // If a domain ID was specified, then allow listing all summaries of domain + return ObjectUtils.defaultIfNull(listAll, Boolean.FALSE) || domainId != null; } public void setListAll(Boolean listAll) { this.listAll = listAll; } - @Override - public long getEntityOwnerId() { - return Account.ACCOUNT_ID_SYSTEM; + public Long getProjectId() { + return projectId; } + public QuotaAccountStateFilter getAccountStateToShow() { + QuotaAccountStateFilter state = QuotaAccountStateFilter.getValue(accountStateToShow); + if (state != null) { + return state; + } + return QuotaAccountStateFilter.ACTIVE; + } + + @Override + public long getEntityOwnerId() { + if (ObjectUtils.allNull(accountId, accountName, projectId)) { + return -1; + } + return _accountService.finalizeAccountId(accountId, accountName, domainId, projectId); + } } diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java index 67f75ebf82f..177fb00d4b5 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java @@ -24,6 +24,7 @@ import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd; import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd; import org.apache.cloudstack.api.command.QuotaPresetVariablesListCmd; import org.apache.cloudstack.api.command.QuotaStatementCmd; +import org.apache.cloudstack.api.command.QuotaSummaryCmd; import org.apache.cloudstack.api.command.QuotaTariffCreateCmd; import org.apache.cloudstack.api.command.QuotaTariffListCmd; import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd; @@ -52,11 +53,7 @@ public interface QuotaResponseBuilder { QuotaBalanceResponse createQuotaBalanceResponse(List quotaUsage, Date startDate, Date endDate); - Pair, Integer> createQuotaSummaryResponse(Boolean listAll); - - Pair, Integer> createQuotaSummaryResponse(Boolean listAll, String keyword, Long startIndex, Long pageSize); - - Pair, Integer> createQuotaSummaryResponse(String accountName, Long domainId); + Pair, Integer> createQuotaSummaryResponse(QuotaSummaryCmd cmd); QuotaBalanceResponse createQuotaLastBalanceResponse(List quotaBalance, Date startDate); diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java index f9456587058..d1992d4c3ad 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java @@ -42,7 +42,9 @@ import java.util.stream.Collectors; import javax.inject.Inject; +import com.cloud.domain.Domain; import com.cloud.exception.PermissionDeniedException; +import com.cloud.projects.dao.ProjectDao; import com.cloud.user.User; import com.cloud.user.UserVO; import com.cloud.utils.DateUtil; @@ -56,6 +58,7 @@ import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd; import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd; import org.apache.cloudstack.api.command.QuotaPresetVariablesListCmd; import org.apache.cloudstack.api.command.QuotaStatementCmd; +import org.apache.cloudstack.api.command.QuotaSummaryCmd; import org.apache.cloudstack.api.command.QuotaTariffCreateCmd; import org.apache.cloudstack.api.command.QuotaTariffListCmd; import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd; @@ -74,11 +77,13 @@ import org.apache.cloudstack.quota.activationrule.presetvariables.PresetVariable import org.apache.cloudstack.quota.activationrule.presetvariables.Value; import org.apache.cloudstack.quota.constant.QuotaConfig; import org.apache.cloudstack.quota.constant.QuotaTypes; + import org.apache.cloudstack.quota.dao.QuotaAccountDao; import org.apache.cloudstack.quota.dao.QuotaBalanceDao; import org.apache.cloudstack.quota.dao.QuotaCreditsDao; import org.apache.cloudstack.quota.dao.QuotaEmailConfigurationDao; import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao; +import org.apache.cloudstack.quota.dao.QuotaSummaryDao; import org.apache.cloudstack.quota.dao.QuotaTariffDao; import org.apache.cloudstack.quota.dao.QuotaUsageDao; import org.apache.cloudstack.quota.vo.QuotaAccountVO; @@ -86,10 +91,13 @@ import org.apache.cloudstack.quota.vo.QuotaBalanceVO; import org.apache.cloudstack.quota.vo.QuotaCreditsVO; import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; +import org.apache.cloudstack.quota.vo.QuotaSummaryVO; import org.apache.cloudstack.quota.vo.QuotaTariffVO; import org.apache.cloudstack.quota.vo.QuotaUsageVO; import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.compress.utils.Sets; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.FieldUtils; @@ -108,7 +116,6 @@ import com.cloud.user.AccountVO; import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.UserDao; import com.cloud.utils.Pair; -import com.cloud.utils.db.Filter; @Component public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { @@ -121,7 +128,7 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { @Inject private QuotaCreditsDao quotaCreditsDao; @Inject - private QuotaUsageDao _quotaUsageDao; + private QuotaUsageDao quotaUsageDao; @Inject private QuotaEmailTemplatesDao _quotaEmailTemplateDao; @@ -132,24 +139,30 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { @Inject private AccountDao _accountDao; @Inject + private ProjectDao projectDao; + @Inject private QuotaAccountDao quotaAccountDao; @Inject - private DomainDao _domainDao; + private DomainDao domainDao; @Inject private AccountManager _accountMgr; @Inject - private QuotaStatement _statement; + private QuotaStatement quotaStatement; @Inject private QuotaManager _quotaManager; @Inject private QuotaEmailConfigurationDao quotaEmailConfigurationDao; @Inject + private QuotaSummaryDao quotaSummaryDao; + @Inject private JsInterpreterHelper jsInterpreterHelper; @Inject private ApiDiscoveryService apiDiscoveryService; private final Class[] assignableClasses = {GenericPresetVariable.class, ComputingResources.class}; + private Set accountTypesThatCanListAllQuotaSummaries = Sets.newHashSet(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN); + protected void checkActivationRulesAllowed(String activationRule) { if (!_quotaService.isJsInterpretationEnabled() && StringUtils.isNotEmpty(activationRule)) { throw new PermissionDeniedException("Quota Tariff Activation Rule cannot be set, as Javascript interpretation is disabled in the configuration."); @@ -180,75 +193,113 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { } @Override - public Pair, Integer> createQuotaSummaryResponse(final String accountName, final Long domainId) { - List result = new ArrayList(); + public Pair, Integer> createQuotaSummaryResponse(QuotaSummaryCmd cmd) { + Account caller = CallContext.current().getCallingAccount(); - if (accountName != null && domainId != null) { - Account account = _accountDao.findActiveAccount(accountName, domainId); - QuotaSummaryResponse qr = getQuotaSummaryResponse(account); - result.add(qr); + if (!accountTypesThatCanListAllQuotaSummaries.contains(caller.getType()) || !cmd.isListAll()) { + return getQuotaSummaryResponse(cmd.getEntityOwnerId(), null, null, cmd); } - return new Pair<>(result, result.size()); + return getQuotaSummaryResponseWithListAll(cmd, caller); } - @Override - public Pair, Integer> createQuotaSummaryResponse(Boolean listAll) { - return createQuotaSummaryResponse(listAll, null, null, null); - } - - @Override - public Pair, Integer> createQuotaSummaryResponse(Boolean listAll, final String keyword, final Long startIndex, final Long pageSize) { - List result = new ArrayList(); - Integer count = 0; - if (listAll) { - Filter filter = new Filter(AccountVO.class, "accountName", true, startIndex, pageSize); - Pair, Integer> data = _accountDao.findAccountsLike(keyword, filter); - count = data.second(); - for (final AccountVO account : data.first()) { - QuotaSummaryResponse qr = getQuotaSummaryResponse(account); - result.add(qr); - } - } else { - Pair, Integer> data = quotaAccountDao.listAllQuotaAccount(startIndex, pageSize); - count = data.second(); - for (final QuotaAccountVO quotaAccount : data.first()) { - AccountVO account = _accountDao.findById(quotaAccount.getId()); - if (account == null) { - continue; - } - QuotaSummaryResponse qr = getQuotaSummaryResponse(account); - result.add(qr); + protected Pair, Integer> getQuotaSummaryResponseWithListAll(QuotaSummaryCmd cmd, Account caller) { + Long domainId = cmd.getDomainId(); + if (domainId != null) { + DomainVO domain = domainDao.findByIdIncludingRemoved(domainId); + if (domain == null) { + throw new InvalidParameterValueException(String.format("Domain [%s] does not exist.", domainId)); } } - return new Pair<>(result, count); + + String domainPath = getDomainPathByDomainIdForDomainAdmin(caller); + + Long accountId = cmd.getEntityOwnerId(); + if (accountId == -1) { + accountId = cmd.isListAll() ? null : caller.getAccountId(); + } + + return getQuotaSummaryResponse(accountId, domainId, domainPath, cmd); } - protected QuotaSummaryResponse getQuotaSummaryResponse(final Account account) { - Calendar[] period = _statement.getCurrentStatementTime(); - - if (account != null) { - QuotaSummaryResponse qr = new QuotaSummaryResponse(); - DomainVO domain = _domainDao.findById(account.getDomainId()); - BigDecimal curBalance = _quotaBalanceDao.lastQuotaBalance(account.getAccountId(), account.getDomainId(), period[1].getTime()); - BigDecimal quotaUsage = _quotaUsageDao.findTotalQuotaUsage(account.getAccountId(), account.getDomainId(), null, period[0].getTime(), period[1].getTime()); - - qr.setAccountId(account.getUuid()); - qr.setAccountName(account.getAccountName()); - qr.setDomainId(domain.getUuid()); - qr.setDomainName(domain.getName()); - qr.setBalance(curBalance); - qr.setQuotaUsage(quotaUsage); - qr.setState(account.getState()); - qr.setStartDate(period[0].getTime()); - qr.setEndDate(period[1].getTime()); - qr.setCurrency(QuotaConfig.QuotaCurrencySymbol.value()); - qr.setQuotaEnabled(QuotaConfig.QuotaAccountEnabled.valueIn(account.getId())); - qr.setObjectName("summary"); - return qr; - } else { - return new QuotaSummaryResponse(); + /** + * Retrieves the domain path of the caller's domain (if the caller is Domain Admin) for filtering in the quota summary query. + * @return null if the caller is an Admin or the domain path of the caller's domain if the caller is a Domain Admin. + * @throws InvalidParameterValueException if it cannot find the domain. + */ + protected String getDomainPathByDomainIdForDomainAdmin(Account caller) { + if (caller.getType() != Account.Type.DOMAIN_ADMIN) { + return null; } + + Long domainId = caller.getDomainId(); + Domain domain = domainDao.findById(domainId); + _accountMgr.checkAccess(caller, domain); + + if (domain == null) { + throw new InvalidParameterValueException(String.format("Domain ID [%s] is invalid.", domainId)); + } + + return domain.getPath(); + } + + /** + * Returns a List of QuotaSummaryResponse based on the provided parameters. + * @param accountId ID of the Account to return the summaries for. If -1, either because no specific + * Account was provided, or list all is disabled, then the summary is generated for the calling Account. + * @param domainId ID of the Domain to return the summaries for. + * @param domainPath path of the Domain to return the summaries for. + */ + protected Pair, Integer> getQuotaSummaryResponse(Long accountId, Long domainId, String domainPath, QuotaSummaryCmd cmd) { + if (accountId != null && accountId == -1) { + accountId = CallContext.current().getCallingAccountId(); + } + + Pair, Integer> pairSummaries = quotaSummaryDao.listQuotaSummariesForAccountAndOrDomain(accountId, cmd.getKeyword(), domainId, domainPath, + cmd.getAccountStateToShow(), cmd.getStartIndex(), cmd.getPageSizeVal()); + List summaries = pairSummaries.first(); + + if (CollectionUtils.isEmpty(summaries)) { + logger.info("There are no summaries to list for parameters [{}].", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(cmd, "accountName", "domainId", "listAll", "page", "pageSize")); + return new Pair<>(new ArrayList<>(), 0); + } + + List responses = summaries.stream().map(this::getQuotaSummaryResponse).collect(Collectors.toList()); + + return new Pair<>(responses, pairSummaries.second()); + } + + protected QuotaSummaryResponse getQuotaSummaryResponse(QuotaSummaryVO summary) { + QuotaSummaryResponse response = new QuotaSummaryResponse(); + Account account = _accountDao.findByUuidIncludingRemoved(summary.getAccountUuid()); + + Calendar[] period = quotaStatement.getCurrentStatementTime(); + Date startDate = period[0].getTime(); + Date endDate = period[1].getTime(); + BigDecimal quotaUsage = quotaUsageDao.findTotalQuotaUsage(account.getAccountId(), account.getDomainId(), null, startDate, endDate); + + response.setQuotaUsage(quotaUsage); + response.setStartDate(startDate); + response.setEndDate(endDate); + response.setAccountId(summary.getAccountUuid()); + response.setAccountName(summary.getAccountName()); + response.setDomainId(summary.getDomainUuid()); + response.setDomainPath(summary.getDomainPath()); + response.setBalance(summary.getQuotaBalance()); + response.setState(summary.getAccountState()); + response.setCurrency(QuotaConfig.QuotaCurrencySymbol.value()); + response.setQuotaEnabled(QuotaConfig.QuotaAccountEnabled.valueIn(account.getId())); + response.setDomainRemoved(summary.getDomainRemoved() != null); + response.setAccountRemoved(summary.getAccountRemoved() != null); + response.setObjectName("summary"); + + if (summary.getProjectUuid() != null) { + response.setProjectId(summary.getProjectUuid()); + response.setProjectName(summary.getProjectName()); + response.setProjectRemoved(summary.getProjectRemoved() != null); + } + + return response; } public boolean isUserAllowedToSeeActivationRules(User user) { diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaSummaryResponse.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaSummaryResponse.java index f8f27b4813d..d76202bfd88 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaSummaryResponse.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaSummaryResponse.java @@ -17,7 +17,6 @@ package org.apache.cloudstack.api.response; import java.math.BigDecimal; -import java.math.RoundingMode; import java.util.Date; import com.google.gson.annotations.SerializedName; @@ -30,40 +29,48 @@ import com.cloud.user.Account.State; public class QuotaSummaryResponse extends BaseResponse { @SerializedName("accountid") - @Param(description = "Account ID") + @Param(description = "Account's ID") private String accountId; @SerializedName("account") - @Param(description = "Account name") + @Param(description = "Account's name") private String accountName; @SerializedName("domainid") - @Param(description = "Domain ID") + @Param(description = "Domain's ID") private String domainId; @SerializedName("domain") - @Param(description = "Domain name") - private String domainName; + @Param(description = "Domain's path") + private String domainPath; @SerializedName("balance") - @Param(description = "Account balance") + @Param(description = "Account's balance") private BigDecimal balance; @SerializedName("state") - @Param(description = "Account state") + @Param(description = "Account's state") private State state; + @SerializedName("domainremoved") + @Param(description = "If the domain is removed or not", since = "4.23.0") + private boolean domainRemoved; + + @SerializedName("accountremoved") + @Param(description = "If the account is removed or not", since = "4.23.0") + private boolean accountRemoved; + @SerializedName("quota") - @Param(description = "Quota usage of this period") + @Param(description = "Quota consumed between the startdate and enddate") private BigDecimal quotaUsage; @SerializedName("startdate") - @Param(description = "Start date") - private Date startDate = null; + @Param(description = "Start date of the quota consumption") + private Date startDate; @SerializedName("enddate") - @Param(description = "End date") - private Date endDate = null; + @Param(description = "End date of the quota consumption") + private Date endDate; @SerializedName("currency") @Param(description = "Currency") @@ -73,9 +80,17 @@ public class QuotaSummaryResponse extends BaseResponse { @Param(description = "If the account has the quota config enabled") private boolean quotaEnabled; - public QuotaSummaryResponse() { - super(); - } + @SerializedName("projectname") + @Param(description = "Name of the project", since = "4.23.0") + private String projectName; + + @SerializedName("projectid") + @Param(description = "Project's id", since = "4.23.0") + private String projectId; + + @SerializedName("projectremoved") + @Param(description = "Whether the project is removed or not", since = "4.23.0") + private Boolean projectRemoved; public String getAccountId() { return accountId; @@ -101,28 +116,16 @@ public class QuotaSummaryResponse extends BaseResponse { this.domainId = domainId; } - public String getDomainName() { - return domainName; - } - - public void setDomainName(String domainName) { - this.domainName = domainName; - } - - public BigDecimal getQuotaUsage() { - return quotaUsage; - } - - public State getState() { - return state; + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; } public void setState(State state) { this.state = state; } - public void setQuotaUsage(BigDecimal startQuota) { - this.quotaUsage = startQuota.setScale(2, RoundingMode.HALF_EVEN); + public void setQuotaUsage(BigDecimal quotaUsage) { + this.quotaUsage = quotaUsage; } public BigDecimal getBalance() { @@ -130,38 +133,42 @@ public class QuotaSummaryResponse extends BaseResponse { } public void setBalance(BigDecimal balance) { - this.balance = balance.setScale(2, RoundingMode.HALF_EVEN); - } - - public Date getStartDate() { - return startDate == null ? null : new Date(startDate.getTime()); + this.balance = balance; } public void setStartDate(Date startDate) { - this.startDate = startDate == null ? null : new Date(startDate.getTime()); - } - - public Date getEndDate() { - return endDate == null ? null : new Date(endDate.getTime()); + this.startDate = startDate; } public void setEndDate(Date endDate) { - this.endDate = endDate == null ? null : new Date(endDate.getTime()); - } - - public String getCurrency() { - return currency; + this.endDate = endDate; } public void setCurrency(String currency) { this.currency = currency; } - public boolean getQuotaEnabled() { - return quotaEnabled; - } - public void setQuotaEnabled(boolean quotaEnabled) { this.quotaEnabled = quotaEnabled; } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public void setProjectId(String projectId) { + this.projectId = projectId; + } + + public void setProjectRemoved(Boolean projectRemoved) { + this.projectRemoved = projectRemoved; + } + + public void setDomainRemoved(boolean domainRemoved) { + this.domainRemoved = domainRemoved; + } + + public void setAccountRemoved(boolean accountRemoved) { + this.accountRemoved = accountRemoved; + } } diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java index f455c3cba14..f39153ae0ac 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java @@ -26,6 +26,8 @@ import java.util.TimeZone; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.projects.ProjectManager; +import com.cloud.user.AccountService; import org.apache.cloudstack.api.command.QuotaBalanceCmd; import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd; import org.apache.cloudstack.api.command.QuotaCreditsCmd; @@ -75,6 +77,8 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi @Inject private AccountDao _accountDao; @Inject + private AccountService accountService; + @Inject private QuotaAccountDao _quotaAcc; @Inject private QuotaUsageDao _quotaUsageDao; @@ -86,6 +90,8 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi private QuotaBalanceDao _quotaBalanceDao; @Inject private QuotaResponseBuilder _respBldr; + @Inject + private ProjectManager projectMgr; private TimeZone _usageTimezone; diff --git a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java index 3629bf2e3fe..ea88a106b84 100644 --- a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java +++ b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java @@ -16,13 +16,11 @@ // under the License. package org.apache.cloudstack.api.response; -import java.lang.reflect.Field; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.ArrayList; -import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -31,6 +29,7 @@ import java.util.Set; import java.util.HashSet; import java.util.function.Consumer; +import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; import com.cloud.exception.PermissionDeniedException; @@ -43,10 +42,10 @@ import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd; import org.apache.cloudstack.api.command.QuotaCreditsListCmd; import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd; import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd; +import org.apache.cloudstack.api.command.QuotaSummaryCmd; import org.apache.cloudstack.api.command.QuotaValidateActivationRuleCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.discovery.ApiDiscoveryService; -import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.jsinterpreter.JsInterpreterHelper; import org.apache.cloudstack.quota.QuotaService; import org.apache.cloudstack.quota.QuotaStatement; @@ -79,7 +78,10 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; import com.cloud.exception.InvalidParameterValueException; import com.cloud.user.Account; @@ -89,8 +91,7 @@ import com.cloud.user.dao.UserDao; import com.cloud.user.User; import junit.framework.TestCase; -import org.mockito.Spy; -import org.mockito.junit.MockitoJUnitRunner; + @RunWith(MockitoJUnitRunner.class) public class QuotaResponseBuilderImplTest extends TestCase { @@ -153,7 +154,7 @@ public class QuotaResponseBuilderImplTest extends TestCase { Account accountMock; @Mock - DomainVO domainVOMock; + DomainVO domainVoMock; @Mock QuotaConfigureEmailCmd quotaConfigureEmailCmdMock; @@ -161,6 +162,9 @@ public class QuotaResponseBuilderImplTest extends TestCase { @Mock QuotaAccountVO quotaAccountVOMock; + @Mock + CallContext callContextMock; + @Mock QuotaEmailTemplatesVO quotaEmailTemplatesVoMock; @@ -184,17 +188,8 @@ public class QuotaResponseBuilderImplTest extends TestCase { CallContext.register(callerUserMock, callerAccountMock); } - private void overrideDefaultQuotaEnabledConfigValue(final Object value) throws IllegalAccessException, NoSuchFieldException { - Field f = ConfigKey.class.getDeclaredField("_defaultValue"); - f.setAccessible(true); - f.set(QuotaConfig.QuotaAccountEnabled, value); - } - - private Calendar[] createPeriodForQuotaSummary() { - final Calendar calendar = Calendar.getInstance(); - calendar.set(Calendar.HOUR, 0); - return new Calendar[] {calendar, calendar}; - } + @Mock + Pair, Integer> quotaSummaryResponseMock1, quotaSummaryResponseMock2; @Mock QuotaValidateActivationRuleCmd quotaValidateActivationRuleCmdMock = Mockito.mock(QuotaValidateActivationRuleCmd.class); @@ -466,36 +461,6 @@ public class QuotaResponseBuilderImplTest extends TestCase { Mockito.verify(quotaTariffVoMock).setRemoved(Mockito.any(Date.class)); } - @Test - public void getQuotaSummaryResponseTestAccountIsNotNullQuotaIsDisabledShouldReturnFalse() throws NoSuchFieldException, IllegalAccessException { - Calendar[] period = createPeriodForQuotaSummary(); - overrideDefaultQuotaEnabledConfigValue("false"); - - Mockito.doReturn(period).when(quotaStatementMock).getCurrentStatementTime(); - Mockito.doReturn(domainVOMock).when(domainDaoMock).findById(Mockito.anyLong()); - Mockito.doReturn(BigDecimal.ZERO).when(quotaBalanceDaoMock).lastQuotaBalance(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(Date.class)); - Mockito.doReturn(BigDecimal.ZERO).when(quotaUsageDaoMock).findTotalQuotaUsage(Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), Mockito.any(Date.class), Mockito.any(Date.class)); - - QuotaSummaryResponse quotaSummaryResponse = quotaResponseBuilderSpy.getQuotaSummaryResponse(accountMock); - - assertFalse(quotaSummaryResponse.getQuotaEnabled()); - } - - @Test - public void getQuotaSummaryResponseTestAccountIsNotNullQuotaIsEnabledShouldReturnTrue() throws NoSuchFieldException, IllegalAccessException { - Calendar[] period = createPeriodForQuotaSummary(); - overrideDefaultQuotaEnabledConfigValue("true"); - - Mockito.doReturn(period).when(quotaStatementMock).getCurrentStatementTime(); - Mockito.doReturn(domainVOMock).when(domainDaoMock).findById(Mockito.anyLong()); - Mockito.doReturn(BigDecimal.ZERO).when(quotaBalanceDaoMock).lastQuotaBalance(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(Date.class)); - Mockito.doReturn(BigDecimal.ZERO).when(quotaUsageDaoMock).findTotalQuotaUsage(Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), Mockito.any(Date.class), Mockito.any(Date.class)); - - QuotaSummaryResponse quotaSummaryResponse = quotaResponseBuilderSpy.getQuotaSummaryResponse(accountMock); - - assertTrue(quotaSummaryResponse.getQuotaEnabled()); - } - @Test public void filterSupportedTypesTestReturnWhenQuotaTypeDoesNotMatch() throws NoSuchFieldException { List> variables = new ArrayList<>(); @@ -576,6 +541,63 @@ public class QuotaResponseBuilderImplTest extends TestCase { quotaResponseBuilderSpy.validateQuotaConfigureEmailCmdParameters(quotaConfigureEmailCmdMock); } + @Test + public void createQuotaSummaryResponseTestNotListAllAndAllAccountTypesReturnsSingleRecord() { + QuotaSummaryCmd cmd = new QuotaSummaryCmd(); + + try(MockedStatic callContextMocked = Mockito.mockStatic(CallContext.class)) { + callContextMocked.when(CallContext::current).thenReturn(callContextMock); + + Mockito.doReturn(accountMock).when(callContextMock).getCallingAccount(); + + Mockito.doReturn(quotaSummaryResponseMock1).when(quotaResponseBuilderSpy).getQuotaSummaryResponse(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); + + for (Account.Type type : Account.Type.values()) { + Mockito.doReturn(type).when(accountMock).getType(); + + Pair, Integer> result = quotaResponseBuilderSpy.createQuotaSummaryResponse(cmd); + Assert.assertEquals(quotaSummaryResponseMock1, result); + } + + Mockito.verify(quotaResponseBuilderSpy, Mockito.times(Account.Type.values().length)).getQuotaSummaryResponse(Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any()); + }; + } + + @Test + public void getDomainPathByDomainIdForDomainAdminTestAccountNotDomainAdminReturnsNull() { + for (Account.Type type : Account.Type.values()) { + if (Account.Type.DOMAIN_ADMIN.equals(type)) { + continue; + } + + Mockito.doReturn(type).when(accountMock).getType(); + Assert.assertNull(quotaResponseBuilderSpy.getDomainPathByDomainIdForDomainAdmin(accountMock)); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void getDomainPathByDomainIdForDomainAdminTestDomainFromCallerIsNullThrowsInvalidParameterValueException() { + Mockito.doReturn(Account.Type.DOMAIN_ADMIN).when(accountMock).getType(); + Mockito.doReturn(null).when(domainDaoMock).findById(Mockito.anyLong()); + Mockito.lenient().doNothing().when(accountManagerMock).checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class)); + + quotaResponseBuilderSpy.getDomainPathByDomainIdForDomainAdmin(accountMock); + } + + @Test + public void getDomainPathByDomainIdForDomainAdminTestDomainFromCallerIsNotNullReturnsPath() { + String expected = "/test/"; + + Mockito.doReturn(Account.Type.DOMAIN_ADMIN).when(accountMock).getType(); + Mockito.doReturn(domainVoMock).when(domainDaoMock).findById(Mockito.anyLong()); + Mockito.doNothing().when(accountManagerMock).checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class)); + Mockito.doReturn(expected).when(domainVoMock).getPath(); + + String result = quotaResponseBuilderSpy.getDomainPathByDomainIdForDomainAdmin(accountMock); + Assert.assertEquals(expected, result); + } + @Test public void getQuotaEmailConfigurationVoTestTemplateNameIsNull() { Mockito.doReturn(null).when(quotaConfigureEmailCmdMock).getTemplateName(); diff --git a/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java b/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java index 19e756d1d97..259264f3b0e 100644 --- a/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java +++ b/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.quota; import com.cloud.configuration.Config; import com.cloud.domain.dao.DomainDao; +import com.cloud.user.AccountVO; import com.cloud.user.dao.AccountDao; import com.cloud.utils.db.TransactionLegacy; import junit.framework.TestCase; @@ -33,8 +34,10 @@ import org.joda.time.DateTime; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import javax.naming.ConfigurationException; @@ -48,7 +51,7 @@ import java.util.List; public class QuotaServiceImplTest extends TestCase { @Mock - AccountDao accountDao; + AccountDao accountDaoMock; @Mock QuotaAccountDao quotaAcc; @Mock @@ -61,8 +64,13 @@ public class QuotaServiceImplTest extends TestCase { QuotaBalanceDao quotaBalanceDao; @Mock QuotaResponseBuilder respBldr; + @Mock + private AccountVO accountVoMock; + + @Spy + @InjectMocks + QuotaServiceImpl quotaServiceImplSpy; - QuotaServiceImpl quotaService = new QuotaServiceImpl(); @Before public void setup() throws IllegalAccessException, NoSuchFieldException, ConfigurationException { @@ -71,34 +79,34 @@ public class QuotaServiceImplTest extends TestCase { Field accountDaoField = QuotaServiceImpl.class.getDeclaredField("_accountDao"); accountDaoField.setAccessible(true); - accountDaoField.set(quotaService, accountDao); + accountDaoField.set(quotaServiceImplSpy, accountDaoMock); Field quotaAccountDaoField = QuotaServiceImpl.class.getDeclaredField("_quotaAcc"); quotaAccountDaoField.setAccessible(true); - quotaAccountDaoField.set(quotaService, quotaAcc); + quotaAccountDaoField.set(quotaServiceImplSpy, quotaAcc); Field quotaUsageDaoField = QuotaServiceImpl.class.getDeclaredField("_quotaUsageDao"); quotaUsageDaoField.setAccessible(true); - quotaUsageDaoField.set(quotaService, quotaUsageDao); + quotaUsageDaoField.set(quotaServiceImplSpy, quotaUsageDao); Field domainDaoField = QuotaServiceImpl.class.getDeclaredField("_domainDao"); domainDaoField.setAccessible(true); - domainDaoField.set(quotaService, domainDao); + domainDaoField.set(quotaServiceImplSpy, domainDao); Field configDaoField = QuotaServiceImpl.class.getDeclaredField("_configDao"); configDaoField.setAccessible(true); - configDaoField.set(quotaService, configDao); + configDaoField.set(quotaServiceImplSpy, configDao); Field balanceDaoField = QuotaServiceImpl.class.getDeclaredField("_quotaBalanceDao"); balanceDaoField.setAccessible(true); - balanceDaoField.set(quotaService, quotaBalanceDao); + balanceDaoField.set(quotaServiceImplSpy, quotaBalanceDao); Field QuotaResponseBuilderField = QuotaServiceImpl.class.getDeclaredField("_respBldr"); QuotaResponseBuilderField.setAccessible(true); - QuotaResponseBuilderField.set(quotaService, respBldr); + QuotaResponseBuilderField.set(quotaServiceImplSpy, respBldr); Mockito.when(configDao.getValue(Mockito.eq(Config.UsageAggregationTimezone.toString()))).thenReturn("IST"); - quotaService.configure("randomName", null); + quotaServiceImplSpy.configure("randomName", null); } @Test @@ -120,9 +128,9 @@ public class QuotaServiceImplTest extends TestCase { Mockito.when(quotaBalanceDao.lastQuotaBalanceVO(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.any(Date.class))).thenReturn(records); // with enddate - assertTrue(quotaService.findQuotaBalanceVO(accountId, accountName, domainId, startDate, endDate).get(0).equals(qb)); + assertTrue(quotaServiceImplSpy.findQuotaBalanceVO(accountId, accountName, domainId, startDate, endDate).get(0).equals(qb)); // without enddate - assertTrue(quotaService.findQuotaBalanceVO(accountId, accountName, domainId, startDate, null).get(0).equals(qb)); + assertTrue(quotaServiceImplSpy.findQuotaBalanceVO(accountId, accountName, domainId, startDate, null).get(0).equals(qb)); } @Test @@ -133,7 +141,7 @@ public class QuotaServiceImplTest extends TestCase { final Date startDate = new DateTime().minusDays(2).toDate(); final Date endDate = new Date(); - quotaService.getQuotaUsage(accountId, accountName, domainId, QuotaTypes.IP_ADDRESS, startDate, endDate); + quotaServiceImplSpy.getQuotaUsage(accountId, accountName, domainId, QuotaTypes.IP_ADDRESS, startDate, endDate); Mockito.verify(quotaUsageDao, Mockito.times(1)).findQuotaUsage(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.eq(QuotaTypes.IP_ADDRESS), Mockito.any(Date.class), Mockito.any(Date.class)); } @@ -142,13 +150,13 @@ public class QuotaServiceImplTest extends TestCase { // existing account QuotaAccountVO quotaAccountVO = new QuotaAccountVO(); Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(quotaAccountVO); - quotaService.setLockAccount(2L, true); + quotaServiceImplSpy.setLockAccount(2L, true); Mockito.verify(quotaAcc, Mockito.times(0)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class)); Mockito.verify(quotaAcc, Mockito.times(1)).updateQuotaAccount(Mockito.anyLong(), Mockito.any(QuotaAccountVO.class)); // new account Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(null); - quotaService.setLockAccount(2L, true); + quotaServiceImplSpy.setLockAccount(2L, true); Mockito.verify(quotaAcc, Mockito.times(1)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class)); } @@ -160,13 +168,14 @@ public class QuotaServiceImplTest extends TestCase { // existing account setting QuotaAccountVO quotaAccountVO = new QuotaAccountVO(); Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(quotaAccountVO); - quotaService.setMinBalance(accountId, balance); + quotaServiceImplSpy.setMinBalance(accountId, balance); Mockito.verify(quotaAcc, Mockito.times(0)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class)); Mockito.verify(quotaAcc, Mockito.times(1)).updateQuotaAccount(Mockito.anyLong(), Mockito.any(QuotaAccountVO.class)); // no account with limit set Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(null); - quotaService.setMinBalance(accountId, balance); + quotaServiceImplSpy.setMinBalance(accountId, balance); Mockito.verify(quotaAcc, Mockito.times(1)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class)); } + } diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/App.config b/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/App.config index b66258ad9f6..e96b6d8b8c5 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/App.config +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/App.config @@ -35,7 +35,7 @@ - + diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/App.config b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/App.config index 3c4f7066082..c5025fb267c 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/App.config +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/App.config @@ -1,5 +1,5 @@ - diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/App.config b/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/App.config index ddf1da0bcf6..184c44a5f27 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/App.config +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/ServerResource.Tests/App.config @@ -1,5 +1,5 @@ - diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/BridgeVifDriver.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/BridgeVifDriver.java index da60f6fd717..e19c3437ba5 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/BridgeVifDriver.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/BridgeVifDriver.java @@ -275,6 +275,7 @@ public class BridgeVifDriver extends VifDriverBase { if (nic.getPxeDisable()) { intf.setPxeDisable(true); } + intf.setLinkStateUp(nic.isEnabled()); return intf; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 55bab118ad0..46cf1da461e 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -4851,6 +4851,12 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv } } + public InterfaceDef getInterface(final Connect conn, final String vmName, final String macAddress) { + List interfaces = getInterfaces(conn, vmName); + return interfaces.stream().filter(interfaceDef -> interfaceDef.getMacAddress().equals(macAddress)) + .findFirst().orElseThrow(() -> new CloudRuntimeException(String.format("Unable to find NIC with MAC address %s.", macAddress))); + } + public List getDisks(final Connect conn, final String vmName) { final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser(); Domain dm = null; diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java index 114b27d3a5b..c0887415c65 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetRemoteVmsCommandWrapper.java @@ -22,6 +22,7 @@ package com.cloud.hypervisor.kvm.resource.wrapper; import com.cloud.agent.api.Answer; import com.cloud.agent.api.GetRemoteVmsAnswer; import com.cloud.agent.api.GetRemoteVmsCommand; +import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; import com.cloud.hypervisor.kvm.resource.LibvirtConnection; import com.cloud.hypervisor.kvm.resource.LibvirtDomainXMLParser; @@ -97,6 +98,7 @@ public final class LibvirtGetRemoteVmsCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(UpdateVmNicCommand command, LibvirtComputingResource libvirtComputingResource) { + String nicMacAddress = command.getNicMacAddress(); + String vmName = command.getVmName(); + boolean isEnabled = command.isEnabled(); + + Domain vm = null; + try { + final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper(); + final Connect conn = libvirtUtilitiesHelper.getConnectionByVmName(vmName); + vm = libvirtComputingResource.getDomain(conn, vmName); + + final InterfaceDef nic = libvirtComputingResource.getInterface(conn, vmName, nicMacAddress); + nic.setLinkStateUp(isEnabled); + vm.updateDeviceFlags(nic.toString(), Domain.DeviceModifyFlags.LIVE); + + return new UpdateVmNicAnswer(command, true, "success"); + } catch (final LibvirtException e) { + final String msg = String.format(" Update NIC failed due to %s.", e); + logger.warn(msg, e); + return new UpdateVmNicAnswer(command, false, msg); + } finally { + if (vm != null) { + try { + vm.free(); + } catch (final LibvirtException l) { + logger.trace("Ignoring libvirt error.", l); + } + } + } + } +} diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java index cde87fd9384..b9629524007 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java @@ -7203,4 +7203,35 @@ public class LibvirtComputingResourceTest { libvirtComputingResourceSpy.defineDiskForDefaultPoolType(diskDef, volume, false, false, false, physicalDisk, DEV_ID, DISK_BUS_TYPE, DISK_BUS_TYPE_DATA, null); Mockito.verify(diskDef).defFileBasedDisk(PHYSICAL_DISK_PATH, DEV_ID, DISK_BUS_TYPE_DATA, DiskDef.DiskFmtType.QCOW2); } + + @Test + public void getInterfaceTestValidMacAddressReturnInterface() { + String macAddress = "a0:90:27:a9:9e:62"; + final String vmName = "Test"; + final InterfaceDef interfaceDef = Mockito.mock(InterfaceDef.class); + final List interfaces = new ArrayList<>(); + interfaces.add(interfaceDef); + + Mockito.doReturn(macAddress).when(interfaceDef).getMacAddress(); + Mockito.doReturn(interfaces).when(libvirtComputingResourceSpy).getInterfaces(Mockito.any(), Mockito.anyString()); + + InterfaceDef result = libvirtComputingResourceSpy.getInterface(connMock, vmName, macAddress); + + Assert.assertNotNull(result); + } + + @Test(expected = CloudRuntimeException.class) + public void getInterfaceTestInvalidMacAddressThrowCloudRuntimeException() { + String invalidMacAddress = "ea:57:5d:f1:64:05"; + String macAddress = "a0:90:27:a9:9e:62"; + final String vmName = "Test"; + final InterfaceDef interfaceDef = Mockito.mock(InterfaceDef.class); + final List interfaces = new ArrayList<>(); + interfaces.add(interfaceDef); + + Mockito.doReturn(macAddress).when(interfaceDef).getMacAddress(); + Mockito.doReturn(interfaces).when(libvirtComputingResourceSpy).getInterfaces(Mockito.any(), Mockito.anyString()); + + libvirtComputingResourceSpy.getInterface(connMock, vmName, invalidMacAddress); + } } diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/utils/KubernetesClusterUtil.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/utils/KubernetesClusterUtil.java index 00625f6e076..023fc9ec7fa 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/utils/KubernetesClusterUtil.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/utils/KubernetesClusterUtil.java @@ -171,11 +171,20 @@ public class KubernetesClusterUtil { // Check if dashboard service is up running. while (System.currentTimeMillis() < timeoutTime) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug(String.format("Checking dashboard service for the Kubernetes cluster: %s to come up", kubernetesCluster)); + LOGGER.debug(String.format("Checking dashboard service (Kubernetes Dashboard or Headlamp) for the Kubernetes cluster: %s to come up", kubernetesCluster)); } + // Check for Headlamp (new dashboard) in kube-system namespace + if (isKubernetesClusterAddOnServiceRunning(kubernetesCluster, ipAddress, port, user, sshKeyFile, "kube-system", "headlamp")) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info(String.format("Headlamp dashboard service for the Kubernetes cluster %s is in running state", kubernetesCluster)); + } + running = true; + break; + } + // For backward compatibility, check for Kubernetes Dashboard in kubernetes-dashboard namespace if (isKubernetesClusterAddOnServiceRunning(kubernetesCluster, ipAddress, port, user, sshKeyFile, "kubernetes-dashboard", "kubernetes-dashboard")) { if (LOGGER.isInfoEnabled()) { - LOGGER.info(String.format("Dashboard service for the Kubernetes cluster %s is in running state", kubernetesCluster)); + LOGGER.info(String.format("Kubernetes Dashboard service for the Kubernetes cluster %s is in running state", kubernetesCluster)); } running = true; break; diff --git a/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml b/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml index 70291dd1c35..4db349ac778 100644 --- a/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml +++ b/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml @@ -331,16 +331,29 @@ write_files: if [[ ${EXTERNAL_CNI_PLUGIN} == false ]]; then /opt/bin/kubectl apply -f ${K8S_CONFIG_SCRIPTS_COPY_DIR}/network.yaml fi - /opt/bin/kubectl apply -f ${K8S_CONFIG_SCRIPTS_COPY_DIR}/dashboard.yaml + if [ -f "${K8S_CONFIG_SCRIPTS_COPY_DIR}/headlamp.yaml" ]; then + echo "Installing Headlamp dashboard from ISO" + /opt/bin/kubectl apply -f ${K8S_CONFIG_SCRIPTS_COPY_DIR}/headlamp.yaml + elif [ -f "${K8S_CONFIG_SCRIPTS_COPY_DIR}/dashboard.yaml" ]; then + echo "Installing Kubernetes Dashboard from ISO" + /opt/bin/kubectl apply -f ${K8S_CONFIG_SCRIPTS_COPY_DIR}/dashboard.yaml + /opt/bin/kubectl create rolebinding admin-binding --role=admin --user=admin || true + /opt/bin/kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=admin || true + /opt/bin/kubectl create clusterrolebinding kubernetes-dashboard-ui --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:kubernetes-dashboard || true + else + echo "Warning: No dashboard YAML found in ISO (neither headlamp.yaml nor dashboard.yaml)" + fi rm -rf "${K8S_CONFIG_SCRIPTS_COPY_DIR}" else + ### Online installation - use Headlamp by default ### /opt/bin/kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(/opt/bin/kubectl version | base64 | tr -d '\n')" - /opt/bin/kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta6/aio/deploy/recommended.yaml + /opt/bin/kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/headlamp/v0.40.1/kubernetes-headlamp.yaml + /opt/bin/kubectl create serviceaccount headlamp-admin -n kube-system || true + /opt/bin/kubectl create clusterrolebinding headlamp-admin --clusterrole=cluster-admin --serviceaccount=kube-system:headlamp-admin || true fi /opt/bin/kubectl create rolebinding admin-binding --role=admin --user=admin || true /opt/bin/kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=admin || true - /opt/bin/kubectl create clusterrolebinding kubernetes-dashboard-ui --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:kubernetes-dashboard || true sudo touch /home/cloud/success echo "true" > /home/cloud/success diff --git a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java index 7953a6a27cd..d9f4963165e 100644 --- a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java +++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java @@ -485,6 +485,12 @@ public class MockAccountManager extends ManagerBase implements AccountManager { return null; } + @Override + public Long finalizeAccountId(Long accountId, String accountName, Long domainId, Long projectId) { + // TODO Auto-generated method stub + return null; + } + @Override public void checkAccess(Account account, ServiceOffering so, DataCenter zone) throws PermissionDeniedException { // TODO Auto-generated method stub diff --git a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAMLUtils.java b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAMLUtils.java index 1a9d677d43a..135e1290f08 100644 --- a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAMLUtils.java +++ b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAMLUtils.java @@ -335,6 +335,9 @@ public class SAMLUtils { resp.addCookie(newCookie(domain, path,"isSAML", URLEncoder.encode("true", HttpUtils.UTF_8))); resp.addCookie(newCookie(domain, path,"twoFaEnabled", URLEncoder.encode(loginResponse.is2FAenabled(), HttpUtils.UTF_8))); resp.addCookie(newCookie(domain, path,"userfullname", URLEncoder.encode(loginResponse.getFirstName() + " " + loginResponse.getLastName(), HttpUtils.UTF_8).replace("+", "%20"))); + if (StringUtils.isNotBlank(loginResponse.getManagementServerId())) { + resp.addCookie(new Cookie(ApiConstants.MANAGEMENT_SERVER_ID, URLEncoder.encode(loginResponse.getManagementServerId(), HttpUtils.UTF_8))); + } } private static Cookie newCookie(final String domain, final String path, final String name, final String value) { diff --git a/scripts/util/create-kubernetes-binaries-iso.sh b/scripts/util/create-kubernetes-binaries-iso.sh index ebaf072771c..00234f20511 100755 --- a/scripts/util/create-kubernetes-binaries-iso.sh +++ b/scripts/util/create-kubernetes-binaries-iso.sh @@ -19,8 +19,8 @@ set -e if [ $# -lt 6 ]; then - echo "Invalid input. Valid usage: ./create-kubernetes-binaries-iso.sh OUTPUT_PATH KUBERNETES_VERSION CNI_VERSION CRICTL_VERSION WEAVENET_NETWORK_YAML_CONFIG DASHBOARD_YAML_CONFIG BUILD_NAME [ARCH] [ETCD_VERSION]" - echo "eg: ./create-kubernetes-binaries-iso.sh ./ 1.11.4 0.7.1 1.11.1 https://github.com/weaveworks/weave/releases/download/latest_release/weave-daemonset-k8s-1.11.yaml https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.0/src/deploy/recommended/kubernetes-dashboard.yaml setup-v1.11.4 amd64" + echo "Invalid input. Valid usage: ./create-kubernetes-binaries-iso.sh OUTPUT_PATH KUBERNETES_VERSION CNI_VERSION CRICTL_VERSION WEAVENET_NETWORK_YAML_CONFIG HEADLAMP_DASHBOARD_VERSION BUILD_NAME [ARCH] [ETCD_VERSION]" + echo "eg: ./create-kubernetes-binaries-iso.sh ./ 1.11.4 0.7.1 1.11.1 https://github.com/weaveworks/weave/releases/download/latest_release/weave-daemonset-k8s-1.11.yaml 0.40.1 setup-v1.11.4 amd64" exit 1 fi @@ -96,10 +96,11 @@ echo "Downloading network config ${NETWORK_CONFIG_URL}" network_conf_file="${working_dir}/network.yaml" curl -sSL ${NETWORK_CONFIG_URL} -o ${network_conf_file} -DASHBORAD_CONFIG_URL="${6}" -echo "Downloading dashboard config ${DASHBORAD_CONFIG_URL}" -dashboard_conf_file="${working_dir}/dashboard.yaml" -curl -sSL ${DASHBORAD_CONFIG_URL} -o ${dashboard_conf_file} +HEADLAMP_DASHBOARD_VERSION="${6}" +HEADLAMP_DASHBOARD_URL="https://raw.githubusercontent.com/kubernetes-sigs/headlamp/v${HEADLAMP_DASHBOARD_VERSION}/kubernetes-headlamp.yaml" +echo "Downloading Headlamp manifest from ${HEADLAMP_DASHBOARD_URL}" +headlamp_conf_file="${working_dir}/headlamp.yaml" +curl -sSL ${HEADLAMP_DASHBOARD_URL} -o ${headlamp_conf_file} # TODO : Change the url once merged AUTOSCALER_URL="https://raw.githubusercontent.com/kubernetes/autoscaler/master/cluster-autoscaler/cloudprovider/cloudstack/examples/cluster-autoscaler-standard.yaml" @@ -135,7 +136,7 @@ mkdir -p "${working_dir}/docker" output=`${k8s_dir}/kubeadm config images list --kubernetes-version=${RELEASE}` # Don't forget about the yaml images ! -for i in ${network_conf_file} ${dashboard_conf_file} +for i in ${network_conf_file} ${headlamp_conf_file} do images=`grep "image:" $i | cut -d ':' -f2- | tr -d ' ' | tr -d "'"` output=`printf "%s\n" ${output} ${images}` diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index 01c2bd3c1a2..5097a3880e0 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -2243,6 +2243,10 @@ public class ApiDBUtils { return s_nicSecondaryIpDao.listByNicId(nicId); } + public static NicVO findNicById(long nicId) { + return s_nicDao.findById(nicId); + } + public static TemplateResponse newTemplateUpdateResponse(TemplateJoinVO vr) { return s_templateJoinDao.newUpdateResponse(vr); } diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 9b92e699fa4..ac7be574983 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -1930,6 +1930,12 @@ public class ApiResponseHelper implements ResponseGenerator { } + @Override + public UserVm findUserVmByNicId(Long nicId) { + NicVO nic = ApiDBUtils.findNicById(nicId); + return ApiDBUtils.findUserVmById(nic.getInstanceId()); + } + @Override public VolumeVO findVolumeById(Long volumeId) { return ApiDBUtils.findVolumeById(volumeId); @@ -4853,6 +4859,8 @@ public class ApiResponseHelper implements ResponseGenerator { VpcVO vpc = _entityMgr.findByUuidIncludingRemoved(VpcVO.class, userVm.getVpcUuid()); response.setVpcName(vpc.getName()); } + + response.setEnabled(result.isEnabled()); return response; } @@ -5402,8 +5410,21 @@ public class ApiResponseHelper implements ResponseGenerator { if (host != null) { response.setHostId(host.getUuid()); response.setHostName(host.getName()); - } else if (instance.getHostName() != null) { - response.setHostName(instance.getHostName()); + if (host.getHypervisorType() != null) { + response.setHypervisor(host.getHypervisorType().name()); + } + response.setHypervisorVersion(host.getHypervisorVersion()); + } else { + // In case the unmanaged instance is on an external host + if (instance.getHostName() != null) { + response.setHostName(instance.getHostName()); + } + if (instance.getHypervisorType() != null) { + response.setHypervisor(instance.getHypervisorType()); + } + if (instance.getHostHypervisorVersion() != null) { + response.setHypervisorVersion(instance.getHostHypervisorVersion()); + } } response.setPowerState((instance.getPowerState() != null)? instance.getPowerState().toString() : UnmanagedInstanceTO.PowerState.PowerUnknown.toString()); response.setCpuCores(instance.getCpuCores()); diff --git a/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java b/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java index 5a634cc0ca8..0205bfa9d8f 100644 --- a/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java +++ b/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java @@ -314,20 +314,7 @@ public class ParamProcessWorker implements DispatchWorker { protected void doAccessChecks(BaseCmd cmd, Map entitiesToAccess) { Account caller = CallContext.current().getCallingAccount(); - List entityOwners = cmd.getEntityOwnerIds(); - Account[] owners = null; - if (entityOwners != null) { - owners = entityOwners.stream().map(id -> _accountMgr.getAccount(id)).toArray(Account[]::new); - } else { - if (cmd.getEntityOwnerId() == Account.ACCOUNT_ID_SYSTEM && cmd instanceof BaseAsyncCmd && ((BaseAsyncCmd)cmd).getApiResourceType() == ApiCommandResourceType.Network) { - if (logger.isDebugEnabled()) { - logger.debug("Skipping access check on the network owner if the owner is ROOT/system."); - } - owners = new Account[]{}; - } else { - owners = new Account[]{_accountMgr.getAccount(cmd.getEntityOwnerId())}; - } - } + Account[] owners = getEntityOwners(cmd); if (cmd instanceof BaseAsyncCreateCmd) { // check that caller can access the owner account. diff --git a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java index 2c6c6481661..9bc409d455e 100644 --- a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java @@ -196,6 +196,7 @@ public class DomainRouterJoinDaoImpl extends GenericDaoBase secondaryIps = ApiDBUtils.findNicSecondaryIps(uvo.getNicId()); if (secondaryIps != null) { List ipList = new ArrayList(); diff --git a/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java index 485b8f0a6b9..db7f75b6f2b 100644 --- a/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java @@ -274,6 +274,9 @@ public class DomainRouterJoinVO extends BaseViewVO implements ControlledViewEnti @Column(name = "mtu") private Integer mtu; + @Column(name = "is_nic_enabled") + private boolean isNicEnabled; + public DomainRouterJoinVO() { } @@ -577,4 +580,8 @@ public class DomainRouterJoinVO extends BaseViewVO implements ControlledViewEnti public Integer getMtu() { return mtu; } + + public boolean isNicEnabled() { + return isNicEnabled; + } } diff --git a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java index 1a6dbffe189..4d475f9bcb3 100644 --- a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java @@ -346,6 +346,9 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro @Column(name = "is_default_nic") private boolean isDefaultNic; + @Column(name = "is_nic_enabled") + private boolean isNicEnabled; + @Column(name = "ip_address") private String ipAddress; @@ -1094,6 +1097,10 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro return leaseActionExecution; } + public boolean isNicEnabled() { + return isNicEnabled; + } + public String getDnsRecordUrl() { return dnsRecordUrl; } diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index 230f97f6fd3..f163a3d52a5 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -1714,7 +1714,7 @@ StateListener, Configurable { @Override public void reorderHostsByPriority(Map priorities, List hosts) { - logger.info("Re-ordering hosts {} by priorities {}", hosts, priorities); + logger.debug("Re-ordering hosts {} by priorities {}", hosts, priorities); hosts.removeIf(host -> DataCenterDeployment.PROHIBITED_HOST_PRIORITY.equals(getHostPriority(priorities, host.getId()))); @@ -1727,7 +1727,7 @@ StateListener, Configurable { } ); - logger.info("Hosts after re-ordering are: {}", hosts); + logger.debug("Hosts after re-ordering are: {}", hosts); } private Integer getHostPriority(Map priorities, Long hostId) { diff --git a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java index 97c453003a8..c7de3d472e3 100644 --- a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java +++ b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java @@ -205,6 +205,7 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis to.setIp6Dns1(profile.getIPv6Dns1()); to.setIp6Dns2(profile.getIPv6Dns2()); to.setNetworkId(profile.getNetworkId()); + to.setEnabled(profile.isEnabled()); NetworkVO network = networkDao.findById(profile.getNetworkId()); to.setNetworkUuid(network.getUuid()); diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index f8c4d1d44d4..dc28b5f98a4 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -44,6 +44,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.acl.ApiKeyPairVO; +import com.cloud.api.query.MutualExclusiveIdsManagerBase; import com.cloud.network.vpc.VpcVO; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; @@ -566,6 +567,7 @@ import org.apache.cloudstack.api.command.user.vm.StartVMCmd; import org.apache.cloudstack.api.command.user.vm.StopVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateDefaultNicForVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; +import org.apache.cloudstack.api.command.user.vm.UpdateVmNicCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVmNicIpCmd; import org.apache.cloudstack.api.command.user.vm.UpgradeVMCmd; import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; @@ -842,7 +844,6 @@ import com.cloud.utils.Pair; import com.cloud.utils.PasswordGenerator; import com.cloud.utils.Ternary; import com.cloud.utils.component.ComponentLifecycle; -import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.db.DB; @@ -885,7 +886,7 @@ import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; import com.cloud.vm.dao.VMInstanceDetailsDao; -public class ManagementServerImpl extends ManagerBase implements ManagementServer, Configurable { +public class ManagementServerImpl extends MutualExclusiveIdsManagerBase implements ManagementServer, Configurable { protected StateMachine2 _stateMachine; static final String FOR_SYSTEMVMS = "forsystemvms"; @@ -2908,7 +2909,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe @Override public Pair, Integer> listGuestOSByCriteria(final ListGuestOsCmd cmd) { - final Long id = cmd.getId(); + List ids = getIdsListFromCmd(cmd.getId(), cmd.getIds()); final Long osCategoryId = cmd.getOsCategoryId(); final String description = cmd.getDescription(); final String keyword = cmd.getKeyword(); @@ -2916,7 +2917,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe final Long pageSize = cmd.getPageSizeVal(); Boolean forDisplay = cmd.getDisplay(); - return _guestOSDao.listGuestOSByCriteria(startIndex, pageSize, id, osCategoryId, description, keyword, forDisplay); + return _guestOSDao.listGuestOSByCriteria(startIndex, pageSize, ids, osCategoryId, description, keyword, forDisplay); } @Override @@ -3048,28 +3049,41 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe throw new InvalidParameterValueException("Hypervisor version parameter cannot be used without specifying a hypervisor : XenServer, KVM or VMware"); } - final SearchCriteria sc = _guestOSHypervisorDao.createSearchCriteria(); + SearchBuilder sb = _guestOSHypervisorDao.createSearchBuilder(); + sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); + sb.and("guestOsName", sb.entity().getGuestOsName(), SearchCriteria.Op.LIKE); + sb.and("hypervisorType", sb.entity().getHypervisorType(), SearchCriteria.Op.LIKE); + sb.and("hypervisorVersion", sb.entity().getHypervisorVersion(), SearchCriteria.Op.LIKE); + sb.and(guestOsId, sb.entity().getGuestOsId(), SearchCriteria.Op.EQ); + SearchBuilder guestOSSearch = _guestOSDao.createSearchBuilder(); + guestOSSearch.and("display", guestOSSearch.entity().isDisplay(), SearchCriteria.Op.LIKE); + sb.join("guestOSSearch", guestOSSearch, sb.entity().getGuestOsId(), guestOSSearch.entity().getId(), JoinBuilder.JoinType.INNER); + + final SearchCriteria sc = sb.create(); if (id != null) { - sc.addAnd("id", SearchCriteria.Op.EQ, id); + sc.setParameters("id", SearchCriteria.Op.EQ, id); } if (osTypeId != null) { - sc.addAnd(guestOsId, SearchCriteria.Op.EQ, osTypeId); + sc.setParameters(guestOsId, osTypeId); } if (osNameForHypervisor != null) { - sc.addAnd("guestOsName", SearchCriteria.Op.LIKE, "%" + osNameForHypervisor + "%"); + sc.setParameters("guestOsName", "%" + osNameForHypervisor + "%"); } if (hypervisor != null) { - sc.addAnd("hypervisorType", SearchCriteria.Op.LIKE, "%" + hypervisor + "%"); + sc.setParameters("hypervisorType", "%" + hypervisor + "%"); } if (hypervisorVersion != null) { - sc.addAnd("hypervisorVersion", SearchCriteria.Op.LIKE, "%" + hypervisorVersion + "%"); + sc.setParameters("hypervisorVersion", "%" + hypervisorVersion + "%"); } + // Exclude the mappings for guest OS marked as display = false + sc.setJoinParameters("guestOSSearch", "display", true); + if (osDisplayName != null) { List guestOSVOS = _guestOSDao.listLikeDisplayName(osDisplayName); if (CollectionUtils.isNotEmpty(guestOSVOS)) { @@ -4152,6 +4166,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(StopVMCmd.class); cmdList.add(UpdateDefaultNicForVMCmd.class); cmdList.add(UpdateVMCmd.class); + cmdList.add(UpdateVmNicCmd.class); cmdList.add(UpgradeVMCmd.class); cmdList.add(CreateVMGroupCmd.class); cmdList.add(DeleteVMGroupCmd.class); diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java index 67f7128e864..1594e0c9da6 100755 --- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java @@ -2381,6 +2381,9 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, @Override public TemplateType validateTemplateType(BaseCmd cmd, boolean isAdmin, boolean isCrossZones, HypervisorType hypervisorType) { + if (cmd instanceof GetUploadParamsForIsoCmd) { + return TemplateType.USER; + } if (!(cmd instanceof UpdateTemplateCmd) && !(cmd instanceof RegisterTemplateCmd) && !(cmd instanceof GetUploadParamsForTemplateCmd)) { return null; } diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index cc2650ff276..141a32da6eb 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -73,7 +73,9 @@ import org.apache.cloudstack.affinity.dao.AffinityGroupDao; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd; import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd; import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd; @@ -3889,6 +3891,48 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M return null; } + @Override + public Long finalizeAccountId(Long accountId, String accountName, Long domainId, Long projectId) { + if (projectId != null) { + if (ObjectUtils.anyNotNull(accountId, accountName)) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Project and account can not be specified together."); + } + return getActiveProjectAccountByProjectId(projectId); + } + if (accountId != null) { + if (getActiveAccountById(accountId) != null) { + return accountId; + } + throw new InvalidParameterValueException(String.format("Unable to find account with ID [%s].", accountId)); + } + + if (accountName == null && domainId == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Either %s or %s must be informed.", ApiConstants.ACCOUNT_ID, ApiConstants.PROJECT_ID)); + } + + try { + Account activeAccount = getActiveAccountByName(accountName, domainId); + if (activeAccount != null) { + return activeAccount.getId(); + } + } catch (InvalidParameterValueException exception) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Both %s and %s are needed if using either. Consider using %s instead.", + ApiConstants.ACCOUNT, ApiConstants.DOMAIN_ID, ApiConstants.ACCOUNT_ID)); + } + throw new InvalidParameterValueException(String.format("Unable to find account by name [%s] on domain [%s].", accountName, domainId)); + } + + protected long getActiveProjectAccountByProjectId(long projectId) { + Project project = _projectMgr.getProject(projectId); + if (project == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Unable to find project with ID [%s].", projectId)); + } + if (project.getState() != Project.State.Active) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Project with ID [%s] is not active.", projectId)); + } + return project.getProjectAccountId(); + } + @Override public UserAccount getUserAccountById(Long userId) { UserAccount userAccount = userAccountDao.findById(userId); diff --git a/server/src/main/java/com/cloud/vm/UserVmManager.java b/server/src/main/java/com/cloud/vm/UserVmManager.java index c035165a3fa..0a744709644 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManager.java +++ b/server/src/main/java/com/cloud/vm/UserVmManager.java @@ -199,4 +199,9 @@ public interface UserVmManager extends UserVmService { Boolean getDestroyRootVolumeOnVmDestruction(Long domainId); + /** + * @return true if the VM is part of a CKS cluster, false otherwise. + */ + boolean isVMPartOfAnyCKSCluster(VMInstanceVO vm); + } diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index e3ed287cbef..5dade1bb93c 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -102,6 +102,7 @@ import org.apache.cloudstack.api.command.user.vm.SecurityGroupAction; import org.apache.cloudstack.api.command.user.vm.StartVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateDefaultNicForVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; +import org.apache.cloudstack.api.command.user.vm.UpdateVmNicCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVmNicIpCmd; import org.apache.cloudstack.api.command.user.vm.UpgradeVMCmd; import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; @@ -1177,21 +1178,21 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir List vmNetworks = _vmNetworkMapDao.getNetworks(vmId); List routers = new ArrayList<>(); //List the stopped routers - for(long vmNetworkId : vmNetworks) { + for (long vmNetworkId : vmNetworks) { List router = _routerDao.listStopped(vmNetworkId); routers.addAll(router); } //A vm may not have many nics attached and even fewer routers might be stopped (only in exceptional cases) //Safe to start the stopped router serially, this is consistent with the way how multiple networks are added to vm during deploy //and routers are started serially ,may revisit to make this process parallel - for(DomainRouterVO routerToStart : routers) { + for (DomainRouterVO routerToStart : routers) { logger.warn("Trying to start router {} as part of vm: {} reboot", routerToStart, vm); _virtualNetAppliance.startRouter(routerToStart.getId(),true); } } } catch (ConcurrentOperationException e) { throw new CloudRuntimeException("Concurrent operations on starting router. " + e); - } catch (Exception ex){ + } catch (Exception ex) { throw new CloudRuntimeException("Router start failed due to" + ex); } finally { if (logger.isInfoEnabled()) { @@ -1476,7 +1477,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir macAddress = validateOrReplaceMacAddress(macAddress, network); - if(_nicDao.findByNetworkIdAndMacAddress(networkId, macAddress) != null) { + if (_nicDao.findByNetworkIdAndMacAddress(networkId, macAddress) != null) { throw new CloudRuntimeException("A NIC with this MAC address exists for network: " + network.getUuid()); } @@ -1502,7 +1503,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir vmInstance, dc, network, dataCenterDao.findById(network.getDataCenterId()))); } - if(_networkModel.getNicInNetwork(vmInstance.getId(),network.getId()) != null){ + if (_networkModel.getNicInNetwork(vmInstance.getId(),network.getId()) != null) { logger.debug("Instance {} already in network {} going to add another NIC", vmInstance, network); } else { //* get all vms hostNames in the network @@ -1530,7 +1531,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } catch (ConcurrentOperationException e) { throw new CloudRuntimeException("Concurrent operations on adding NIC to " + vmInstance + ": " + e); } finally { - if(cleanUp) { + if (cleanUp) { try { _itMgr.removeVmFromNetwork(vmInstance, network, null); } catch (ResourceUnavailableException e) { @@ -1935,6 +1936,54 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return vm; } + @Override + public UserVm updateVirtualMachineNic(UpdateVmNicCmd cmd) { + Long nicId = cmd.getNicId(); + Boolean isNicEnabled = cmd.isEnabled(); + Account caller = CallContext.current().getCallingAccount(); + + NicVO nic = _nicDao.findById(nicId); + if (nic == null) { + throw new InvalidParameterValueException("Unable to find the specified NIC."); + } + + UserVmVO vmInstance = _vmDao.findById(nic.getInstanceId()); + if (vmInstance == null) { + throw new InvalidParameterValueException("Unable to find a virtual machine associated with the specified NIC."); + } + + if (vmInstance.getHypervisorType() != HypervisorType.KVM) { + throw new InvalidParameterValueException("Updating the VM NIC is only supported by the KVM hypervisor."); + } + + NetworkVO network = _networkDao.findById(nic.getNetworkId()); + if (network == null) { + throw new InvalidParameterValueException("Unable to find NIC's network."); + } + + _accountMgr.checkAccess(caller, null, true, vmInstance); + + if (isNicEnabled == null) { + return vmInstance; + } + + boolean success = false; + try { + success = _itMgr.updateVmNic(vmInstance, nic, isNicEnabled); + } catch (ResourceUnavailableException e) { + throw new CloudRuntimeException(String.format("Unable to update NIC %s of VM %s in network %s due to: %s.", nic, vmInstance, network.getUuid(), e.getMessage())); + } catch (ConcurrentOperationException e) { + throw new CloudRuntimeException(String.format("Concurrent operations while updating NIC %s for VM %s: %s.", nic, vmInstance, e.getMessage())); + } + + if (!success) { + throw new CloudRuntimeException(String.format("Failed to update NIC %s of VM %s in network %s.", nic, vmInstance, network.getUuid())); + } + + logger.debug("Successfully updated NIC {} in network {} for VM {}.", nic, network.getUuid(), vmInstance); + return vmInstance; + } + private void updatePublicIpDnatVmIp(long vmId, long networkId, String oldIp, String newIp) { if (!_networkModel.areServicesSupportedInNetwork(networkId, Service.StaticNat)) { return; @@ -2311,7 +2360,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir answer = _agentMgr.easySend(neighbor.getId(), cmd); } - if (answer != null && answer instanceof GetVolumeStatsAnswer){ + if (answer != null && answer instanceof GetVolumeStatsAnswer) { GetVolumeStatsAnswer volstats = (GetVolumeStatsAnswer)answer; if (volstats.getVolumeStats() != null) { volumeStatsByUuid.putAll(volstats.getVolumeStats()); @@ -2322,7 +2371,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return volumeStatsByUuid.size() > 0 ? volumeStatsByUuid : null; } - private List getVolumesByHost(HostVO host, StoragePool pool){ + private List getVolumesByHost(HostVO host, StoragePool pool) { List vmsPerHost = _vmInstanceDao.listByHostId(host.getId()); return vmsPerHost.stream() .flatMap(vm -> _volsDao.findNonDestroyedVolumesByInstanceIdAndPoolId(vm.getId(),pool.getId()).stream().map(vol -> @@ -2594,6 +2643,22 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } } + private void transitionExpungingToError(long vmId) { + UserVmVO vm = _vmDao.findById(vmId); + if (vm != null && vm.getState() == State.Expunging) { + try { + boolean transitioned = _itMgr.stateTransitTo(vm, VirtualMachine.Event.OperationFailedToError, null); + if (transitioned) { + logger.info("Transitioned VM [{}] from Expunging to Error after failed expunge", vm.getUuid()); + } else { + logger.warn("Failed to persist transition of VM [{}] from Expunging to Error after failed expunge, possibly due to concurrent update", vm.getUuid()); + } + } catch (NoTransitionException e) { + logger.warn("Failed to transition VM {} to Error state: {}", vm, e.getMessage()); + } + } + } + /** * Release network resources, it was done on vm stop previously. * @param id vm id @@ -2602,7 +2667,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir */ private void releaseNetworkResourcesOnExpunge(long id) throws ConcurrentOperationException, ResourceUnavailableException { final VMInstanceVO vmInstance = _vmDao.findById(id); - if (vmInstance != null){ + if (vmInstance != null) { final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vmInstance); _networkMgr.release(profile, false); } @@ -2867,17 +2932,17 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir ) { if (newCpu > currentCpu) { _resourceLimitMgr.incrementVmCpuResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, newCpu - currentCpu); - } else if (newCpu > 0 && currentCpu > newCpu){ + } else if (newCpu > 0 && currentCpu > newCpu) { _resourceLimitMgr.decrementVmCpuResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, currentCpu - newCpu); } if (newMemory > currentMemory) { _resourceLimitMgr.incrementVmMemoryResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, newMemory - currentMemory); - } else if (newMemory > 0 && currentMemory > newMemory){ + } else if (newMemory > 0 && currentMemory > newMemory) { _resourceLimitMgr.decrementVmMemoryResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, currentMemory - newMemory); } if (newGpu > currentGpu) { _resourceLimitMgr.incrementVmGpuResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, newGpu - currentGpu); - } else if (newGpu > 0 && currentGpu > newGpu){ + } else if (newGpu > 0 && currentGpu > newGpu) { _resourceLimitMgr.decrementVmGpuResourceCount(owner.getAccountId(), vmInstance.isDisplay(), svcOffering, template, currentGpu - newGpu); } } @@ -3008,7 +3073,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if (userReadOnlySettings.contains(detailName)) { throw new InvalidParameterValueException("You're not allowed to add or edit the read-only setting: " + detailName); } - if (existingDetails.stream().anyMatch(d -> Objects.equals(d.getName(), detailName) && !d.isDisplay())){ + if (existingDetails.stream().anyMatch(d -> Objects.equals(d.getName(), detailName) && !d.isDisplay())) { throw new InvalidParameterValueException("You're not allowed to add or edit the non-displayable setting: " + detailName); } } @@ -3100,25 +3165,24 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private void saveUsageEvent(UserVmVO vm) { // If vm not destroyed - if( vm.getState() != State.Destroyed && vm.getState() != State.Expunging && vm.getState() != State.Error){ + if (vm.getState() != State.Destroyed && vm.getState() != State.Expunging && vm.getState() != State.Error) { - if(vm.isDisplayVm()){ + if (vm.isDisplayVm()) { //1. Allocated VM Usage Event generateUsageEvent(vm, true, EventTypes.EVENT_VM_CREATE); - if(vm.getState() == State.Running || vm.getState() == State.Stopping){ + if (vm.getState() == State.Running || vm.getState() == State.Stopping) { //2. Running VM Usage Event generateUsageEvent(vm, true, EventTypes.EVENT_VM_START); // 3. Network offering usage generateNetworkUsageForVm(vm, true, EventTypes.EVENT_NETWORK_OFFERING_ASSIGN); } - - }else { + } else { //1. Allocated VM Usage Event generateUsageEvent(vm, true, EventTypes.EVENT_VM_DESTROY); - if(vm.getState() == State.Running || vm.getState() == State.Stopping){ + if (vm.getState() == State.Running || vm.getState() == State.Stopping) { //2. Running VM Usage Event generateUsageEvent(vm, true, EventTypes.EVENT_VM_STOP); @@ -3130,8 +3194,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } - private void generateNetworkUsageForVm(VirtualMachine vm, boolean isDisplay, String eventType){ - + private void generateNetworkUsageForVm(VirtualMachine vm, boolean isDisplay, String eventType) { List nics = _nicDao.listByVmId(vm.getId()); for (NicVO nic : nics) { NetworkVO network = _networkDao.findById(nic.getNetworkId()); @@ -3156,9 +3219,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir throw new CloudRuntimeException("Unable to find virtual machine with id " + id); } - if(instanceName != null){ + if (instanceName != null) { VMInstanceVO vmInstance = _vmInstanceDao.findVMByInstanceName(instanceName); - if(vmInstance != null && vmInstance.getId() != id){ + if (vmInstance != null && vmInstance.getId() != id) { throw new CloudRuntimeException("Instance name : " + instanceName + " is not unique"); } } @@ -3300,7 +3363,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir networkIds = networks.stream().map(Network::getId).collect(Collectors.toList()); } } catch (InvalidParameterValueException e) { - if(logger.isDebugEnabled()) { + if (logger.isDebugEnabled()) { logger.debug(e.getMessage(),e); } } @@ -3592,8 +3655,19 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir detachVolumesFromVm(vm, dataVols); UserVm destroyedVm = destroyVm(vmId, expunge); - if (expunge && !expunge(vm)) { - throw new CloudRuntimeException("Failed to expunge vm " + destroyedVm); + if (expunge) { + boolean expunged = false; + String errorMsg = ""; + try { + expunged = expunge(vm); + } catch (RuntimeException e) { + logger.error("Failed to expunge VM [{}] due to: {}", vm, e.getMessage(), e); + errorMsg = e.getMessage(); + } + if (!expunged) { + transitionExpungingToError(vm.getId()); + throw new CloudRuntimeException("Failed to expunge VM " + vm.getUuid() + (StringUtils.isNotBlank(errorMsg) ? " due to: " + errorMsg : "")); + } } autoScaleManager.removeVmFromVmGroup(vmId); @@ -4439,7 +4513,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } } - if (template.getTemplateType().equals(TemplateType.SYSTEM) && !CKS_NODE.equals(vmType) && !SHAREDFSVM.equals(vmType)) { + if (TemplateType.SYSTEM.equals(template.getTemplateType()) && !CKS_NODE.equals(vmType) && !SHAREDFSVM.equals(vmType)) { throw new InvalidParameterValueException(String.format("Unable to use system template %s to deploy a user vm", template)); } @@ -4834,12 +4908,13 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private UserVmVO commitUserVm(final boolean isImport, final DataCenter zone, final Host host, final Host lastHost, final VirtualMachineTemplate template, final String hostName, final String displayName, final Account owner, final Long diskOfferingId, final Long diskSize, final String userData, Long userDataId, String userDataDetails, final Boolean isDisplayVm, final String keyboard, - final long accountId, final long userId, final ServiceOffering offering, final boolean isIso, final String sshPublicKeys, final LinkedHashMap> networkNicMap, + final long accountId, final long userId, final ServiceOffering offering, final boolean isIso, final Long guestOsId, final String sshPublicKeys, final LinkedHashMap> networkNicMap, final long id, final String instanceName, final String uuidName, final HypervisorType hypervisorType, final Map customParameters, final Map> extraDhcpOptionMap, final Map dataDiskTemplateToDiskOfferingMap, final Map userVmOVFPropertiesMap, final VirtualMachine.PowerState powerState, final boolean dynamicScalingEnabled, String vmType, final Long rootDiskOfferingId, String sshkeypairs, List dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { - UserVmVO vm = new UserVmVO(id, instanceName, displayName, template.getId(), hypervisorType, template.getGuestOSId(), offering.isOfferHA(), + Long selectedGuestOsId = guestOsId != null ? guestOsId : template.getGuestOSId(); + UserVmVO vm = new UserVmVO(id, instanceName, displayName, template.getId(), hypervisorType, selectedGuestOsId, offering.isOfferHA(), offering.getLimitCpuUse(), owner.getDomainId(), owner.getId(), userId, offering.getId(), userData, userDataId, userDataDetails, hostName); vm.setUuid(uuidName); vm.setDynamicallyScalable(dynamicScalingEnabled); @@ -4865,8 +4940,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir vm.setIsoId(template.getId()); } - long guestOSId = template.getGuestOSId(); - GuestOSVO guestOS = _guestOSDao.findById(guestOSId); + GuestOSVO guestOS = _guestOSDao.findById(selectedGuestOsId); long guestOSCategoryId = guestOS.getCategoryId(); GuestOSCategoryVO guestOSCategory = _guestOSCategoryDao.findById(guestOSCategoryId); if (hypervisorType.equals(HypervisorType.VMware)) { @@ -5124,7 +5198,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir List dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { return commitUserVm(false, zone, null, null, template, hostName, displayName, owner, diskOfferingId, diskSize, userData, userDataId, userDataDetails, isDisplayVm, keyboard, - accountId, userId, offering, isIso, sshPublicKeys, networkNicMap, + accountId, userId, offering, isIso, null, sshPublicKeys, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, extraDhcpOptionMap, dataDiskTemplateToDiskOfferingMap, userVmOVFPropertiesMap, null, dynamicScalingEnabled, vmType, rootDiskOfferingId, sshkeypairs, dataDiskInfoList, volume, snapshot); @@ -5156,7 +5230,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir @Override - public void generateUsageEvent(VirtualMachine vm, boolean isDisplay, String eventType){ + public void generateUsageEvent(VirtualMachine vm, boolean isDisplay, String eventType) { ServiceOfferingVO serviceOffering = serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId()); if (!serviceOffering.isDynamic()) { UsageEventUtils.publishUsageEvent(eventType, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), @@ -5414,7 +5488,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } // add userdata info into vm profile Nic defaultNic = _networkModel.getDefaultNic(vm.getId()); - if(defaultNic != null) { + if (defaultNic != null) { Network network = _networkModel.getNetwork(defaultNic.getNetworkId()); if (_networkModel.isSharedNetworkWithoutServices(network.getId())) { final String serviceOffering = serviceOfferingDao.findByIdIncludingRemoved(vm.getId(), vm.getServiceOfferingId()).getDisplayText(); @@ -5665,7 +5739,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir try { VirtualMachineEntity vmEntity = _orchSrvc.getVirtualMachine(vm.getUuid()); - if(forced) { + if (forced) { status = vmEntity.stopForced(Long.toString(userId)); } else { status = vmEntity.stop(Long.toString(userId)); @@ -5846,7 +5920,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir params = createParameterInParameterMap(params, additionalParams, VirtualMachineProfile.Param.VmPassword, password); } - if(additionalParams.containsKey(VirtualMachineProfile.Param.BootIntoSetup)) { + if (additionalParams.containsKey(VirtualMachineProfile.Param.BootIntoSetup)) { if (! HypervisorType.VMware.equals(vm.getHypervisorType())) { throw new InvalidParameterValueException(ApiConstants.BOOT_INTO_SETUP + " makes no sense for " + vm.getHypervisorType()); } @@ -6324,8 +6398,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } if (!serviceOffering.isDynamic()) { - for(String detail: cmd.getDetails().keySet()) { - if(detail.equalsIgnoreCase(VmDetailConstants.CPU_NUMBER) || detail.equalsIgnoreCase(VmDetailConstants.CPU_SPEED) || detail.equalsIgnoreCase(VmDetailConstants.MEMORY)) { + for (String detail: cmd.getDetails().keySet()) { + if (detail.equalsIgnoreCase(VmDetailConstants.CPU_NUMBER) || detail.equalsIgnoreCase(VmDetailConstants.CPU_SPEED) || detail.equalsIgnoreCase(VmDetailConstants.MEMORY)) { throw new InvalidParameterValueException("cpuNumber or cpuSpeed or memory should not be specified for static service offering"); } } @@ -6544,8 +6618,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir // check if this templateId has a child ISO List child_templates = _templateDao.listByParentTemplatetId(template.getId()); - for (VMTemplateVO tmpl: child_templates){ - if (tmpl.getFormat() == Storage.ImageFormat.ISO){ + for (VMTemplateVO tmpl: child_templates) { + if (tmpl.getFormat() == Storage.ImageFormat.ISO) { logger.info("MDOV trying to attach disk {} to the VM {}", tmpl, vm); _tmplService.attachIso(tmpl.getId(), vm.getId(), true); } @@ -7202,7 +7276,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir checkIfHostOfVMIsInPrepareForMaintenanceState(vm, "Migrate"); - if(serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) { + if (serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) { throw new InvalidParameterValueException("Live Migration of GPU enabled VM is not supported"); } @@ -7817,7 +7891,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir throw ex; } - if(serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) { + if (serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) { throw new InvalidParameterValueException("Live Migration of GPU enabled VM is not supported"); } @@ -8794,6 +8868,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir throw new InvalidParameterValueException(String.format("Operation not supported for instance: %s", vm.getName())); } + if (isVMPartOfAnyCKSCluster(vm)) { + throw new UnsupportedServiceException("Cannot restore VM with id = " + vm.getUuid() + + " as it belongs to a CKS cluster. Please remove the VM from the CKS cluster before restoring."); + } _accountMgr.checkAccess(caller, null, true, vm); VMTemplateVO template; @@ -9228,8 +9306,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir Long hostId = vm.getHostId() != null ? vm.getHostId() : vm.getLastHostId(); if (hostId != null) { - VolumeInfo volumeInfo = volFactory.getVolume(root.getId()); + // default findById() won't search entries with removed field not null Host host = _hostDao.findById(hostId); + if (host == null) { + logger.warn("Host {} not found", hostId); + return; + } + + VolumeInfo volumeInfo = volFactory.getVolume(root.getId()); final Command cmd; @@ -9532,7 +9616,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir @Override public UserVm importVM(final DataCenter zone, final Host host, final VirtualMachineTemplate template, final String instanceNameInternal, final String displayName, final Account owner, final String userData, final Account caller, final Boolean isDisplayVm, final String keyboard, - final long accountId, final long userId, final ServiceOffering serviceOffering, final String sshPublicKeys, + final long accountId, final long userId, final ServiceOffering serviceOffering, final String sshPublicKeys, final Long guestOsId, final String hostName, final HypervisorType hypervisorType, final Map customParameters, final VirtualMachine.PowerState powerState, final LinkedHashMap> networkNicMap) throws InsufficientCapacityException { return Transaction.execute((TransactionCallbackWithException) status -> { @@ -9558,7 +9642,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir final Boolean dynamicScalingEnabled = checkIfDynamicScalingCanBeEnabled(null, serviceOffering, template, zone.getId()); return commitUserVm(true, zone, host, lastHost, template, hostName, displayName, owner, null, null, userData, null, null, isDisplayVm, keyboard, - accountId, userId, serviceOffering, template.getFormat().equals(ImageFormat.ISO), sshPublicKeys, networkNicMap, + accountId, userId, serviceOffering, template.getFormat().equals(ImageFormat.ISO), guestOsId, sshPublicKeys, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, null, null, null, powerState, dynamicScalingEnabled, null, serviceOffering.getDiskOfferingId(), null, null, null, null); }); @@ -9985,10 +10069,15 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } } - public Boolean getDestroyRootVolumeOnVmDestruction(Long domainId){ + public Boolean getDestroyRootVolumeOnVmDestruction(Long domainId) { return DestroyRootVolumeOnVmDestruction.valueIn(domainId); } + @Override + public boolean isVMPartOfAnyCKSCluster(VMInstanceVO vm) { + return kubernetesServiceHelpers.get(0).findByVmId(vm.getId()) != null; + } + private void setVncPasswordForKvmIfAvailable(Map customParameters, UserVmVO vm) { if (customParameters.containsKey(VmDetailConstants.KVM_VNC_PASSWORD) && StringUtils.isNotEmpty(customParameters.get(VmDetailConstants.KVM_VNC_PASSWORD))) { diff --git a/server/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageManagerImpl.java b/server/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageManagerImpl.java index 383644f9aa2..62b5ac38ee2 100644 --- a/server/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageManagerImpl.java @@ -68,6 +68,7 @@ import org.apache.cloudstack.api.response.VolumeForImportResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; @@ -394,7 +395,7 @@ public class VolumeImportUnmanageManagerImpl implements VolumeImportUnmanageServ Map volumeDetails = volume.getDetails(); if (volumeDetails != null && volumeDetails.containsKey(VolumeOnStorageTO.Detail.BACKING_FILE)) { String backingFile = volumeDetails.get(VolumeOnStorageTO.Detail.BACKING_FILE); - if (StringUtils.isNotBlank(backingFile)) { + if (StringUtils.isNotBlank(backingFile) && !AllowImportVolumeWithBackingFile.value()) { logFailureAndThrowException("Volume with backing file cannot be imported or unmanaged."); } } @@ -513,4 +514,16 @@ public class VolumeImportUnmanageManagerImpl implements VolumeImportUnmanageServ volume.setRemoved(new Date()); volumeDao.update(volume.getId(), volume); } + + @Override + public String getConfigComponentName() { + return VolumeImportUnmanageManagerImpl.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[]{ + AllowImportVolumeWithBackingFile + }; + } } diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 14c67417015..e043791c6bf 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -155,8 +155,6 @@ import org.apache.cloudstack.api.command.admin.vm.ListUnmanagedInstancesCmd; import org.apache.cloudstack.api.command.admin.vm.ListVmsForImportCmd; import org.apache.cloudstack.api.command.admin.vm.UnmanageVMInstanceCmd; import org.apache.cloudstack.api.response.ListResponse; -import org.apache.cloudstack.api.response.NicResponse; -import org.apache.cloudstack.api.response.UnmanagedInstanceDiskResponse; import org.apache.cloudstack.api.response.UnmanagedInstanceResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.context.CallContext; @@ -195,15 +193,15 @@ import java.util.stream.Collectors; import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS; import static org.apache.cloudstack.api.ApiConstants.MIN_IOPS; +import static org.apache.cloudstack.storage.volume.VolumeImportUnmanageService.AllowImportVolumeWithBackingFile; import static org.apache.cloudstack.vm.ImportVmTask.Step.CloningInstance; import static org.apache.cloudstack.vm.ImportVmTask.Step.Completed; import static org.apache.cloudstack.vm.ImportVmTask.Step.ConvertingInstance; import static org.apache.cloudstack.vm.ImportVmTask.Step.Importing; public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { - public static final String VM_IMPORT_DEFAULT_TEMPLATE_NAME = "system-default-vm-import-dummy-template.iso"; - public static final String KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME = "kvm-default-vm-import-dummy-template"; protected Logger logger = LogManager.getLogger(UnmanagedVMsManagerImpl.class); + private static final long OTHER_LINUX_64_GUEST_OS_ID = 99; private static final List importUnmanagedInstancesSupportedHypervisors = Arrays.asList(Hypervisor.HypervisorType.VMware, Hypervisor.HypervisorType.KVM); @@ -326,7 +324,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { try { template = VMTemplateVO.createSystemIso(templateDao.getNextInSequence(Long.class, "id"), templateName, templateName, true, "", true, 64, Account.ACCOUNT_ID_SYSTEM, "", - "VM Import Default Template", false, 1); + "VM Import Default Template", false, OTHER_LINUX_64_GUEST_OS_ID); template.setState(VirtualMachineTemplate.State.Inactive); template = templateDao.persist(template); if (template == null) { @@ -340,68 +338,6 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { return template; } - private UnmanagedInstanceResponse createUnmanagedInstanceResponse(UnmanagedInstanceTO instance, Cluster cluster, Host host) { - UnmanagedInstanceResponse response = new UnmanagedInstanceResponse(); - response.setName(instance.getName()); - - if (cluster != null) { - response.setClusterId(cluster.getUuid()); - } - if (host != null) { - response.setHostId(host.getUuid()); - response.setHostName(host.getName()); - } - response.setPowerState(instance.getPowerState().toString()); - response.setCpuCores(instance.getCpuCores()); - response.setCpuSpeed(instance.getCpuSpeed()); - response.setCpuCoresPerSocket(instance.getCpuCoresPerSocket()); - response.setMemory(instance.getMemory()); - response.setOperatingSystemId(instance.getOperatingSystemId()); - response.setOperatingSystem(instance.getOperatingSystem()); - response.setObjectName("unmanagedinstance"); - - if (instance.getDisks() != null) { - for (UnmanagedInstanceTO.Disk disk : instance.getDisks()) { - UnmanagedInstanceDiskResponse diskResponse = new UnmanagedInstanceDiskResponse(); - diskResponse.setDiskId(disk.getDiskId()); - if (StringUtils.isNotEmpty(disk.getLabel())) { - diskResponse.setLabel(disk.getLabel()); - } - diskResponse.setCapacity(disk.getCapacity()); - diskResponse.setController(disk.getController()); - diskResponse.setControllerUnit(disk.getControllerUnit()); - diskResponse.setPosition(disk.getPosition()); - diskResponse.setImagePath(disk.getImagePath()); - diskResponse.setDatastoreName(disk.getDatastoreName()); - diskResponse.setDatastoreHost(disk.getDatastoreHost()); - diskResponse.setDatastorePath(disk.getDatastorePath()); - diskResponse.setDatastoreType(disk.getDatastoreType()); - response.addDisk(diskResponse); - } - } - - if (instance.getNics() != null) { - for (UnmanagedInstanceTO.Nic nic : instance.getNics()) { - NicResponse nicResponse = new NicResponse(); - nicResponse.setId(nic.getNicId()); - nicResponse.setNetworkName(nic.getNetwork()); - nicResponse.setMacAddress(nic.getMacAddress()); - if (StringUtils.isNotEmpty(nic.getAdapterType())) { - nicResponse.setAdapterType(nic.getAdapterType()); - } - if (!CollectionUtils.isEmpty(nic.getIpAddress())) { - nicResponse.setIpAddresses(nic.getIpAddress()); - } - nicResponse.setVlanId(nic.getVlan()); - nicResponse.setIsolatedPvlanId(nic.getPvlan()); - nicResponse.setIsolatedPvlanType(nic.getPvlanType()); - response.addNic(nicResponse); - } - } - - return response; - } - private List getAdditionalNameFilters(Cluster cluster) { List additionalNameFilter = new ArrayList<>(); if (cluster == null) { @@ -1145,7 +1081,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { private UserVm importVirtualMachineInternal(final UnmanagedInstanceTO unmanagedInstance, final String instanceNameInternal, final DataCenter zone, final Cluster cluster, final HostVO host, final VirtualMachineTemplate template, final String displayName, final String hostName, final Account caller, final Account owner, final Long userId, final ServiceOfferingVO serviceOffering, final Map dataDiskOfferingMap, - final Map nicNetworkMap, final Map callerNicIpAddressMap, + final Map nicNetworkMap, final Map callerNicIpAddressMap, final Long guestOsId, final Map details, final boolean migrateAllowed, final boolean forced, final boolean isImportUnmanagedFromSameHypervisor) { logger.debug(LogUtils.logGsonWithoutException("Trying to import VM [%s] with name [%s], in zone [%s], cluster [%s], and host [%s], using template [%s], service offering [%s], disks map [%s], NICs map [%s] and details [%s].", unmanagedInstance, displayName, zone, cluster, host, template, serviceOffering, dataDiskOfferingMap, nicNetworkMap, details)); @@ -1231,7 +1167,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { try { userVm = userVmManager.importVM(zone, host, template, internalCSName, displayName, owner, null, caller, true, null, owner.getAccountId(), userId, - validatedServiceOffering, null, hostName, + validatedServiceOffering, null, guestOsId, hostName, cluster.getHypervisorType(), allDetails, powerState, null); } catch (InsufficientCapacityException ice) { String errorMsg = String.format("Failed to import VM [%s] due to [%s].", displayName, ice.getMessage()); @@ -1550,10 +1486,11 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { protected VMTemplateVO getTemplateForImportInstance(Long templateId, Hypervisor.HypervisorType hypervisorType) { VMTemplateVO template; if (templateId == null) { - String templateName = (Hypervisor.HypervisorType.KVM.equals(hypervisorType)) ? KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME : VM_IMPORT_DEFAULT_TEMPLATE_NAME; + boolean isKVMHypervisor = Hypervisor.HypervisorType.KVM.equals(hypervisorType); + String templateName = (isKVMHypervisor) ? KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME : VM_IMPORT_DEFAULT_TEMPLATE_NAME; template = templateDao.findByName(templateName); if (template == null) { - template = createDefaultDummyVmImportTemplate(Hypervisor.HypervisorType.KVM == hypervisorType); + template = createDefaultDummyVmImportTemplate(isKVMHypervisor); if (template == null) { throw new InvalidParameterValueException(String.format("Default VM import template with unique name: %s for hypervisor: %s cannot be created. Please use templateid parameter for import", templateName, hypervisorType.toString())); } @@ -1633,7 +1570,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { userVm = importVirtualMachineInternal(unmanagedInstance, instanceName, zone, cluster, host, template, displayName, hostName, CallContext.current().getCallingAccount(), owner, userId, serviceOffering, dataDiskOfferingMap, - nicNetworkMap, nicIpAddressMap, + nicNetworkMap, nicIpAddressMap, null, details, migrateAllowed, forced, true); break; } @@ -1713,6 +1650,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { Long convertStoragePoolId = cmd.getConvertStoragePoolId(); String extraParams = cmd.getExtraParams(); boolean forceConvertToPool = cmd.getForceConvertToPool(); + Long guestOsId = cmd.getGuestOsId(); if ((existingVcenterId == null && vcenter == null) || (existingVcenterId != null && vcenter != null)) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, @@ -1800,7 +1738,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { UserVm userVm = importVirtualMachineInternal(convertedInstance, null, zone, destinationCluster, null, template, displayName, hostName, caller, owner, userId, serviceOffering, dataDiskOfferingMap, - nicNetworkMap, nicIpAddressMap, + nicNetworkMap, nicIpAddressMap, guestOsId, details, false, forced, false); long timeElapsedInSecs = (System.currentTimeMillis() - importStartTime) / 1000; logger.debug(String.format("VMware VM %s imported successfully to CloudStack instance %s (%s), Time taken: %d secs, OVF files imported from %s, Source VMware VM details - OS: %s, PowerState: %s, Disks: %s, NICs: %s", @@ -2366,6 +2304,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { * Perform validations before attempting to unmanage a VM from CloudStack: * - VM must not have any associated volume snapshot * - VM must not have an attached ISO + * - VM must not belong to any CKS cluster + * @throws UnsupportedServiceException in case any of the validations above fail */ void performUnmanageVMInstancePrechecks(VMInstanceVO vmVO) { if (hasVolumeSnapshotsPriorToUnmanageVM(vmVO)) { @@ -2377,6 +2317,11 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { throw new UnsupportedServiceException("Cannot unmanage VM with id = " + vmVO.getUuid() + " as there is an ISO attached. Please detach ISO before unmanaging."); } + + if (userVmManager.isVMPartOfAnyCKSCluster(vmVO)) { + throw new UnsupportedServiceException("Cannot unmanage VM with id = " + vmVO.getUuid() + + " as it belongs to a CKS cluster. Please remove the VM from the CKS cluster before unmanaging."); + } } private boolean hasVolumeSnapshotsPriorToUnmanageVM(VMInstanceVO vmVO) { @@ -2677,7 +2622,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { try { userVm = userVmManager.importVM(zone, null, template, null, displayName, owner, null, caller, true, null, owner.getAccountId(), userId, - serviceOffering, null, hostName, + serviceOffering, null, null, hostName, Hypervisor.HypervisorType.KVM, allDetails, powerState, null); } catch (InsufficientCapacityException ice) { logger.error(String.format("Failed to import vm name: %s", instanceName), ice); @@ -2815,7 +2760,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { try { userVm = userVmManager.importVM(zone, null, template, null, displayName, owner, null, caller, true, null, owner.getAccountId(), userId, - serviceOffering, null, hostName, + serviceOffering, null, null, hostName, Hypervisor.HypervisorType.KVM, allDetails, powerState, networkNicMap); } catch (InsufficientCapacityException ice) { logger.error(String.format("Failed to import vm name: %s", instanceName), ice); @@ -2909,7 +2854,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { } if (volumeDetails.containsKey(VolumeOnStorageTO.Detail.BACKING_FILE)) { String backingFile = volumeDetails.get(VolumeOnStorageTO.Detail.BACKING_FILE); - if (StringUtils.isNotBlank(backingFile)) { + if (StringUtils.isNotBlank(backingFile) && !AllowImportVolumeWithBackingFile.value()) { logFailureAndThrowException("Volume with backing file cannot be imported or unmanaged."); } } @@ -3000,9 +2945,8 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { !instance.getName().toLowerCase().contains(keyword)) { continue; } - responses.add(createUnmanagedInstanceResponse(instance, null, null)); + responses.add(responseGenerator.createUnmanagedInstanceResponse(instance, null, null)); } - ListResponse listResponses = new ListResponse<>(); listResponses.setResponses(responses, responses.size()); return listResponses; diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index f7439386fab..1a38c1b0a06 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -77,6 +77,7 @@ import org.apache.cloudstack.api.command.user.vm.ResetVMSSHKeyCmd; import org.apache.cloudstack.api.command.user.vm.ResetVMUserDataCmd; import org.apache.cloudstack.api.command.user.vm.RestoreVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; +import org.apache.cloudstack.api.command.user.vm.UpdateVmNicCmd; import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.backup.BackupVO; @@ -198,6 +199,7 @@ import com.cloud.utils.db.EntityManager; import com.cloud.utils.db.UUIDManager; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.ExceptionProxyObject; +import com.cloud.utils.fsm.NoTransitionException; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDetailsDao; @@ -244,6 +246,9 @@ public class UserVmManagerImplTest { @Mock private UpdateVMCmd updateVmCommand; + @Mock + private UpdateVmNicCmd updateVmNicCmd; + @Mock private AccountManager accountManager; @@ -271,6 +276,9 @@ public class UserVmManagerImplTest { @Mock private UserVO callerUser; + @Mock + private NicVO nicMock; + @Mock private VMTemplateDao templateDao; @@ -454,6 +462,8 @@ public class UserVmManagerImplTest { private static final long vmId = 1l; private static final long zoneId = 2L; private static final long accountId = 3L; + private static final long nicId = 4L; + private static final long networkId = 5L; private static final long serviceOfferingId = 10L; private static final long templateId = 11L; private static final long volumeId = 1L; @@ -1501,6 +1511,7 @@ public class UserVmManagerImplTest { when(cmd.getVmId()).thenReturn(vmId); when(cmd.getTemplateId()).thenReturn(2L); when(userVmDao.findById(vmId)).thenReturn(userVmVoMock); + Mockito.doReturn(false).when(userVmManagerImpl).isVMPartOfAnyCKSCluster(userVmVoMock); userVmManagerImpl.restoreVM(cmd); } @@ -4251,4 +4262,111 @@ public class UserVmManagerImplTest { verify(vmInstanceDetailsDao, never()).removeDetailsWithPrefix(anyLong(), anyString()); verify(userVmManagerImpl, never()).addExtraConfig(any(UserVmVO.class), anyString()); } + + @Test(expected = InvalidParameterValueException.class) + public void updateVirtualMachineNicTestInvalidNicThrowInvalidParameterValueException() { + Long invalidId = -1L; + Mockito.doReturn(invalidId).when(updateVmNicCmd).getNicId(); + + userVmManagerImpl.updateVirtualMachineNic(updateVmNicCmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void updateVirtualMachineNicTestInvalidNicUserVmThrowInvalidParameterValueException() { + Mockito.doReturn(nicId).when(updateVmNicCmd).getNicId(); + Mockito.doReturn(nicMock).when(nicDao).findById(nicId); + + userVmManagerImpl.updateVirtualMachineNic(updateVmNicCmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void updateVirtualMachineNicTestInvalidNicNetworkThrowInvalidParameterValueException() { + Mockito.doReturn(nicId).when(updateVmNicCmd).getNicId(); + Mockito.doReturn(true).when(updateVmNicCmd).isEnabled(); + Mockito.doReturn(nicMock).when(nicDao).findById(nicId); + Mockito.doReturn(vmId).when(nicMock).getInstanceId(); + Mockito.doReturn(userVmVoMock).when(userVmDao).findById(vmId); + + userVmManagerImpl.updateVirtualMachineNic(updateVmNicCmd); + } + + @Test(expected = CloudRuntimeException.class) + public void updateVirtualMachineNicTestInvalidNicNetworkThrowCloudRuntimeException() { + Mockito.doReturn(nicId).when(updateVmNicCmd).getNicId(); + Mockito.doReturn(true).when(updateVmNicCmd).isEnabled(); + Mockito.doReturn(nicMock).when(nicDao).findById(nicId); + Mockito.doReturn(vmId).when(nicMock).getInstanceId(); + Mockito.doReturn(userVmVoMock).when(userVmDao).findById(vmId); + + userVmManagerImpl.updateVirtualMachineNic(updateVmNicCmd); + } + + @Test + public void updateVirtualMachineNicTestValidInputReturnNicUserVm() throws ResourceUnavailableException { + Mockito.doReturn(nicId).when(updateVmNicCmd).getNicId(); + Mockito.doReturn(true).when(updateVmNicCmd).isEnabled(); + Mockito.doReturn(nicMock).when(nicDao).findById(nicId); + Mockito.doReturn(vmId).when(nicMock).getInstanceId(); + Mockito.doReturn(userVmVoMock).when(userVmDao).findById(vmId); + Mockito.doReturn(Hypervisor.HypervisorType.KVM).when(userVmVoMock).getHypervisorType(); + Mockito.doReturn(networkId).when(nicMock).getNetworkId(); + Mockito.doReturn(networkMock).when(_networkDao).findById(networkId); + Mockito.doReturn(true).when(virtualMachineManager).updateVmNic(Mockito.any(), Mockito.any(), Mockito.any()); + + UserVm result = userVmManagerImpl.updateVirtualMachineNic(updateVmNicCmd); + + Assert.assertNotNull(result); + } + + @Test + public void testTransitionExpungingToErrorVmInExpungingState() throws Exception { + UserVmVO vm = mock(UserVmVO.class); + when(vm.getState()).thenReturn(VirtualMachine.State.Expunging); + when(vm.getUuid()).thenReturn("test-uuid"); + when(userVmDao.findById(vmId)).thenReturn(vm); + when(virtualMachineManager.stateTransitTo(eq(vm), eq(VirtualMachine.Event.OperationFailedToError), eq(null))).thenReturn(true); + + java.lang.reflect.Method method = UserVmManagerImpl.class.getDeclaredMethod("transitionExpungingToError", long.class); + method.setAccessible(true); + method.invoke(userVmManagerImpl, vmId); + + Mockito.verify(virtualMachineManager).stateTransitTo(vm, VirtualMachine.Event.OperationFailedToError, null); + } + + @Test + public void testTransitionExpungingToErrorVmNotInExpungingState() throws Exception { + UserVmVO vm = mock(UserVmVO.class); + when(vm.getState()).thenReturn(VirtualMachine.State.Stopped); + when(userVmDao.findById(vmId)).thenReturn(vm); + + java.lang.reflect.Method method = UserVmManagerImpl.class.getDeclaredMethod("transitionExpungingToError", long.class); + method.setAccessible(true); + method.invoke(userVmManagerImpl, vmId); + + Mockito.verify(virtualMachineManager, Mockito.never()).stateTransitTo(any(VirtualMachine.class), any(VirtualMachine.Event.class), any()); + } + + @Test + public void testTransitionExpungingToErrorVmNotFound() throws Exception { + when(userVmDao.findById(vmId)).thenReturn(null); + + java.lang.reflect.Method method = UserVmManagerImpl.class.getDeclaredMethod("transitionExpungingToError", long.class); + method.setAccessible(true); + method.invoke(userVmManagerImpl, vmId); + + Mockito.verify(virtualMachineManager, Mockito.never()).stateTransitTo(any(VirtualMachine.class), any(VirtualMachine.Event.class), any()); + } + + @Test + public void testTransitionExpungingToErrorHandlesNoTransitionException() throws Exception { + UserVmVO vm = mock(UserVmVO.class); + when(vm.getState()).thenReturn(VirtualMachine.State.Expunging); + when(userVmDao.findById(vmId)).thenReturn(vm); + when(virtualMachineManager.stateTransitTo(eq(vm), eq(VirtualMachine.Event.OperationFailedToError), eq(null))) + .thenThrow(new NoTransitionException("no transition")); + + java.lang.reflect.Method method = UserVmManagerImpl.class.getDeclaredMethod("transitionExpungingToError", long.class); + method.setAccessible(true); + method.invoke(userVmManagerImpl, vmId); + } } diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index a24ba7f068b..98e6388f3d6 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.when; import java.net.URI; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; @@ -39,6 +40,9 @@ import java.util.Map; import java.util.UUID; import com.cloud.offering.DiskOffering; +import com.cloud.storage.Snapshot; +import com.cloud.storage.SnapshotVO; +import com.cloud.storage.dao.SnapshotDao; import com.cloud.vm.ImportVMTaskVO; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ResponseGenerator; @@ -241,6 +245,8 @@ public class UnmanagedVMsManagerImplTest { private StoragePoolHostDao storagePoolHostDao; @Mock private ImportVmTasksManager importVmTasksManager; + @Mock + private SnapshotDao snapshotDao; @Mock private VMInstanceVO virtualMachine; @@ -360,7 +366,7 @@ public class UnmanagedVMsManagerImplTest { when(primaryDataStoreDao.listPoolByHostPath(Mockito.anyString(), Mockito.anyString())).thenReturn(pools); when(userVmManager.importVM(nullable(DataCenter.class), nullable(Host.class), nullable(VirtualMachineTemplate.class), nullable(String.class), nullable(String.class), nullable(Account.class), nullable(String.class), nullable(Account.class), nullable(Boolean.class), nullable(String.class), - nullable(Long.class), nullable(Long.class), nullable(ServiceOffering.class), nullable(String.class), + nullable(Long.class), nullable(Long.class), nullable(ServiceOffering.class), nullable(String.class), nullable(Long.class), nullable(String.class), nullable(Hypervisor.HypervisorType.class), nullable(Map.class), nullable(VirtualMachine.PowerState.class), nullable(LinkedHashMap.class))).thenReturn(userVm); NetworkVO networkVO = Mockito.mock(NetworkVO.class); when(networkVO.getGuestType()).thenReturn(Network.GuestType.L2); @@ -568,6 +574,53 @@ public class UnmanagedVMsManagerImplTest { unmanagedVMsManager.unmanageVMInstance(virtualMachineId, null, false); } + @Test(expected = UnsupportedServiceException.class) + public void testUnmanageVMInstanceWithVolumeSnapshotsFail() { + when(virtualMachine.getType()).thenReturn(VirtualMachine.Type.User); + when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Stopped); + when(virtualMachine.getId()).thenReturn(virtualMachineId); + UserVmVO userVmVO = mock(UserVmVO.class); + when(userVmDao.findById(anyLong())).thenReturn(userVmVO); + when(vmDao.findById(virtualMachineId)).thenReturn(virtualMachine); + VolumeVO volumeVO = mock(VolumeVO.class); + long volumeId = 20L; + when(volumeVO.getId()).thenReturn(volumeId); + SnapshotVO snapshotVO = mock(SnapshotVO.class); + when(snapshotVO.getState()).thenReturn(Snapshot.State.BackedUp); + when(snapshotDao.listByVolumeId(volumeId)).thenReturn(Collections.singletonList(snapshotVO)); + when(volumeDao.findByInstance(virtualMachineId)).thenReturn(Collections.singletonList(volumeVO)); + unmanagedVMsManager.unmanageVMInstance(virtualMachineId, null, false); + } + + @Test(expected = UnsupportedServiceException.class) + public void testUnmanageVMInstanceWithAssociatedIsoFail() { + when(virtualMachine.getType()).thenReturn(VirtualMachine.Type.User); + when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Stopped); + when(virtualMachine.getId()).thenReturn(virtualMachineId); + UserVmVO userVmVO = mock(UserVmVO.class); + when(userVmVO.getIsoId()).thenReturn(null); + when(userVmDao.findById(anyLong())).thenReturn(userVmVO); + when(vmDao.findById(virtualMachineId)).thenReturn(virtualMachine); + when(userVmVO.getIsoId()).thenReturn(1L); + unmanagedVMsManager.unmanageVMInstance(virtualMachineId, null, false); + } + + @Test(expected = UnsupportedServiceException.class) + public void testUnmanageVMInstanceBelongingToCksClusterFail() { + when(virtualMachine.getType()).thenReturn(VirtualMachine.Type.User); + when(virtualMachine.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Stopped); + when(virtualMachine.getId()).thenReturn(virtualMachineId); + UserVmVO userVmVO = mock(UserVmVO.class); + when(userVmVO.getIsoId()).thenReturn(null); + when(userVmDao.findById(anyLong())).thenReturn(userVmVO); + when(vmDao.findById(virtualMachineId)).thenReturn(virtualMachine); + when(userVmManager.isVMPartOfAnyCKSCluster(virtualMachine)).thenReturn(true); + unmanagedVMsManager.unmanageVMInstance(virtualMachineId, null, false); + } + @Test public void testListRemoteInstancesTest() { ListVmsForImportCmd cmd = Mockito.mock(ListVmsForImportCmd.class); diff --git a/test/integration/component/test_blocker_bugs.py b/test/integration/component/test_blocker_bugs.py index 7b497cfe294..e393c7bed02 100644 --- a/test/integration/component/test_blocker_bugs.py +++ b/test/integration/component/test_blocker_bugs.py @@ -542,7 +542,7 @@ class TestRouters(cloudstackTestCase): # Validate the following - # 1. PreReq: have rounters that are owned by other account + # 1. PreReq: have routers that are owned by other account # 2. Create domain and create accounts in that domain # 3. Create one VM for each account # 4. Using Admin , run listRouters. It should return all the routers diff --git a/test/integration/smoke/test_secondary_storage.py b/test/integration/smoke/test_secondary_storage.py index 4b26950ea64..2ed60a84425 100644 --- a/test/integration/smoke/test_secondary_storage.py +++ b/test/integration/smoke/test_secondary_storage.py @@ -136,7 +136,11 @@ class TestSecStorageServices(cloudstackTestCase): 'Up', "Check state of primary storage pools is Up or not" ) - for _ in range(2): + # Poll until all SSVMs are Running, or timeout after 3 minutes + timeout = 180 + interval = 15 + list_ssvm_response = [] + while timeout > 0: list_ssvm_response = list_ssvms( self.apiclient, systemvmtype='secondarystoragevm', @@ -154,10 +158,12 @@ class TestSecStorageServices(cloudstackTestCase): "Check list System VMs response" ) - for ssvm in list_ssvm_response: - if ssvm.state != 'Running': - time.sleep(30) - continue + if all(ssvm.state == 'Running' for ssvm in list_ssvm_response): + break + + time.sleep(interval) + timeout -= interval + for ssvm in list_ssvm_response: self.assertEqual( ssvm.state, diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index ee7624250f6..4f2c5d28aaf 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -174,6 +174,7 @@ known_categories = { 'removeIpFromNic': 'Nic', 'updateVmNicIp': 'Nic', 'listNics':'Nic', + 'updateVmNic': 'Nic', 'AffinityGroup': 'Affinity Group', 'ImageStore': 'Image Store', 'addImageStore': 'Image Store', diff --git a/tools/devcloud4/binary-installation-advanced/marvin.cfg.erb b/tools/devcloud4/binary-installation-advanced/marvin.cfg.erb index bd65d3d0172..999dc47dc34 100644 --- a/tools/devcloud4/binary-installation-advanced/marvin.cfg.erb +++ b/tools/devcloud4/binary-installation-advanced/marvin.cfg.erb @@ -5,9 +5,9 @@ # 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 diff --git a/tools/devcloud4/binary-installation-basic/marvin.cfg.erb b/tools/devcloud4/binary-installation-basic/marvin.cfg.erb index 721fc07d6c7..03e0dd6562b 100644 --- a/tools/devcloud4/binary-installation-basic/marvin.cfg.erb +++ b/tools/devcloud4/binary-installation-basic/marvin.cfg.erb @@ -5,9 +5,9 @@ # 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 diff --git a/tools/utils/database_comparision_during_upgrade/README b/tools/utils/database_comparision_during_upgrade/README index 45c55eaf38b..ace71379a11 100644 --- a/tools/utils/database_comparision_during_upgrade/README +++ b/tools/utils/database_comparision_during_upgrade/README @@ -15,14 +15,14 @@ What this tool is capable to do is The usage is as follows 1. First run fresh_install_data_collection.sh file to generate data from fresh install setup . - This will be used for comparing between fresh install and upgrade setup. + This will be used for comparing between fresh install and upgrade setup. This is a onetime activity and need to be repeated only when there is some DB changes for that release . - Output of this script will come in a base_data folder + Output of this script will come in a base_data folder 2. Just before upgrade you need to run before_upgrade_data_collection.sh file to collect required data needed to compare before upgrade and after upgrade setup data The output of this script will come in folder data_before_upgrade -3. After upgrade run cloud_schema_comparision.sh to compare cloud database all tables schema between fresh and upgraded setup. +3. After upgrade run cloud_schema_comparision.sh to compare cloud database all tables schema between fresh and upgraded setup. NOTE: this script requires step 1 output in current working directory 4. After upgrade run usage_schema_comparision.sh to compare cloud usage all tables schema between fresh and upgraded setup @@ -41,4 +41,4 @@ The usage is as follows • Database user • Database user password -8. Result will be shown in the form of files . +8. Result will be shown in the form of files . diff --git a/ui/docs/development.md b/ui/docs/development.md index 363c6a3795b..43b346fad8f 100644 --- a/ui/docs/development.md +++ b/ui/docs/development.md @@ -32,7 +32,7 @@ The following tree shows the basic UI codebase filesystem: ```bash src - ├── assests # sprites, icons, images + ├── assets # sprites, icons, images ├── components # Shared vue files used to render various generic / widely used components ├── config # Contains the layout details of the various routes / sections available in the UI ├── locales # Custom translation keys for the various supported languages diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index b14c2c35b6e..3193429c068 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1024,6 +1024,7 @@ "label.edit.acl": "Edit ACL", "label.edit.acl.rule": "Edit ACL rule", "label.edit.autoscale.vmprofile": "Edit AutoScale Instance Profile", +"label.edit.nic": "Edit NIC", "label.edit.project.details": "Edit project details", "label.edit.project.role": "Edit project role", "label.edit.role": "Edit Role", @@ -1788,6 +1789,7 @@ "label.no.data": "No data to show", "label.no.errors": "No recent errors", "label.no.items": "No available Items", +"label.no.matching.guest.os.vmware.import": "No matching guest OS mapping found, using the default import template guest OS", "label.no.matching.offering": "No matching offering found", "label.no.matching.network": "No matching Networks found", "label.node.version": "Node version", @@ -3775,6 +3777,7 @@ "message.network.selection": "Choose one or more Networks to attach the Instance to.", "message.network.selection.new.network": "A new Network can also be created here.", "message.network.updateip": "Please confirm that you would like to change the IP address for this NIC on the Instance.", +"message.network.update.nic": "Please confirm that you would like to update this NIC.", "message.network.usage.info.data.points": "Each data point represents the difference in data traffic since the last data point.", "message.network.usage.info.sum.of.vnics": "The Network usage shown is made up of the sum of data traffic from all the vNICs in the Instance.", "message.nfs.mount.options.description": "Comma separated list of NFS mount options for KVM hosts. Supported options : vers=[3,4.0,4.1,4.2], nconnect=[1...16]", @@ -4066,13 +4069,14 @@ "message.success.delete.vgpu.profile": "Successfully deleted vGPU profile", "message.success.update.custom.action": "Successfully updated Custom Action", "message.success.update.extension": "Successfully updated Extension", -"message.success.update.sharedfs": "Successfully updated Shared FileSystem", "message.success.update.ipaddress": "Successfully updated IP address", "message.success.update.iprange": "Successfully updated IP range", "message.success.update.ipv4.subnet": "Successfully updated IPv4 subnet", "message.success.update.iso": "Successfully updated ISO", "message.success.update.kubeversion": "Successfully updated Kubernetes supported version", "message.success.update.network": "Successfully updated Network", +"message.success.update.nic": "Successfully updated NIC", +"message.success.update.sharedfs": "Successfully updated Shared FileSystem", "message.success.update.template": "Successfully updated Template", "message.success.update.user": "Successfully updated User", "message.success.update.vpn.customer.gateway": "Successfully updated VPN Customer Gateway", @@ -4122,6 +4126,7 @@ "message.two.fa.setup.page": "Two factor authentication (2FA) is an extra layer of security to your account.
Once setup is done, on every login you will be prompted to enter the 2FA code.
", "message.two.fa.view.setup.key": "Click here to view the setup key", "message.two.fa.view.static.pin": "Click here to view the static PIN", +"message.update.nic.processing": "Updating NIC...", "message.update.ipaddress.processing": "Updating IP Address...", "message.update.resource.count": "Please confirm that you want to update resource counts for this Account.", "message.update.resource.count.domain": "Please confirm that you want to update resource counts for this domain.", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index 59123a5e45c..c7ca36c1278 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -623,6 +623,7 @@ "label.edit": "Editar", "label.edit.acl": "Editar lista ACL", "label.edit.acl.rule": "Editar regra ACL", +"label.edit.nic": "Editar NIC", "label.edit.project.details": "Editar detalhes do projeto", "label.edit.project.role": "Editar fun\u00e7\u00e3o do projeto", "label.edit.role": "Editar fun\u00e7\u00e3o", @@ -2298,6 +2299,7 @@ "message.network.removenic": "Por favor, confirme que deseja remover esta interface de rede, esta a\u00e7\u00e3o tamb\u00e9m ir\u00e1 remover a rede associada \u00e0 VM.", "message.network.secondaryip": "Por favor, confirme que voc\u00ea gostaria de adquirir um novo IP secund\u00e1rio para este NIC. \n NOTA: Voc\u00ea precisa configurar manualmente o IP secund\u00e1rio rec\u00e9m-adquirido dentro da m\u00e1quina virtual.", "message.network.updateip": "Por favor, confirme que voc\u00ea gostaria de mudar o endere\u00e7o IP da NIC em VM.", +"message.network.update.nic": "Por favor, confirme que voc\u00ea gostaria de atualizar esta NIC.", "message.network.usage.info.data.points": "Cada ponto no gr\u00e1fico representa a diferen\u00e7a de dados trafegados desde a \u00faltima coleta de estat\u00edstica realizada (o ponto anterior)", "message.network.usage.info.sum.of.vnics": "O uso de rede apresentado \u00e9 composto pela soma de dados trafegados por todas as vNICs da VM", "message.no.data.to.show.for.period": "Nenhum dado para mostrar no per\u00edodo selecionado.", @@ -2455,6 +2457,7 @@ "message.success.update.iprange": "Intervalo de IP atualizado com sucesso", "message.success.update.kubeversion": "Vers\u00e3o compat\u00edvel com Kubernetes atualizada com sucesso", "message.success.update.network": "Rede atualizada com sucesso", +"message.success.update.nic": "NIC atualizada com sucesso", "message.success.update.user": "Usu\u00e1rio atualizado com sucesso", "message.success.upgrade.kubernetes": "Cluster do Kubernetes atualizado com sucesso", "message.success.upload": "Carregado com sucesso", @@ -2471,6 +2474,7 @@ "message.template.iso": "Selecione o template ou ISO para continuar", "message.tooltip.reserved.system.netmask": "O prefixo de rede que define a subrede deste pod. utilize a nota\u00e7\u00e3o CIDR.", "message.traffic.type.to.basic.zone": "Tipo de tr\u00e1fego para a zona b\u00e1sica", +"message.update.nic.processing": "Atualizando NIC...", "message.update.ipaddress.processing": "Atualizando endere\u00e7o IP...", "message.update.resource.count": "Por favor confirme que voc\u00ea quer atualizar a contagem de recursos para esta conta.", "message.update.resource.count.domain": "Por favor, confirme que voc\u00ea deseja atualizar as contagens de recursos para este dom\u00ednio.", diff --git a/ui/src/assets/icons/dark.svg b/ui/src/assets/icons/dark.svg index b1dd4b38e0a..524521a3208 100644 --- a/ui/src/assets/icons/dark.svg +++ b/ui/src/assets/icons/dark.svg @@ -1,6 +1,6 @@ - diff --git a/ui/src/permission.js b/ui/src/permission.js index 671d6626b93..0b87de92c6b 100644 --- a/ui/src/permission.js +++ b/ui/src/permission.js @@ -93,6 +93,7 @@ router.beforeEach((to, from, next) => { return } store.commit('SET_LOGIN_FLAG', true) + store.commit('SET_MS_ID', Cookies.get('managementserverid')) } // store already loaded if (store.getters.passwordChangeRequired) { diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue index 26176e76005..474c3bb01ac 100644 --- a/ui/src/views/compute/DeployVM.vue +++ b/ui/src/views/compute/DeployVM.vue @@ -459,6 +459,9 @@ :value="securitygroupids" :loading="loading.networks" :preFillContent="dataPreFill" + :domainId="owner.domainid" + :account="owner.account" + :projectId="owner.projectid" @select-security-group-item="($event) => updateSecurityGroups($event)"> @@ -1501,6 +1504,9 @@ export default { return tabList }, showSecurityGroupSection () { + if (this.zone && this.zone.networktype === 'Basic') { + return true + } if (this.networks.length < 1) { return false } diff --git a/ui/src/views/compute/KubernetesServiceTab.vue b/ui/src/views/compute/KubernetesServiceTab.vue index fc8f9c60213..3c45feea063 100644 --- a/ui/src/views/compute/KubernetesServiceTab.vue +++ b/ui/src/views/compute/KubernetesServiceTab.vue @@ -66,32 +66,62 @@ +

Note: CloudStack Kubernetes clusters use Headlamp dashboard (deployed in kube-system namespace). For backward compatibility with older clusters using Kubernetes Dashboard, please check your cluster configuration.

- {{ $t('label.run.proxy.locally') }}

- kubectl --kubeconfig /custom/path/kube.conf proxy + Access Headlamp Dashboard (new clusters)

+ Step 1: Run port-forward command:
+ kubectl --kubeconfig /custom/path/kube.conf port-forward -n kube-system service/headlamp 8080:80

+ Step 2: Open in your browser:
+ http://localhost:8080

- {{ $t('label.open.url') }}

+ Access Kubernetes Dashboard (legacy clusters)

+ Step 1: {{ $t('label.run.proxy.locally') }}
+ kubectl --kubeconfig /custom/path/kube.conf proxy

+ Step 2: {{ $t('label.open.url') }}
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

+

+ Create Access Token for Headlamp (new clusters) +

- + +

+

{{ $t('label.token.for.dashboard.login') }}:

+ kubectl --kubeconfig /custom/path/kube.conf describe secret headlamp-admin-token -n kube-system

- {{ $t('label.token.for.dashboard.login') }}

- kubectl --kubeconfig /custom/path/kube.conf describe secret $(kubectl --kubeconfig /custom/path/kube.conf get secrets -n kubernetes-dashboard | grep kubernetes-dashboard-token | awk '{print $1}') -n kubernetes-dashboard + Create Access Token for Kubernetes Dashboard (legacy clusters) +

+

+ +

+

{{ $t('label.token.for.dashboard.login') }}:

+ kubectl --kubeconfig /custom/path/kube.conf describe secret kubernetes-dashboard-token -n kubernetes-dashboard +
+ +

+ Important Notes:
+ • Port-forwarding is recommended for Headlamp - simpler and more reliable than kubectl proxy
+ • Token is only needed if accessing Headlamp via NodePort or LoadBalancer with external access
+ • For Kubernetes 1.24+, service account tokens are no longer auto-generated - use the Secret resource shown above or kubectl create token command
+ • Cluster-admin role grants full control - use with caution and only for trusted administrators
+ • Keep the port-forward command running while using the dashboard (press Ctrl+C to stop)

-

{{ $t('label.more.access.dashboard.ui') }}, https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#accessing-the-dashboard-ui

+

{{ $t('label.more.access.dashboard.ui') }}: + Headlamp Documentation | + Kubernetes Dashboard (Legacy) +

diff --git a/ui/src/views/compute/wizard/SecurityGroupSelection.vue b/ui/src/views/compute/wizard/SecurityGroupSelection.vue index 9d91cc1cbe1..0ea08ec8887 100644 --- a/ui/src/views/compute/wizard/SecurityGroupSelection.vue +++ b/ui/src/views/compute/wizard/SecurityGroupSelection.vue @@ -75,6 +75,18 @@ export default { preFillContent: { type: Object, default: () => {} + }, + domainId: { + type: String, + default: () => '' + }, + account: { + type: String, + default: () => '' + }, + projectId: { + type: String, + default: () => '' } }, data () { @@ -102,6 +114,9 @@ export default { } }, computed: { + ownerParams () { + return `${this.domainId}-${this.account}-${this.projectId}` + }, rowSelection () { return { type: 'checkbox', @@ -121,6 +136,11 @@ export default { this.selectedRowKeys = newValue } }, + ownerParams () { + this.selectedRowKeys = [] + this.$emit('select-security-group-item', null) + this.fetchData() + }, loading () { if (!this.loading) { if (this.preFillContent.securitygroupids) { @@ -140,9 +160,9 @@ export default { methods: { fetchData () { const params = { - projectid: this.$store.getters.project ? this.$store.getters.project.id : null, - domainid: this.$store.getters.project && this.$store.getters.project.id ? null : this.$store.getters.userInfo.domainid, - account: this.$store.getters.project && this.$store.getters.project.id ? null : this.$store.getters.userInfo.account, + projectid: this.projectId || (this.$store.getters.project ? this.$store.getters.project.id : null), + domainid: this.projectId || (this.$store.getters.project && this.$store.getters.project.id) ? null : (this.domainId || this.$store.getters.userInfo.domainid), + account: this.projectId || (this.$store.getters.project && this.$store.getters.project.id) ? null : (this.account || this.$store.getters.userInfo.account), page: this.page, pageSize: this.pageSize } diff --git a/ui/src/views/image/TemplateZones.vue b/ui/src/views/image/TemplateZones.vue index e517aad5167..31b65e556a4 100644 --- a/ui/src/views/image/TemplateZones.vue +++ b/ui/src/views/image/TemplateZones.vue @@ -91,7 +91,7 @@ :rowKey="record => record.datastoreId">