diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60e2a558994..6fb8844e8ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -236,7 +236,7 @@ jobs: - name: Install Python dependencies run: | - python3 -m pip install --user --upgrade urllib3 lxml paramiko nose texttable ipmisim pyopenssl pycrypto mock flask netaddr pylint pycodestyle six astroid pynose + python3 -m pip install --user --upgrade urllib3 lxml paramiko nose texttable ipmisim pyopenssl pycryptodome mock flask netaddr pylint pycodestyle six astroid pynose - name: Install jacoco dependencies run: | diff --git a/core/src/main/java/com/cloud/agent/api/GetVmIpAddressCommand.java b/core/src/main/java/com/cloud/agent/api/GetVmIpAddressCommand.java index a9c7413b9f0..4efc0781f91 100644 --- a/core/src/main/java/com/cloud/agent/api/GetVmIpAddressCommand.java +++ b/core/src/main/java/com/cloud/agent/api/GetVmIpAddressCommand.java @@ -24,11 +24,13 @@ public class GetVmIpAddressCommand extends Command { String vmName; String vmNetworkCidr; boolean windows = false; + String macAddress; - public GetVmIpAddressCommand(String vmName, String vmNetworkCidr, boolean windows) { + public GetVmIpAddressCommand(String vmName, String vmNetworkCidr, boolean windows, String macAddress) { this.vmName = vmName; this.windows = windows; this.vmNetworkCidr = vmNetworkCidr; + this.macAddress = macAddress; } @Override @@ -47,4 +49,8 @@ public class GetVmIpAddressCommand extends Command { public String getVmNetworkCidr() { return vmNetworkCidr; } + + public String getMacAddress() { + return macAddress; + } } diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java index 53e25b6b009..585c479f65f 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java @@ -247,6 +247,11 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl protected final ConfigKey CheckTxnBeforeSending = new ConfigKey<>("Developer", Boolean.class, "check.txn.before.sending.agent.commands", "false", "This parameter allows developers to enable a check to see if a transaction wraps commands that are sent to the resource. This is not to be enabled on production systems.", true); + public static final List HOST_DOWN_ALERT_UNSUPPORTED_HOST_TYPES = Arrays.asList( + Host.Type.SecondaryStorage, + Host.Type.ConsoleProxy + ); + @Override public boolean configure(final String name, final Map params) throws ConfigurationException { @@ -1093,9 +1098,11 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl if (determinedState == Status.Down) { final String message = String.format("Host %s is down. Starting HA on the VMs", host); logger.error(message); - if (host.getType() != Host.Type.SecondaryStorage && host.getType() != Host.Type.ConsoleProxy) { - _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(), - host.getPodId(), String.format("Host down, %s", host), message); + if (Status.Down.equals(host.getStatus())) { + logger.debug(String.format("Skipping sending alert for %s as it already in %s state", + host, host.getStatus())); + } else if (!HOST_DOWN_ALERT_UNSUPPORTED_HOST_TYPES.contains(host.getType())) { + _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(), host.getPodId(), "Host down, " + host.getId(), message); } event = Status.Event.HostDown; } else if (determinedState == Status.Up) { 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 f72b10cc0c0..967838ba9b9 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 @@ -595,7 +595,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworks) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworks, "Offering for Isolated VPC networks with Source Nat service enabled", TrafficType.Guest, null, false, Availability.Optional, null, - defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, false, false, null, null, null,true, null, null, false); + defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, true, null, false, false, null, false, null, true, true, false, false, null, null, null,true, null, null, false); } //#6 - default vpc offering with no LB service diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDao.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDao.java index 9be4c49b55e..4d84caaad44 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDao.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDao.java @@ -28,4 +28,6 @@ public interface UsageNetworksDao extends GenericDao { void remove(long networkId, Date removed); List getUsageRecords(Long accountId, Date startDate, Date endDate); + + List listAll(long networkId); } diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDaoImpl.java index 99ba3587688..e7ae622ae54 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDaoImpl.java @@ -19,6 +19,7 @@ package com.cloud.usage.dao; import com.cloud.usage.UsageNetworksVO; import com.cloud.utils.DateUtil; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; @@ -26,6 +27,7 @@ import org.springframework.stereotype.Component; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import javax.annotation.PostConstruct; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; @@ -40,6 +42,14 @@ public class UsageNetworksDaoImpl extends GenericDaoBase " account_id = ? AND ((removed IS NULL AND created <= ?) OR (created BETWEEN ? AND ?) OR (removed BETWEEN ? AND ?) " + " OR ((created <= ?) AND (removed >= ?)))"; + private SearchBuilder usageNetworksSearch; + + @PostConstruct + public void init() { + usageNetworksSearch = createSearchBuilder(); + usageNetworksSearch.and("networkId", usageNetworksSearch.entity().getNetworkId(), SearchCriteria.Op.EQ); + usageNetworksSearch.done(); + } @Override public void update(long networkId, long newNetworkOffering, String state) { @@ -131,4 +141,11 @@ public class UsageNetworksDaoImpl extends GenericDaoBase return usageRecords; } + + @Override + public List listAll(long networkId) { + SearchCriteria sc = usageNetworksSearch.create(); + sc.setParameters("networkId", networkId); + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDao.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDao.java index a1514aba4ca..5167bf88c48 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDao.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDao.java @@ -24,6 +24,10 @@ import java.util.List; public interface UsageVpcDao extends GenericDao { void update(UsageVpcVO usage); + void remove(long vpcId, Date removed); + List getUsageRecords(Long accountId, Date startDate, Date endDate); + + List listAll(long vpcId); } diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDaoImpl.java index 70cdadd1629..b5d8e46ef09 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDaoImpl.java @@ -19,10 +19,12 @@ package com.cloud.usage.dao; import com.cloud.usage.UsageVpcVO; import com.cloud.utils.DateUtil; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; import org.springframework.stereotype.Component; +import javax.annotation.PostConstruct; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; @@ -36,6 +38,15 @@ public class UsageVpcDaoImpl extends GenericDaoBase implements " account_id = ? AND ((removed IS NULL AND created <= ?) OR (created BETWEEN ? AND ?) OR (removed BETWEEN ? AND ?) " + " OR ((created <= ?) AND (removed >= ?)))"; + private SearchBuilder usageVpcSearch; + + @PostConstruct + public void init() { + usageVpcSearch = createSearchBuilder(); + usageVpcSearch.and("vpcId", usageVpcSearch.entity().getVpcId(), SearchCriteria.Op.EQ); + usageVpcSearch.done(); + } + @Override public void update(UsageVpcVO usage) { TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.USAGE_DB); @@ -124,4 +135,11 @@ public class UsageVpcDaoImpl extends GenericDaoBase implements return usageRecords; } + + @Override + public List listAll(long vpcId) { + SearchCriteria sc = usageVpcSearch.create(); + sc.setParameters("vpcId", vpcId); + return listBy(sc); + } } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql b/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql index e60378a2dc8..b8c44c40c46 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42000to42010.sql @@ -75,3 +75,6 @@ CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Read-Only Admin - Default', 'va CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Support Admin - Default', 'setupUserTwoFactorAuthentication', 'ALLOW'); CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Support Admin - Default', 'validateUserTwoFactorAuthenticationCode', 'ALLOW'); + +-- Re-apply VPC: update default network offering for vpc tier to conserve_mode=1 (#8309) +UPDATE `cloud`.`network_offerings` SET conserve_mode=1 WHERE name='DefaultIsolatedNetworkOfferingForVpcNetworks'; diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java index e050cb4e85d..93545f3adcf 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java @@ -66,10 +66,13 @@ public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper commands = new ArrayList<>(); commands.add(new String[]{virsh_path, "domifaddr", sanitizedVmName, "--source", "agent"}); Pair response = executePipedCommands(commands, 0); if (response != null) { String output = response.second(); - String[] lines = output.split("\n"); - for (String line : lines) { - if (line.contains("ipv4")) { - String[] parts = line.split(" "); - String[] ipParts = parts[parts.length-1].split("/"); - if (ipParts.length > 1) { - if (NetUtils.isIpWithInCidrRange(ipParts[0], networkCidr)) { - ip = ipParts[0]; - break; - } - } - } + Pair ipAddresses = getIpAddresses(output, macAddress); + String ipv4 = ipAddresses.first(); + if (networkCidr == null || NetUtils.isIpWithInCidrRange(ipv4, networkCidr)) { + ip = ipv4; } } else { logger.error("ipFromDomIf: Command execution failed for VM: " + sanitizedVmName); @@ -113,6 +108,38 @@ public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper getIpAddresses(String output, String macAddress) { + String ipv4 = null; + String ipv6 = null; + boolean found = false; + String[] lines = output.split("\n"); + for (String line : lines) { + String[] parts = line.replaceAll(" +", " ").trim().split(" "); + if (parts.length < 4) { + continue; + } + String device = parts[0]; + String mac = parts[1]; + if (found) { + if (!device.equals("-") || !mac.equals("-")) { + break; + } + } else if (!mac.equals(macAddress)) { + continue; + } + found = true; + String ipFamily = parts[2]; + String ipPart = parts[3].split("/")[0]; + if (ipFamily.equals("ipv4")) { + ipv4 = ipPart; + } else if (ipFamily.equals("ipv6")) { + ipv6 = ipPart; + } + } + logger.debug(String.format("Found ipv4: %s and ipv6: %s with mac address %s", ipv4, ipv6, macAddress)); + return new Pair<>(ipv4, ipv6); + } + private String ipFromDhcpLeaseFile(String sanitizedVmName, String networkCidr) { String ip = null; List commands = new ArrayList<>(); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java index 49b67194356..141c1d5ea19 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java @@ -150,16 +150,16 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper(0, VIRSH_DOMIF_OUTPUT)); @@ -88,6 +89,7 @@ public class LibvirtGetVmIpAddressCommandWrapperTest { when(getVmIpAddressCommand.getVmName()).thenReturn("invalidVmName!"); when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24"); + when(getVmIpAddressCommand.getMacAddress()).thenReturn("02:0c:02:f9:00:80"); when(getVmIpAddressCommand.isWindows()).thenReturn(false); when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, VIRSH_DOMIF_OUTPUT)); @@ -114,6 +116,7 @@ public class LibvirtGetVmIpAddressCommandWrapperTest { when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName"); when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24"); + when(getVmIpAddressCommand.getMacAddress()).thenReturn("02:0c:02:f9:00:80"); when(getVmIpAddressCommand.isWindows()).thenReturn(true); when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, "192.168.0.10")); diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/XenServerGuru.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/XenServerGuru.java index af10ded2e22..89dde338ca6 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/XenServerGuru.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/XenServerGuru.java @@ -34,6 +34,7 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.commons.lang3.StringUtils; +import com.cloud.agent.api.CleanupVMCommand; import com.cloud.agent.api.Command; import com.cloud.agent.api.to.DataObjectType; import com.cloud.agent.api.to.DataStoreTO; @@ -234,4 +235,12 @@ public class XenServerGuru extends HypervisorGuruBase implements HypervisorGuru, public ConfigKey[] getConfigKeys() { return new ConfigKey[] {MaxNumberOfVCPUSPerVM}; } + + @Override + public List finalizeExpunge(VirtualMachine vm) { + List commands = new ArrayList<>(); + final CleanupVMCommand cleanupVMCommand = new CleanupVMCommand(vm.getInstanceName(), true); + commands.add(cleanupVMCommand); + return commands; + } } diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCleanupVMCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCleanupVMCommandWrapper.java new file mode 100644 index 00000000000..fc29883ad8c --- /dev/null +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCleanupVMCommandWrapper.java @@ -0,0 +1,78 @@ +// 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.hypervisor.xenserver.resource.wrapper.xenbase; + +import java.util.Iterator; +import java.util.Set; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CleanupVMCommand; +import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.xensource.xenapi.Connection; +import com.xensource.xenapi.Types; +import com.xensource.xenapi.VM; + +@ResourceWrapper(handles = CleanupVMCommand.class) +public class CitrixCleanupVMCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(final CleanupVMCommand command, final CitrixResourceBase citrixResourceBase) { + if (citrixResourceBase.isDestroyHaltedVms()) { + logger.debug(String.format("Cleanup VM is not needed for host with version %s", + citrixResourceBase.getHost().getProductVersion())); + return new Answer(command); + } + final String vmName = command.getVmName(); + try { + final Connection conn = citrixResourceBase.getConnection(); + final Set vms = VM.getByNameLabel(conn, vmName); + if (vms.isEmpty()) { + return new Answer(command, true, "VM does not exist"); + } + // destroy vm which is in HALTED state on this host + final Iterator iter = vms.iterator(); + while (iter.hasNext()) { + final VM vm = iter.next(); + final VM.Record vmr = vm.getRecord(conn); + if (!Types.VmPowerState.HALTED.equals(vmr.powerState)) { + final String msg = String.format("VM %s is not in %s state", vmName, Types.VmPowerState.HALTED); + logger.error(msg); + return new Answer(command, false, msg); + } + if (citrixResourceBase.isRefNull(vmr.residentOn)) { + continue; + } + if (vmr.residentOn.getUuid(conn).equals(citrixResourceBase.getHost().getUuid())) { + continue; + } + iter.remove(); + } + for (final VM vm : vms) { + citrixResourceBase.destroyVm(vm, conn, true); + } + + } catch (final Exception e) { + final String msg = String.format("Clean up VM %s fail due to %s", vmName, e); + logger.error(msg, e); + return new Answer(command, false, e.getMessage()); + } + return new Answer(command); + } +} diff --git a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java index 9ecb14d841f..d464a266493 100755 --- a/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java +++ b/plugins/hypervisors/xenserver/src/test/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java @@ -1934,7 +1934,7 @@ public class CitrixRequestWrapperTest { vmIpsMap.put("Test", "127.0.0.1"); rec.networks = vmIpsMap; - final GetVmIpAddressCommand getVmIpAddrCmd = new GetVmIpAddressCommand("Test", "127.0.0.1/24", false); + final GetVmIpAddressCommand getVmIpAddrCmd = new GetVmIpAddressCommand("Test", "127.0.0.1/24", false, null); final CitrixRequestWrapper wrapper = CitrixRequestWrapper.getInstance(); assertNotNull(wrapper); diff --git a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java index 18d924bcd52..6ca67cb5923 100644 --- a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java +++ b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java @@ -412,13 +412,14 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { if (err == null && needResize) { err = notifyQemuForTheNewSize(data, err, vol, payload); } - if (err != null) { // try restoring volume to its initial size SpApiResponse response = StorPoolUtil.volumeUpdate(name, oldSize, true, oldMaxIops, conn); if (response.getError() != null) { logger.debug(String.format("Could not resize StorPool volume %s back to its original size. Error: %s", name, response.getError())); } + } else { + updateVolumeWithTheNewSize(vol, payload); } } catch (Exception e) { logger.debug("sending resize command failed", e); @@ -427,6 +428,17 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { return err; } + private void updateVolumeWithTheNewSize(VolumeObject vol, ResizeVolumePayload payload) { + vol.setSize(payload.newSize); + vol.update(); + if (payload.newMaxIops != null) { + VolumeVO volume = volumeDao.findById(vol.getId()); + volume.setMaxIops(payload.newMaxIops); + volumeDao.update(volume.getId(), volume); + } + updateStoragePool(vol.getPoolId(), payload.newSize - vol.getSize()); + } + private String notifyQemuForTheNewSize(DataObject data, String err, VolumeObject vol, ResizeVolumePayload payload) throws StorageUnavailableException { StoragePool pool = (StoragePool)data.getDataStore(); @@ -455,37 +467,34 @@ public class StorPoolPrimaryDataStoreDriver implements PrimaryDataStoreDriver { } SpApiResponse resp = new SpApiResponse(); if (tier != null || template != null) { - Map tags = StorPoolHelper.addStorPoolTags(null, null, null, null, tier); - StorPoolVolumeDef spVolume = new StorPoolVolumeDef(name, payload.newSize, tags, null, null, template, null, null, - payload.shrinkOk); - resp = StorPoolUtil.volumeUpdate(spVolume, conn); + resp = updateVolumeByStorPoolQoS(payload, conn, name, tier, template); } else { - long maxIops = payload.newMaxIops == null ? Long.valueOf(0) : payload.newMaxIops; - - StorPoolVolumeDef spVolume = new StorPoolVolumeDef(name, payload.newSize, null, null, maxIops, null, null, null, - payload.shrinkOk); - StorPoolUtil.spLog( - "StorpoolPrimaryDataStoreDriverImpl.resize: name=%s, uuid=%s, oldSize=%d, newSize=%s, shrinkOk=%s, maxIops=%s", - name, vol.getUuid(), vol.getSize(), payload.newSize, payload.shrinkOk, maxIops); - - resp = StorPoolUtil.volumeUpdate(spVolume, conn); + resp = updateVolumeByOffering(vol, payload, conn, name); } if (resp.getError() != null) { err = String.format("Could not resize StorPool volume %s. Error: %s", name, resp.getError()); - } else { - vol.setSize(payload.newSize); - vol.update(); - if (payload.newMaxIops != null) { - VolumeVO volume = volumeDao.findById(vol.getId()); - volume.setMaxIops(payload.newMaxIops); - volumeDao.update(volume.getId(), volume); - } - - updateStoragePool(vol.getPoolId(), payload.newSize - vol.getSize()); } return err; } + private static SpApiResponse updateVolumeByStorPoolQoS(ResizeVolumePayload payload, SpConnectionDesc conn, String name, String tier, String template) { + Map tags = StorPoolHelper.addStorPoolTags(null, null, null, null, tier); + StorPoolVolumeDef spVolume = new StorPoolVolumeDef(name, payload.newSize, tags, null, null, template, null, null, + payload.shrinkOk); + return StorPoolUtil.volumeUpdate(spVolume, conn); + } + + private static SpApiResponse updateVolumeByOffering(VolumeObject vol, ResizeVolumePayload payload, SpConnectionDesc conn, String name) { + long maxIops = payload.newMaxIops == null ? Long.valueOf(0) : payload.newMaxIops; + + StorPoolVolumeDef spVolume = new StorPoolVolumeDef(name, payload.newSize, null, null, maxIops, null, null, null, + payload.shrinkOk); + StorPoolUtil.spLog( + "StorpoolPrimaryDataStoreDriverImpl.resize: name=%s, uuid=%s, oldSize=%d, newSize=%s, shrinkOk=%s, maxIops=%s", + name, vol.getUuid(), vol.getSize(), payload.newSize, payload.shrinkOk, maxIops); + return StorPoolUtil.volumeUpdate(spVolume, conn); + } + @Override public void deleteAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback callback) { String err = null; diff --git a/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java b/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java index 7c0d1bf4afc..1e8c30ba27c 100644 --- a/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java +++ b/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java @@ -24,6 +24,7 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -45,6 +46,7 @@ import org.apache.cloudstack.managed.context.ManagedContext; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.management.ManagementServerHost; import org.apache.logging.log4j.ThreadContext; +import org.apache.commons.collections.CollectionUtils; import com.cloud.agent.AgentManager; import com.cloud.alert.AlertManager; @@ -73,7 +75,6 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.VpcVirtualNetworkApplianceService; import com.cloud.resource.ResourceManager; import com.cloud.server.ManagementServer; -import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.StorageManager; @@ -236,6 +237,18 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur long _timeBetweenCleanups; String _haTag = null; + private boolean vmHasPendingHAJob(final List pendingHaWorks, final VMInstanceVO vm) { + Optional item = pendingHaWorks.stream() + .filter(h -> h.getInstanceId() == vm.getId()) + .reduce((first, second) -> second); + if (item.isPresent() && (item.get().getTimesTried() < _maxRetries || + !item.get().canScheduleNew(_timeBetweenFailures))) { + logger.debug(String.format("Skipping HA on %s as there is already a running HA job for it", vm)); + return true; + } + return false; + } + protected HighAvailabilityManagerImpl() { } @@ -295,28 +308,37 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur logger.warn("Scheduling restart for VMs on host {}", host); final List vms = _instanceDao.listByHostId(host.getId()); + final List pendingHaWorks = _haDao.listPendingHAWorkForHost(host.getId()); final DataCenterVO dcVO = _dcDao.findById(host.getDataCenterId()); // send an email alert that the host is down StringBuilder sb = null; List reorderedVMList = new ArrayList(); - if ((vms != null) && !vms.isEmpty()) { + int skippedHAVms = 0; + if (CollectionUtils.isNotEmpty(vms)) { sb = new StringBuilder(); sb.append(" Starting HA on the following VMs:"); // collect list of vm names for the alert email - for (int i = 0; i < vms.size(); i++) { - VMInstanceVO vm = vms.get(i); + for (VMInstanceVO vm : vms) { + if (vmHasPendingHAJob(pendingHaWorks, vm)) { + skippedHAVms++; + continue; + } if (vm.getType() == VirtualMachine.Type.User) { reorderedVMList.add(vm); } else { reorderedVMList.add(0, vm); } if (vm.isHaEnabled()) { - sb.append(" " + vm.getHostName()); + sb.append(" ").append(vm.getHostName()); } } } - + if (reorderedVMList.isEmpty() && skippedHAVms > 0 && skippedHAVms == vms.size()) { + logger.debug(String.format( + "Skipping sending alert for %s as it is suspected to be a duplicate of a recent alert", host)); + return; + } // send an email alert that the host is down, include VMs HostPodVO podVO = _podDao.findById(host.getPodId()); String hostDesc = "name: " + host.getName() + " (id:" + host.getId() + "), availability zone: " + dcVO.getName() + ", pod: " + podVO.getName(); @@ -324,7 +346,6 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur "Host [" + hostDesc + "] is down." + ((sb != null) ? sb.toString() : "")); for (VMInstanceVO vm : reorderedVMList) { - ServiceOfferingVO vmOffering = _serviceOfferingDao.findById(vm.getServiceOfferingId()); if (_itMgr.isRootVolumeOnLocalStorage(vm.getId())) { if (logger.isDebugEnabled()){ logger.debug("Skipping HA on vm " + vm + ", because it uses local storage. Its fate is tied to the host."); diff --git a/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDao.java b/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDao.java index 42c8aabe41a..42b0c32a763 100644 --- a/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDao.java +++ b/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDao.java @@ -85,6 +85,9 @@ public interface HighAvailabilityDao extends GenericDao { List listPendingHaWorkForVm(long vmId); List listPendingMigrationsForVm(long vmId); + + List listPendingHAWorkForHost(long hostId); + int expungeByVmList(List vmIds, Long batchSize); void markPendingWorksAsInvestigating(); void markServerPendingWorksAsInvestigating(long managementServerId); diff --git a/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDaoImpl.java b/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDaoImpl.java index 00b62e0d601..9d8e75745df 100644 --- a/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDaoImpl.java +++ b/server/src/main/java/com/cloud/ha/dao/HighAvailabilityDaoImpl.java @@ -260,6 +260,19 @@ public class HighAvailabilityDaoImpl extends GenericDaoBase impl return update(vo, sc); } + @Override + public List listPendingHAWorkForHost(long hostId) { + SearchBuilder sb = createSearchBuilder(); + sb.and("hostId", sb.entity().getHostId(), Op.EQ); + sb.and("type", sb.entity().getWorkType(), Op.EQ); + sb.and("step", sb.entity().getStep(), Op.NIN); + SearchCriteria sc = sb.create(); + sc.setParameters("hostId", hostId); + sc.setParameters("type", WorkType.HA); + sc.setParameters("step", Step.Done, Step.Cancelled, Step.Error); + return listBy(sc); + } + @Override public int expungeByVmList(List vmIds, Long batchSize) { if (CollectionUtils.isEmpty(vmIds)) { diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index bc21a3a9282..bb0297c5abb 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -749,8 +749,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir boolean isWindows; Long hostId; String networkCidr; + String macAddress; - public VmIpAddrFetchThread(long vmId, String vmUuid, long nicId, String instanceName, boolean windows, Long hostId, String networkCidr) { + public VmIpAddrFetchThread(long vmId, long nicId, String instanceName, boolean windows, Long hostId, String networkCidr, String macAddress) { this.vmId = vmId; this.vmUuid = vmUuid; this.nicId = nicId; @@ -758,11 +759,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir this.isWindows = windows; this.hostId = hostId; this.networkCidr = networkCidr; + this.macAddress = macAddress; } @Override protected void runInContext() { - GetVmIpAddressCommand cmd = new GetVmIpAddressCommand(vmName, networkCidr, isWindows); + GetVmIpAddressCommand cmd = new GetVmIpAddressCommand(vmName, networkCidr, isWindows, macAddress); boolean decrementCount = true; NicVO nic = _nicDao.findById(nicId); @@ -2435,9 +2437,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private void loadVmDetailsInMapForExternalDhcpIp() { List networks = _networkDao.listByGuestType(Network.GuestType.Shared); + networks.addAll(_networkDao.listByGuestType(Network.GuestType.L2)); for (NetworkVO network: networks) { - if(_networkModel.isSharedNetworkWithoutServices(network.getId())) { + if (GuestType.L2.equals(network.getGuestType()) || _networkModel.isSharedNetworkWithoutServices(network.getId())) { List nics = _nicDao.listByNetworkId(network.getId()); for (NicVO nic : nics) { @@ -2685,9 +2688,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir VirtualMachineProfile vmProfile = new VirtualMachineProfileImpl(userVm); VirtualMachine vm = vmProfile.getVirtualMachine(); boolean isWindows = _guestOSCategoryDao.findById(_guestOSDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows"); - - _vmIpFetchThreadExecutor.execute(new VmIpAddrFetchThread(vmId, vmInstance.getUuid(), nicId, vmInstance.getInstanceName(), - isWindows, vm.getHostId(), network.getCidr())); + _vmIpFetchThreadExecutor.execute(new VmIpAddrFetchThread(vmId, nicId, vmInstance.getInstanceName(), + isWindows, vm.getHostId(), network.getCidr(), nicVo.getMacAddress())); } } catch (Exception e) { @@ -3352,8 +3354,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir final List nics = _nicDao.listByVmId(vmId); for (NicVO nic : nics) { Network network = _networkModel.getNetwork(nic.getNetworkId()); - if (_networkModel.isSharedNetworkWithoutServices(network.getId())) { - logger.debug("Adding vm {} nic {} into vmIdCountMap as part of vm reboot for vm ip fetch ", userVm, nic); + if (GuestType.L2.equals(network.getGuestType()) || _networkModel.isSharedNetworkWithoutServices(network.getId())) { + logger.debug("Adding vm " +vmId +" nic id "+ nic.getId() +" into vmIdCountMap as part of vm " + + "reboot for vm ip fetch "); vmIdCountMap.put(nic.getId(), new VmAndCountDetails(nic.getInstanceId(), VmIpFetchTrialMax.value())); } } @@ -5366,7 +5369,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir final List nics = _nicDao.listByVmId(vm.getId()); for (NicVO nic : nics) { Network network = _networkModel.getNetwork(nic.getNetworkId()); - if (_networkModel.isSharedNetworkWithoutServices(network.getId())) { + if (GuestType.L2.equals(network.getGuestType()) || _networkModel.isSharedNetworkWithoutServices(network.getId())) { vmIdCountMap.put(nic.getId(), new VmAndCountDetails(nic.getInstanceId(), VmIpFetchTrialMax.value())); } } diff --git a/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java b/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java index 24714b72388..9274bd1ff08 100644 --- a/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java +++ b/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java @@ -65,7 +65,6 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.VpcVirtualNetworkApplianceService; import com.cloud.resource.ResourceManager; import com.cloud.server.ManagementServer; -import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.StorageManager; import com.cloud.storage.dao.GuestOSCategoryDao; @@ -237,7 +236,6 @@ public class HighAvailabilityManagerImplTest { Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(DataCenterVO.class)); Mockito.when(_haDao.findPreviousHA(Mockito.anyLong())).thenReturn(Arrays.asList(Mockito.mock(HaWorkVO.class))); Mockito.when(_haDao.persist((HaWorkVO)Mockito.any())).thenReturn(Mockito.mock(HaWorkVO.class)); - Mockito.when(_serviceOfferingDao.findById(vm1.getServiceOfferingId())).thenReturn(Mockito.mock(ServiceOfferingVO.class)); ConfigKey haEnabled = Mockito.mock(ConfigKey.class); highAvailabilityManager.VmHaEnabled = haEnabled; diff --git a/test/integration/component/test_affinity_groups_projects.py b/test/integration/component/test_affinity_groups_projects.py index 07811e79fde..ae94909a051 100644 --- a/test/integration/component/test_affinity_groups_projects.py +++ b/test/integration/component/test_affinity_groups_projects.py @@ -99,9 +99,12 @@ class TestCreateAffinityGroup(cloudstackTestCase): cls.api_client = cls.testClient.getApiClient() cls.services = Services().services + cls._cleanup = [] + #Get Zone, Domain and templates cls.rootdomain = get_domain(cls.api_client) cls.domain = Domain.create(cls.api_client, cls.services["domain"]) + cls._cleanup.append(cls.domain) cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) cls.template = get_template( @@ -177,21 +180,11 @@ class TestCreateAffinityGroup(cloudstackTestCase): self.cleanup = [] def tearDown(self): - try: -# #Clean up, terminate the created instance, volumes and snapshots - cleanup_resources(self.apiclient, self.cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return + super(TestCreateAffinityGroup, self).tearDownClass() @classmethod def tearDownClass(cls): - try: - #Clean up, terminate the created templates - cls.domain.delete(cls.api_client, cleanup=True) - cleanup_resources(cls.api_client, cls._cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) + super(TestCreateAffinityGroup, cls).tearDownClass() def create_aff_grp(self, api_client=None, aff_grp=None, aff_grp_name=None, projectid=None): diff --git a/test/integration/smoke/test_guest_os.py b/test/integration/smoke/test_guest_os.py index c9d50a7c523..bb09b2b86a1 100644 --- a/test/integration/smoke/test_guest_os.py +++ b/test/integration/smoke/test_guest_os.py @@ -47,19 +47,14 @@ class TestGuestOS(cloudstackTestCase): cls.hypervisor = cls.get_hypervisor_type() - @classmethod def setUp(self): self.apiclient = self.testClient.getApiClient() #build cleanup list self.cleanup = [] - @classmethod def tearDown(self): - try: - cleanup_resources(self.apiclient, self.cleanup) - except Exception as e: - self.debug("Warning! Exception in tearDown: %s" % e) + super(TestGuestOS, self).tearDown() @classmethod def get_hypervisor_type(cls): @@ -95,6 +90,7 @@ class TestGuestOS(cloudstackTestCase): osdisplayname="testCentOS", oscategoryid=os_category.id ) + self.cleanup.append(self.guestos1) list_guestos = GuestOS.list(self.apiclient, id=self.guestos1.id, listall=True) self.assertNotEqual( len(list_guestos), @@ -112,6 +108,7 @@ class TestGuestOS(cloudstackTestCase): self.apiclient, id=self.guestos1.id ) + self.cleanup.remove(self.guestos1) @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) def test_CRUD_operations_guest_OS_mapping(self): @@ -127,6 +124,7 @@ class TestGuestOS(cloudstackTestCase): osdisplayname="testCentOS", oscategoryid=os_category.id ) + self.cleanup.append(self.guestos1) if self.hypervisor.hypervisor.lower() not in ["xenserver", "vmware"]: raise unittest.SkipTest("OS name check with hypervisor is supported only on XenServer and VMware") @@ -138,6 +136,7 @@ class TestGuestOS(cloudstackTestCase): hypervisorversion=self.hypervisor.hypervisorversion, osnameforhypervisor="testOSMappingName" ) + self.cleanup.append(self.guestosmapping1) list_guestos_mapping = GuestOsMapping.list(self.apiclient, id=self.guestosmapping1.id, listall=True) self.assertNotEqual( @@ -156,11 +155,13 @@ class TestGuestOS(cloudstackTestCase): self.apiclient, id=self.guestosmapping1.id ) + self.cleanup.remove(self.guestosmapping1) GuestOS.remove( self.apiclient, id=self.guestos1.id ) + self.cleanup.remove(self.guestos1) @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) def test_guest_OS_mapping_check_with_hypervisor(self): @@ -176,6 +177,7 @@ class TestGuestOS(cloudstackTestCase): osdisplayname="testOSname1", oscategoryid=os_category.id ) + self.cleanup.append(self.guestos1) if self.hypervisor.hypervisor.lower() not in ["xenserver", "vmware"]: raise unittest.SkipTest("OS name check with hypervisor is supported only on XenServer and VMware") @@ -193,6 +195,7 @@ class TestGuestOS(cloudstackTestCase): osnameforhypervisor=testosname, osmappingcheckenabled=True ) + self.cleanup.append(self.guestosmapping1) list_guestos_mapping = GuestOsMapping.list(self.apiclient, id=self.guestosmapping1.id, listall=True) self.assertNotEqual( @@ -211,11 +214,13 @@ class TestGuestOS(cloudstackTestCase): self.apiclient, id=self.guestosmapping1.id ) + self.cleanup.remove(self.guestosmapping1) GuestOS.remove( self.apiclient, id=self.guestos1.id ) + self.cleanup.remove(self.guestos1) @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) def test_guest_OS_mapping_check_with_hypervisor_failure(self): @@ -231,6 +236,7 @@ class TestGuestOS(cloudstackTestCase): osdisplayname="testOSname2", oscategoryid=os_category.id ) + self.cleanup.append(self.guestos1) if self.hypervisor.hypervisor.lower() not in ["xenserver", "vmware"]: raise unittest.SkipTest("OS name check with hypervisor is supported only on XenServer and VMware") @@ -246,10 +252,12 @@ class TestGuestOS(cloudstackTestCase): osnameforhypervisor=testosname, osmappingcheckenabled=True ) + self.cleanup.append(self.guestosmapping1) GuestOsMapping.remove( self.apiclient, id=self.guestosmapping1.id ) + self.cleanup.remove(self.guestosmapping1) self.fail("Since os mapping name is wrong, this API should fail") except CloudstackAPIException as e: self.debug("Addition guest OS mapping failed as expected %s " % e) @@ -257,4 +265,5 @@ class TestGuestOS(cloudstackTestCase): self.apiclient, id=self.guestos1.id ) + self.cleanup.remove(self.guestos1) return diff --git a/test/integration/smoke/test_hostha_simulator.py b/test/integration/smoke/test_hostha_simulator.py index 1b3dd815a30..20d77e82743 100644 --- a/test/integration/smoke/test_hostha_simulator.py +++ b/test/integration/smoke/test_hostha_simulator.py @@ -457,7 +457,7 @@ class TestHostHA(cloudstackTestCase): retry_interval = 1 + (pingInterval * pingTimeout / 10) - res, _ = wait_until(retry_interval, 20, removeFakeMgmtServer, self.getFakeMsRunId()) + res, _ = wait_until(retry_interval, 100, removeFakeMgmtServer, self.getFakeMsRunId()) if not res: self.fail("Management server failed to turn down or remove fake mgmt server") diff --git a/test/integration/smoke/test_outofbandmanagement.py b/test/integration/smoke/test_outofbandmanagement.py index 79762719f69..3c2114dac9a 100644 --- a/test/integration/smoke/test_outofbandmanagement.py +++ b/test/integration/smoke/test_outofbandmanagement.py @@ -544,7 +544,7 @@ class TestOutOfBandManagement(cloudstackTestCase): retry_interval = 1 + (pingInterval * pingTimeout / 10) - res, _ = wait_until(retry_interval, 10, removeFakeMgmtServer, self.getFakeMsRunId()) + res, _ = wait_until(retry_interval, 100, removeFakeMgmtServer, self.getFakeMsRunId()) if not res: self.fail("Management server failed to turn down or remove fake mgmt server") diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 557434ea2ee..3d232d887c8 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -4576,6 +4576,7 @@ class Project: def __init__(self, items): self.__dict__.update(items) + @classmethod def create(cls, apiclient, services, account=None, domainid=None, userid=None, accountid=None): """Create project""" @@ -6720,7 +6721,7 @@ class GuestOSCategory: class GuestOS: """Manage Guest OS""" - def __init__(self, items, services): + def __init__(self, items): self.__dict__.update(items) @classmethod @@ -6735,7 +6736,7 @@ class GuestOS: if details is not None: cmd.details = details - return (apiclient.addGuestOs(cmd)) + return GuestOS(apiclient.addGuestOs(cmd).__dict__) @classmethod def remove(cls, apiclient, id): @@ -6772,10 +6773,13 @@ class GuestOS: return (apiclient.listOsTypes(cmd)) + def delete(self, apiclient): + self.remove(apiclient, self.id) + class GuestOsMapping: """Manage Guest OS Mappings""" - def __init__(self, items, services): + def __init__(self, items): self.__dict__.update(items) @classmethod @@ -6793,7 +6797,7 @@ class GuestOsMapping: if forced is not None: cmd.forced = forced - return (apiclient.addGuestOsMapping(cmd)) + return GuestOsMapping(apiclient.addGuestOsMapping(cmd).__dict__) @classmethod def remove(cls, apiclient, id): @@ -6837,6 +6841,9 @@ class GuestOsMapping: return (apiclient.listGuestOsMapping(cmd)) + def delete(self, apiclient): + self.remove(apiclient, self.id) + class VMSchedule: def __init__(self, items): diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 7d2f0327dfb..ebfe6bda1b2 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1,4 +1,5 @@ { +"message.delete.account.not.disabled": "Please disable the account before attempting to delete it.", "alert.service.domainrouter": "Domain router", "error.dedicate.bgp.peer.failed":"Failed to dedicate BGP peer", "error.dedicate.cluster.failed": "Failed to dedicate cluster.", @@ -942,6 +943,7 @@ "label.endipv6": "IPv6 end IP", "label.endpoint": "Endpoint", "label.endport": "End port", +"label.enter.account.name": "Enter the account name", "label.enter.code": "Enter 2FA code to verify", "label.enter.static.pin": "Enter static PIN to verify", "label.enter.token": "Enter token", @@ -1970,6 +1972,7 @@ "label.rolename": "Role", "label.roles": "Roles", "label.roletype": "Role Type", +"label.rolepermissiontab.searchbar": "Search Rule", "label.root.certificate": "Root certificate", "label.root.disk.size": "Root disk size (GB)", "label.rootdisk": "ROOT disk", @@ -2980,7 +2983,11 @@ "message.dedicating.host": "Dedicating host...", "message.dedicating.pod": "Dedicating pod...", "message.dedicating.zone": "Dedicating zone...", -"message.delete.account": "Please confirm that you want to delete this Account.", +"message.delete.account.confirm": "Please confirm that you want to delete this account by entering the name of the account below.", +"message.delete.account.failed": "Delete account failed", +"message.delete.account.processing": "Deleting account", +"message.delete.account.success": "Successfully deleted account", +"message.delete.account.warning": "Deleting this account will delete all of the instances, volumes and snapshots associated with the account.", "message.delete.acl.processing": "Removing ACL rule...", "message.delete.acl.rule": "Remove ACL rule", "message.delete.acl.rule.failed": "Failed to remove ACL rule.", @@ -3073,6 +3080,7 @@ "message.enabling.security.group.provider": "Enabling security group provider", "message.enter.valid.nic.ip": "Please enter a valid IP address for NIC", "message.error.access.key": "Please enter access key.", +"message.error.account.delete.name.mismatch": "Name entered doesn't match the account name.", "message.error.add.guest.network": "Either IPv4 fields or IPv6 fields need to be filled when adding a guest Network.", "message.error.add.interface.static.route": "Adding interface Static Route failed", "message.error.add.logical.router": "Adding Logical Router failed", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index aadbd2fedc3..8aba6fe2c78 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -1390,6 +1390,7 @@ "label.rolename": "Fun\u00e7\u00e3o", "label.roles": "Fun\u00e7\u00f5es", "label.roletype": "Tipo de fun\u00e7\u00e3o", +"label.rolepermissiontab.searchbar": "Pesquisa de regras", "label.root.certificate": "Certificado ra\u00edz", "label.root.disk.size": "Tamanho do disco ra\u00edz (GB)", "label.rootdisk": "Disco ra\u00edz", diff --git a/ui/src/components/view/StatsTab.vue b/ui/src/components/view/StatsTab.vue index 5c96a160722..cf592709da3 100644 --- a/ui/src/components/view/StatsTab.vue +++ b/ui/src/components/view/StatsTab.vue @@ -522,7 +522,7 @@ export default { this.chartLabels.push(currentLabel) if (this.resourceIsVirtualMachine) { - cpuLine.data.push({ timestamp: currentLabel, stat: element.cpuused.split('%')[0] }) + cpuLine.data.push({ timestamp: currentLabel, stat: element.cpuused.replace(',', '.').split('%')[0] }) element.memoryusedkbs = element.memorykbs - element.memoryintfreekbs memFreeLinePercent.data.push({ timestamp: currentLabel, stat: this.calculateMemoryPercentage(false, element.memorykbs, element.memoryintfreekbs) }) diff --git a/ui/src/config/router.js b/ui/src/config/router.js index 16599a0c367..aa85f452b73 100644 --- a/ui/src/config/router.js +++ b/ui/src/config/router.js @@ -75,7 +75,6 @@ function generateRouterMap (section) { icon: child.icon, docHelp: vueProps.$applyDocHelpMappings(child.docHelp), permission: child.permission, - getApiToCall: child.getApiToCall, resourceType: child.resourceType, filters: child.filters, params: child.params ? child.params : {}, diff --git a/ui/src/config/section/account.js b/ui/src/config/section/account.js index b5e962752f8..57416585e18 100644 --- a/ui/src/config/section/account.js +++ b/ui/src/config/section/account.js @@ -228,11 +228,10 @@ export default { message: 'message.delete.account', dataView: true, disabled: (record, store) => { - return record.id !== 'undefined' && store.userInfo.accountid === record.id + return store.userInfo.accountid === record?.id }, - groupAction: true, popup: true, - groupMap: (selection) => { return selection.map(x => { return { id: x } }) } + component: shallowRef(defineAsyncComponent(() => import('@/views/iam/DeleteAccountWrapper.vue'))) } ] } diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js index 5b7bd0f8b9b..b6a72b82f91 100644 --- a/ui/src/config/section/compute.js +++ b/ui/src/config/section/compute.js @@ -29,8 +29,7 @@ export default { title: 'label.instances', icon: 'cloud-server-outlined', docHelp: 'adminguide/virtual_machines.html', - permission: ['listVirtualMachines', 'listVirtualMachinesMetrics'], - getApiToCall: () => store.getters.metrics ? 'listVirtualMachinesMetrics' : 'listVirtualMachines', + permission: ['listVirtualMachinesMetrics'], resourceType: 'UserVm', params: () => { var params = { details: 'group,nics,secgrp,tmpl,servoff,diskoff,iso,volume,affgrp,backoff' } diff --git a/ui/src/config/section/infra/clusters.js b/ui/src/config/section/infra/clusters.js index 8021b1f6760..883efd463c3 100644 --- a/ui/src/config/section/infra/clusters.js +++ b/ui/src/config/section/infra/clusters.js @@ -26,8 +26,8 @@ export default { permission: ['listClustersMetrics'], searchFilters: ['name', 'zoneid', 'podid', 'arch', 'hypervisor'], columns: () => { - const fields = ['name', 'state', 'allocationstate', 'clustertype', 'arch', 'hypervisortype', 'hosts'] - const metricsFields = ['cpuused', 'cpumaxdeviation', 'cpuallocated', 'cputotal', 'memoryused', 'memorymaxdeviation', 'memoryallocated', 'memorytotal', 'drsimbalance'] + const fields = ['name', 'allocationstate', 'clustertype', 'arch', 'hypervisortype'] + const metricsFields = ['state', 'hosts', 'cpuused', 'cpumaxdeviation', 'cpuallocated', 'cputotal', 'memoryused', 'memorymaxdeviation', 'memoryallocated', 'memorytotal', 'drsimbalance'] if (store.getters.metrics) { fields.push(...metricsFields) } diff --git a/ui/src/config/section/infra/hosts.js b/ui/src/config/section/infra/hosts.js index 5810c9b146f..501283984b8 100644 --- a/ui/src/config/section/infra/hosts.js +++ b/ui/src/config/section/infra/hosts.js @@ -33,12 +33,10 @@ export default { params: { type: 'routing' }, columns: () => { const fields = [ - 'name', 'state', 'resourcestate', 'ipaddress', - 'arch', 'hypervisor', 'instances', - { field: 'systeminstances', customTitle: 'system.vms' }, - 'powerstate', 'version' + 'name', 'state', 'resourcestate', 'ipaddress', 'arch', 'hypervisor', + { field: 'systeminstances', customTitle: 'system.vms' }, 'version' ] - const metricsFields = ['cpunumber', 'cputotalghz', 'cpuusedghz', 'cpuallocatedghz', 'memorytotalgb', 'memoryusedgb', 'memoryallocatedgb', 'networkread', 'networkwrite'] + const metricsFields = ['instances', 'powerstate', 'cpunumber', 'cputotalghz', 'cpuusedghz', 'cpuallocatedghz', 'memorytotalgb', 'memoryusedgb', 'memoryallocatedgb', 'networkread', 'networkwrite'] if (store.getters.metrics) { fields.push(...metricsFields) } diff --git a/ui/src/config/section/infra/zones.js b/ui/src/config/section/infra/zones.js index b4a34372e10..cb95bce8f75 100644 --- a/ui/src/config/section/infra/zones.js +++ b/ui/src/config/section/infra/zones.js @@ -26,8 +26,8 @@ export default { permission: ['listZonesMetrics'], searchFilters: ['name', 'domainid', 'tags'], columns: () => { - const fields = ['name', 'allocationstate', 'type', 'networktype', 'clusters'] - const metricsFields = ['cpuused', 'cpumaxdeviation', 'cpuallocated', 'cputotal', 'memoryused', 'memorymaxdeviation', 'memoryallocated', 'memorytotal'] + const fields = ['name', 'allocationstate', 'type', 'networktype'] + const metricsFields = ['clusters', 'cpuused', 'cpumaxdeviation', 'cpuallocated', 'cputotal', 'memoryused', 'memorymaxdeviation', 'memoryallocated', 'memorytotal'] if (store.getters.metrics) { fields.push(...metricsFields) } diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue index e2ac07fbf7b..17f3460614c 100644 --- a/ui/src/views/AutogenView.vue +++ b/ui/src/views/AutogenView.vue @@ -829,7 +829,12 @@ export default { } if (this.$route && this.$route.meta && this.$route.meta.permission) { - this.apiName = (this.$route.meta.getApiToCall && this.$route.meta.getApiToCall()) || this.$route.meta.permission[0] + this.apiName = this.$route.meta.permission[0] + if (!store.getters.metrics && !this.dataView && + this.apiName && this.apiName.endsWith('Metrics') && + store.getters.apis[this.apiName.replace(/Metrics$/, '')]) { + this.apiName = this.apiName.replace(/Metrics$/, '') + } if (this.$route.meta.columns) { const columns = this.$route.meta.columns if (columns && typeof columns === 'function') { diff --git a/ui/src/views/iam/DeleteAccount.vue b/ui/src/views/iam/DeleteAccount.vue new file mode 100644 index 00000000000..0e7c6139e4c --- /dev/null +++ b/ui/src/views/iam/DeleteAccount.vue @@ -0,0 +1,176 @@ +// 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. + + + + + + diff --git a/ui/src/views/iam/DeleteAccountWrapper.vue b/ui/src/views/iam/DeleteAccountWrapper.vue new file mode 100644 index 00000000000..2c6a07ca3c6 --- /dev/null +++ b/ui/src/views/iam/DeleteAccountWrapper.vue @@ -0,0 +1,74 @@ +// 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. + + + + + + diff --git a/ui/src/views/iam/RolePermissionTab.vue b/ui/src/views/iam/RolePermissionTab.vue index 6d2bd71b284..18181d4d407 100644 --- a/ui/src/views/iam/RolePermissionTab.vue +++ b/ui/src/views/iam/RolePermissionTab.vue @@ -18,15 +18,24 @@