Merge release branch 4.20 to main

* 4.20:
  VR: apply iptables rules when add/remove static routes (#10064)
  Certificate and VM hostname validation improvements (#10051)
  set ulimit for server according to redhat spec (#10040)
  kvm-storage: provide isVMMigrate information to storage plugins (#10093)
  Allow config drive deletion of migrated VM, on host maintenance (#10045)
  linstor: improve heartbeat check with also asking linstor (#10105)
  server: simplify role change validation (#9173)
  UI: create VPC network offering with conserve mode (#10082)
  server: fix typo removeaccessvpn in VirtualRouterElement (#10086)
  UI: remove duplicated Instance Name in Public IP details page (#10087)
  UI: Fixes in the Usage UI (#10000)
  SAML2: add cookie with HttpOnly too #10013 (#10047)
  ui: Allow font-awesome icon usage and optimise icon size inconsistency (#9744)
This commit is contained in:
Daan Hoogland 2024-12-20 14:37:49 +01:00
commit 9295a1624d
44 changed files with 451 additions and 151 deletions

View File

@ -74,6 +74,7 @@ public interface VmDetailConstants {
String ENCRYPTED_PASSWORD = "Encrypted.Password";
String CONFIG_DRIVE_LOCATION = "configDriveLocation";
String LAST_CONFIG_DRIVE_LOCATION = "lastConfigDriveLocation";
String SKIP_DRS = "skipFromDRS";

View File

@ -47,6 +47,7 @@ import com.cloud.agent.api.CleanupNetworkRulesCmd;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.CreateStoragePoolCommand;
import com.cloud.agent.api.DeleteStoragePoolCommand;
import com.cloud.agent.api.HandleConfigDriveIsoCommand;
import com.cloud.agent.api.MaintainCommand;
import com.cloud.agent.api.MigrateCommand;
import com.cloud.agent.api.ModifySshKeysCommand;
@ -122,11 +123,10 @@ public abstract class AgentAttache {
public final static String[] s_commandsAllowedInMaintenanceMode = new String[] { MaintainCommand.class.toString(), MigrateCommand.class.toString(),
StopCommand.class.toString(), CheckVirtualMachineCommand.class.toString(), PingTestCommand.class.toString(), CheckHealthCommand.class.toString(),
ReadyCommand.class.toString(), ShutdownCommand.class.toString(), SetupCommand.class.toString(),
CleanupNetworkRulesCmd.class.toString(), CheckNetworkCommand.class.toString(), PvlanSetupCommand.class.toString(), CheckOnHostCommand.class.toString(),
ModifyTargetsCommand.class.toString(), ModifySshKeysCommand.class.toString(),
CreateStoragePoolCommand.class.toString(), DeleteStoragePoolCommand.class.toString(), ModifyStoragePoolCommand.class.toString(),
SetupMSListCommand.class.toString(), RollingMaintenanceCommand.class.toString(), CleanupPersistentNetworkResourceCommand.class.toString()};
ReadyCommand.class.toString(), ShutdownCommand.class.toString(), SetupCommand.class.toString(), CleanupNetworkRulesCmd.class.toString(),
CheckNetworkCommand.class.toString(), PvlanSetupCommand.class.toString(), CheckOnHostCommand.class.toString(), ModifyTargetsCommand.class.toString(),
ModifySshKeysCommand.class.toString(), CreateStoragePoolCommand.class.toString(), DeleteStoragePoolCommand.class.toString(), ModifyStoragePoolCommand.class.toString(),
SetupMSListCommand.class.toString(), RollingMaintenanceCommand.class.toString(), CleanupPersistentNetworkResourceCommand.class.toString(), HandleConfigDriveIsoCommand.class.toString()};
protected final static String[] s_commandsNotAllowedInConnectingMode = new String[] { StartCommand.class.toString(), CreateCommand.class.toString() };
static {
Arrays.sort(s_commandsAllowedInMaintenanceMode);

View File

@ -245,6 +245,7 @@ mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/lib
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup
mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/management
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management
mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/systemd/system/%{name}-management.service.d
mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/run
mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel
@ -294,6 +295,7 @@ install -D utils/target/cloud-utils-%{_maventag}-bundled.jar ${RPM_BUILD_ROOT}%{
install -D packaging/el8/cloud-ipallocator.rc ${RPM_BUILD_ROOT}%{_initrddir}/%{name}-ipallocator
install -D packaging/el8/cloud.limits ${RPM_BUILD_ROOT}%{_sysconfdir}/security/limits.d/cloud
install -D packaging/el8/filelimit.conf ${RPM_BUILD_ROOT}%{_sysconfdir}/systemd/system/%{name}-management.service.d
install -D packaging/systemd/cloudstack-management.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-management.service
install -D packaging/systemd/cloudstack-management.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-management
install -D server/target/conf/cloudstack-sudoers ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d/%{name}-management
@ -575,6 +577,7 @@ pip3 install --upgrade /usr/share/cloudstack-marvin/Marvin-*.tar.gz
%config(noreplace) %{_sysconfdir}/default/%{name}-management
%config(noreplace) %{_sysconfdir}/sudoers.d/%{name}-management
%config(noreplace) %{_sysconfdir}/security/limits.d/cloud
%config(noreplace) %{_sysconfdir}/systemd/system/%{name}-management.service.d
%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/db.properties
%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/server.properties
%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/config.json

View File

@ -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.
# should go in /etc/systemd/system/cloudstack-management.service.d/
[Service]
LimitNPROC=infinity

View File

@ -1031,7 +1031,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
hostHealthCheckScriptPath = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HEALTH_CHECK_SCRIPT_PATH);
if (StringUtils.isNotBlank(hostHealthCheckScriptPath) && !new File(hostHealthCheckScriptPath).exists()) {
logger.info(String.format("Unable to find the host health check script at: %s, " +
LOGGER.info(String.format("Unable to find the host health check script at: %s, " +
"discarding it", hostHealthCheckScriptPath));
}
@ -2796,11 +2796,11 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
if (hostCpuMaxCapacity > 0) {
int updatedCpuShares = (int) Math.ceil((requestedCpuShares * CGROUP_V2_UPPER_LIMIT) / (double) hostCpuMaxCapacity);
logger.debug(String.format("This host utilizes cgroupv2 (as the max shares value is [%s]), thus, the VM requested shares of [%s] will be converted to " +
LOGGER.debug(String.format("This host utilizes cgroupv2 (as the max shares value is [%s]), thus, the VM requested shares of [%s] will be converted to " +
"consider the host limits; the new CPU shares value is [%s].", hostCpuMaxCapacity, requestedCpuShares, updatedCpuShares));
return updatedCpuShares;
}
logger.debug(String.format("This host does not have a maximum CPU shares set; therefore, this host utilizes cgroupv1 and the VM requested CPU shares [%s] will not be " +
LOGGER.debug(String.format("This host does not have a maximum CPU shares set; therefore, this host utilizes cgroupv1 and the VM requested CPU shares [%s] will not be " +
"converted.", requestedCpuShares));
return requestedCpuShares;
}
@ -2931,7 +2931,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
LOGGER.warn(String.format("Setting System VM's [%s] current memory as max memory [%s].", vmTO.toString(), maxRam));
} else {
long minRam = ByteScaleUtils.bytesToKibibytes(vmTO.getMinRam());
logger.debug(String.format("Setting VM's [%s] current memory as min memory [%s] due to memory ballooning is enabled.", vmTO.toString(), minRam));
LOGGER.debug(String.format("Setting VM's [%s] current memory as min memory [%s] due to memory ballooning is enabled.", vmTO.toString(), minRam));
retVal = minRam;
}
return retVal;
@ -3002,9 +3002,10 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
public static boolean useBLOCKDiskType(KVMPhysicalDisk physicalDisk) {
return physicalDisk != null &&
physicalDisk.getPool().getType() == StoragePoolType.Linstor &&
physicalDisk.getPool() != null &&
StoragePoolType.Linstor.equals(physicalDisk.getPool().getType()) &&
physicalDisk.getFormat() != null &&
physicalDisk.getFormat()== PhysicalDiskFormat.RAW;
PhysicalDiskFormat.RAW.equals(physicalDisk.getFormat());
}
public static DiskDef.DiskType getDiskType(KVMPhysicalDisk physicalDisk) {
@ -3283,7 +3284,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
* (ii) Libvirt >= 6.3.0
*/
public void setDiskIoDriver(DiskDef disk, IoDriverPolicy ioDriver) {
logger.debug(String.format("Disk IO driver policy [%s]. The host supports the io_uring policy [%s]", ioDriver, enableIoUring));
LOGGER.debug(String.format("Disk IO driver policy [%s]. The host supports the io_uring policy [%s]", ioDriver, enableIoUring));
if (ioDriver != null) {
if (IoDriverPolicy.IO_URING != ioDriver) {
disk.setIoDriver(ioDriver);
@ -3426,13 +3427,15 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
}
if (configdrive != null) {
try {
LOGGER.debug(String.format("Detaching ConfigDrive ISO of the VM %s, at path %s", vmName, configdrive.getDiskPath()));
String result = attachOrDetachISO(conn, vmName, configdrive.getDiskPath(), false, CONFIG_DRIVE_ISO_DEVICE_ID);
if (result != null) {
LOGGER.warn("Detach ConfigDrive ISO with result: " + result);
LOGGER.warn("Detach ConfigDrive ISO of the VM {}, at path {} with result: {}", vmName, configdrive.getDiskPath(), result);
}
LOGGER.debug(String.format("Attaching ConfigDrive ISO of the VM %s, at path %s", vmName, configdrive.getDiskPath()));
result = attachOrDetachISO(conn, vmName, configdrive.getDiskPath(), true, CONFIG_DRIVE_ISO_DEVICE_ID);
if (result != null) {
LOGGER.warn("Attach ConfigDrive ISO with result: " + result);
LOGGER.warn("Attach ConfigDrive ISO of the VM {}, at path {} with result: {}", vmName, configdrive.getDiskPath(), result);
}
} catch (final LibvirtException | InternalErrorException | URISyntaxException e) {
final String msg = "Detach and attach ConfigDrive ISO failed due to " + e.toString();
@ -3444,16 +3447,20 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
public synchronized String attachOrDetachISO(final Connect conn, final String vmName, String isoPath, final boolean isAttach, final Integer diskSeq) throws LibvirtException, URISyntaxException,
InternalErrorException {
final DiskDef iso = new DiskDef();
if (isoPath != null && isAttach) {
final int index = isoPath.lastIndexOf("/");
final String path = isoPath.substring(0, index);
final String name = isoPath.substring(index + 1);
final KVMStoragePool secondaryPool = storagePoolManager.getStoragePoolByURI(path);
final KVMPhysicalDisk isoVol = secondaryPool.getPhysicalDisk(name);
final DiskDef.DiskType diskType = getDiskType(isoVol);
isoPath = isoVol.getPath();
if (isAttach && StringUtils.isNotBlank(isoPath) && isoPath.lastIndexOf("/") > 0) {
if (isoPath.startsWith(getConfigPath() + "/" + ConfigDrive.CONFIGDRIVEDIR) && isoPath.contains(vmName)) {
iso.defISODisk(isoPath, diskSeq, DiskDef.DiskType.FILE);
} else {
final int index = isoPath.lastIndexOf("/");
final String path = isoPath.substring(0, index);
final String name = isoPath.substring(index + 1);
final KVMStoragePool storagePool = storagePoolManager.getStoragePoolByURI(path);
final KVMPhysicalDisk isoVol = storagePool.getPhysicalDisk(name);
final DiskDef.DiskType diskType = getDiskType(isoVol);
isoPath = isoVol.getPath();
iso.defISODisk(isoPath, diskSeq, diskType);
iso.defISODisk(isoPath, diskSeq, diskType);
}
} else {
iso.defISODisk(null, diskSeq, DiskDef.DiskType.FILE);
}
@ -3640,19 +3647,19 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
*/
private HealthCheckResult getHostHealthCheckResult() {
if (StringUtils.isBlank(hostHealthCheckScriptPath)) {
logger.debug("Host health check script path is not specified");
LOGGER.debug("Host health check script path is not specified");
return HealthCheckResult.IGNORE;
}
File script = new File(hostHealthCheckScriptPath);
if (!script.exists() || !script.isFile() || !script.canExecute()) {
logger.warn(String.format("The host health check script file set at: %s cannot be executed, " +
LOGGER.warn(String.format("The host health check script file set at: %s cannot be executed, " +
"reason: %s", hostHealthCheckScriptPath,
!script.exists() ? "file does not exist" : "please check file permissions to execute this file"));
return HealthCheckResult.IGNORE;
}
int exitCode = executeBashScriptAndRetrieveExitValue(hostHealthCheckScriptPath);
if (logger.isDebugEnabled()) {
logger.debug(String.format("Host health check script exit code: %s", exitCode));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("Host health check script exit code: %s", exitCode));
}
return retrieveHealthCheckResultFromExitCode(exitCode);
}
@ -3761,17 +3768,17 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
*/
protected void calculateHostCpuMaxCapacity(int cpuCores, Long cpuSpeed) {
String output = Script.runSimpleBashScript(COMMAND_GET_CGROUP_HOST_VERSION);
logger.info(String.format("Host uses control group [%s].", output));
LOGGER.info(String.format("Host uses control group [%s].", output));
if (!CGROUP_V2.equals(output)) {
logger.info(String.format("Setting host CPU max capacity to 0, as it uses cgroup v1.", getHostCpuMaxCapacity()));
LOGGER.info(String.format("Setting host CPU max capacity to 0, as it uses cgroup v1.", getHostCpuMaxCapacity()));
setHostCpuMaxCapacity(0);
return;
}
logger.info(String.format("Calculating the max shares of the host."));
LOGGER.info(String.format("Calculating the max shares of the host."));
setHostCpuMaxCapacity(cpuCores * cpuSpeed.intValue());
logger.info(String.format("The max shares of the host is [%d].", getHostCpuMaxCapacity()));
LOGGER.info(String.format("The max shares of the host is [%d].", getHostCpuMaxCapacity()));
}
private StartupStorageCommand createLocalStoragePool(String localStoragePath, String localStorageUUID, StartupRoutingCommand cmd) {
@ -3834,7 +3841,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
String sourcePath = null;
try {
String mountResult = Script.runSimpleBashScript("mount | grep \"" + diskPath + "\"");
logger.debug("Got mount result for " + diskPath + "\n\n" + mountResult);
LOGGER.debug("Got mount result for " + diskPath + "\n\n" + mountResult);
if (StringUtils.isNotEmpty(mountResult)) {
String[] res = mountResult.strip().split(" ");
if (res[0].contains(":")) {
@ -3851,7 +3858,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
return new Pair<>(sourceHostIp, sourcePath);
}
} catch (Exception ex) {
logger.warn("Failed to list source host and IP for " + diskPath + ex.toString());
LOGGER.warn("Failed to list source host and IP for " + diskPath + ex.toString());
}
return null;
}
@ -3864,14 +3871,14 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
domainNames.add(names[i]);
}
} catch (final LibvirtException e) {
logger.warn("Failed to list defined domains", e);
LOGGER.warn("Failed to list defined domains", e);
}
int[] ids = null;
try {
ids = conn.listDomains();
} catch (final LibvirtException e) {
logger.warn("Failed to list domains", e);
LOGGER.warn("Failed to list domains", e);
return domainNames;
}
@ -4959,7 +4966,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
public boolean setupTungstenVRouter(final String oper, final String inf, final String subnet, final String route,
final String vrf) {
final Script cmd = new Script(setupTungstenVrouterPath, timeout, logger);
final Script cmd = new Script(setupTungstenVrouterPath, timeout, LOGGER);
cmd.add(oper);
cmd.add(inf);
cmd.add(subnet);
@ -4972,7 +4979,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
public boolean updateTungstenLoadbalancerStats(final String lbUuid, final String lbStatsPort,
final String lbStatsUri, final String lbStatsAuth) {
final Script cmd = new Script(updateTungstenLoadbalancerStatsPath, timeout, logger);
final Script cmd = new Script(updateTungstenLoadbalancerStatsPath, timeout, LOGGER);
cmd.add(lbUuid);
cmd.add(lbStatsPort);
cmd.add(lbStatsUri);
@ -4984,7 +4991,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
public boolean updateTungstenLoadbalancerSsl(final String lbUuid, final String sslCertName,
final String certificateKey, final String privateKey, final String privateIp, final String port) {
final Script cmd = new Script(updateTungstenLoadbalancerSslPath, timeout, logger);
final Script cmd = new Script(updateTungstenLoadbalancerSslPath, timeout, LOGGER);
cmd.add(lbUuid);
cmd.add(sslCertName);
cmd.add(certificateKey);
@ -4997,7 +5004,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
}
public boolean setupTfRoute(final String privateIpAddress, final String fromNetwork, final String toNetwork) {
final Script setupTfRouteScript = new Script(routerProxyPath, timeout, logger);
final Script setupTfRouteScript = new Script(routerProxyPath, timeout, LOGGER);
setupTfRouteScript.add("setup_tf_route.py");
setupTfRouteScript.add(privateIpAddress);
setupTfRouteScript.add(fromNetwork);
@ -5006,7 +5013,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
final OutputInterpreter.OneLineParser setupTfRouteParser = new OutputInterpreter.OneLineParser();
final String result = setupTfRouteScript.execute(setupTfRouteParser);
if (result != null) {
logger.debug("Failed to execute setup TF Route:" + result);
LOGGER.debug("Failed to execute setup TF Route:" + result);
return false;
}
return true;
@ -5525,7 +5532,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
interfaceDef.setMultiQueueNumber(nicMultiqueueNumberInteger);
}
} catch (NumberFormatException ex) {
logger.warn(String.format("VM details %s is not a valid integer value %s", VmDetailConstants.NIC_MULTIQUEUE_NUMBER, nicMultiqueueNumber));
LOGGER.warn(String.format("VM details %s is not a valid integer value %s", VmDetailConstants.NIC_MULTIQUEUE_NUMBER, nicMultiqueueNumber));
}
}
String nicPackedEnabled = details.get(VmDetailConstants.NIC_PACKED_VIRTQUEUES_ENABLED);
@ -5533,7 +5540,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
try {
interfaceDef.setPackedVirtQueues(Boolean.valueOf(nicPackedEnabled));
} catch (NumberFormatException ex) {
logger.warn(String.format("VM details %s is not a valid Boolean value %s", VmDetailConstants.NIC_PACKED_VIRTQUEUES_ENABLED, nicPackedEnabled));
LOGGER.warn(String.format("VM details %s is not a valid Boolean value %s", VmDetailConstants.NIC_PACKED_VIRTQUEUES_ENABLED, nicPackedEnabled));
}
}
}
@ -5548,11 +5555,11 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
command.append(remoteFile);
command.append(" " + tmpPath);
command.append(outputFile);
logger.debug(String.format("Converting remote disk file: %s, output file: %s%s (timeout: %d secs)", remoteFile, tmpPath, outputFile, timeoutInSecs));
LOGGER.debug(String.format("Converting remote disk file: %s, output file: %s%s (timeout: %d secs)", remoteFile, tmpPath, outputFile, timeoutInSecs));
SshHelper.sshExecute(srcIp, 22, username, null, password, command.toString(), timeoutInSecs * 1000);
logger.debug("Copying converted remote disk file " + outputFile + " to: " + localDir);
LOGGER.debug("Copying converted remote disk file " + outputFile + " to: " + localDir);
SshHelper.scpFrom(srcIp, 22, username, null, password, localDir, tmpPath + outputFile);
logger.debug("Successfully copied converted remote disk file to: " + localDir + "/" + outputFile);
LOGGER.debug("Successfully copied converted remote disk file to: " + localDir + "/" + outputFile);
return outputFile;
} catch (Exception e) {
try {

View File

@ -39,6 +39,9 @@ public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper<Ge
String ip = null;
boolean result = false;
String vmName = command.getVmName();
if (!NetUtils.verifyDomainNameLabel(vmName, true)) {
return new Answer(command, result, ip);
}
String sanitizedVmName = sanitizeBashCommandArgument(vmName);
String networkCidr = command.getVmNetworkCidr();
List<String[]> commands = new ArrayList<>();

View File

@ -118,7 +118,7 @@ public final class LibvirtPrepareForMigrationCommandWrapper extends CommandWrapp
skipDisconnect = true;
if (!storagePoolMgr.connectPhysicalDisksViaVmSpec(vm)) {
if (!storagePoolMgr.connectPhysicalDisksViaVmSpec(vm, true)) {
return new PrepareForMigrationAnswer(command, "failed to connect physical disks to host");
}

View File

@ -36,6 +36,7 @@ import com.cloud.resource.ResourceWrapper;
import com.cloud.utils.FileUtil;
import com.cloud.utils.PropertiesUtil;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils;
import com.cloud.utils.script.Script;
@ResourceWrapper(handles = SetupDirectDownloadCertificateCommand.class)
@ -131,6 +132,9 @@ public class LibvirtSetupDirectDownloadCertificateCommandWrapper extends Command
public Answer execute(SetupDirectDownloadCertificateCommand cmd, LibvirtComputingResource serverResource) {
String certificate = cmd.getCertificate();
String certificateName = cmd.getCertificateName();
if (!NetUtils.verifyDomainNameLabel(certificateName, false)) {
return new Answer(cmd, false, "The provided certificate name is invalid");
}
try {
File agentFile = getAgentPropertiesFile();

View File

@ -75,7 +75,7 @@ public final class LibvirtStartCommandWrapper extends CommandWrapper<StartComman
libvirtComputingResource.createVbd(conn, vmSpec, vmName, vm);
if (!storagePoolMgr.connectPhysicalDisksViaVmSpec(vmSpec)) {
if (!storagePoolMgr.connectPhysicalDisksViaVmSpec(vmSpec, false)) {
return new StartAnswer(command, "Failed to connect physical disks to host");
}

View File

@ -84,7 +84,7 @@ public class IscsiAdmStorageAdaptor implements StorageAdaptor {
}
@Override
public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<String, String> details) {
public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<String, String> details, boolean isVMMigrate) {
// ex. sudo iscsiadm -m node -T iqn.2012-03.com.test:volume1 -p 192.168.233.10:3260 -o new
Script iScsiAdmCmd = new Script(true, "iscsiadm", 0, logger);

View File

@ -106,7 +106,7 @@ public class IscsiAdmStoragePool implements KVMStoragePool {
@Override
public boolean connectPhysicalDisk(String name, Map<String, String> details) {
return this._storageAdaptor.connectPhysicalDisk(name, this, details);
return this._storageAdaptor.connectPhysicalDisk(name, this, details, false);
}
@Override

View File

@ -161,10 +161,10 @@ public class KVMStoragePoolManager {
StorageAdaptor adaptor = getStorageAdaptor(type);
KVMStoragePool pool = adaptor.getStoragePool(poolUuid);
return adaptor.connectPhysicalDisk(volPath, pool, details);
return adaptor.connectPhysicalDisk(volPath, pool, details, false);
}
public boolean connectPhysicalDisksViaVmSpec(VirtualMachineTO vmSpec) {
public boolean connectPhysicalDisksViaVmSpec(VirtualMachineTO vmSpec, boolean isVMMigrate) {
boolean result = false;
final String vmName = vmSpec.getName();
@ -187,7 +187,7 @@ public class KVMStoragePoolManager {
KVMStoragePool pool = getStoragePool(store.getPoolType(), store.getUuid());
StorageAdaptor adaptor = getStorageAdaptor(pool.getType());
result = adaptor.connectPhysicalDisk(vol.getPath(), pool, disk.getDetails());
result = adaptor.connectPhysicalDisk(vol.getPath(), pool, disk.getDetails(), isVMMigrate);
if (!result) {
logger.error("Failed to connect disks via vm spec for vm: " + vmName + " volume:" + vol.toString());
@ -315,6 +315,7 @@ public class KVMStoragePoolManager {
URI storageUri = null;
try {
logger.debug("Get storage pool by uri: " + uri);
storageUri = new URI(uri);
} catch (URISyntaxException e) {
throw new CloudRuntimeException(e.toString());
@ -324,7 +325,7 @@ public class KVMStoragePoolManager {
String uuid = null;
String sourceHost = "";
StoragePoolType protocol = null;
final String scheme = storageUri.getScheme().toLowerCase();
final String scheme = (storageUri.getScheme() != null) ? storageUri.getScheme().toLowerCase() : "";
List<String> acceptedSchemes = List.of("nfs", "networkfilesystem", "filesystem");
if (acceptedSchemes.contains(scheme)) {
sourcePath = storageUri.getPath();

View File

@ -1018,7 +1018,7 @@ public class LibvirtStorageAdaptor implements StorageAdaptor {
}
@Override
public boolean connectPhysicalDisk(String name, KVMStoragePool pool, Map<String, String> details) {
public boolean connectPhysicalDisk(String name, KVMStoragePool pool, Map<String, String> details, boolean isVMMigrate) {
// this is for managed storage that needs to prep disks prior to use
return true;
}

View File

@ -97,7 +97,7 @@ public class ManagedNfsStorageAdaptor implements StorageAdaptor {
* creates a nfs storage pool using libvirt
*/
@Override
public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<String, String> details) {
public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<String, String> details, boolean isVMMigrate) {
StoragePool sp = null;
Connect conn = null;

View File

@ -182,7 +182,7 @@ public abstract class MultipathSCSIAdapterBase implements StorageAdaptor {
}
@Override
public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details) {
public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details, boolean isVMMigrate) {
LOGGER.info("connectPhysicalDisk called for [" + volumePath + "]");
if (StringUtils.isEmpty(volumePath)) {

View File

@ -78,7 +78,7 @@ public class MultipathSCSIPool implements KVMStoragePool {
@Override
public boolean connectPhysicalDisk(String volumeUuid, Map<String, String> details) {
return storageAdaptor.connectPhysicalDisk(volumeUuid, this, details);
return storageAdaptor.connectPhysicalDisk(volumeUuid, this, details, false);
}
@Override

View File

@ -181,7 +181,7 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
return null;
}
if(!connectPhysicalDisk(name, pool, null)) {
if(!connectPhysicalDisk(name, pool, null, false)) {
throw new CloudRuntimeException(String.format("Failed to ensure disk %s was present", name));
}
@ -224,7 +224,7 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
}
@Override
public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details) {
public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details, boolean isMigration) {
if (StringUtils.isEmpty(volumePath) || pool == null) {
logger.error("Unable to connect physical disk due to insufficient data");
throw new CloudRuntimeException("Unable to connect physical disk due to insufficient data");

View File

@ -89,7 +89,7 @@ public class ScaleIOStoragePool implements KVMStoragePool {
@Override
public boolean connectPhysicalDisk(String volumeUuid, Map<String, String> details) {
return storageAdaptor.connectPhysicalDisk(volumeUuid, this, details);
return storageAdaptor.connectPhysicalDisk(volumeUuid, this, details, false);
}
@Override

View File

@ -52,8 +52,15 @@ public interface StorageAdaptor {
public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool,
PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase);
// given disk path (per database) and pool, prepare disk on host
public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details);
/**
* given disk path (per database) and pool, prepare disk on host
* @param volumePath volume path
* @param pool storage pool the disk is part of
* @param details disk details map
* @param isVMMigrate Indicates if request is while VM is migration
* @return true if connect was a success
*/
public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details, boolean isVMMigrate);
// given disk path (per database) and pool, clean up disk on host
public boolean disconnectPhysicalDisk(String volumePath, KVMStoragePool pool);

View File

@ -1477,7 +1477,7 @@ public class LibvirtComputingResourceTest {
when(libvirtComputingResourceMock.getVifDriver(nicTO.getType(), nicTO.getName())).thenReturn(vifDriver);
when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolManager);
when(storagePoolManager.connectPhysicalDisksViaVmSpec(vm)).thenReturn(true);
when(storagePoolManager.connectPhysicalDisksViaVmSpec(vm, true)).thenReturn(true);
final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
assertNotNull(wrapper);
@ -5023,7 +5023,7 @@ public class LibvirtComputingResourceTest {
fail(e.getMessage());
}
when(storagePoolMgr.connectPhysicalDisksViaVmSpec(vmSpec)).thenReturn(false);
when(storagePoolMgr.connectPhysicalDisksViaVmSpec(vmSpec, false)).thenReturn(false);
final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
assertNotNull(wrapper);
@ -5224,7 +5224,7 @@ public class LibvirtComputingResourceTest {
fail(e.getMessage());
}
when(storagePoolMgr.connectPhysicalDisksViaVmSpec(vmSpec)).thenReturn(true);
when(storagePoolMgr.connectPhysicalDisksViaVmSpec(vmSpec, false)).thenReturn(true);
try {
doNothing().when(libvirtComputingResourceMock).createVifs(vmSpec, vmDef);
@ -5303,7 +5303,7 @@ public class LibvirtComputingResourceTest {
fail(e.getMessage());
}
when(storagePoolMgr.connectPhysicalDisksViaVmSpec(vmSpec)).thenReturn(true);
when(storagePoolMgr.connectPhysicalDisksViaVmSpec(vmSpec, false)).thenReturn(true);
try {
doNothing().when(libvirtComputingResourceMock).createVifs(vmSpec, vmDef);
@ -5380,7 +5380,7 @@ public class LibvirtComputingResourceTest {
fail(e.getMessage());
}
when(storagePoolMgr.connectPhysicalDisksViaVmSpec(vmSpec)).thenReturn(true);
when(storagePoolMgr.connectPhysicalDisksViaVmSpec(vmSpec, false)).thenReturn(true);
final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
assertNotNull(wrapper);

View File

@ -190,7 +190,7 @@ public class ScaleIOStoragePoolTest {
when(adapter.getPhysicalDisk(volumeId, pool)).thenReturn(disk);
final boolean result = adapter.connectPhysicalDisk(volumePath, pool, null);
final boolean result = adapter.connectPhysicalDisk(volumePath, pool, null, false);
assertTrue(result);
}
}

View File

@ -61,20 +61,19 @@ public final class CitrixGetVmIpAddressCommandWrapper extends CommandWrapper<Get
}
if (vmIp != null) {
logger.debug("VM " +vmName + " ip address got retrieved "+vmIp);
logger.debug("VM {} ip address got retrieved {}", vmName, vmIp);
result = true;
return new Answer(command, result, vmIp);
}
}catch (Types.XenAPIException e) {
logger.debug("Got exception in GetVmIpAddressCommand "+ e.getMessage());
errorMsg = "Failed to retrived vm ip addr, exception: "+e.getMessage();
}catch (XmlRpcException e) {
logger.debug("Got exception in GetVmIpAddressCommand "+ e.getMessage());
errorMsg = "Failed to retrived vm ip addr, exception: "+e.getMessage();
} catch (Types.XenAPIException e) {
logger.debug("Got exception in GetVmIpAddressCommand " + e.getMessage());
errorMsg = "Failed to retrived vm ip addr, exception: " + e.getMessage();
} catch (XmlRpcException e) {
logger.debug("Got exception in GetVmIpAddressCommand " + e.getMessage());
errorMsg = "Failed to retrived vm ip addr, exception: " + e.getMessage();
}
return new Answer(command, result, errorMsg);
}
}

View File

@ -5,6 +5,18 @@ All notable changes to Linstor CloudStack plugin will be documented in this file
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2024-12-13]
### Fixed
- Linstor heartbeat check now also ask linstor-controller if there is no connection between nodes
## [2024-12-11]
### Fixed
- Only set allow-two-primaries if a live migration is performed
## [2024-10-28]
### Fixed

View File

@ -46,6 +46,7 @@ import com.linbit.linstor.api.Configuration;
import com.linbit.linstor.api.DevelopersApi;
import com.linbit.linstor.api.model.ApiCallRc;
import com.linbit.linstor.api.model.ApiCallRcList;
import com.linbit.linstor.api.model.Node;
import com.linbit.linstor.api.model.Properties;
import com.linbit.linstor.api.model.ProviderKind;
import com.linbit.linstor.api.model.Resource;
@ -277,6 +278,7 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
* @throws ApiException if any problem connecting to the Linstor controller
*/
private void allow2PrimariesIfInUse(DevelopersApi api, String rscName) throws ApiException {
logger.debug("enabling allow-two-primaries");
String inUseNode = LinstorUtil.isResourceInUse(api, rscName);
if (inUseNode != null && !inUseNode.equalsIgnoreCase(localNodeName)) {
// allow 2 primaries for live migration, should be removed by disconnect on the other end
@ -292,7 +294,8 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
}
@Override
public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details)
public boolean connectPhysicalDisk(
String volumePath, KVMStoragePool pool, Map<String, String> details, boolean isVMMigration)
{
logger.debug("Linstor: connectPhysicalDisk {}:{} -> {}", pool.getUuid(), volumePath, details);
if (volumePath == null) {
@ -315,12 +318,13 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
}
try
{
allow2PrimariesIfInUse(api, rscName);
} catch (ApiException apiEx) {
logger.error(apiEx);
// do not fail here as adding allow-two-primaries property is only a problem while live migrating
if (isVMMigration) {
try {
allow2PrimariesIfInUse(api, rscName);
} catch (ApiException apiEx) {
logger.error(apiEx);
// do not fail here as adding allow-two-primaries property is only a problem while live migrating
}
}
return true;
}
@ -716,4 +720,19 @@ public class LinstorStorageAdaptor implements StorageAdaptor {
throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
}
}
public boolean isNodeOnline(LinstorStoragePool pool, String nodeName) {
DevelopersApi linstorApi = getLinstorAPI(pool);
try {
List<Node> node = linstorApi.nodeList(Collections.singletonList(nodeName), Collections.emptyList(), null, null);
if (node == null || node.isEmpty()) {
return false;
}
return Node.ConnectionStatusEnum.ONLINE.equals(node.get(0).getConnectionStatus());
} catch (ApiException apiEx) {
logger.error(apiEx.getMessage());
throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
}
}
}

View File

@ -75,7 +75,7 @@ public class LinstorStoragePool implements KVMStoragePool {
@Override
public boolean connectPhysicalDisk(String volumeUuid, Map<String, String> details)
{
return _storageAdaptor.connectPhysicalDisk(volumeUuid, this, details);
return _storageAdaptor.connectPhysicalDisk(volumeUuid, this, details, false);
}
@Override
@ -280,22 +280,52 @@ public class LinstorStoragePool implements KVMStoragePool {
return sc.execute(parser);
}
private boolean checkLinstorNodeOnline(String nodeName) {
return ((LinstorStorageAdaptor)_storageAdaptor).isNodeOnline(this, nodeName);
}
/**
* Checks output of drbdsetup status output if this node has any valid connection to the specified
* otherNodeName.
* If there is no connection, ask the Linstor controller if the node is seen online and return false if not.
* If there is a connection but not connected(valid) return false.
* @param output Output of the drbdsetup status --json command
* @param otherNodeName Name of the node to check against
* @return true if we could say that this node thinks the node in question is reachable, otherwise false.
*/
private boolean checkDrbdSetupStatusOutput(String output, String otherNodeName) {
JsonParser jsonParser = new JsonParser();
JsonArray jResources = (JsonArray) jsonParser.parse(output);
boolean connectionFound = false;
for (JsonElement jElem : jResources) {
JsonObject jRes = (JsonObject) jElem;
JsonArray jConnections = jRes.getAsJsonArray("connections");
for (JsonElement jConElem : jConnections) {
JsonObject jConn = (JsonObject) jConElem;
if (jConn.getAsJsonPrimitive("name").getAsString().equals(otherNodeName)
&& jConn.getAsJsonPrimitive("connection-state").getAsString().equalsIgnoreCase("Connected")) {
return true;
if (jConn.getAsJsonPrimitive("name").getAsString().equals(otherNodeName))
{
connectionFound = true;
if (jConn.getAsJsonPrimitive("connection-state").getAsString()
.equalsIgnoreCase("Connected")) {
return true;
}
}
}
}
LOGGER.warn(String.format("checkDrbdSetupStatusOutput: no resource connected to %s.", otherNodeName));
return false;
boolean otherNodeOnline = false;
if (connectionFound) {
LOGGER.warn(String.format(
"checkingHeartBeat: connection found, but not in state 'Connected' to %s", otherNodeName));
} else {
LOGGER.warn(String.format(
"checkingHeartBeat: no resource connected to %s, checking LINSTOR", otherNodeName));
otherNodeOnline = checkLinstorNodeOnline(otherNodeName);
}
LOGGER.info(String.format(
"checkingHeartBeat: other node %s is %s.",
otherNodeName,
otherNodeOnline ? "online on controller" : "down"));
return otherNodeOnline;
}
private String executeDrbdEventsNow(OutputInterpreter.AllLinesParser parser) {

View File

@ -249,7 +249,7 @@ public class StorPoolStorageAdaptor implements StorageAdaptor {
}
@Override
public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<String, String> details) {
public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<String, String> details, boolean isVMMigrate) {
SP_LOG("StorPoolStorageAdaptor.connectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool);
LOGGER.debug(String.format("connectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool));

View File

@ -132,7 +132,7 @@ public class StorPoolStoragePool implements KVMStoragePool {
@Override
public boolean connectPhysicalDisk(String name, Map<String, String> details) {
return _storageAdaptor.connectPhysicalDisk(name, this, details);
return _storageAdaptor.connectPhysicalDisk(name, this, details, false);
}
@Override

View File

@ -321,6 +321,7 @@ public class SAMLUtils {
String sessionKeyCookie = String.format("%s=%s;Domain=%s;Path=%s;%s", ApiConstants.SESSIONKEY, loginResponse.getSessionKey(), domain, path, sameSite);
LOGGER.debug("Adding sessionkey cookie to response: " + sessionKeyCookie);
resp.addHeader("SET-COOKIE", sessionKeyCookie);
resp.addHeader("SET-COOKIE", String.format("%s=%s;HttpOnly;Path=/client/api;%s", ApiConstants.SESSIONKEY, loginResponse.getSessionKey(), sameSite));
}
/**

View File

@ -345,10 +345,10 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle
try {
if (isConfigDriveIsoOnHostCache(vm.getId())) {
vm.setConfigDriveLocation(Location.HOST);
configureConfigDriveData(vm, nic, dest);
// Create the config drive on dest host cache
createConfigDriveIsoOnHostCache(nic, vm, dest.getHost().getId());
if (configureConfigDriveData(vm, nic, dest)) {
// Create the config drive on dest host cache
createConfigDriveIsoOnHostCache(vm, dest.getHost().getId());
}
} else {
vm.setConfigDriveLocation(getConfigDriveLocation(vm.getId()));
boolean result = addPasswordAndUserdata(network, nic, vm, dest, context);
@ -380,7 +380,7 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle
@Override
public void commitMigration(NicProfile nic, Network network, VirtualMachineProfile vm, ReservationContext src, ReservationContext dst) {
try {
if (isConfigDriveIsoOnHostCache(vm.getId())) {
if (isLastConfigDriveIsoOnHostCache(vm.getId())) {
vm.setConfigDriveLocation(Location.HOST);
// Delete the config drive on src host cache
deleteConfigDriveIsoOnHostCache(vm.getVirtualMachine(), vm.getHostId());
@ -537,7 +537,18 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle
return false;
}
private boolean createConfigDriveIsoOnHostCache(NicProfile nic, VirtualMachineProfile profile, Long hostId) throws ResourceUnavailableException {
private boolean isLastConfigDriveIsoOnHostCache(long vmId) {
final UserVmDetailVO vmDetailLastConfigDriveLocation = _userVmDetailsDao.findDetail(vmId, VmDetailConstants.LAST_CONFIG_DRIVE_LOCATION);
if (vmDetailLastConfigDriveLocation == null) {
return isConfigDriveIsoOnHostCache(vmId);
}
if (Location.HOST.toString().equalsIgnoreCase(vmDetailLastConfigDriveLocation.getValue())) {
return true;
}
return false;
}
private boolean createConfigDriveIsoOnHostCache(VirtualMachineProfile profile, Long hostId) throws ResourceUnavailableException {
if (hostId == null) {
throw new ResourceUnavailableException("Config drive iso creation failed, dest host not available",
ConfigDriveNetworkElement.class, 0L);
@ -549,7 +560,7 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle
final String isoFileName = ConfigDrive.configIsoFileName(profile.getInstanceName());
final String isoPath = ConfigDrive.createConfigDrivePath(profile.getInstanceName());
List<NicProfile> nicProfiles = _networkOrchestrationService.getNicProfiles(nic.getVirtualMachineId(), profile.getHypervisorType());
List<NicProfile> nicProfiles = _networkOrchestrationService.getNicProfiles(profile.getVirtualMachine().getId(), profile.getHypervisorType());
final Map<Long, List<Service>> supportedServices = getSupportedServicesByElementForNetwork(nicProfiles);
final String isoData = ConfigDriveBuilder.buildConfigDrive(nicProfiles, profile.getVmData(), isoFileName, profile.getConfigDriveLabel(), customUserdataParamMap, supportedServices);
final HandleConfigDriveIsoCommand configDriveIsoCommand = new HandleConfigDriveIsoCommand(isoPath, isoData, null, false, true, true);
@ -565,7 +576,7 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle
}
profile.setConfigDriveLocation(answer.getConfigDriveLocation());
_userVmDetailsDao.addDetail(profile.getId(), VmDetailConstants.CONFIG_DRIVE_LOCATION, answer.getConfigDriveLocation().toString(), false);
updateConfigDriveLocationInVMDetails(profile.getId(), answer.getConfigDriveLocation());
addConfigDriveDisk(profile, null);
return true;
}
@ -650,11 +661,23 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle
answer.getDetails()), ConfigDriveNetworkElement.class, 0L);
}
profile.setConfigDriveLocation(answer.getConfigDriveLocation());
_userVmDetailsDao.addDetail(profile.getId(), VmDetailConstants.CONFIG_DRIVE_LOCATION, answer.getConfigDriveLocation().toString(), false);
updateConfigDriveLocationInVMDetails(profile.getId(), answer.getConfigDriveLocation());
addConfigDriveDisk(profile, dataStore);
return true;
}
private void updateConfigDriveLocationInVMDetails(long vmId, NetworkElement.Location configDriveLocation) {
final UserVmDetailVO vmDetailConfigDriveLocation = _userVmDetailsDao.findDetail(vmId, VmDetailConstants.CONFIG_DRIVE_LOCATION);
if (vmDetailConfigDriveLocation != null) {
if (!configDriveLocation.toString().equalsIgnoreCase(vmDetailConfigDriveLocation.getValue())) {
_userVmDetailsDao.addDetail(vmId, VmDetailConstants.LAST_CONFIG_DRIVE_LOCATION, vmDetailConfigDriveLocation.getValue(), false);
} else {
_userVmDetailsDao.removeDetail(vmId, VmDetailConstants.LAST_CONFIG_DRIVE_LOCATION);
}
}
_userVmDetailsDao.addDetail(vmId, VmDetailConstants.CONFIG_DRIVE_LOCATION, configDriveLocation.toString(), false);
}
private Map<String, String> getVMCustomUserdataParamMap(long vmId) {
UserVmVO userVm = _userVmDao.findById(vmId);
String userDataDetails = userVm.getUserDataDetails();
@ -769,7 +792,7 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle
private boolean configureConfigDriveData(final VirtualMachineProfile profile, final NicProfile nic, final DeployDestination dest) {
final UserVmVO vm = _userVmDao.findById(profile.getId());
if (vm.getType() != VirtualMachine.Type.User) {
if (vm == null || vm.getType() != VirtualMachine.Type.User) {
return false;
}
final Nic defaultNic = _networkModel.getDefaultNic(vm.getId());

View File

@ -543,7 +543,7 @@ NetworkMigrationResponder, AggregatedCommandExecutor, RedundantResource, DnsServ
// Set capabilities for vpn
final Map<Capability, String> vpnCapabilities = new HashMap<Capability, String>();
vpnCapabilities.put(Capability.SupportedVpnProtocols, "pptp,l2tp,ipsec");
vpnCapabilities.put(Capability.VpnTypes, "removeaccessvpn");
vpnCapabilities.put(Capability.VpnTypes, "remoteaccessvpn");
capabilities.put(Service.Vpn, vpnCapabilities);
final Map<Capability, String> dnsCapabilities = new HashMap<Capability, String>();

View File

@ -122,8 +122,6 @@ import com.cloud.network.IpAddress;
import com.cloud.network.IpAddressManager;
import com.cloud.network.Network;
import com.cloud.network.NetworkModel;
import com.cloud.network.security.SecurityGroupService;
import com.cloud.network.security.SecurityGroupVO;
import com.cloud.network.VpnUserVO;
import com.cloud.network.as.AutoScaleManager;
import com.cloud.network.dao.AccountGuestVlanMapDao;
@ -137,6 +135,8 @@ import com.cloud.network.dao.RemoteAccessVpnVO;
import com.cloud.network.dao.VpnUserDao;
import com.cloud.network.router.VirtualRouter;
import com.cloud.network.security.SecurityGroupManager;
import com.cloud.network.security.SecurityGroupService;
import com.cloud.network.security.SecurityGroupVO;
import com.cloud.network.security.dao.SecurityGroupDao;
import com.cloud.network.vpc.Vpc;
import com.cloud.network.vpc.VpcManager;
@ -1335,16 +1335,33 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
return _userAccountDao.findById(userId);
}
private boolean isValidRoleChange(Account account, Role role) {
Long currentAccRoleId = account.getRoleId();
Role currentRole = roleService.findRole(currentAccRoleId);
if (role.getRoleType().ordinal() < currentRole.getRoleType().ordinal() && ((account.getType() == Account.Type.NORMAL && role.getRoleType().getAccountType().ordinal() > Account.Type.NORMAL.ordinal()) ||
account.getType().ordinal() > Account.Type.NORMAL.ordinal() && role.getRoleType().getAccountType().ordinal() < account.getType().ordinal() && role.getRoleType().getAccountType().ordinal() > 0)) {
throw new PermissionDeniedException(String.format("Unable to update account role to %s as you are " +
"attempting to escalate the account %s to account type %s which has higher privileges", role.getName(), account.getAccountName(), role.getRoleType().getAccountType().name()));
/*
Role change should follow the below conditions:
- Caller should not be of Unknown role type
- New role's type should not be Unknown
- Caller should not be able to escalate or de-escalate an account's role which is of higher role type
- New role should not be of type Admin with domain other than ROOT domain
*/
protected void validateRoleChange(Account account, Role role, Account caller) {
Role currentRole = roleService.findRole(account.getRoleId());
Role callerRole = roleService.findRole(caller.getRoleId());
String errorMsg = String.format("Unable to update account role to %s, ", role.getName());
if (RoleType.Unknown.equals(callerRole.getRoleType())) {
throw new PermissionDeniedException(String.format("%s as the caller privileges are unknown", errorMsg));
}
if (RoleType.Unknown.equals(role.getRoleType())) {
throw new PermissionDeniedException(String.format("%s as the new role privileges are unknown", errorMsg));
}
if (!callerRole.getRoleType().equals(RoleType.Admin) &&
(role.getRoleType().ordinal() < callerRole.getRoleType().ordinal() ||
currentRole.getRoleType().ordinal() < callerRole.getRoleType().ordinal())) {
throw new PermissionDeniedException(String.format("%s as either current or new role has higher " +
"privileges than the caller", errorMsg));
}
if (role.getRoleType().equals(RoleType.Admin) && account.getDomainId() != Domain.ROOT_DOMAIN) {
throw new PermissionDeniedException(String.format("%s as the user does not belong to the ROOT domain",
errorMsg));
}
return true;
}
/**
@ -2136,7 +2153,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
}
Role role = roleService.findRole(roleId);
isValidRoleChange(account, role);
validateRoleChange(account, role, caller);
acctForUpdate.setRoleId(roleId);
acctForUpdate.setType(role.getRoleType().getAccountType());
checkRoleEscalation(getCurrentCallingAccount(), acctForUpdate);

View File

@ -742,8 +742,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
private class VmIpAddrFetchThread extends ManagedContextRunnable {
long nicId;
long vmId;
String vmName;
@ -766,7 +764,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
boolean decrementCount = true;
try {
logger.debug("Trying for vm "+ vmId +" nic Id "+nicId +" ip retrieval ...");
logger.debug("Trying IP retrieval for VM {} ({}), nic Id {}", vmName, vmId, nicId);
Answer answer = _agentMgr.send(hostId, cmd);
NicVO nic = _nicDao.findById(nicId);
if (answer.getResult()) {
@ -777,12 +775,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
if (nic != null) {
nic.setIPv4Address(vmIp);
_nicDao.update(nicId, nic);
logger.debug("Vm "+ vmId +" IP "+vmIp +" got retrieved successfully");
logger.debug("VM {} ({}) - IP {} retrieved successfully", vmName, vmId, vmIp);
vmIdCountMap.remove(nicId);
decrementCount = false;
ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM,
Domain.ROOT_DOMAIN, EventTypes.EVENT_NETWORK_EXTERNAL_DHCP_VM_IPFETCH,
"VM " + vmId + " nic id " + nicId + " ip address " + vmIp + " got fetched successfully", vmId, ApiCommandResourceType.VirtualMachine.toString());
"VM " + vmId + ", nic id " + nicId + ", IP address " + vmIp + " fetched successfully", vmId, ApiCommandResourceType.VirtualMachine.toString());
}
}
} else {
@ -793,7 +791,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
_nicDao.update(nicId, nic);
}
if (answer.getDetails() != null) {
logger.debug("Failed to get vm ip for Vm "+ vmId + answer.getDetails());
logger.debug("Failed to get vm ip for Vm {} ({}), details: {}", vmName, vmId, answer.getDetails());
}
}
} catch (OperationTimedoutException e) {
@ -804,7 +802,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
if (decrementCount) {
VmAndCountDetails vmAndCount = vmIdCountMap.get(nicId);
vmAndCount.decrementCount();
logger.debug("Ip is not retrieved for VM " + vmId +" nic "+nicId + " ... decremented count to "+vmAndCount.getRetrievalCount());
logger.debug("Ip is not retrieved for VM {} ({}) nic {} ... decremented count to {}", vmName, vmId, nicId, vmAndCount.getRetrievalCount());
vmIdCountMap.put(nicId, vmAndCount);
}
}

View File

@ -102,6 +102,7 @@ import com.cloud.storage.dao.VMTemplatePoolDao;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.concurrency.NamedThreadFactory;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils;
import com.cloud.utils.security.CertificateHelper;
import sun.security.x509.X509CertImpl;
@ -469,10 +470,18 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
@Override
public Pair<DirectDownloadCertificate, List<HostCertificateStatus>> uploadCertificateToHosts(
String certificateCer, String alias, String hypervisor, Long zoneId, Long hostId) {
if (alias != null && (alias.equalsIgnoreCase("cloud") || alias.startsWith("cloudca"))) {
if (StringUtils.isBlank(alias)) {
throw new CloudRuntimeException("Certificate name not provided, please provide a valid name");
}
if (alias.equalsIgnoreCase("cloud") || alias.startsWith("cloudca")) {
throw new CloudRuntimeException("Please provide a different alias name for the certificate");
}
if (!NetUtils.verifyDomainNameLabel(alias, false)) {
throw new CloudRuntimeException("The provided certificate name is invalid, please provide a valid name");
}
List<HostVO> hosts;
DirectDownloadCertificateVO certificateVO;
HypervisorType hypervisorType = HypervisorType.getType(hypervisor);

View File

@ -27,11 +27,14 @@ import java.util.List;
import java.util.Map;
import com.cloud.event.ActionEventUtils;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
@ -50,6 +53,7 @@ import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import com.cloud.acl.DomainChecker;
@ -120,6 +124,8 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
@Mock
ConfigKey<Boolean> enableUserTwoFactorAuthenticationMock;
@Mock
RoleService roleService;
@Before
public void setUp() throws Exception {
@ -1225,4 +1231,112 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
accountManagerImpl.deleteWebhooksForAccount(1L);
}
}
@Test(expected = PermissionDeniedException.class)
public void testValidateRoleChangeUnknownCaller() {
Account account = Mockito.mock(Account.class);
Mockito.when(account.getRoleId()).thenReturn(1L);
Role role = Mockito.mock(Role.class);
Mockito.when(role.getRoleType()).thenReturn(RoleType.Unknown);
Account caller = Mockito.mock(Account.class);
Mockito.when(caller.getRoleId()).thenReturn(2L);
Mockito.when(roleService.findRole(2L)).thenReturn(role);
accountManagerImpl.validateRoleChange(account, Mockito.mock(Role.class), caller);
}
@Test(expected = PermissionDeniedException.class)
public void testValidateRoleChangeUnknownNewRole() {
Account account = Mockito.mock(Account.class);
Mockito.when(account.getRoleId()).thenReturn(1L);
Role newRole = Mockito.mock(Role.class);
Mockito.when(newRole.getRoleType()).thenReturn(RoleType.Unknown);
Role callerRole = Mockito.mock(Role.class);
Mockito.when(callerRole.getRoleType()).thenReturn(RoleType.DomainAdmin);
Account caller = Mockito.mock(Account.class);
Mockito.when(caller.getRoleId()).thenReturn(2L);
Mockito.when(roleService.findRole(2L)).thenReturn(callerRole);
accountManagerImpl.validateRoleChange(account, newRole, caller);
}
@Test
public void testValidateRoleNewRoleSameCaller() {
Account account = Mockito.mock(Account.class);
Mockito.when(account.getRoleId()).thenReturn(1L);
Role currentRole = Mockito.mock(Role.class);
Mockito.when(currentRole.getRoleType()).thenReturn(RoleType.User);
Mockito.when(roleService.findRole(1L)).thenReturn(currentRole);
Role newRole = Mockito.mock(Role.class);
Mockito.when(newRole.getRoleType()).thenReturn(RoleType.DomainAdmin);
Role callerRole = Mockito.mock(Role.class);
Mockito.when(callerRole.getRoleType()).thenReturn(RoleType.DomainAdmin);
Account caller = Mockito.mock(Account.class);
Mockito.when(caller.getRoleId()).thenReturn(2L);
Mockito.when(roleService.findRole(2L)).thenReturn(callerRole);
accountManagerImpl.validateRoleChange(account, newRole, caller);
}
@Test
public void testValidateRoleCurrentRoleSameCaller() {
Account account = Mockito.mock(Account.class);
Mockito.when(account.getRoleId()).thenReturn(1L);
Role accountRole = Mockito.mock(Role.class);
Mockito.when(accountRole.getRoleType()).thenReturn(RoleType.DomainAdmin);
Role newRole = Mockito.mock(Role.class);
Mockito.when(newRole.getRoleType()).thenReturn(RoleType.User);
Role callerRole = Mockito.mock(Role.class);
Mockito.when(callerRole.getRoleType()).thenReturn(RoleType.DomainAdmin);
Account caller = Mockito.mock(Account.class);
Mockito.when(caller.getRoleId()).thenReturn(2L);
Mockito.when(roleService.findRole(1L)).thenReturn(accountRole);
Mockito.when(roleService.findRole(2L)).thenReturn(callerRole);
accountManagerImpl.validateRoleChange(account, newRole, caller);
}
@Test(expected = PermissionDeniedException.class)
public void testValidateRoleNewRoleHigherCaller() {
Account account = Mockito.mock(Account.class);
Mockito.when(account.getRoleId()).thenReturn(1L);
Role newRole = Mockito.mock(Role.class);
Mockito.when(newRole.getRoleType()).thenReturn(RoleType.Admin);
Role callerRole = Mockito.mock(Role.class);
Mockito.when(callerRole.getRoleType()).thenReturn(RoleType.DomainAdmin);
Account caller = Mockito.mock(Account.class);
Mockito.when(caller.getRoleId()).thenReturn(2L);
Mockito.when(roleService.findRole(2L)).thenReturn(callerRole);
accountManagerImpl.validateRoleChange(account, newRole, caller);
}
@Test
public void testValidateRoleNewRoleLowerCaller() {
Account account = Mockito.mock(Account.class);
Mockito.when(account.getRoleId()).thenReturn(1L);
Role newRole = Mockito.mock(Role.class);
Mockito.when(newRole.getRoleType()).thenReturn(RoleType.User);
Role accountRole = Mockito.mock(Role.class);
Mockito.when(accountRole.getRoleType()).thenReturn(RoleType.User);
Role callerRole = Mockito.mock(Role.class);
Mockito.when(callerRole.getRoleType()).thenReturn(RoleType.DomainAdmin);
Account caller = Mockito.mock(Account.class);
Mockito.when(caller.getRoleId()).thenReturn(2L);
Mockito.when(roleService.findRole(1L)).thenReturn(accountRole);
Mockito.when(roleService.findRole(2L)).thenReturn(callerRole);
accountManagerImpl.validateRoleChange(account, newRole, caller);
}
@Test(expected = PermissionDeniedException.class)
public void testValidateRoleAdminCannotEscalateAdminFromNonRootDomain() {
Account account = Mockito.mock(Account.class);
Mockito.when(account.getRoleId()).thenReturn(1L);
Mockito.when(account.getDomainId()).thenReturn(2L);
Role newRole = Mockito.mock(Role.class);
Mockito.when(newRole.getRoleType()).thenReturn(RoleType.Admin);
Role accountRole = Mockito.mock(Role.class);
Role callerRole = Mockito.mock(Role.class);
Mockito.when(callerRole.getRoleType()).thenReturn(RoleType.Admin);
Account caller = Mockito.mock(Account.class);
Mockito.when(caller.getRoleId()).thenReturn(2L);
Mockito.when(roleService.findRole(1L)).thenReturn(accountRole);
Mockito.when(roleService.findRole(2L)).thenReturn(callerRole);
accountManagerImpl.validateRoleChange(account, newRole, caller);
}
}

View File

@ -1646,7 +1646,7 @@ def main(argv):
("dhcp", {"process_iptables": False, "executor": [CsDhcp("dhcpentry", config)]}),
("load_balancer", {"process_iptables": True, "executor": []}),
("monitor_service", {"process_iptables": False, "executor": [CsMonitor("monitorservice", config)]}),
("static_routes", {"process_iptables": False, "executor": [CsStaticRoutes("staticroutes", config)]})
("static_routes", {"process_iptables": True, "executor": [CsStaticRoutes("staticroutes", config)]})
])
if not config.is_vpc():

View File

@ -1530,6 +1530,7 @@
"label.networkspeed": "Network speed",
"label.networktype": "Network type",
"label.networkwrite": "Network write",
"label.never": "Never",
"label.new": "New",
"label.new.autoscale.vmgroup": "New AutoScaling Group",
"label.new.instance.group": "New Instance group",

View File

@ -58,6 +58,12 @@
v-if="item.meta.icon && typeof (item.meta.icon) === 'string'"
:icon="item.meta.icon"
@click="() => { handleClickParentMenu(item) }" />
<font-awesome-icon
v-else-if="item.meta.icon && Array.isArray(item.meta.icon)"
:icon="item.meta.icon"
class="anticon"
:style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#888' }]"
@click="() => { handleClickParentMenu(item) }" />
<span>{{ $t(item.meta.title) }}</span>
</router-link>
</a-menu-item>

View File

@ -34,12 +34,12 @@
<resource-icon :image="getImage(resource.icon && resource.icon.base64image || images.template || images.iso || resourceIcon)" size="4x" style="margin-right: 5px"/>
</span>
<span v-else>
<os-logo v-if="resource.ostypeid || resource.ostypename" :osId="resource.ostypeid" :osName="resource.ostypename" size="4x" @update-osname="setResourceOsType"/>
<os-logo v-if="resource.ostypeid || resource.ostypename" :osId="resource.ostypeid" :osName="resource.ostypename" size="3x" @update-osname="setResourceOsType"/>
<render-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 36px" :icon="$route.meta.icon" />
<font-awesome-icon
v-else-if="$route.meta.icon && Array.isArray($route.meta.icon)"
:icon="$route.meta.icon"
size="4x"
size="3x"
class="anticon"
:style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#888' }]" />
<render-icon v-else style="font-size: 36px" :svgIcon="$route.meta.icon" />
@ -185,7 +185,10 @@
<div class="resource-detail-item" v-if="('cpunumber' in resource && 'cpuspeed' in resource) || resource.cputotal">
<div class="resource-detail-item__label">{{ $t('label.cpu') }}</div>
<div class="resource-detail-item__details">
<appstore-outlined />
<font-awesome-icon
:icon="['fa-solid', 'fa-microchip']"
class="anticon"
:style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#888' }]" />
<span v-if="'cpunumber' in resource && 'cpuspeed' in resource">{{ resource.cpunumber }} CPU x {{ (resource.cpuspeed / 1000.0).toFixed(2) }} GHz
<a-tooltip placement="top">
<template #title>
@ -223,7 +226,11 @@
<div class="resource-detail-item" v-if="'memory' in resource">
<div class="resource-detail-item__label">{{ $t('label.memory') }}</div>
<div class="resource-detail-item__details">
<bulb-outlined />{{ resource.memory + ' ' + $t('label.mb.memory') }}
<font-awesome-icon
:icon="['fa-solid', 'fa-memory']"
class="anticon"
:style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#888' }]" />
{{ resource.memory + ' ' + $t('label.mb.memory') }}
</div>
<div>
<span v-if="resource.memorykbs && resource.memoryintfreekbs">
@ -364,7 +371,10 @@
v-for="(eth, index) in resource.nic"
:key="eth.id"
style="margin-left: -24px; margin-top: 5px;">
<api-outlined />
<font-awesome-icon
:icon="['fa-solid', 'fa-ethernet']"
class="anticon"
:style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#888' }]" />
<strong>eth{{ index }}</strong>&nbsp;
<copy-label :label="eth.ip6address ? eth.ipaddress + ', ' + eth.ip6address : eth.ipaddress" />&nbsp;
<a-tag v-if="eth.isdefault">
@ -389,7 +399,11 @@
v-for="network in resource.networks"
:key="network.id"
style="margin-top: 5px;">
<api-outlined />{{ network.name }}
<font-awesome-icon
:icon="['fa-solid', 'fa-ethernet']"
class="anticon"
:style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#888' }]" />
{{ network.name }}
<span v-if="resource.defaultnetworkid === network.id">
({{ $t('label.default') }})
</span>

View File

@ -26,7 +26,7 @@
:rowSelection="explicitlyAllowRowSelection || enableGroupAction() || $route.name === 'event' ? {selectedRowKeys: selectedRowKeys, onChange: onSelectChange, columnWidth: 30} : null"
:rowClassName="getRowClassName"
@resizeColumn="handleResizeColumn"
style="overflow-y: auto"
:style="{ 'overflow-y': this.$route.name === 'usage' ? 'hidden' : 'auto' }"
>
<template #customFilterDropdown>
<div style="padding: 8px" class="filter-dropdown">
@ -44,7 +44,7 @@
<span v-if="record.icon && record.icon.base64image">
<resource-icon :image="record.icon.base64image" size="2x"/>
</span>
<os-logo v-else :osId="record.ostypeid" :osName="record.osdisplayname" size="2x" />
<os-logo v-else :osId="record.ostypeid" :osName="record.osdisplayname" size="xl" />
</span>
<span style="min-width: 120px" >
<QuickView
@ -58,12 +58,12 @@
</span>
<span v-if="$showIcon() && !['vm', 'vnfapp'].includes($route.path.split('/')[1])" style="margin-right: 5px">
<resource-icon v-if="$showIcon() && record.icon && record.icon.base64image" :image="record.icon.base64image" size="2x"/>
<os-logo v-else-if="record.ostypename" :osName="record.ostypename" size="2x" />
<os-logo v-else-if="record.ostypename" :osName="record.ostypename" size="xl" />
<render-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 16px;" :icon="$route.meta.icon"/>
<render-icon v-else style="font-size: 16px;" :svgIcon="$route.meta.icon" />
</span>
<span v-else :style="{ 'margin-right': record.ostypename ? '5px' : '0' }">
<os-logo v-if="record.ostypename" :osName="record.ostypename" size="1x" />
<os-logo v-if="record.ostypename" :osName="record.ostypename" size="xl" />
</span>
<span v-if="record.hasannotations">

View File

@ -769,7 +769,7 @@ export default {
fields.push(...['domain', 'zonename'])
return fields
},
details: ['ipaddress', 'id', 'associatednetworkname', 'virtualmachinename', 'networkid', 'issourcenat', 'isstaticnat', 'virtualmachinename', 'vmipaddress', 'vlan', 'allocated', 'account', 'domain', 'zonename'],
details: ['ipaddress', 'id', 'associatednetworkname', 'networkid', 'issourcenat', 'isstaticnat', 'virtualmachinename', 'vmipaddress', 'vlan', 'allocated', 'account', 'domain', 'zonename'],
filters: ['allocated', 'reserved', 'free'],
component: shallowRef(() => import('@/views/network/PublicIpResource.vue')),
tabs: [{

View File

@ -18,15 +18,11 @@
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
// import { fab } from '@fortawesome/free-brands-svg-icons'
// import { fas } from '@fortawesome/free-solid-svg-icons'
// import { far } from '@fortawesome/free-regular-svg-icons'
import { faCentos, faUbuntu, faDebian, faSuse, faRedhat, faFedora, faLinux, faFreebsd, faApple, faWindows, faJava } from '@fortawesome/free-brands-svg-icons'
import { faCompactDisc, faCameraRetro, faDharmachakra } from '@fortawesome/free-solid-svg-icons'
import { fas, faCompactDisc, faCameraRetro, faDharmachakra } from '@fortawesome/free-solid-svg-icons'
library.add(faCentos, faUbuntu, faDebian, faSuse, faRedhat, faFedora, faLinux, faFreebsd, faApple, faWindows, faJava)
library.add(faCompactDisc, faCameraRetro, faDharmachakra)
library.add(fas, faCompactDisc, faCameraRetro, faDharmachakra)
export default {
install: (app) => {

View File

@ -50,6 +50,7 @@
<a-row justify="end">
<a-col>
<tooltip-button
v-if="'generateUsageRecords' in $store.getters.apis"
type="primary"
icon="hdd-outlined"
:tooltip="$t('label.usage.records.generate')"
@ -58,6 +59,7 @@
</a-col>&nbsp;&nbsp;
<a-col>
<tooltip-button
v-if="'removeRawUsageRecords' in $store.getters.apis"
type="danger"
icon="delete-outlined"
:tooltip="$t('label.usage.records.purge')"
@ -70,7 +72,7 @@
</a-card>
</a-affix>
<a-col>
<a-card size="small" :loading="serverMetricsLoading">
<a-card size="small" :loading="serverMetricsLoading" v-if="'listUsageServerMetrics' in $store.getters.apis">
<a-row justify="space-around">
<a-card-grid style="width: 30%; text-align: center; font-size: small;">
<a-statistic
@ -86,10 +88,10 @@
<a-card-grid style="width: 35%; text-align: center; font-size: small;">
<a-statistic
:title="$t('label.lastheartbeat')"
:value="$toLocaleDate(serverStats.lastheartbeat)"
:value="serverStats.lastheartbeat ? $toLocaleDate(serverStats.lastheartbeat) : $t('label.never')"
valueStyle="font-size: medium"
/>
<a-card-meta :description="getTimeSince(serverStats.collectiontime)" />
<a-card-meta v-if="!!serverStats.lastheartbeat" :description="getTimeSince(serverStats.collectiontime)" />
</a-card-grid>
<a-card-grid style="width: 35%; text-align: center; font-size: small;">
<a-statistic
@ -159,7 +161,7 @@
/>
</a-form-item>
</a-col>
<a-col :span="3">
<a-col :span="3" v-if="'listUsageTypes' in $store.getters.apis">
<a-form-item
ref="type"
name="type"
@ -173,7 +175,7 @@
/>
</a-form-item>
</a-col>
<a-col :span="3">
<a-col :span="3" v-if="'listUsageTypes' in $store.getters.apis">
<a-form-item
ref="id"
name="id"
@ -501,6 +503,11 @@ export default {
}
},
listUsageServerMetrics () {
if (!('listUsageServerMetrics' in this.$store.getters.apis)) {
this.serverMetricsLoading = false
return
}
this.serverMetricsLoading = true
api('listUsageServerMetrics').then(json => {
this.stats = []
@ -637,6 +644,10 @@ export default {
})
},
getUsageTypes () {
if (!('listUsageTypes' in this.$store.getters.apis)) {
return
}
api('listUsageTypes').then(json => {
if (json && json.listusagetypesresponse && json.listusagetypesresponse.usagetype) {
this.usageTypes = [{ id: null, value: '' }, ...json.listusagetypesresponse.usagetype.map(x => {

View File

@ -452,7 +452,7 @@
<a-form-item
name="conservemode"
ref="conservemode"
v-if="(guestType === 'shared' || guestType === 'isolated') && !isVpcVirtualRouterForAtLeastOneService && !forNsx && networkmode !== 'ROUTED'">
v-if="(guestType === 'shared' || guestType === 'isolated') && !forNsx && networkmode !== 'ROUTED'">
<template #label>
<tooltip-label :title="$t('label.conservemode')" :tooltip="apiParams.conservemode.description"/>
</template>

View File

@ -102,6 +102,10 @@ public class NetUtils {
public final static int IPV6_EUI64_11TH_BYTE = -1;
public final static int IPV6_EUI64_12TH_BYTE = -2;
// Regex
public final static Pattern HOSTNAME_PATTERN = Pattern.compile("[a-zA-Z0-9-]+");
public final static Pattern START_HOSTNAME_PATTERN = Pattern.compile("^[0-9-].*");
public static String extractHost(String uri) throws URISyntaxException {
return (new URI(uri)).getHost();
}
@ -1064,13 +1068,13 @@ public class NetUtils {
if (hostName.length() > 63 || hostName.length() < 1) {
LOGGER.warn("Domain name label must be between 1 and 63 characters long");
return false;
} else if (!hostName.toLowerCase().matches("[a-z0-9-]*")) {
} else if (!HOSTNAME_PATTERN.matcher(hostName).matches()) {
LOGGER.warn("Domain name label may contain only the ASCII letters 'a' through 'z' (in a case-insensitive manner)");
return false;
} else if (hostName.startsWith("-") || hostName.endsWith("-")) {
LOGGER.warn("Domain name label can not start with a hyphen and digit, and must not end with a hyphen");
LOGGER.warn("Domain name label can not start or end with a hyphen");
return false;
} else if (isHostName && hostName.matches("^[0-9-].*")) {
} else if (isHostName && START_HOSTNAME_PATTERN.matcher(hostName).matches()) {
LOGGER.warn("Host name can't start with digit");
return false;
}