Merge branch '4.20'

This commit is contained in:
Daan Hoogland 2025-04-17 15:59:41 +02:00
commit 3c75d9363b
42 changed files with 824 additions and 258 deletions

View File

@ -17,6 +17,7 @@
package com.cloud.storage; package com.cloud.storage;
import java.util.Date; import java.util.Date;
import java.util.List;
import org.apache.cloudstack.api.InternalIdentity; import org.apache.cloudstack.api.InternalIdentity;
@ -25,6 +26,8 @@ public interface VMTemplateStorageResourceAssoc extends InternalIdentity {
UNKNOWN, DOWNLOAD_ERROR, NOT_DOWNLOADED, DOWNLOAD_IN_PROGRESS, DOWNLOADED, ABANDONED, UPLOADED, NOT_UPLOADED, UPLOAD_ERROR, UPLOAD_IN_PROGRESS, CREATING, CREATED, BYPASSED UNKNOWN, DOWNLOAD_ERROR, NOT_DOWNLOADED, DOWNLOAD_IN_PROGRESS, DOWNLOADED, ABANDONED, UPLOADED, NOT_UPLOADED, UPLOAD_ERROR, UPLOAD_IN_PROGRESS, CREATING, CREATED, BYPASSED
} }
List<Status> PENDING_DOWNLOAD_STATES = List.of(Status.NOT_DOWNLOADED, Status.DOWNLOAD_IN_PROGRESS);
String getInstallPath(); String getInstallPath();
long getTemplateId(); long getTemplateId();

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.api.command.user.iso; package org.apache.cloudstack.api.command.user.iso;
import com.cloud.dc.DataCenter;
import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants;
@ -101,7 +102,15 @@ public class ExtractIsoCmd extends BaseAsyncCmd {
@Override @Override
public String getEventDescription() { public String getEventDescription() {
return "extracting ISO: " + getId() + " from zone: " + getZoneId(); String isoId = this._uuidMgr.getUuid(VirtualMachineTemplate.class, getId());
String baseDescription = String.format("Extracting ISO: %s", isoId);
Long zoneId = getZoneId();
if (zoneId == null) {
return baseDescription;
}
return String.format("%s from zone: %s", baseDescription, this._uuidMgr.getUuid(DataCenter.class, zoneId));
} }
@Override @Override

View File

@ -101,7 +101,15 @@ public class ExtractTemplateCmd extends BaseAsyncCmd {
@Override @Override
public String getEventDescription() { public String getEventDescription() {
return "extracting template: " + this._uuidMgr.getUuid(VirtualMachineTemplate.class, getId()) + ((getZoneId() != null) ? " from zone: " + this._uuidMgr.getUuid(DataCenter.class, getZoneId()) : ""); String templateId = this._uuidMgr.getUuid(VirtualMachineTemplate.class, getId());
String baseDescription = String.format("Extracting template: %s", templateId);
Long zoneId = getZoneId();
if (zoneId == null) {
return baseDescription;
}
return String.format("%s from zone: %s", baseDescription, this._uuidMgr.getUuid(DataCenter.class, zoneId));
} }
@Override @Override

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.api.command.user.volume; package org.apache.cloudstack.api.command.user.volume;
import com.cloud.dc.DataCenter;
import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.APICommand;
@ -114,12 +115,15 @@ public class ExtractVolumeCmd extends BaseAsyncCmd {
@Override @Override
public String getEventDescription() { public String getEventDescription() {
return "Extraction job"; String volumeId = this._uuidMgr.getUuid(Volume.class, getId());
String zoneId = this._uuidMgr.getUuid(DataCenter.class, getZoneId());
return String.format("Extracting volume: %s from zone: %s", volumeId, zoneId);
} }
@Override @Override
public void execute() { public void execute() {
CallContext.current().setEventDetails("Volume Id: " + this._uuidMgr.getUuid(Volume.class, getId())); CallContext.current().setEventDetails(getEventDescription());
String uploadUrl = _volumeService.extractVolume(this); String uploadUrl = _volumeService.extractVolume(this);
if (uploadUrl != null) { if (uploadUrl != null) {
ExtractResponse response = _responseGenerator.createVolumeExtractResponse(id, zoneId, getEntityOwnerId(), mode, uploadUrl); ExtractResponse response = _responseGenerator.createVolumeExtractResponse(id, zoneId, getEntityOwnerId(), mode, uploadUrl);

View File

@ -5240,10 +5240,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
workJob = newVmWorkJobAndInfo.first(); workJob = newVmWorkJobAndInfo.first();
VmWorkMigrateAway workInfo = new VmWorkMigrateAway(newVmWorkJobAndInfo.second(), srcHostId); VmWorkMigrateAway workInfo = new VmWorkMigrateAway(newVmWorkJobAndInfo.second(), srcHostId);
workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo)); setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId);
} }
_jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vmId);
AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId());

View File

@ -208,9 +208,7 @@ public class DataMigrationUtility {
List<TemplateInfo> files = new LinkedList<>(); List<TemplateInfo> files = new LinkedList<>();
for (TemplateDataStoreVO template : templates) { for (TemplateDataStoreVO template : templates) {
VMTemplateVO templateVO = templateDao.findById(template.getTemplateId()); VMTemplateVO templateVO = templateDao.findById(template.getTemplateId());
if (template.getState() == ObjectInDataStoreStateMachine.State.Ready && templateVO != null && if (shouldMigrateTemplate(template, templateVO)) {
(!templateVO.isPublicTemplate() || (templateVO.isPublicTemplate() && templateVO.getUrl() == null)) &&
templateVO.getHypervisorType() != Hypervisor.HypervisorType.Simulator && templateVO.getParentTemplateId() == null) {
files.add(templateFactory.getTemplate(template.getTemplateId(), srcDataStore)); files.add(templateFactory.getTemplate(template.getTemplateId(), srcDataStore));
} }
} }
@ -231,6 +229,34 @@ public class DataMigrationUtility {
return getAllReadyTemplates(srcDataStore, childTemplates, templates); return getAllReadyTemplates(srcDataStore, childTemplates, templates);
} }
/**
* Returns whether a template should be migrated. A template should be migrated if:
* <ol>
* <li>its state is ready, and</li>
* <li>its hypervisor type is not simulator, and</li>
* <li>it is not a child template.</li>
* </ol>
*/
protected boolean shouldMigrateTemplate(TemplateDataStoreVO template, VMTemplateVO templateVO) {
if (template.getState() != State.Ready) {
logger.debug("Template [{}] should not be migrated as it is not ready.", template);
return false;
}
if (templateVO.getHypervisorType() == Hypervisor.HypervisorType.Simulator) {
logger.debug("Template [{}] should not be migrated as its hypervisor type is simulator.", template);
return false;
}
if (templateVO.getParentTemplateId() != null) {
logger.debug("Template [{}] should not be migrated as it has a parent template.", template);
return false;
}
logger.debug("Template [{}] should be migrated.", template);
return true;
}
/** Returns parent snapshots and snapshots that do not have any children; snapshotChains comprises of the snapshot chain info /** Returns parent snapshots and snapshots that do not have any children; snapshotChains comprises of the snapshot chain info
* for each parent snapshot and the cumulative size of the chain - this is done to ensure that all the snapshots in a chain * for each parent snapshot and the cumulative size of the chain - this is done to ensure that all the snapshots in a chain
* are migrated to the same datastore * are migrated to the same datastore

View File

@ -0,0 +1,88 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.engine.orchestration;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.storage.VMTemplateVO;
import junit.framework.TestCase;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class DataMigrationUtilityTest extends TestCase {
@Spy
private DataMigrationUtility dataMigrationUtility;
@Mock
private VMTemplateVO templateVoMock;
@Mock
private TemplateDataStoreVO templateDataStoreVoMock;
private void prepareForShouldMigrateTemplateTests() {
Mockito.when(templateDataStoreVoMock.getState()).thenReturn(ObjectInDataStoreStateMachine.State.Ready);
Mockito.when(templateVoMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
Mockito.when(templateVoMock.getParentTemplateId()).thenReturn(null);
}
@Test
public void shouldMigrateTemplateTestReturnsFalseWhenTemplateIsNotReady() {
prepareForShouldMigrateTemplateTests();
Mockito.when(templateDataStoreVoMock.getState()).thenReturn(ObjectInDataStoreStateMachine.State.Migrating);
boolean result = dataMigrationUtility.shouldMigrateTemplate(templateDataStoreVoMock, templateVoMock);
Assert.assertFalse(result);
}
@Test
public void shouldMigrateTemplateTestReturnsFalseWhenHypervisorTypeIsSimulator() {
prepareForShouldMigrateTemplateTests();
Mockito.when(templateVoMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.Simulator);
boolean result = dataMigrationUtility.shouldMigrateTemplate(templateDataStoreVoMock, templateVoMock);
Assert.assertFalse(result);
}
@Test
public void shouldMigrateTemplateTestReturnsFalseWhenTemplateHasParentTemplate() {
prepareForShouldMigrateTemplateTests();
Mockito.when(templateVoMock.getParentTemplateId()).thenReturn(1L);
boolean result = dataMigrationUtility.shouldMigrateTemplate(templateDataStoreVoMock, templateVoMock);
Assert.assertFalse(result);
}
@Test
public void shouldMigrateTemplateTestReturnsTrueWhenTemplateIsReadyAndDoesNotHaveParentTemplateAndHypervisorTypeIsNotSimulator() {
prepareForShouldMigrateTemplateTests();
boolean result = dataMigrationUtility.shouldMigrateTemplate(templateDataStoreVoMock, templateVoMock);
Assert.assertTrue(result);
}
}

View File

@ -503,7 +503,7 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject<State, Vi
@Override @Override
public String toString() { public String toString() {
return String.format("VM instance %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "instanceName", "uuid", "type")); return String.format("VM instance %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "instanceName", "uuid", "type", "state"));
} }
@Override @Override

View File

@ -47,3 +47,25 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.storage_pool', 'used_iops', 'bigint
-- Add reason column for op_ha_work -- Add reason column for op_ha_work
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.op_ha_work', 'reason', 'varchar(32) DEFAULT NULL COMMENT "Reason for the HA work"'); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.op_ha_work', 'reason', 'varchar(32) DEFAULT NULL COMMENT "Reason for the HA work"');
-- Grant access to 2FA APIs for the "Read-Only User - Default" role
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Read-Only User - Default', 'setupUserTwoFactorAuthentication', 'ALLOW');
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Read-Only User - Default', 'validateUserTwoFactorAuthenticationCode', 'ALLOW');
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Read-Only User - Default', 'listUserTwoFactorAuthenticatorProviders', 'ALLOW');
-- Grant access to 2FA APIs for the "Support User - Default" role
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Support User - Default', 'setupUserTwoFactorAuthentication', 'ALLOW');
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Support User - Default', 'validateUserTwoFactorAuthenticationCode', 'ALLOW');
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Support User - Default', 'listUserTwoFactorAuthenticatorProviders', 'ALLOW');
-- Grant access to 2FA APIs for the "Read-Only Admin - Default" role
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Read-Only Admin - Default', 'setupUserTwoFactorAuthentication', 'ALLOW');
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Read-Only Admin - Default', 'validateUserTwoFactorAuthenticationCode', 'ALLOW');
-- Grant access to 2FA APIs for the "Support Admin - Default" role
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Support Admin - Default', 'setupUserTwoFactorAuthentication', 'ALLOW');
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Support Admin - Default', 'validateUserTwoFactorAuthenticationCode', 'ALLOW');

View File

@ -24,6 +24,9 @@ import java.util.concurrent.ExecutionException;
import javax.inject.Inject; import javax.inject.Inject;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
import com.cloud.storage.download.DownloadListener;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionService; import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionService;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
@ -118,26 +121,21 @@ public class SecondaryStorageServiceImpl implements SecondaryStorageService {
} }
} else if (srcDataObject instanceof TemplateInfo && templateChain != null && templateChain.containsKey(srcDataObject)) { } else if (srcDataObject instanceof TemplateInfo && templateChain != null && templateChain.containsKey(srcDataObject)) {
for (TemplateInfo templateInfo : templateChain.get(srcDataObject).first()) { for (TemplateInfo templateInfo : templateChain.get(srcDataObject).first()) {
if (templateIsOnDestination(templateInfo, destDatastore)) {
res.setResult("Template already exists on destination.");
res.setSuccess(true);
logger.debug("Deleting template {} from source data store [{}].", srcDataObject.getTO().toString(),
srcDataObject.getDataStore().getTO().toString());
srcDataObject.getDataStore().delete(srcDataObject);
future.complete(res);
continue;
}
destDataObject = destDatastore.create(templateInfo); destDataObject = destDatastore.create(templateInfo);
templateInfo.processEvent(ObjectInDataStoreStateMachine.Event.MigrateDataRequested); templateInfo.processEvent(ObjectInDataStoreStateMachine.Event.MigrateDataRequested);
destDataObject.processEvent(ObjectInDataStoreStateMachine.Event.MigrateDataRequested); destDataObject.processEvent(ObjectInDataStoreStateMachine.Event.MigrateDataRequested);
migrateJob(future, templateInfo, destDataObject, destDatastore); migrateJob(future, templateInfo, destDataObject, destDatastore);
} }
} } else {
else {
// Check if template in destination store, if yes, do not proceed
if (srcDataObject instanceof TemplateInfo) {
logger.debug("Checking if template present at destination");
TemplateDataStoreVO templateStoreVO = templateStoreDao.findByStoreTemplate(destDatastore.getId(), srcDataObject.getId());
if (templateStoreVO != null) {
String msg = "Template already exists in destination store";
logger.debug(msg);
res.setResult(msg);
res.setSuccess(true);
future.complete(res);
return future;
}
}
destDataObject = destDatastore.create(srcDataObject); destDataObject = destDatastore.create(srcDataObject);
srcDataObject.processEvent(ObjectInDataStoreStateMachine.Event.MigrateDataRequested); srcDataObject.processEvent(ObjectInDataStoreStateMachine.Event.MigrateDataRequested);
destDataObject.processEvent(ObjectInDataStoreStateMachine.Event.MigrateDataRequested); destDataObject.processEvent(ObjectInDataStoreStateMachine.Event.MigrateDataRequested);
@ -160,6 +158,69 @@ public class SecondaryStorageServiceImpl implements SecondaryStorageService {
return future; return future;
} }
/**
* Returns a boolean indicating whether a template is ready on the provided data store. If the template is being downloaded,
* waits until the download finishes.
* @param srcDataObject the template.
* @param destDatastore the data store.
*/
protected boolean templateIsOnDestination(DataObject srcDataObject, DataStore destDatastore) {
if (!(srcDataObject instanceof TemplateInfo)) {
return false;
}
String templateAsString = srcDataObject.getTO().toString();
String destDatastoreAsString = destDatastore.getTO().toString();
TemplateDataStoreVO templateStoreVO;
long timer = getTemplateDownloadTimeout();
long msToSleep = 10000L;
int previousDownloadPercentage = -1;
while (true) {
templateStoreVO = templateStoreDao.findByStoreTemplate(destDatastore.getId(), srcDataObject.getId());
if (templateStoreVO == null) {
logger.debug("{} is not present at destination [{}].", templateAsString, destDatastoreAsString);
return false;
}
VMTemplateStorageResourceAssoc.Status downloadState = templateStoreVO.getDownloadState();
if (downloadState == null || !VMTemplateStorageResourceAssoc.PENDING_DOWNLOAD_STATES.contains(downloadState)) {
break;
}
if (previousDownloadPercentage == templateStoreVO.getDownloadPercent()) {
timer -= msToSleep;
} else {
timer = getTemplateDownloadTimeout();
}
if (timer <= 0) {
throw new CloudRuntimeException(String.format("Timeout while waiting for %s to be downloaded to image store [%s]. " +
"The download percentage has not changed for %d milliseconds.", templateAsString, destDatastoreAsString, getTemplateDownloadTimeout()));
}
waitForTemplateDownload(msToSleep, templateAsString, destDatastoreAsString);
}
if (templateStoreVO.getState() == ObjectInDataStoreStateMachine.State.Ready) {
logger.debug("{} already exists on destination [{}].", templateAsString, destDatastoreAsString);
return true;
}
return false;
}
protected long getTemplateDownloadTimeout() {
return DownloadListener.DOWNLOAD_TIMEOUT;
}
protected void waitForTemplateDownload(long msToSleep, String templateAsString, String destDatastoreAsString) {
logger.debug("{} is being downloaded to destination [{}]; we will verify in {} milliseconds if the download has finished.",
templateAsString, destDatastoreAsString, msToSleep);
try {
Thread.sleep(msToSleep);
} catch (InterruptedException e) {
logger.warn("[ignored] interrupted while waiting for template {} download to finish before trying to migrate it to data store [{}].",
templateAsString, destDatastoreAsString);
}
}
protected void migrateJob(AsyncCallFuture<DataObjectResult> future, DataObject srcDataObject, DataObject destDataObject, DataStore destDatastore) throws ExecutionException, InterruptedException { protected void migrateJob(AsyncCallFuture<DataObjectResult> future, DataObject srcDataObject, DataObject destDataObject, DataStore destDatastore) throws ExecutionException, InterruptedException {
MigrateDataContext<DataObjectResult> context = new MigrateDataContext<DataObjectResult>(null, future, srcDataObject, destDataObject, destDatastore); MigrateDataContext<DataObjectResult> context = new MigrateDataContext<DataObjectResult>(null, future, srcDataObject, destDataObject, destDatastore);
AsyncCallbackDispatcher<SecondaryStorageServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this); AsyncCallbackDispatcher<SecondaryStorageServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);

View File

@ -0,0 +1,138 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.image;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.storage.VMTemplateStorageResourceAssoc;
import com.cloud.utils.exception.CloudRuntimeException;
import junit.framework.TestCase;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class SecondaryStorageServiceImplTest extends TestCase {
@Spy
@InjectMocks
private SecondaryStorageServiceImpl secondaryStorageService;
@Mock
TemplateDataStoreDao templateDataStoreDaoMock;
@Mock
TemplateDataStoreVO templateDataStoreVoMock;
@Mock
TemplateInfo templateInfoMock;
@Mock
TemplateObjectTO templateObjectToMock;
@Mock
DataStore dataStoreMock;
@Mock
DataStoreTO dataStoreToMock;
private void prepareForTemplateIsOnDestinationTests() {
long dataStoreId = 1;
long templateId = 2;
Mockito.when(dataStoreMock.getId()).thenReturn(dataStoreId);
Mockito.when(dataStoreMock.getTO()).thenReturn(dataStoreToMock);
Mockito.when(templateInfoMock.getId()).thenReturn(templateId);
Mockito.when(templateInfoMock.getTO()).thenReturn(templateObjectToMock);
Mockito.doReturn(templateDataStoreVoMock).when(templateDataStoreDaoMock).findByStoreTemplate(dataStoreId, templateId);
Mockito.when(templateDataStoreVoMock.getState()).thenReturn(ObjectInDataStoreStateMachine.State.Ready);
}
@Test
public void templateIsOnDestinationTestReturnsFalseWhenTemplateStoreRefDoesNotExist() {
prepareForTemplateIsOnDestinationTests();
Mockito.doReturn(null).when(templateDataStoreDaoMock).findByStoreTemplate(Mockito.anyLong(), Mockito.anyLong());
boolean result = secondaryStorageService.templateIsOnDestination(templateInfoMock, dataStoreMock);
Assert.assertFalse(result);
}
@Test
public void templateIsOnDestinationTestReturnsTrueWhenTemplateIsReady() {
prepareForTemplateIsOnDestinationTests();
boolean result = secondaryStorageService.templateIsOnDestination(templateInfoMock, dataStoreMock);
Assert.assertTrue(result);
}
@Test
public void templateIsOnDestinationTestReturnsFalseWhenTemplateIsNotReady() {
prepareForTemplateIsOnDestinationTests();
Mockito.when(templateDataStoreVoMock.getState()).thenReturn(ObjectInDataStoreStateMachine.State.Creating);
boolean result = secondaryStorageService.templateIsOnDestination(templateInfoMock, dataStoreMock);
Assert.assertFalse(result);
}
@Test
public void templateIsOnDestinationTestReturnsTrueIfTemplateIsDownloadedSuccessfully() {
prepareForTemplateIsOnDestinationTests();
Mockito.when(templateDataStoreVoMock.getDownloadState()).thenReturn(VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS);
Mockito.doAnswer(I -> Mockito.when(templateDataStoreVoMock.getDownloadState()).thenReturn(VMTemplateStorageResourceAssoc.Status.DOWNLOADED)).when(secondaryStorageService).waitForTemplateDownload(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString());
boolean result = secondaryStorageService.templateIsOnDestination(templateInfoMock, dataStoreMock);
Assert.assertTrue(result);
}
@Test
public void templateIsOnDestinationTestReturnsFalseIfTemplateIsNotDownloadedSuccessfully() {
prepareForTemplateIsOnDestinationTests();
Mockito.when(templateDataStoreVoMock.getDownloadState()).thenReturn(VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS);
Mockito.doAnswer(I -> {
Mockito.when(templateDataStoreVoMock.getDownloadState()).thenReturn(VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR);
Mockito.when(templateDataStoreVoMock.getState()).thenReturn(ObjectInDataStoreStateMachine.State.Failed);
return "mocked download fail";
}).when(secondaryStorageService).waitForTemplateDownload(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString());
boolean result = secondaryStorageService.templateIsOnDestination(templateInfoMock, dataStoreMock);
Assert.assertFalse(result);
}
@Test(expected = CloudRuntimeException.class)
public void templateIsOnDestinationTestThrowsExceptionIfDownloadTimesOut() {
prepareForTemplateIsOnDestinationTests();
Mockito.when(templateDataStoreVoMock.getDownloadState()).thenReturn(VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS);
Mockito.doReturn(0L).when(secondaryStorageService).getTemplateDownloadTimeout();
secondaryStorageService.templateIsOnDestination(templateInfoMock, dataStoreMock);
}
}

View File

@ -73,8 +73,8 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
private static final int EXECUTOR_SHUTDOWN_TIMEOUT = 1000; // 1 second private static final int EXECUTOR_SHUTDOWN_TIMEOUT = 1000; // 1 second
private static final int DEFAULT_OUTGOING_WORKERS = 5; private static final int DEFAULT_OUTGOING_WORKERS = 5;
private final List<ClusterManagerListener> _listeners = new ArrayList<ClusterManagerListener>(); private final List<ClusterManagerListener> _listeners = new ArrayList<>();
private final Map<Long, ManagementServerHostVO> _activePeers = new HashMap<Long, ManagementServerHostVO>(); private final Map<Long, ManagementServerHostVO> _activePeers = new HashMap<>();
private final Map<String, ClusterService> _clusterPeers; private final Map<String, ClusterService> _clusterPeers;
@ -83,7 +83,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
private final ScheduledExecutorService _heartbeatScheduler = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Cluster-Heartbeat")); private final ScheduledExecutorService _heartbeatScheduler = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Cluster-Heartbeat"));
private final ExecutorService _notificationExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("Cluster-Notification")); private final ExecutorService _notificationExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("Cluster-Notification"));
private final List<ClusterManagerMessage> _notificationMsgs = new ArrayList<ClusterManagerMessage>(); private final List<ClusterManagerMessage> _notificationMsgs = new ArrayList<>();
private ConnectionConcierge _heartbeatConnection = null; private ConnectionConcierge _heartbeatConnection = null;
private final ExecutorService _executor; private final ExecutorService _executor;
@ -118,12 +118,12 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
private String _clusterNodeIP = "127.0.0.1"; private String _clusterNodeIP = "127.0.0.1";
private final List<ClusterServicePdu> _clusterPduOutgoingQueue = new ArrayList<ClusterServicePdu>(); private final List<ClusterServicePdu> _clusterPduOutgoingQueue = new ArrayList<>();
private final List<ClusterServicePdu> _clusterPduIncomingQueue = new ArrayList<ClusterServicePdu>(); private final List<ClusterServicePdu> _clusterPduIncomingQueue = new ArrayList<>();
private final Map<Long, ClusterServiceRequestPdu> _outgoingPdusWaitingForAck = new HashMap<Long, ClusterServiceRequestPdu>(); private final Map<Long, ClusterServiceRequestPdu> _outgoingPdusWaitingForAck = new HashMap<>();
public ClusterManagerImpl() { public ClusterManagerImpl() {
_clusterPeers = new HashMap<String, ClusterService>(); _clusterPeers = new HashMap<>();
// executor to perform remote-calls in another thread context, to avoid potential // executor to perform remote-calls in another thread context, to avoid potential
// recursive remote calls between nodes // recursive remote calls between nodes
@ -161,7 +161,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
} }
private void cancelClusterRequestToPeer(final String strPeer) { private void cancelClusterRequestToPeer(final String strPeer) {
final List<ClusterServiceRequestPdu> candidates = new ArrayList<ClusterServiceRequestPdu>(); final List<ClusterServiceRequestPdu> candidates = new ArrayList<>();
synchronized (_outgoingPdusWaitingForAck) { synchronized (_outgoingPdusWaitingForAck) {
for (final Map.Entry<Long, ClusterServiceRequestPdu> entry : _outgoingPdusWaitingForAck.entrySet()) { for (final Map.Entry<Long, ClusterServiceRequestPdu> entry : _outgoingPdusWaitingForAck.entrySet()) {
if (entry.getValue().getDestPeer().equalsIgnoreCase(strPeer)) { if (entry.getValue().getDestPeer().equalsIgnoreCase(strPeer)) {
@ -193,7 +193,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
synchronized (_clusterPduOutgoingQueue) { synchronized (_clusterPduOutgoingQueue) {
try { try {
_clusterPduOutgoingQueue.wait(timeoutMs); _clusterPduOutgoingQueue.wait(timeoutMs);
} catch (final InterruptedException e) { } catch (final InterruptedException ignored) {
} }
if (_clusterPduOutgoingQueue.size() > 0) { if (_clusterPduOutgoingQueue.size() > 0) {
@ -216,7 +216,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
synchronized (_clusterPduIncomingQueue) { synchronized (_clusterPduIncomingQueue) {
try { try {
_clusterPduIncomingQueue.wait(timeoutMs); _clusterPduIncomingQueue.wait(timeoutMs);
} catch (final InterruptedException e) { } catch (final InterruptedException ignored) {
} }
if (_clusterPduIncomingQueue.size() > 0) { if (_clusterPduIncomingQueue.size() > 0) {
@ -449,7 +449,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
synchronized (pdu) { synchronized (pdu) {
try { try {
pdu.wait(); pdu.wait();
} catch (final InterruptedException e) { } catch (final InterruptedException ignored) {
} }
} }
@ -620,8 +620,8 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
if (profiler.getDurationInMillis() >= HeartbeatInterval.value()) { if (profiler.getDurationInMillis() >= HeartbeatInterval.value()) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Management server heartbeat takes too long to finish. profiler: " + profiler.toString() + ", profilerHeartbeatUpdate: " + logger.debug("Management server heartbeat takes too long to finish. profiler: " + profiler + ", profilerHeartbeatUpdate: " +
profilerHeartbeatUpdate.toString() + ", profilerPeerScan: " + profilerPeerScan.toString()); profilerHeartbeatUpdate + ", profilerPeerScan: " + profilerPeerScan);
} }
} }
} }
@ -685,7 +685,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
synchronized (_notificationMsgs) { synchronized (_notificationMsgs) {
try { try {
_notificationMsgs.wait(1000); _notificationMsgs.wait(1000);
} catch (final InterruptedException e) { } catch (final InterruptedException ignored) {
} }
} }
@ -745,7 +745,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
try { try {
Thread.sleep(1000); Thread.sleep(1000);
} catch (final InterruptedException e) { } catch (final InterruptedException ignored) {
} }
} }
} }
@ -816,7 +816,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
} }
} }
final List<ManagementServerHostVO> downHostList = new ArrayList<ManagementServerHostVO>(); final List<ManagementServerHostVO> downHostList = new ArrayList<>();
for (final ManagementServerHostVO host : inactiveList) { for (final ManagementServerHostVO host : inactiveList) {
// Check if peer state is Up in the period // Check if peer state is Up in the period
if (!_mshostPeerDao.isPeerUpState(_mshostId, host.getId(), new Date(cutTime.getTime() - HeartbeatThreshold.value()))) { if (!_mshostPeerDao.isPeerUpState(_mshostId, host.getId(), new Date(cutTime.getTime() - HeartbeatThreshold.value()))) {
@ -846,8 +846,8 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
final Profiler profilerSyncClusterInfo = new Profiler(); final Profiler profilerSyncClusterInfo = new Profiler();
profilerSyncClusterInfo.start(); profilerSyncClusterInfo.start();
final List<ManagementServerHostVO> removedNodeList = new ArrayList<ManagementServerHostVO>(); final List<ManagementServerHostVO> removedNodeList = new ArrayList<>();
final List<ManagementServerHostVO> invalidatedNodeList = new ArrayList<ManagementServerHostVO>(); final List<ManagementServerHostVO> invalidatedNodeList = new ArrayList<>();
if (_mshostId != null) { if (_mshostId != null) {
@ -941,7 +941,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
try { try {
JmxUtil.unregisterMBean("ClusterManager", "Node " + mshost.getId()); JmxUtil.unregisterMBean("ClusterManager", "Node " + mshost.getId());
} catch (final Exception e) { } catch (final Exception e) {
logger.warn("Unable to deregister cluster node from JMX monitoring due to exception " + e.toString()); logger.warn("Unable to deregister cluster node from JMX monitoring due to exception " + e);
} }
} }
@ -961,7 +961,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
try { try {
JmxUtil.unregisterMBean("ClusterManager", "Node " + mshost.getId()); JmxUtil.unregisterMBean("ClusterManager", "Node " + mshost.getId());
} catch (final Exception e) { } catch (final Exception e) {
logger.warn("Unable to deregiester cluster node from JMX monitoring due to exception " + e.toString()); logger.warn("Unable to deregiester cluster node from JMX monitoring due to exception " + e);
} }
} else { } else {
logger.info("Management node {} is detected inactive by timestamp but sent node status to this node", mshost); logger.info("Management node {} is detected inactive by timestamp but sent node status to this node", mshost);
@ -975,7 +975,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
} }
private void processNewNodes(Date cutTime, List<ManagementServerHostVO> currentList) { private void processNewNodes(Date cutTime, List<ManagementServerHostVO> currentList) {
final List<ManagementServerHostVO> newNodeList = new ArrayList<ManagementServerHostVO>(); final List<ManagementServerHostVO> newNodeList = new ArrayList<>();
for (final ManagementServerHostVO mshost : currentList) { for (final ManagementServerHostVO mshost : currentList) {
if (!_activePeers.containsKey(mshost.getId())) { if (!_activePeers.containsKey(mshost.getId())) {
_activePeers.put(mshost.getId(), mshost); _activePeers.put(mshost.getId(), mshost);
@ -1037,7 +1037,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
logger.info("Starting Cluster manager, msid: {}, mshost: {}", _msId, _mshost); logger.info("Starting Cluster manager, msid: {}, mshost: {}", _msId, _mshost);
} }
final ManagementServerHostVO mshost = Transaction.execute(new TransactionCallback<ManagementServerHostVO>() { final ManagementServerHostVO mshost = Transaction.execute(new TransactionCallback<>() {
@Override @Override
public ManagementServerHostVO doInTransaction(final TransactionStatus status) { public ManagementServerHostVO doInTransaction(final TransactionStatus status) {
@ -1105,11 +1105,13 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
} }
if (_mshostId != null) { if (_mshostId != null) {
final ManagementServerHostVO mshost = _mshostDao.findByMsid(_msId); ManagementServerHostVO mshost = _mshostDao.findByMsid(_msId);
if (mshost != null) { if (mshost != null) {
ManagementServerStatusVO mshostStatus = mshostStatusDao.findByMsId(mshost.getUuid()); ManagementServerStatusVO mshostStatus = mshostStatusDao.findByMsId(mshost.getUuid());
if (mshostStatus != null) { if (mshostStatus != null) {
mshost.setState(ManagementServerHost.State.Down);
mshostStatus.setLastJvmStop(new Date()); mshostStatus.setLastJvmStop(new Date());
_mshostDao.update(_mshostId, mshost);
mshostStatusDao.update(mshostStatus.getId(), mshostStatus); mshostStatusDao.update(mshostStatus.getId(), mshostStatus);
} else { } else {
logger.warn("Found a management server host [{}] without a status. This should never happen!", mshost); logger.warn("Found a management server host [{}] without a status. This should never happen!", mshost);
@ -1135,7 +1137,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
try { try {
_heartbeatScheduler.awaitTermination(EXECUTOR_SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS); _heartbeatScheduler.awaitTermination(EXECUTOR_SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS);
_executor.awaitTermination(EXECUTOR_SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS); _executor.awaitTermination(EXECUTOR_SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (final InterruptedException e) { } catch (final InterruptedException ignored) {
} }
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
@ -1271,14 +1273,14 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
if (sch != null) { if (sch != null) {
try { try {
sch.close(); sch.close();
} catch (final IOException e) { } catch (final IOException ignored) {
} }
} }
} }
try { try {
Thread.sleep(1000); Thread.sleep(1000);
} catch (final InterruptedException ex) { } catch (final InterruptedException ignored) {
} }
} }

View File

@ -5104,9 +5104,9 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
answer.setVolumeChainInfo(chainInfo); answer.setVolumeChainInfo(chainInfo);
return answer; return answer;
} catch (Exception e) { } catch (Exception e) {
String msg = "Catch Exception " + e.getClass().getName() + " due to " + e.toString(); String msg = "Catch Exception " + e.getClass().getName() + " due to " + e.getMessage();
logger.error(msg, e); logger.error(msg, e);
return new MigrateVolumeAnswer(cmd, false, msg, null); return new MigrateVolumeAnswer(cmd, false, e.getMessage(), null);
} }
} }

View File

@ -260,7 +260,7 @@ public class VmwareStorageMotionStrategy implements DataMotionStrategy {
} else { } else {
answer = agentMgr.sendTo(sourcePool.getDataCenterId(), HypervisorType.VMware, cmd); answer = agentMgr.sendTo(sourcePool.getDataCenterId(), HypervisorType.VMware, cmd);
} }
updateVolumeAfterMigration(answer, srcData, destData); handleAnswerAndUpdateVolumeAfterMigration(answer, srcData, destData);
CopyCommandResult result = new CopyCommandResult(null, answer); CopyCommandResult result = new CopyCommandResult(null, answer);
callback.complete(result); callback.complete(result);
} }
@ -286,21 +286,19 @@ public class VmwareStorageMotionStrategy implements DataMotionStrategy {
return hostId; return hostId;
} }
private void updateVolumeAfterMigration(Answer answer, DataObject srcData, DataObject destData) { private void handleAnswerAndUpdateVolumeAfterMigration(Answer answer, DataObject srcData, DataObject destData) {
VolumeVO destinationVO = volDao.findById(destData.getId()); VolumeVO destinationVO = volDao.findById(destData.getId());
if (!(answer instanceof MigrateVolumeAnswer)) { if (!(answer instanceof MigrateVolumeAnswer)) {
// OfflineVmwareMigration: reset states and such // OfflineVmwareMigration: reset states and such
VolumeVO sourceVO = volDao.findById(srcData.getId()); resetVolumeState(srcData, destinationVO);
sourceVO.setState(Volume.State.Ready);
volDao.update(sourceVO.getId(), sourceVO);
if (destinationVO.getId() != sourceVO.getId()) {
destinationVO.setState(Volume.State.Expunged);
destinationVO.setRemoved(new Date());
volDao.update(destinationVO.getId(), destinationVO);
}
throw new CloudRuntimeException("unexpected answer from hypervisor agent: " + answer.getDetails()); throw new CloudRuntimeException("unexpected answer from hypervisor agent: " + answer.getDetails());
} }
MigrateVolumeAnswer ans = (MigrateVolumeAnswer) answer; MigrateVolumeAnswer ans = (MigrateVolumeAnswer) answer;
if (!answer.getResult()) {
String msg = "Unable to migrate volume: " + srcData.getName() + " due to " + answer.getDetails();
resetVolumeState(srcData, destinationVO);
throw new CloudRuntimeException(msg);
}
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
String format = "retrieved '%s' as new path for volume(%d)"; String format = "retrieved '%s' as new path for volume(%d)";
logger.debug(String.format(format, ans.getVolumePath(), destData.getId())); logger.debug(String.format(format, ans.getVolumePath(), destData.getId()));
@ -311,6 +309,17 @@ public class VmwareStorageMotionStrategy implements DataMotionStrategy {
volDao.update(destinationVO.getId(), destinationVO); volDao.update(destinationVO.getId(), destinationVO);
} }
private void resetVolumeState(DataObject srcData, VolumeVO destinationVO) {
VolumeVO sourceVO = volDao.findById(srcData.getId());
sourceVO.setState(Volume.State.Ready);
volDao.update(sourceVO.getId(), sourceVO);
if (destinationVO.getId() != sourceVO.getId()) {
destinationVO.setState(Volume.State.Expunged);
destinationVO.setRemoved(new Date());
volDao.update(destinationVO.getId(), destinationVO);
}
}
@Override @Override
public void copyAsync(Map<VolumeInfo, DataStore> volumeMap, VirtualMachineTO vmTo, Host srcHost, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) { public void copyAsync(Map<VolumeInfo, DataStore> volumeMap, VirtualMachineTO vmTo, Host srcHost, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
Answer answer = null; Answer answer = null;

View File

@ -81,10 +81,10 @@ public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthentic
//////////////// API parameters ///////////////////// //////////////// API parameters /////////////////////
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
@Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, required = false, description = "User uuid") @Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, description = "User uuid")
private Long userId; private Long userId;
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, required = false, description = "Domain uuid") @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "Domain uuid")
private Long domainId; private Long domainId;
@Override @Override
@ -131,10 +131,12 @@ public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthentic
} }
if (userUuid != null && domainUuid != null) { if (userUuid != null && domainUuid != null) {
logger.debug("User [" + currentUserAccount.getUsername() + "] is requesting to switch from user profile [" + currentUserAccount.getId() + "] to useraccount [" + userUuid + "] in domain [" + domainUuid + "]");
final User user = _userDao.findByUuid(userUuid); final User user = _userDao.findByUuid(userUuid);
final Domain domain = _domainDao.findByUuid(domainUuid); final Domain domain = _domainDao.findByUuid(domainUuid);
final UserAccount nextUserAccount = _accountService.getUserAccountById(user.getId()); final UserAccount nextUserAccount = _accountService.getUserAccountById(user.getId());
if (nextUserAccount != null && !nextUserAccount.getAccountState().equals(Account.State.ENABLED.toString())) { if (nextUserAccount != null && !nextUserAccount.getAccountState().equals(Account.State.ENABLED.toString())) {
logger.warn("User [" + currentUserAccount.getUsername() + "] is requesting to switch from user profile [" + currentUserId + "] to user profile [" + userUuid + "] in domain [" + domainUuid + "] but the associated target account [" + nextUserAccount.getAccountName() + "] is not enabled");
throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.PARAM_ERROR.getHttpCode(), throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.PARAM_ERROR.getHttpCode(),
"The requested user account is locked and cannot be switched to, please contact your administrator.", "The requested user account is locked and cannot be switched to, please contact your administrator.",
params, responseType)); params, responseType));
@ -145,25 +147,31 @@ public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthentic
|| !nextUserAccount.getExternalEntity().equals(currentUserAccount.getExternalEntity()) || !nextUserAccount.getExternalEntity().equals(currentUserAccount.getExternalEntity())
|| (nextUserAccount.getDomainId() != domain.getId()) || (nextUserAccount.getDomainId() != domain.getId())
|| (nextUserAccount.getSource() != User.Source.SAML2)) { || (nextUserAccount.getSource() != User.Source.SAML2)) {
logger.warn("User [" + currentUserAccount.getUsername() + "] is requesting to switch from user profile [" + currentUserId + "] to user profile [" + userUuid + "] in domain [" + domainUuid + "] but the associated target account is not found or invalid");
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.PARAM_ERROR.getHttpCode(), throw new ServerApiException(ApiErrorCode.PARAM_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.PARAM_ERROR.getHttpCode(),
"User account is not allowed to switch to the requested account", "User account is not allowed to switch to the requested account",
params, responseType)); params, responseType));
} }
try { try {
if (_apiServer.verifyUser(nextUserAccount.getId())) { if (_apiServer.verifyUser(nextUserAccount.getId())) {
logger.info("User [" + currentUserAccount.getUsername() + "] user profile switch is accepted: from [" + currentUserId + "] to user profile [" + userUuid + "] in domain [" + domainUuid + "] with account [" + nextUserAccount.getAccountName() + "]");
// need to set a sessoin variable to inform the login function of the specific user to login as, rather than using email only (which could have multiple matches)
session.setAttribute("nextUserId", user.getId());
final LoginCmdResponse loginResponse = (LoginCmdResponse) _apiServer.loginUser(session, nextUserAccount.getUsername(), nextUserAccount.getUsername() + nextUserAccount.getSource().toString(), final LoginCmdResponse loginResponse = (LoginCmdResponse) _apiServer.loginUser(session, nextUserAccount.getUsername(), nextUserAccount.getUsername() + nextUserAccount.getSource().toString(),
nextUserAccount.getDomainId(), null, remoteAddress, params); nextUserAccount.getDomainId(), null, remoteAddress, params);
SAMLUtils.setupSamlUserCookies(loginResponse, resp); SAMLUtils.setupSamlUserCookies(loginResponse, resp);
resp.sendRedirect(SAML2AuthManager.SAMLCloudStackRedirectionUrl.value()); session.removeAttribute("nextUserId");
logger.debug("User [" + currentUserAccount.getUsername() + "] user profile switch cookies set: from [" + currentUserId + "] to user profile [" + userUuid + "] in domain [" + domainUuid + "] with account [" + nextUserAccount.getAccountName() + "]");
//resp.sendRedirect(SAML2AuthManager.SAMLCloudStackRedirectionUrl.value());
return ApiResponseSerializer.toSerializedString(loginResponse, responseType); return ApiResponseSerializer.toSerializedString(loginResponse, responseType);
} }
} catch (CloudAuthenticationException | IOException exception) { } catch (CloudAuthenticationException | IOException exception) {
logger.debug("Failed to switch to request SAML user account due to: " + exception.getMessage()); logger.debug("User [{}] user profile switch cookies set FAILED: from [{}] to user profile [{}] in domain [{}] with account [{}]", currentUserAccount.getUsername(), currentUserId, userUuid, domainUuid, nextUserAccount.getAccountName(), exception);
} }
} else { } else {
List<UserAccountVO> switchableAccounts = _userAccountDao.getAllUsersByNameAndEntity(currentUserAccount.getUsername(), currentUserAccount.getExternalEntity()); List<UserAccountVO> switchableAccounts = _userAccountDao.getAllUsersByNameAndEntity(currentUserAccount.getUsername(), currentUserAccount.getExternalEntity());
if (switchableAccounts != null && switchableAccounts.size() > 0 && currentUserId != User.UID_SYSTEM) { if (switchableAccounts != null && !switchableAccounts.isEmpty() && currentUserId != User.UID_SYSTEM) {
List<SamlUserAccountResponse> accountResponses = new ArrayList<SamlUserAccountResponse>(); List<SamlUserAccountResponse> accountResponses = new ArrayList<>();
for (UserAccountVO userAccount: switchableAccounts) { for (UserAccountVO userAccount: switchableAccounts) {
User user = _userDao.getUser(userAccount.getId()); User user = _userDao.getUser(userAccount.getId());
Domain domain = _domainService.getDomain(userAccount.getDomainId()); Domain domain = _domainService.getDomain(userAccount.getDomainId());
@ -176,8 +184,9 @@ public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthentic
accountResponse.setAccountName(userAccount.getAccountName()); accountResponse.setAccountName(userAccount.getAccountName());
accountResponse.setIdpId(user.getExternalEntity()); accountResponse.setIdpId(user.getExternalEntity());
accountResponses.add(accountResponse); accountResponses.add(accountResponse);
logger.debug("Returning available useraccount for [{}]: UserUUID: [{}], DomainUUID: [{}], Account: [{}]", currentUserAccount.getUsername(), user.getUuid(), domain.getUuid(), userAccount.getAccountName());
} }
ListResponse<SamlUserAccountResponse> response = new ListResponse<SamlUserAccountResponse>(); ListResponse<SamlUserAccountResponse> response = new ListResponse<>();
response.setResponses(accountResponses); response.setResponses(accountResponses);
response.setResponseName(getCommandName()); response.setResponseName(getCommandName());
return ApiResponseSerializer.toSerializedString(response, responseType); return ApiResponseSerializer.toSerializedString(response, responseType);
@ -196,7 +205,7 @@ public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthentic
@Override @Override
public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) { public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) {
for (PluggableAPIAuthenticator authManager: authenticators) { for (PluggableAPIAuthenticator authManager: authenticators) {
if (authManager != null && authManager instanceof SAML2AuthManager) { if (authManager instanceof SAML2AuthManager) {
_samlAuthManager = (SAML2AuthManager) authManager; _samlAuthManager = (SAML2AuthManager) authManager;
} }
} }

View File

@ -78,7 +78,7 @@ import com.cloud.user.UserAccountVO;
import com.cloud.user.dao.UserAccountDao; import com.cloud.user.dao.UserAccountDao;
import com.cloud.utils.db.EntityManager; import com.cloud.utils.db.EntityManager;
@APICommand(name = "samlSso", description = "SP initiated SAML Single Sign On", requestHasSensitiveInfo = true, responseObject = LoginCmdResponse.class, entityType = {}) @APICommand(name = "samlSso", description = "SP initiated SAML Single Sign On", responseObject = LoginCmdResponse.class)
public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator, Configurable { public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator, Configurable {
private static final String s_name = "loginresponse"; private static final String s_name = "loginresponse";
@ -97,7 +97,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
@Inject @Inject
private UserAccountDao userAccountDao; private UserAccountDao userAccountDao;
protected static ConfigKey<String> saml2FailedLoginRedirectUrl = new ConfigKey<String>("Advanced", String.class, "saml2.failed.login.redirect.url", "", protected static ConfigKey<String> saml2FailedLoginRedirectUrl = new ConfigKey<>("Advanced", String.class, "saml2.failed.login.redirect.url", "",
"The URL to redirect the SAML2 login failed message (the default vaulue is empty).", true); "The URL to redirect the SAML2 login failed message (the default vaulue is empty).", true);
SAML2AuthManager samlAuthManager; SAML2AuthManager samlAuthManager;
@ -190,7 +190,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
String authnId = SAMLUtils.generateSecureRandomId(); String authnId = SAMLUtils.generateSecureRandomId();
samlAuthManager.saveToken(authnId, domainPath, idpMetadata.getEntityId()); samlAuthManager.saveToken(authnId, domainPath, idpMetadata.getEntityId());
logger.debug("Sending SAMLRequest id=" + authnId); logger.debug("Sending SAMLRequest id=" + authnId);
String redirectUrl = SAMLUtils.buildAuthnRequestUrl(authnId, spMetadata, idpMetadata, SAML2AuthManager.SAMLSignatureAlgorithm.value()); String redirectUrl = SAMLUtils.buildAuthnRequestUrl(authnId, spMetadata, idpMetadata, SAML2AuthManager.SAMLSignatureAlgorithm.value(), SAML2AuthManager.SAMLRequirePasswordLogin.value());
resp.sendRedirect(redirectUrl); resp.sendRedirect(redirectUrl);
return ""; return "";
} if (params.containsKey("SAMLart")) { } if (params.containsKey("SAMLart")) {
@ -207,7 +207,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
params, responseType)); params, responseType));
} }
String username = null; String username;
Issuer issuer = processedSAMLResponse.getIssuer(); Issuer issuer = processedSAMLResponse.getIssuer();
SAMLProviderMetadata spMetadata = samlAuthManager.getSPMetadata(); SAMLProviderMetadata spMetadata = samlAuthManager.getSPMetadata();
SAMLProviderMetadata idpMetadata = samlAuthManager.getIdPMetadata(issuer.getValue()); SAMLProviderMetadata idpMetadata = samlAuthManager.getIdPMetadata(issuer.getValue());
@ -273,7 +273,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
try { try {
assertion = decrypter.decrypt(encryptedAssertion); assertion = decrypter.decrypt(encryptedAssertion);
} catch (DecryptionException e) { } catch (DecryptionException e) {
logger.warn("SAML EncryptedAssertion error: " + e.toString()); logger.warn("SAML EncryptedAssertion error: " + e);
} }
if (assertion == null) { if (assertion == null) {
continue; continue;
@ -310,7 +310,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
UserAccount userAccount = null; UserAccount userAccount = null;
List<UserAccountVO> possibleUserAccounts = userAccountDao.getAllUsersByNameAndEntity(username, issuer.getValue()); List<UserAccountVO> possibleUserAccounts = userAccountDao.getAllUsersByNameAndEntity(username, issuer.getValue());
if (possibleUserAccounts != null && possibleUserAccounts.size() > 0) { if (possibleUserAccounts != null && !possibleUserAccounts.isEmpty()) {
// Log into the first enabled user account // Log into the first enabled user account
// Users can switch to other allowed accounts later // Users can switch to other allowed accounts later
for (UserAccountVO possibleUserAccount : possibleUserAccounts) { for (UserAccountVO possibleUserAccount : possibleUserAccounts) {
@ -370,7 +370,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
@Override @Override
public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) { public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) {
for (PluggableAPIAuthenticator authManager: authenticators) { for (PluggableAPIAuthenticator authManager: authenticators) {
if (authManager != null && authManager instanceof SAML2AuthManager) { if (authManager instanceof SAML2AuthManager) {
samlAuthManager = (SAML2AuthManager) authManager; samlAuthManager = (SAML2AuthManager) authManager;
} }
} }

View File

@ -79,6 +79,10 @@ public interface SAML2AuthManager extends PluggableAPIAuthenticator, PluggableSe
ConfigKey<String> SAMLUserSessionKeyPathAttribute = new ConfigKey<String>("Advanced", String.class, "saml2.user.sessionkey.path", "", ConfigKey<String> SAMLUserSessionKeyPathAttribute = new ConfigKey<String>("Advanced", String.class, "saml2.user.sessionkey.path", "",
"The Path attribute of sessionkey cookie when SAML users have logged in. If not set, it will be set to the path of SAML redirection URL (saml2.redirect.url).", true); "The Path attribute of sessionkey cookie when SAML users have logged in. If not set, it will be set to the path of SAML redirection URL (saml2.redirect.url).", true);
ConfigKey<Boolean> SAMLRequirePasswordLogin = new ConfigKey<Boolean>("Advanced", Boolean.class, "saml2.require.password", "true",
"When enabled SAML2 will validate that the SAML login was performed with a password. If disabled, other forms of authentication are allowed (two-factor, certificate, etc) on the SAML Authentication Provider", true);
SAMLProviderMetadata getSPMetadata(); SAMLProviderMetadata getSPMetadata();
SAMLProviderMetadata getIdPMetadata(String entityId); SAMLProviderMetadata getIdPMetadata(String entityId);
Collection<SAMLProviderMetadata> getAllIdPMetadata(); Collection<SAMLProviderMetadata> getAllIdPMetadata();

View File

@ -541,6 +541,6 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
SAMLCloudStackRedirectionUrl, SAMLUserAttributeName, SAMLCloudStackRedirectionUrl, SAMLUserAttributeName,
SAMLIdentityProviderMetadataURL, SAMLDefaultIdentityProviderId, SAMLIdentityProviderMetadataURL, SAMLDefaultIdentityProviderId,
SAMLSignatureAlgorithm, SAMLAppendDomainSuffix, SAMLTimeout, SAMLCheckSignature, SAMLSignatureAlgorithm, SAMLAppendDomainSuffix, SAMLTimeout, SAMLCheckSignature,
SAMLForceAuthn, SAMLUserSessionKeyPathAttribute}; SAMLForceAuthn, SAMLUserSessionKeyPathAttribute, SAMLRequirePasswordLogin};
} }
} }

View File

@ -152,11 +152,11 @@ public class SAMLUtils {
return null; return null;
} }
public static String buildAuthnRequestUrl(final String authnId, final SAMLProviderMetadata spMetadata, final SAMLProviderMetadata idpMetadata, final String signatureAlgorithm) { public static String buildAuthnRequestUrl(final String authnId, final SAMLProviderMetadata spMetadata, final SAMLProviderMetadata idpMetadata, final String signatureAlgorithm, boolean requirePasswordAuthentication) {
String redirectUrl = ""; String redirectUrl = "";
try { try {
DefaultBootstrap.bootstrap(); DefaultBootstrap.bootstrap();
AuthnRequest authnRequest = SAMLUtils.buildAuthnRequestObject(authnId, spMetadata.getEntityId(), idpMetadata.getSsoUrl(), spMetadata.getSsoUrl()); AuthnRequest authnRequest = SAMLUtils.buildAuthnRequestObject(authnId, spMetadata.getEntityId(), idpMetadata.getSsoUrl(), spMetadata.getSsoUrl(), requirePasswordAuthentication);
PrivateKey privateKey = null; PrivateKey privateKey = null;
if (spMetadata.getKeyPair() != null) { if (spMetadata.getKeyPair() != null) {
privateKey = spMetadata.getKeyPair().getPrivate(); privateKey = spMetadata.getKeyPair().getPrivate();
@ -169,13 +169,36 @@ public class SAMLUtils {
return redirectUrl; return redirectUrl;
} }
public static AuthnRequest buildAuthnRequestObject(final String authnId, final String spId, final String idpUrl, final String consumerUrl) { public static AuthnRequest buildAuthnRequestObject(final String authnId, final String spId, final String idpUrl, final String consumerUrl, boolean requirePasswordAuthentication) {
// Issuer object // Issuer object
IssuerBuilder issuerBuilder = new IssuerBuilder(); IssuerBuilder issuerBuilder = new IssuerBuilder();
Issuer issuer = issuerBuilder.buildObject(); Issuer issuer = issuerBuilder.buildObject();
issuer.setValue(spId); issuer.setValue(spId);
// AuthnContextClass // Creation of AuthRequestObject
AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
AuthnRequest authnRequest = authRequestBuilder.buildObject();
// AuthnContextClass. When this is false, the authentication requirements are defered to the SAML IDP and its default or configured workflow
if (requirePasswordAuthentication) {
setRequestedAuthnContext(authnRequest, requirePasswordAuthentication);
}
authnRequest.setID(authnId);
authnRequest.setDestination(idpUrl);
authnRequest.setVersion(SAMLVersion.VERSION_20);
authnRequest.setForceAuthn(SAML2AuthManager.SAMLForceAuthn.value());
authnRequest.setIsPassive(false);
authnRequest.setIssueInstant(new DateTime());
authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
authnRequest.setAssertionConsumerServiceURL(consumerUrl);
authnRequest.setProviderName(spId);
authnRequest.setIssuer(issuer);
return authnRequest;
}
public static void setRequestedAuthnContext(AuthnRequest authnRequest, boolean requirePasswordAuthentication) {
AuthnContextClassRefBuilder authnContextClassRefBuilder = new AuthnContextClassRefBuilder(); AuthnContextClassRefBuilder authnContextClassRefBuilder = new AuthnContextClassRefBuilder();
AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder.buildObject( AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder.buildObject(
SAMLConstants.SAML20_NS, SAMLConstants.SAML20_NS,
@ -187,23 +210,7 @@ public class SAMLUtils {
RequestedAuthnContext requestedAuthnContext = requestedAuthnContextBuilder.buildObject(); RequestedAuthnContext requestedAuthnContext = requestedAuthnContextBuilder.buildObject();
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT); requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef); requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);
// Creation of AuthRequestObject
AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
AuthnRequest authnRequest = authRequestBuilder.buildObject();
authnRequest.setID(authnId);
authnRequest.setDestination(idpUrl);
authnRequest.setVersion(SAMLVersion.VERSION_20);
authnRequest.setForceAuthn(SAML2AuthManager.SAMLForceAuthn.value());
authnRequest.setIsPassive(false);
authnRequest.setIssueInstant(new DateTime());
authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
authnRequest.setAssertionConsumerServiceURL(consumerUrl);
authnRequest.setProviderName(spId);
authnRequest.setIssuer(issuer);
authnRequest.setRequestedAuthnContext(requestedAuthnContext); authnRequest.setRequestedAuthnContext(requestedAuthnContext);
return authnRequest;
} }
public static LogoutRequest buildLogoutRequest(String logoutUrl, String spId, String nameIdString) { public static LogoutRequest buildLogoutRequest(String logoutUrl, String spId, String nameIdString) {
@ -285,23 +292,6 @@ public class SAMLUtils {
} }
public static void setupSamlUserCookies(final LoginCmdResponse loginResponse, final HttpServletResponse resp) throws IOException { public static void setupSamlUserCookies(final LoginCmdResponse loginResponse, final HttpServletResponse resp) throws IOException {
resp.addCookie(new Cookie("userid", URLEncoder.encode(loginResponse.getUserId(), HttpUtils.UTF_8)));
resp.addCookie(new Cookie("domainid", URLEncoder.encode(loginResponse.getDomainId(), HttpUtils.UTF_8)));
resp.addCookie(new Cookie("role", URLEncoder.encode(loginResponse.getType(), HttpUtils.UTF_8)));
resp.addCookie(new Cookie("username", URLEncoder.encode(loginResponse.getUsername(), HttpUtils.UTF_8)));
resp.addCookie(new Cookie("account", URLEncoder.encode(loginResponse.getAccount(), HttpUtils.UTF_8)));
resp.addCookie(new Cookie("isSAML", URLEncoder.encode("true", HttpUtils.UTF_8)));
resp.addCookie(new Cookie("twoFaEnabled", URLEncoder.encode(loginResponse.is2FAenabled(), HttpUtils.UTF_8)));
String providerFor2FA = loginResponse.getProviderFor2FA();
if (StringUtils.isNotEmpty(providerFor2FA)) {
resp.addCookie(new Cookie("twoFaProvider", URLEncoder.encode(loginResponse.getProviderFor2FA(), HttpUtils.UTF_8)));
}
String timezone = loginResponse.getTimeZone();
if (timezone != null) {
resp.addCookie(new Cookie("timezone", URLEncoder.encode(timezone, HttpUtils.UTF_8)));
}
resp.addCookie(new Cookie("userfullname", URLEncoder.encode(loginResponse.getFirstName() + " " + loginResponse.getLastName(), HttpUtils.UTF_8).replace("+", "%20")));
String redirectUrl = SAML2AuthManager.SAMLCloudStackRedirectionUrl.value(); String redirectUrl = SAML2AuthManager.SAMLCloudStackRedirectionUrl.value();
String path = SAML2AuthManager.SAMLUserSessionKeyPathAttribute.value(); String path = SAML2AuthManager.SAMLUserSessionKeyPathAttribute.value();
String domain = null; String domain = null;
@ -317,6 +307,18 @@ public class SAMLUtils {
} catch (URISyntaxException ex) { } catch (URISyntaxException ex) {
throw new CloudRuntimeException("Invalid URI: " + redirectUrl); throw new CloudRuntimeException("Invalid URI: " + redirectUrl);
} }
addBaseCookies(loginResponse, resp, domain, path);
String providerFor2FA = loginResponse.getProviderFor2FA();
if (StringUtils.isNotEmpty(providerFor2FA)) {
resp.addCookie(newCookie(domain, path,"twoFaProvider", URLEncoder.encode(loginResponse.getProviderFor2FA(), HttpUtils.UTF_8)));
}
String timezone = loginResponse.getTimeZone();
if (timezone != null) {
resp.addCookie(newCookie(domain, path,"timezone", URLEncoder.encode(timezone, HttpUtils.UTF_8)));
}
String sameSite = ApiServlet.getApiSessionKeySameSite(); String sameSite = ApiServlet.getApiSessionKeySameSite();
String sessionKeyCookie = String.format("%s=%s;Domain=%s;Path=%s;%s", ApiConstants.SESSIONKEY, loginResponse.getSessionKey(), domain, path, sameSite); 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); LOGGER.debug("Adding sessionkey cookie to response: " + sessionKeyCookie);
@ -324,6 +326,24 @@ public class SAMLUtils {
resp.addHeader("SET-COOKIE", String.format("%s=%s;HttpOnly;Path=/client/api;%s", ApiConstants.SESSIONKEY, loginResponse.getSessionKey(), sameSite)); resp.addHeader("SET-COOKIE", String.format("%s=%s;HttpOnly;Path=/client/api;%s", ApiConstants.SESSIONKEY, loginResponse.getSessionKey(), sameSite));
} }
private static void addBaseCookies(final LoginCmdResponse loginResponse, final HttpServletResponse resp, String domain, String path) throws IOException {
resp.addCookie(newCookie(domain, path, "userid", URLEncoder.encode(loginResponse.getUserId(), HttpUtils.UTF_8)));
resp.addCookie(newCookie(domain, path,"domainid", URLEncoder.encode(loginResponse.getDomainId(), HttpUtils.UTF_8)));
resp.addCookie(newCookie(domain, path,"role", URLEncoder.encode(loginResponse.getType(), HttpUtils.UTF_8)));
resp.addCookie(newCookie(domain, path,"username", URLEncoder.encode(loginResponse.getUsername(), HttpUtils.UTF_8)));
resp.addCookie(newCookie(domain, path,"account", URLEncoder.encode(loginResponse.getAccount(), HttpUtils.UTF_8)));
resp.addCookie(newCookie(domain, path,"isSAML", URLEncoder.encode("true", HttpUtils.UTF_8)));
resp.addCookie(newCookie(domain, path,"twoFaEnabled", URLEncoder.encode(loginResponse.is2FAenabled(), HttpUtils.UTF_8)));
resp.addCookie(newCookie(domain, path,"userfullname", URLEncoder.encode(loginResponse.getFirstName() + " " + loginResponse.getLastName(), HttpUtils.UTF_8).replace("+", "%20")));
}
private static Cookie newCookie(final String domain, final String path, final String name, final String value) {
Cookie cookie = new Cookie(name, value);
cookie.setDomain(domain);
cookie.setPath(path);
return cookie;
}
/** /**
* Returns base64 encoded PublicKey * Returns base64 encoded PublicKey
* @param key PublicKey * @param key PublicKey

View File

@ -58,7 +58,7 @@ public class SAMLUtilsTest extends TestCase {
String idpUrl = "http://idp.domain.example"; String idpUrl = "http://idp.domain.example";
String spId = "cloudstack"; String spId = "cloudstack";
String authnId = SAMLUtils.generateSecureRandomId(); String authnId = SAMLUtils.generateSecureRandomId();
AuthnRequest req = SAMLUtils.buildAuthnRequestObject(authnId, spId, idpUrl, consumerUrl); AuthnRequest req = SAMLUtils.buildAuthnRequestObject(authnId, spId, idpUrl, consumerUrl, true);
assertEquals(req.getAssertionConsumerServiceURL(), consumerUrl); assertEquals(req.getAssertionConsumerServiceURL(), consumerUrl);
assertEquals(req.getDestination(), idpUrl); assertEquals(req.getDestination(), idpUrl);
assertEquals(req.getIssuer().getValue(), spId); assertEquals(req.getIssuer().getValue(), spId);
@ -86,7 +86,7 @@ public class SAMLUtilsTest extends TestCase {
idpMetadata.setSsoUrl(idpUrl); idpMetadata.setSsoUrl(idpUrl);
idpMetadata.setEntityId(idpId); idpMetadata.setEntityId(idpId);
URI redirectUrl = new URI(SAMLUtils.buildAuthnRequestUrl(authnId, spMetadata, idpMetadata, SAML2AuthManager.SAMLSignatureAlgorithm.value())); URI redirectUrl = new URI(SAMLUtils.buildAuthnRequestUrl(authnId, spMetadata, idpMetadata, SAML2AuthManager.SAMLSignatureAlgorithm.value(), true));
assertThat(redirectUrl).hasScheme(urlScheme).hasHost(idpDomain).hasParameter("SAMLRequest"); assertThat(redirectUrl).hasScheme(urlScheme).hasHost(idpDomain).hasParameter("SAMLRequest");
assertEquals(urlScheme, redirectUrl.getScheme()); assertEquals(urlScheme, redirectUrl.getScheme());
assertEquals(idpDomain, redirectUrl.getHost()); assertEquals(idpDomain, redirectUrl.getHost());
@ -115,7 +115,7 @@ public class SAMLUtilsTest extends TestCase {
idpMetadata.setSsoUrl(idpUrl); idpMetadata.setSsoUrl(idpUrl);
idpMetadata.setEntityId(idpId); idpMetadata.setEntityId(idpId);
URI redirectUrl = new URI(SAMLUtils.buildAuthnRequestUrl(authnId, spMetadata, idpMetadata, SAML2AuthManager.SAMLSignatureAlgorithm.value())); URI redirectUrl = new URI(SAMLUtils.buildAuthnRequestUrl(authnId, spMetadata, idpMetadata, SAML2AuthManager.SAMLSignatureAlgorithm.value(), true));
assertThat(redirectUrl).hasScheme(urlScheme).hasHost(idpDomain).hasParameter("idpid").hasParameter("SAMLRequest"); assertThat(redirectUrl).hasScheme(urlScheme).hasHost(idpDomain).hasParameter("idpid").hasParameter("SAMLRequest");
assertEquals(urlScheme, redirectUrl.getScheme()); assertEquals(urlScheme, redirectUrl.getScheme());
assertEquals(idpDomain, redirectUrl.getHost()); assertEquals(idpDomain, redirectUrl.getHost());

View File

@ -213,7 +213,6 @@ public class ListAndSwitchSAMLAccountCmdTest extends TestCase {
loginCmdResponse.set2FAenabled("false"); loginCmdResponse.set2FAenabled("false");
Mockito.when(apiServer.loginUser(nullable(HttpSession.class), nullable(String.class), nullable(String.class), Mockito.when(apiServer.loginUser(nullable(HttpSession.class), nullable(String.class), nullable(String.class),
nullable(Long.class), nullable(String.class), nullable(InetAddress.class), nullable(Map.class))).thenReturn(loginCmdResponse); nullable(Long.class), nullable(String.class), nullable(InetAddress.class), nullable(Map.class))).thenReturn(loginCmdResponse);
Mockito.doNothing().when(resp).sendRedirect(nullable(String.class));
try { try {
cmd.authenticate("command", params, session, null, HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp); cmd.authenticate("command", params, session, null, HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
} catch (ServerApiException exception) { } catch (ServerApiException exception) {
@ -221,7 +220,6 @@ public class ListAndSwitchSAMLAccountCmdTest extends TestCase {
} finally { } finally {
// accountService should have been called 4 times by now, for this case twice and 2 for cases above // accountService should have been called 4 times by now, for this case twice and 2 for cases above
Mockito.verify(accountService, Mockito.times(4)).getUserAccountById(Mockito.anyLong()); Mockito.verify(accountService, Mockito.times(4)).getUserAccountById(Mockito.anyLong());
Mockito.verify(resp, Mockito.times(1)).sendRedirect(anyString());
} }
} }

View File

@ -196,6 +196,7 @@ import static org.apache.cloudstack.user.UserPasswordResetManager.UserPasswordRe
@Component @Component
public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiServerService, Configurable { public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiServerService, Configurable {
private static final Logger ACCESSLOGGER = LogManager.getLogger("apiserver." + ApiServer.class.getName());
private static final String SANITIZATION_REGEX = "[\n\r]"; private static final String SANITIZATION_REGEX = "[\n\r]";
@ -240,9 +241,9 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
private EventDistributor eventDistributor = null; private EventDistributor eventDistributor = null;
private static int s_workerCount = 0; private static int s_workerCount = 0;
private static Map<String, List<Class<?>>> s_apiNameCmdClassMap = new HashMap<String, List<Class<?>>>(); private static Map<String, List<Class<?>>> s_apiNameCmdClassMap = new HashMap<>();
private static ExecutorService s_executor = new ThreadPoolExecutor(10, 150, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new NamedThreadFactory( private static ExecutorService s_executor = new ThreadPoolExecutor(10, 150, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new NamedThreadFactory(
"ApiServer")); "ApiServer"));
@Inject @Inject
@ -358,7 +359,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
String jobEvent = eventInfo.second(); String jobEvent = eventInfo.second();
if (logger.isTraceEnabled()) if (logger.isTraceEnabled())
logger.trace("Handle asyjob publish event " + jobEvent); logger.trace("Handle asyjob publish event {}", jobEvent);
if (eventDistributor == null) { if (eventDistributor == null) {
setEventDistributor(ComponentContext.getComponent(EventDistributor.class)); setEventDistributor(ComponentContext.getComponent(EventDistributor.class));
} }
@ -383,7 +384,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
cmdEventType = eventTypeObj; cmdEventType = eventTypeObj;
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
logger.debug("Retrieved cmdEventType from job info: " + cmdEventType); logger.debug("Retrieved cmdEventType from job info: {}", cmdEventType);
} else { } else {
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
logger.debug("Unable to locate cmdEventType marker in job info. publish as unknown event"); logger.debug("Unable to locate cmdEventType marker in job info. publish as unknown event");
@ -427,11 +428,11 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
protected void setupIntegrationPortListener(Integer apiPort) { protected void setupIntegrationPortListener(Integer apiPort) {
if (apiPort == null || apiPort <= 0) { if (apiPort == null || apiPort <= 0) {
logger.trace(String.format("Skipping setting up listener for integration port as %s is set to %d", logger.trace("Skipping setting up listener for integration port as {} is set to {}",
IntegrationAPIPort.key(), apiPort)); IntegrationAPIPort.key(), apiPort);
return; return;
} }
logger.debug(String.format("Setting up integration API service listener on port: %d", apiPort)); logger.debug("Setting up integration API service listener on port: {}", apiPort);
final ListenerThread listenerThread = new ListenerThread(this, apiPort); final ListenerThread listenerThread = new ListenerThread(this, apiPort);
listenerThread.start(); listenerThread.start();
} }
@ -442,24 +443,24 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
Integer apiPort = IntegrationAPIPort.value(); // api port, null by default Integer apiPort = IntegrationAPIPort.value(); // api port, null by default
final Long snapshotLimit = ConcurrentSnapshotsThresholdPerHost.value(); final Long snapshotLimit = ConcurrentSnapshotsThresholdPerHost.value();
if (snapshotLimit == null || snapshotLimit.longValue() <= 0) { if (snapshotLimit == null || snapshotLimit <= 0) {
logger.debug("Global concurrent snapshot config parameter " + ConcurrentSnapshotsThresholdPerHost.value() + " is less or equal 0; defaulting to unlimited"); logger.debug("Global concurrent snapshot config parameter " + ConcurrentSnapshotsThresholdPerHost.value() + " is less or equal 0; defaulting to unlimited");
} else { } else {
dispatcher.setCreateSnapshotQueueSizeLimit(snapshotLimit); dispatcher.setCreateSnapshotQueueSizeLimit(snapshotLimit);
} }
final Long migrationLimit = VolumeApiService.ConcurrentMigrationsThresholdPerDatastore.value(); final Long migrationLimit = VolumeApiService.ConcurrentMigrationsThresholdPerDatastore.value();
if (migrationLimit == null || migrationLimit.longValue() <= 0) { if (migrationLimit == null || migrationLimit <= 0) {
logger.debug("Global concurrent migration config parameter " + VolumeApiService.ConcurrentMigrationsThresholdPerDatastore.value() + " is less or equal 0; defaulting to unlimited"); logger.debug("Global concurrent migration config parameter " + VolumeApiService.ConcurrentMigrationsThresholdPerDatastore.value() + " is less or equal 0; defaulting to unlimited");
} else { } else {
dispatcher.setMigrateQueueSizeLimit(migrationLimit); dispatcher.setMigrateQueueSizeLimit(migrationLimit);
} }
final Set<Class<?>> cmdClasses = new HashSet<Class<?>>(); final Set<Class<?>> cmdClasses = new HashSet<>();
for (final PluggableService pluggableService : pluggableServices) { for (final PluggableService pluggableService : pluggableServices) {
cmdClasses.addAll(pluggableService.getCommands()); cmdClasses.addAll(pluggableService.getCommands());
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Discovered plugin " + pluggableService.getClass().getSimpleName()); logger.debug("Discovered plugin {}", pluggableService.getClass().getSimpleName());
} }
} }
@ -472,7 +473,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
String apiName = at.name(); String apiName = at.name();
List<Class<?>> apiCmdList = s_apiNameCmdClassMap.get(apiName); List<Class<?>> apiCmdList = s_apiNameCmdClassMap.get(apiName);
if (apiCmdList == null) { if (apiCmdList == null) {
apiCmdList = new ArrayList<Class<?>>(); apiCmdList = new ArrayList<>();
s_apiNameCmdClassMap.put(apiName, apiCmdList); s_apiNameCmdClassMap.put(apiName, apiCmdList);
} }
apiCmdList.add(cmdClass); apiCmdList.add(cmdClass);
@ -574,14 +575,14 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
throw e; throw e;
} }
} finally { } finally {
logger.info(sb.toString()); ACCESSLOGGER.info(sb.toString());
CallContext.unregister(); CallContext.unregister();
} }
} }
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
public void checkCharacterInkParams(final Map params) { public void checkCharacterInkParams(final Map params) {
final Map<String, String> stringMap = new HashMap<String, String>(); final Map<String, String> stringMap = new HashMap<>();
final Set keys = params.keySet(); final Set keys = params.keySet();
final Iterator keysIter = keys.iterator(); final Iterator keysIter = keys.iterator();
while (keysIter.hasNext()) { while (keysIter.hasNext()) {
@ -604,7 +605,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
public String handleRequest(final Map params, final String responseType, final StringBuilder auditTrailSb) throws ServerApiException { public String handleRequest(final Map params, final String responseType, final StringBuilder auditTrailSb) throws ServerApiException {
checkCharacterInkParams(params); checkCharacterInkParams(params);
String response = null; String response;
String[] command = null; String[] command = null;
try { try {
@ -625,7 +626,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
if (authManager.getAPIAuthenticator(command[0]) != null) { if (authManager.getAPIAuthenticator(command[0]) != null) {
return null; return null;
} }
final Map<String, String> paramMap = new HashMap<String, String>(); final Map<String, String> paramMap = new HashMap<>();
final Set keys = params.keySet(); final Set keys = params.keySet();
final Iterator keysIter = keys.iterator(); final Iterator keysIter = keys.iterator();
while (keysIter.hasNext()) { while (keysIter.hasNext()) {
@ -641,16 +642,16 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
if (cmdClass != null) { if (cmdClass != null) {
APICommand annotation = cmdClass.getAnnotation(APICommand.class); APICommand annotation = cmdClass.getAnnotation(APICommand.class);
if (annotation == null) { if (annotation == null) {
logger.error("No APICommand annotation found for class " + cmdClass.getCanonicalName()); logger.error("No APICommand annotation found for class {}", cmdClass.getCanonicalName());
throw new CloudRuntimeException("No APICommand annotation found for class " + cmdClass.getCanonicalName()); throw new CloudRuntimeException("No APICommand annotation found for class " + cmdClass.getCanonicalName());
} }
BaseCmd cmdObj = (BaseCmd)cmdClass.newInstance(); BaseCmd cmdObj = (BaseCmd)cmdClass.getDeclaredConstructor().newInstance();
cmdObj = ComponentContext.inject(cmdObj); cmdObj = ComponentContext.inject(cmdObj);
cmdObj.configure(); cmdObj.configure();
cmdObj.setFullUrlParams(paramMap); cmdObj.setFullUrlParams(paramMap);
cmdObj.setResponseType(responseType); cmdObj.setResponseType(responseType);
cmdObj.setHttpMethod(paramMap.get(ApiConstants.HTTPMETHOD).toString()); cmdObj.setHttpMethod(paramMap.get(ApiConstants.HTTPMETHOD));
// This is where the command is either serialized, or directly dispatched // This is where the command is either serialized, or directly dispatched
StringBuilder log = new StringBuilder(); StringBuilder log = new StringBuilder();
@ -659,14 +660,11 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
} else { } else {
final String errorString = "Unknown API command: " + command[0]; final String errorString = "Unknown API command: " + command[0];
logger.warn(errorString); logger.warn(errorString);
auditTrailSb.append(" " + errorString); auditTrailSb.append(" ").append(errorString);
throw new ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, errorString); throw new ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, errorString);
} }
} }
} catch (final InvalidParameterValueException ex) { } catch (final InvalidParameterValueException | IllegalArgumentException ex) {
logger.info(ex.getMessage());
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, ex.getMessage(), ex);
} catch (final IllegalArgumentException ex) {
logger.info(ex.getMessage()); logger.info(ex.getMessage());
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, ex.getMessage(), ex); throw new ServerApiException(ApiErrorCode.PARAM_ERROR, ex.getMessage(), ex);
} catch (final PermissionDeniedException ex) { } catch (final PermissionDeniedException ex) {
@ -679,9 +677,9 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
buf.append(obj.getUuid()); buf.append(obj.getUuid());
buf.append(" "); buf.append(" ");
} }
logger.info("PermissionDenied: " + ex.getMessage() + " on objs: [" + buf.toString() + "]"); logger.info("PermissionDenied: " + ex.getMessage() + " on objs: [" + buf + "]");
} else { } else {
logger.info("PermissionDenied: " + ex.getMessage()); logger.info("PermissionDenied: {}", ex.getMessage());
} }
throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, ex.getMessage(), ex); throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, ex.getMessage(), ex);
} catch (final AccountLimitException ex) { } catch (final AccountLimitException ex) {
@ -756,7 +754,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
throw new ServerApiException(ApiErrorCode.SERVICE_UNAVAILABLE, msg); throw new ServerApiException(ApiErrorCode.SERVICE_UNAVAILABLE, msg);
} }
Long objectId = null; Long objectId = null;
String objectUuid = null; String objectUuid;
if (cmdObj instanceof BaseAsyncCreateCmd) { if (cmdObj instanceof BaseAsyncCreateCmd) {
final BaseAsyncCreateCmd createCmd = (BaseAsyncCreateCmd)cmdObj; final BaseAsyncCreateCmd createCmd = (BaseAsyncCreateCmd)cmdObj;
dispatcher.dispatchCreateCmd(createCmd, params); dispatcher.dispatchCreateCmd(createCmd, params);
@ -797,7 +795,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
} }
params.put("ctxStartEventId", String.valueOf(startEventId)); params.put("ctxStartEventId", String.valueOf(startEventId));
params.put("cmdEventType", asyncCmd.getEventType().toString()); params.put("cmdEventType", asyncCmd.getEventType());
params.put("ctxDetails", ApiGsonHelper.getBuilder().create().toJson(ctx.getContextParameters())); params.put("ctxDetails", ApiGsonHelper.getBuilder().create().toJson(ctx.getContextParameters()));
if (asyncCmd.getHttpMethod() != null) { if (asyncCmd.getHttpMethod() != null) {
params.put(ApiConstants.HTTPMETHOD, asyncCmd.getHttpMethod().toString()); params.put(ApiConstants.HTTPMETHOD, asyncCmd.getHttpMethod().toString());
@ -860,9 +858,9 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void buildAsyncListResponse(final BaseListCmd command, final Account account) { private void buildAsyncListResponse(final BaseListCmd command, final Account account) {
final List<ResponseObject> responses = ((ListResponse)command.getResponseObject()).getResponses(); final List<ResponseObject> responses = ((ListResponse<ResponseObject>)command.getResponseObject()).getResponses();
if (responses != null && responses.size() > 0) { if (responses != null && !responses.isEmpty()) {
List<? extends AsyncJob> jobs = null; List<? extends AsyncJob> jobs;
// list all jobs for ROOT admin // list all jobs for ROOT admin
if (accountMgr.isRootAdmin(account.getId())) { if (accountMgr.isRootAdmin(account.getId())) {
@ -871,11 +869,11 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
jobs = asyncMgr.findInstancePendingAsyncJobs(command.getApiResourceType().toString(), account.getId()); jobs = asyncMgr.findInstancePendingAsyncJobs(command.getApiResourceType().toString(), account.getId());
} }
if (jobs.size() == 0) { if (jobs.isEmpty()) {
return; return;
} }
final Map<String, AsyncJob> objectJobMap = new HashMap<String, AsyncJob>(); final Map<String, AsyncJob> objectJobMap = new HashMap<>();
for (final AsyncJob job : jobs) { for (final AsyncJob job : jobs) {
if (job.getInstanceId() == null) { if (job.getInstanceId() == null) {
continue; continue;
@ -912,7 +910,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
if (Boolean.TRUE.equals(apiKeyAccessEnabled)) { if (Boolean.TRUE.equals(apiKeyAccessEnabled)) {
return true; return true;
} else { } else {
logger.info("Api-Key access is disabled for the User " + user.toString()); logger.info("Api-Key access is disabled for the User {}", user);
return false; return false;
} }
} }
@ -921,7 +919,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
if (Boolean.TRUE.equals(apiKeyAccessEnabled)) { if (Boolean.TRUE.equals(apiKeyAccessEnabled)) {
return true; return true;
} else { } else {
logger.info("Api-Key access is disabled for the Account " + account.toString()); logger.info("Api-Key access is disabled for the Account {}", account);
return false; return false;
} }
} }
@ -938,7 +936,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
public boolean verifyRequest(final Map<String, Object[]> requestParameters, final Long userId, InetAddress remoteAddress) throws ServerApiException { public boolean verifyRequest(final Map<String, Object[]> requestParameters, final Long userId, InetAddress remoteAddress) throws ServerApiException {
try { try {
String apiKey = null; String apiKey = null;
String secretKey = null; String secretKey;
String signature = null; String signature = null;
String unsignedRequest = null; String unsignedRequest = null;
@ -966,11 +964,9 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
// - build a request string with sorted params, make sure it's all lowercase // - build a request string with sorted params, make sure it's all lowercase
// - sign the request, verify the signature is the same // - sign the request, verify the signature is the same
final List<String> parameterNames = new ArrayList<String>();
for (final Object paramNameObj : requestParameters.keySet()) { // put the name in a list that we'll sort later
parameterNames.add((String)paramNameObj); // put the name in a list that we'll sort later final List<String> parameterNames = new ArrayList<>(requestParameters.keySet());
}
Collections.sort(parameterNames); Collections.sort(parameterNames);
@ -1006,7 +1002,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
return false; // no signature, bad request return false; // no signature, bad request
} }
Date expiresTS = null; Date expiresTS;
// FIXME: Hard coded signature, why not have an enum // FIXME: Hard coded signature, why not have an enum
if ("3".equals(signatureVersion)) { if ("3".equals(signatureVersion)) {
// New signature authentication. Check for expire parameter and its validity // New signature authentication. Check for expire parameter and its validity
@ -1026,18 +1022,18 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
if (expiresTS.before(now)) { if (expiresTS.before(now)) {
signature = signature.replaceAll(SANITIZATION_REGEX, "_"); signature = signature.replaceAll(SANITIZATION_REGEX, "_");
apiKey = apiKey.replaceAll(SANITIZATION_REGEX, "_"); apiKey = apiKey.replaceAll(SANITIZATION_REGEX, "_");
logger.debug(String.format("Request expired -- ignoring ...sig [%s], apiKey [%s].", signature, apiKey)); logger.debug("Request expired -- ignoring ...sig [{}], apiKey [{}].", signature, apiKey);
return false; return false;
} }
} }
final TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB); final TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB);
txn.close(); txn.close();
User user = null; User user;
// verify there is a user with this api key // verify there is a user with this api key
final Pair<User, Account> userAcctPair = accountMgr.findUserByApiKey(apiKey); final Pair<User, Account> userAcctPair = accountMgr.findUserByApiKey(apiKey);
if (userAcctPair == null) { if (userAcctPair == null) {
logger.debug("apiKey does not map to a valid user -- ignoring request, apiKey: " + apiKey); logger.debug("apiKey does not map to a valid user -- ignoring request, apiKey: {}", apiKey);
return false; return false;
} }
@ -1078,7 +1074,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
if (!equalSig) { if (!equalSig) {
signature = signature.replaceAll(SANITIZATION_REGEX, "_"); signature = signature.replaceAll(SANITIZATION_REGEX, "_");
logger.info(String.format("User signature [%s] is not equaled to computed signature [%s].", signature, computedSignature)); logger.info("User signature [{}] is not equaled to computed signature [{}].", signature, computedSignature);
} else { } else {
CallContext.register(user, account); CallContext.register(user, account);
} }
@ -1137,10 +1133,10 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
session.removeAttribute("domain_UUID"); session.removeAttribute("domain_UUID");
} }
final Enumeration attrNames = session.getAttributeNames(); final Enumeration<String> attrNames = session.getAttributeNames();
if (attrNames != null) { if (attrNames != null) {
while (attrNames.hasMoreElements()) { while (attrNames.hasMoreElements()) {
final String attrName = (String) attrNames.nextElement(); final String attrName = attrNames.nextElement();
final Object attrObj = session.getAttribute(attrName); final Object attrObj = session.getAttribute(attrName);
if (ApiConstants.USERNAME.equalsIgnoreCase(attrName)) { if (ApiConstants.USERNAME.equalsIgnoreCase(attrName)) {
response.setUsername(attrObj.toString()); response.setUsername(attrObj.toString());
@ -1202,7 +1198,14 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
domainId = userDomain.getId(); domainId = userDomain.getId();
} }
UserAccount userAcct = accountMgr.authenticateUser(username, password, domainId, loginIpAddress, requestParameters); Long userId = (Long)session.getAttribute("nextUserId");
UserAccount userAcct = null;
if (userId != null) {
userAcct = accountMgr.getUserAccountById(userId);
} else {
userAcct = accountMgr.authenticateUser(username, password, domainId, loginIpAddress, requestParameters);
}
if (userAcct != null) { if (userAcct != null) {
final String timezone = userAcct.getTimezone(); final String timezone = userAcct.getTimezone();
float offsetInHrs = 0f; float offsetInHrs = 0f;
@ -1214,7 +1217,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
final long longDate = date.getTime(); final long longDate = date.getTime();
final float offsetInMs = (t.getOffset(longDate)); final float offsetInMs = (t.getOffset(longDate));
offsetInHrs = offsetInMs / (1000 * 60 * 60); offsetInHrs = offsetInMs / (1000 * 60 * 60);
logger.info("Timezone offset from UTC is: " + offsetInHrs); logger.info("Timezone offset from UTC is: {}", offsetInHrs);
} }
final Account account = accountMgr.getAccount(userAcct.getAccountId()); final Account account = accountMgr.getAccount(userAcct.getAccountId());
@ -1272,7 +1275,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
// (bug 5483) generate a session key that the user must submit on every request to prevent CSRF, add that // (bug 5483) generate a session key that the user must submit on every request to prevent CSRF, add that
// to the login response so that session-based authenticators know to send the key back // to the login response so that session-based authenticators know to send the key back
final SecureRandom sesssionKeyRandom = new SecureRandom(); final SecureRandom sesssionKeyRandom = new SecureRandom();
final byte sessionKeyBytes[] = new byte[20]; final byte[] sessionKeyBytes = new byte[20];
sesssionKeyRandom.nextBytes(sessionKeyBytes); sesssionKeyRandom.nextBytes(sessionKeyBytes);
final String sessionKey = Base64.encodeBase64URLSafeString(sessionKeyBytes); final String sessionKey = Base64.encodeBase64URLSafeString(sessionKeyBytes);
session.setAttribute(ApiConstants.SESSIONKEY, sessionKey); session.setAttribute(ApiConstants.SESSIONKEY, sessionKey);
@ -1285,7 +1288,6 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
@Override @Override
public void logoutUser(final long userId) { public void logoutUser(final long userId) {
accountMgr.logoutUser(userId); accountMgr.logoutUser(userId);
return;
} }
@Override @Override
@ -1313,30 +1315,26 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
throw new CloudRuntimeException(errorMessage); throw new CloudRuntimeException(errorMessage);
} }
if (StringUtils.isBlank(userAccount.getEmail())) { if (StringUtils.isBlank(userAccount.getEmail())) {
logger.error(String.format( logger.error("Email is not set. username: {} account id: {} domain id: {}",
"Email is not set. username: %s account id: %d domain id: %d", userAccount.getUsername(), userAccount.getAccountId(), userAccount.getDomainId());
userAccount.getUsername(), userAccount.getAccountId(), userAccount.getDomainId()));
throw new CloudRuntimeException("Email is not set for the user."); throw new CloudRuntimeException("Email is not set for the user.");
} }
if (!EnumUtils.getEnumIgnoreCase(Account.State.class, userAccount.getState()).equals(Account.State.ENABLED)) { if (!EnumUtils.getEnumIgnoreCase(Account.State.class, userAccount.getState()).equals(Account.State.ENABLED)) {
logger.error(String.format( logger.error("User is not enabled. username: {} account id: {} domain id: {}",
"User is not enabled. username: %s account id: %d domain id: %s", userAccount.getUsername(), userAccount.getAccountId(), domain.getUuid());
userAccount.getUsername(), userAccount.getAccountId(), domain.getUuid()));
throw new CloudRuntimeException("User is not enabled."); throw new CloudRuntimeException("User is not enabled.");
} }
if (!EnumUtils.getEnumIgnoreCase(Account.State.class, userAccount.getAccountState()).equals(Account.State.ENABLED)) { if (!EnumUtils.getEnumIgnoreCase(Account.State.class, userAccount.getAccountState()).equals(Account.State.ENABLED)) {
logger.error(String.format( logger.error("Account is not enabled. username: {} account id: {} domain id: {}",
"Account is not enabled. username: %s account id: %d domain id: %s", userAccount.getUsername(), userAccount.getAccountId(), domain.getUuid());
userAccount.getUsername(), userAccount.getAccountId(), domain.getUuid()));
throw new CloudRuntimeException("Account is not enabled."); throw new CloudRuntimeException("Account is not enabled.");
} }
if (!domain.getState().equals(Domain.State.Active)) { if (!domain.getState().equals(Domain.State.Active)) {
logger.error(String.format( logger.error("Domain is not active. username: {} account id: {} domain id: {}",
"Domain is not active. username: %s account id: %d domain id: %s", userAccount.getUsername(), userAccount.getAccountId(), domain.getUuid());
userAccount.getUsername(), userAccount.getAccountId(), domain.getUuid()));
throw new CloudRuntimeException("Domain is not active."); throw new CloudRuntimeException("Domain is not active.");
} }
@ -1444,7 +1442,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
// code to be very specific to our needs // code to be very specific to our needs
static class ListenerThread extends Thread { static class ListenerThread extends Thread {
private static Logger LOGGER = LogManager.getLogger(ListenerThread.class); private static final Logger LOGGER = LogManager.getLogger(ListenerThread.class);
private HttpService _httpService = null; private HttpService _httpService = null;
private ServerSocket _serverSocket = null; private ServerSocket _serverSocket = null;
private HttpParams _params = null; private HttpParams _params = null;
@ -1483,7 +1481,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
@Override @Override
public void run() { public void run() {
LOGGER.info("ApiServer listening on port " + _serverSocket.getLocalPort()); LOGGER.info("ApiServer listening on port {}", _serverSocket.getLocalPort());
while (!Thread.interrupted()) { while (!Thread.interrupted()) {
try { try {
// Set up HTTP connection // Set up HTTP connection
@ -1526,10 +1524,10 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
} }
} catch (final IOException ex) { } catch (final IOException ex) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("ApiServer: IOException - " + ex); logger.trace("ApiServer: IOException - {}", ex.toString());
} }
} catch (final HttpException ex) { } catch (final HttpException ex) {
logger.warn("ApiServer: Unrecoverable HTTP protocol violation" + ex); logger.warn("ApiServer: Unrecoverable HTTP protocol violation {}", ex.toString());
} finally { } finally {
try { try {
_conn.shutdown(); _conn.shutdown();
@ -1542,7 +1540,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
@Override @Override
public String getSerializedApiError(final int errorCode, final String errorText, final Map<String, Object[]> apiCommandParams, final String responseType) { public String getSerializedApiError(final int errorCode, final String errorText, final Map<String, Object[]> apiCommandParams, final String responseType) {
String responseName = null; String responseName = null;
Class<?> cmdClass = null; Class<?> cmdClass;
String responseText = null; String responseText = null;
try { try {
@ -1555,7 +1553,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
final String cmdName = ((String[])cmdObj)[0]; final String cmdName = ((String[])cmdObj)[0];
cmdClass = getCmdClass(cmdName); cmdClass = getCmdClass(cmdName);
if (cmdClass != null) { if (cmdClass != null) {
responseName = ((BaseCmd)cmdClass.newInstance()).getCommandName(); responseName = ((BaseCmd)cmdClass.getDeclaredConstructor().newInstance()).getCommandName();
} else { } else {
responseName = "errorresponse"; responseName = "errorresponse";
} }
@ -1577,7 +1575,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
@Override @Override
public String getSerializedApiError(final ServerApiException ex, final Map<String, Object[]> apiCommandParams, final String responseType) { public String getSerializedApiError(final ServerApiException ex, final Map<String, Object[]> apiCommandParams, final String responseType) {
String responseName = null; String responseName = null;
Class<?> cmdClass = null; Class<?> cmdClass;
String responseText = null; String responseText = null;
if (ex == null) { if (ex == null) {
@ -1595,7 +1593,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
final String cmdName = ((String[])cmdObj)[0]; final String cmdName = ((String[])cmdObj)[0];
cmdClass = getCmdClass(cmdName); cmdClass = getCmdClass(cmdName);
if (cmdClass != null) { if (cmdClass != null) {
responseName = ((BaseCmd)cmdClass.newInstance()).getCommandName(); responseName = ((BaseCmd)cmdClass.getDeclaredConstructor().newInstance()).getCommandName();
} else { } else {
responseName = "errorresponse"; responseName = "errorresponse";
} }
@ -1607,8 +1605,8 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
apiResponse.setResponseName(responseName); apiResponse.setResponseName(responseName);
final ArrayList<ExceptionProxyObject> idList = ex.getIdProxyList(); final ArrayList<ExceptionProxyObject> idList = ex.getIdProxyList();
if (idList != null) { if (idList != null) {
for (int i = 0; i < idList.size(); i++) { for (ExceptionProxyObject exceptionProxyObject : idList) {
apiResponse.addProxyObject(idList.get(i)); apiResponse.addProxyObject(exceptionProxyObject);
} }
} }
// Also copy over the cserror code and the function/layer in which // Also copy over the cserror code and the function/layer in which

View File

@ -21,7 +21,6 @@ import java.net.InetAddress;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -76,9 +75,7 @@ import com.cloud.utils.net.NetUtils;
@Component("apiServlet") @Component("apiServlet")
public class ApiServlet extends HttpServlet { public class ApiServlet extends HttpServlet {
protected static Logger LOGGER = LogManager.getLogger(ApiServlet.class); protected static Logger LOGGER = LogManager.getLogger(ApiServlet.class);
private final static List<String> s_clientAddressHeaders = Collections private static final Logger ACCESSLOGGER = LogManager.getLogger("apiserver." + ApiServlet.class.getName());
.unmodifiableList(Arrays.asList("X-Forwarded-For",
"HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR", "Remote_Addr"));
private static final String REPLACEMENT = "_"; private static final String REPLACEMENT = "_";
private static final String LOGGER_REPLACEMENTS = "[\n\r\t]"; private static final String LOGGER_REPLACEMENTS = "[\n\r\t]";
@ -374,7 +371,7 @@ public class ApiServlet extends HttpServlet {
LOGGER.error("unknown exception writing api response", ex); LOGGER.error("unknown exception writing api response", ex);
auditTrailSb.append(" unknown exception writing api response"); auditTrailSb.append(" unknown exception writing api response");
} finally { } finally {
LOGGER.info(auditTrailSb.toString()); ACCESSLOGGER.info(auditTrailSb.toString());
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug("===END=== " + reqStr); LOGGER.debug("===END=== " + reqStr);
} }

View File

@ -151,11 +151,6 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<Templa
activeTmpltSearch.and("store_id", activeTmpltSearch.entity().getDataStoreId(), SearchCriteria.Op.EQ); activeTmpltSearch.and("store_id", activeTmpltSearch.entity().getDataStoreId(), SearchCriteria.Op.EQ);
activeTmpltSearch.and("type", activeTmpltSearch.entity().getTemplateType(), SearchCriteria.Op.EQ); activeTmpltSearch.and("type", activeTmpltSearch.entity().getTemplateType(), SearchCriteria.Op.EQ);
activeTmpltSearch.and("templateState", activeTmpltSearch.entity().getTemplateState(), SearchCriteria.Op.EQ); activeTmpltSearch.and("templateState", activeTmpltSearch.entity().getTemplateState(), SearchCriteria.Op.EQ);
activeTmpltSearch.and().op("public", activeTmpltSearch.entity().isPublicTemplate(), SearchCriteria.Op.EQ);
activeTmpltSearch.or().op("publicNoUrl", activeTmpltSearch.entity().isPublicTemplate(), SearchCriteria.Op.EQ);
activeTmpltSearch.and("url", activeTmpltSearch.entity().getUrl(), SearchCriteria.Op.NULL);
activeTmpltSearch.cp();
activeTmpltSearch.cp();
activeTmpltSearch.done(); activeTmpltSearch.done();
publicTmpltSearch = createSearchBuilder(); publicTmpltSearch = createSearchBuilder();
@ -687,8 +682,6 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<Templa
sc.setParameters("store_id", storeId); sc.setParameters("store_id", storeId);
sc.setParameters("type", TemplateType.USER); sc.setParameters("type", TemplateType.USER);
sc.setParameters("templateState", VirtualMachineTemplate.State.Active); sc.setParameters("templateState", VirtualMachineTemplate.State.Active);
sc.setParameters("public", Boolean.FALSE);
sc.setParameters("publicNoUrl",Boolean.TRUE);
return searchIncludingRemoved(sc, null, null, false); return searchIncludingRemoved(sc, null, null, false);
} }

View File

@ -384,11 +384,10 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
} }
@Override @Override
public boolean scheduleMigration(final VMInstanceVO vm, ReasonType reasonType) { public boolean scheduleMigration(final VMInstanceVO vm, HighAvailabilityManager.ReasonType reasonType) {
if (vm.getHostId() == null) { if (vm.getHostId() == null) {
return false; return false;
} }
if (!VmHaEnabled.valueIn(vm.getDataCenterId())) { if (!VmHaEnabled.valueIn(vm.getDataCenterId())) {
String message = String.format("Unable to schedule migration for the VM %s on host %s, VM high availability manager is disabled.", vm, _hostDao.findById(vm.getHostId())); String message = String.format("Unable to schedule migration for the VM %s on host %s, VM high availability manager is disabled.", vm, _hostDao.findById(vm.getHostId()));
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@ -398,6 +397,7 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
return false; return false;
} }
Long hostId = VirtualMachine.State.Migrating.equals(vm.getState()) ? vm.getLastHostId() : vm.getHostId();
final HaWorkVO work = new HaWorkVO(vm.getId(), vm.getType(), WorkType.Migration, Step.Scheduled, vm.getHostId(), vm.getState(), 0, vm.getUpdated(), reasonType); final HaWorkVO work = new HaWorkVO(vm.getId(), vm.getType(), WorkType.Migration, Step.Scheduled, vm.getHostId(), vm.getState(), 0, vm.getUpdated(), reasonType);
_haDao.persist(work); _haDao.persist(work);
logger.info("Scheduled migration work of VM {} from host {} with HAWork {}", vm, _hostDao.findById(vm.getHostId()), work); logger.info("Scheduled migration work of VM {} from host {} with HAWork {}", vm, _hostDao.findById(vm.getHostId()), work);
@ -813,6 +813,18 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
return null; return null;
} }
logger.info("Migration attempt: for VM {}from host {}. Starting attempt: {}/{} times.", vm, srcHost, 1 + work.getTimesTried(), _maxRetries); logger.info("Migration attempt: for VM {}from host {}. Starting attempt: {}/{} times.", vm, srcHost, 1 + work.getTimesTried(), _maxRetries);
if (VirtualMachine.State.Stopped.equals(vm.getState())) {
logger.info(String.format("vm %s is Stopped, skipping migrate.", vm));
return null;
}
if (VirtualMachine.State.Running.equals(vm.getState()) && srcHostId != vm.getHostId()) {
logger.info(String.format("VM %s is running on a different host %s, skipping migration", vm, vm.getHostId()));
return null;
}
logger.info("Migration attempt: for VM " + vm.getUuid() + "from host id " + srcHostId +
". Starting attempt: " + (1 + work.getTimesTried()) + "/" + _maxRetries + " times.");
try { try {
work.setStep(Step.Migrating); work.setStep(Step.Migrating);
_haDao.update(work.getId(), work); _haDao.update(work.getId(), work);
@ -1148,6 +1160,15 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
@Override @Override
public void run() { public void run() {
logger.info("Starting work"); logger.info("Starting work");
try {
synchronized (this) {
wait(_timeToSleep);
}
} catch (final InterruptedException e) {
logger.info("Interrupted");
}
logger.info("Starting work");
while (!_stopped) { while (!_stopped) {
_managedContext.runWithContext(new Runnable() { _managedContext.runWithContext(new Runnable() {
@Override @Override

View File

@ -1779,10 +1779,19 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
throwInvalidIdException("Network offering with specified id doesn't support adding multiple ip ranges", ntwkOff.getUuid(), NETWORK_OFFERING_ID); throwInvalidIdException("Network offering with specified id doesn't support adding multiple ip ranges", ntwkOff.getUuid(), NETWORK_OFFERING_ID);
} }
if (GuestType.Shared == ntwkOff.getGuestType() && !ntwkOff.isSpecifyVlan() && Objects.isNull(associatedNetworkId)) {
throw new CloudRuntimeException("Associated network must be provided when creating Shared networks when specifyVlan is false");
}
if (GuestType.Shared == ntwkOff.getGuestType()) {
if (!ntwkOff.isSpecifyIpRanges()) {
throw new CloudRuntimeException("The 'specifyipranges' parameter should be true for Shared Networks");
}
if (ipv4 && Objects.isNull(startIP)) {
throw new CloudRuntimeException("IPv4 address range needs to be provided");
}
if (ipv6 && Objects.isNull(startIPv6)) {
throw new CloudRuntimeException("IPv6 address range needs to be provided");
}
}
Pair<Integer, Integer> interfaceMTUs = validateMtuConfig(publicMtu, privateMtu, zone.getId()); Pair<Integer, Integer> interfaceMTUs = validateMtuConfig(publicMtu, privateMtu, zone.getId());
mtuCheckForVpcNetwork(vpcId, interfaceMTUs, publicMtu); mtuCheckForVpcNetwork(vpcId, interfaceMTUs, publicMtu);

View File

@ -203,8 +203,8 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle
private static Map<Service, Map<Capability, String>> setCapabilities() { private static Map<Service, Map<Capability, String>> setCapabilities() {
Map<Service, Map<Capability, String>> capabilities = new HashMap<>(); Map<Service, Map<Capability, String>> capabilities = new HashMap<>();
capabilities.put(Service.UserData, null); capabilities.put(Service.UserData, null);
capabilities.put(Service.Dhcp, new HashMap<>()); capabilities.put(Service.Dhcp, Map.of(Network.Capability.DhcpAccrossMultipleSubnets, "true"));
capabilities.put(Service.Dns, new HashMap<>()); capabilities.put(Service.Dns, Map.of(Capability.AllowDnsSuffixModification, "true"));
return capabilities; return capabilities;
} }
@ -841,7 +841,7 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle
public boolean configDhcpSupportForSubnet(Network network, NicProfile nic, VirtualMachineProfile vm, public boolean configDhcpSupportForSubnet(Network network, NicProfile nic, VirtualMachineProfile vm,
DeployDestination dest, DeployDestination dest,
ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException { ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException {
return false; return true;
} }
@Override @Override

View File

@ -3740,7 +3740,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
VolumeApiResult result = future.get(); VolumeApiResult result = future.get();
if (result.isFailed()) { if (result.isFailed()) {
logger.debug("migrate volume failed:" + result.getResult()); logger.debug("migrate volume failed:" + result.getResult());
throw new StorageUnavailableException("Migrate volume failed: " + result.getResult(), destPool.getId()); throw new CloudRuntimeException("Migrate volume failed: " + result.getResult());
} }
return result.getVolume(); return result.getVolume();
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -4117,7 +4117,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
Optional<String> extractUrl = setExtractVolumeSearchCriteria(sc, volume); Optional<String> extractUrl = setExtractVolumeSearchCriteria(sc, volume);
if (extractUrl.isPresent()) { if (extractUrl.isPresent()) {
return extractUrl.get(); String url = extractUrl.get();
CallContext.current().setEventDetails(String.format("Download URL: %s, volume ID: %s", url, volume.getUuid()));
return url;
} }
VMInstanceVO vm = null; VMInstanceVO vm = null;
@ -4134,7 +4136,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
VmWorkJobVO placeHolder = null; VmWorkJobVO placeHolder = null;
placeHolder = createPlaceHolderWork(vm.getId()); placeHolder = createPlaceHolderWork(vm.getId());
try { try {
return orchestrateExtractVolume(volume.getId(), zoneId); String url = orchestrateExtractVolume(volume.getId(), zoneId);
CallContext.current().setEventDetails(String.format("Download URL: %s, volume ID: %s", url, volume.getUuid()));
return url;
} finally { } finally {
_workJobDao.expunge(placeHolder.getId()); _workJobDao.expunge(placeHolder.getId());
} }
@ -4163,13 +4167,17 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
// retrieve the entity url from job result // retrieve the entity url from job result
if (jobResult != null && jobResult instanceof String) { if (jobResult != null && jobResult instanceof String) {
return (String)jobResult; String url = (String) jobResult;
CallContext.current().setEventDetails(String.format("Download URL: %s, volume ID: %s", url, volume.getUuid()));
return url;
} }
return null; return null;
} }
} }
return orchestrateExtractVolume(volume.getId(), zoneId); String url = orchestrateExtractVolume(volume.getId(), zoneId);
CallContext.current().setEventDetails(String.format("Download URL: %s, volume ID: %s", url, volume.getUuid()));
return url;
} }
@Override @Override

View File

@ -76,7 +76,7 @@ public abstract class DownloadActiveState extends DownloadState {
getDownloadListener().log("handleTimeout, updateMs=" + updateMs + ", curr state= " + getName(), Level.TRACE); getDownloadListener().log("handleTimeout, updateMs=" + updateMs + ", curr state= " + getName(), Level.TRACE);
} }
String newState = getName(); String newState = getName();
if (updateMs > 5 * DownloadListener.STATUS_POLL_INTERVAL) { if (updateMs > DownloadListener.DOWNLOAD_TIMEOUT) {
newState = Status.DOWNLOAD_ERROR.toString(); newState = Status.DOWNLOAD_ERROR.toString();
getDownloadListener().log("timeout: transitioning to download error state, currstate=" + getName(), Level.DEBUG); getDownloadListener().log("timeout: transitioning to download error state, currstate=" + getName(), Level.DEBUG);
} else if (updateMs > 3 * DownloadListener.STATUS_POLL_INTERVAL) { } else if (updateMs > 3 * DownloadListener.STATUS_POLL_INTERVAL) {

View File

@ -100,6 +100,7 @@ public class DownloadListener implements Listener {
protected Logger logger = LogManager.getLogger(getClass()); protected Logger logger = LogManager.getLogger(getClass());
public static final int SMALL_DELAY = 100; public static final int SMALL_DELAY = 100;
public static final long STATUS_POLL_INTERVAL = 10000L; public static final long STATUS_POLL_INTERVAL = 10000L;
public static final long DOWNLOAD_TIMEOUT = 5 * STATUS_POLL_INTERVAL;
public static final String DOWNLOADED = Status.DOWNLOADED.toString(); public static final String DOWNLOADED = Status.DOWNLOADED.toString();
public static final String NOT_DOWNLOADED = Status.NOT_DOWNLOADED.toString(); public static final String NOT_DOWNLOADED = Status.NOT_DOWNLOADED.toString();

View File

@ -293,7 +293,7 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
} }
/** /**
* For each zone ID in {@link TemplateProfile#zoneIdList}, verifies if there is active heuristic rules for allocating template and returns the * For each zone ID in {@link TemplateProfile#getZoneIdList()}, verifies if there is active heuristic rules for allocating template and returns the
* {@link DataStore} returned by the heuristic rule. If there is not an active heuristic rule, then allocate it to a random {@link DataStore}, if the ISO/template is private * {@link DataStore} returned by the heuristic rule. If there is not an active heuristic rule, then allocate it to a random {@link DataStore}, if the ISO/template is private
* or allocate it to all {@link DataStore} in the zone, if it is public. * or allocate it to all {@link DataStore} in the zone, if it is public.
* @param profile * @param profile
@ -453,10 +453,10 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
/** /**
* If the template/ISO is marked as private, then it is allocated to a random secondary storage; otherwise, allocates to every storage pool in every zone given by the * If the template/ISO is marked as private, then it is allocated to a random secondary storage; otherwise, allocates to every storage pool in every zone given by the
* {@link TemplateProfile#zoneIdList}. * {@link TemplateProfile#getZoneIdList()}.
*/ */
private void postUploadAllocation(List<DataStore> imageStores, VMTemplateVO template, List<TemplateOrVolumePostUploadCommand> payloads) { private void postUploadAllocation(List<DataStore> imageStores, VMTemplateVO template, List<TemplateOrVolumePostUploadCommand> payloads) {
Set<Long> zoneSet = new HashSet<Long>(); Set<Long> zoneSet = new HashSet<>();
Collections.shuffle(imageStores); Collections.shuffle(imageStores);
for (DataStore imageStore : imageStores) { for (DataStore imageStore : imageStores) {
Long zoneId_is = imageStore.getScope().getScopeId(); Long zoneId_is = imageStore.getScope().getScopeId();
@ -697,8 +697,8 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
} }
// delete all cache entries for this template // delete all cache entries for this template
List<TemplateInfo> cacheTmpls = imageFactory.listTemplateOnCache(template.getId()); List<TemplateInfo> cachedTemplates = imageFactory.listTemplateOnCache(template.getId());
for (TemplateInfo tmplOnCache : cacheTmpls) { for (TemplateInfo tmplOnCache : cachedTemplates) {
logger.info("Delete template: {} from image cache store: {}", tmplOnCache, tmplOnCache.getDataStore()); logger.info("Delete template: {} from image cache store: {}", tmplOnCache, tmplOnCache.getDataStore());
tmplOnCache.delete(); tmplOnCache.delete();
} }
@ -727,27 +727,32 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
} }
// remove its related ACL permission // remove its related ACL permission
Pair<Class<?>, Long> tmplt = new Pair<Class<?>, Long>(VirtualMachineTemplate.class, template.getId()); Pair<Class<?>, Long> templateClassForId = new Pair<>(VirtualMachineTemplate.class, template.getId());
_messageBus.publish(_name, EntityManager.MESSAGE_REMOVE_ENTITY_EVENT, PublishScope.LOCAL, tmplt); _messageBus.publish(_name, EntityManager.MESSAGE_REMOVE_ENTITY_EVENT, PublishScope.LOCAL, templateClassForId);
checkAndRemoveTemplateDetails(template);
// Remove comments (if any)
AnnotationService.EntityType entityType = template.getFormat().equals(ImageFormat.ISO) ?
AnnotationService.EntityType.ISO : AnnotationService.EntityType.TEMPLATE;
annotationDao.removeByEntityType(entityType.name(), template.getUuid());
List<VMTemplateZoneVO> zoneRegistrations = templateZoneDao.listByTemplateId(template.getId());
if (zoneRegistrations.isEmpty()) {
removeTemplateDetails(template);
removeTemplateAnnotations(template);
}
} }
return success; return success;
} }
private void removeTemplateAnnotations(VMTemplateVO template) {
// Remove comments (if any)
AnnotationService.EntityType entityType = template.getFormat().equals(ImageFormat.ISO) ?
AnnotationService.EntityType.ISO : AnnotationService.EntityType.TEMPLATE;
annotationDao.removeByEntityType(entityType.name(), template.getUuid());
}
/** /**
* removes details of the template and * removes details of the template and
* if the template is registered as deploy as is, * if the template is registered as deploy as is,
* then it also deletes the details related to deploy as is only if there are no VMs using the template * then it also deletes the details related to deploy as is only if there are no VMs using the template
* @param template * @param template
*/ */
void checkAndRemoveTemplateDetails(VMTemplateVO template) { private void removeTemplateDetails(VMTemplateVO template) {
templateDetailsDao.removeDetails(template.getId()); templateDetailsDao.removeDetails(template.getId());
if (template.isDeployAsIs()) { if (template.isDeployAsIs()) {

View File

@ -488,7 +488,9 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
String mode = cmd.getMode(); String mode = cmd.getMode();
Long eventId = cmd.getStartEventId(); Long eventId = cmd.getStartEventId();
return extract(account, templateId, url, zoneId, mode, eventId, true); String extractUrl = extract(account, templateId, url, zoneId, mode, eventId, true);
CallContext.current().setEventDetails(String.format("Download URL: %s, ISO ID: %s", extractUrl, _tmpltDao.findById(templateId).getUuid()));
return extractUrl;
} }
@Override @Override
@ -506,7 +508,9 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
throw new InvalidParameterValueException("unable to find template with id " + templateId); throw new InvalidParameterValueException("unable to find template with id " + templateId);
} }
return extract(caller, templateId, url, zoneId, mode, eventId, false); String extractUrl = extract(caller, templateId, url, zoneId, mode, eventId, false);
CallContext.current().setEventDetails(String.format("Download URL: %s, template ID: %s", extractUrl, template.getUuid()));
return extractUrl;
} }
@Override @Override

View File

@ -386,6 +386,14 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
true, true,
ConfigKey.Scope.Domain); ConfigKey.Scope.Domain);
static ConfigKey<Boolean> userAllowMultipleAccounts = new ConfigKey<>("Advanced",
Boolean.class,
"user.allow.multiple.accounts",
"false",
"Determines if the same username can be added to more than one account in the same domain (SAML-only).",
true,
ConfigKey.Scope.Domain);
protected AccountManagerImpl() { protected AccountManagerImpl() {
super(); super();
} }
@ -1289,7 +1297,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
// Check permissions // Check permissions
checkAccess(getCurrentCallingAccount(), domain); checkAccess(getCurrentCallingAccount(), domain);
if (!_userAccountDao.validateUsernameInDomain(userName, domainId)) { if (!userAllowMultipleAccounts.valueInDomain(domainId) && !_userAccountDao.validateUsernameInDomain(userName, domainId)) {
throw new InvalidParameterValueException(String.format("The user %s already exists in domain %s", userName, domain)); throw new InvalidParameterValueException(String.format("The user %s already exists in domain %s", userName, domain));
} }
@ -1477,9 +1485,15 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
throw new PermissionDeniedException(String.format("Account: %s is a system account, can't add a user to it", account)); throw new PermissionDeniedException(String.format("Account: %s is a system account, can't add a user to it", account));
} }
if (!_userAccountDao.validateUsernameInDomain(userName, domainId)) { if (!userAllowMultipleAccounts.valueInDomain(domainId) && !_userAccountDao.validateUsernameInDomain(userName, domainId)) {
throw new CloudRuntimeException(String.format("The user %s already exists in domain %s", userName, domain)); throw new CloudRuntimeException("The user " + userName + " already exists in domain " + domainId);
} }
List<UserVO> duplicatedUsers = _userDao.findUsersByName(userName);
for (UserVO duplicatedUser : duplicatedUsers) {
// users can't exist in same account
assertUserNotAlreadyInAccount(duplicatedUser, account);
}
UserVO user; UserVO user;
user = createUser(account.getId(), userName, password, firstName, lastName, email, timeZone, userUUID, source); user = createUser(account.getId(), userName, password, firstName, lastName, email, timeZone, userUUID, source);
return user; return user;
@ -1607,7 +1621,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
* <li> The username must be unique in each domain. Therefore, if there is already another user with the same username, an {@link InvalidParameterValueException} is thrown. * <li> The username must be unique in each domain. Therefore, if there is already another user with the same username, an {@link InvalidParameterValueException} is thrown.
* </ul> * </ul>
*/ */
protected void validateAndUpdateUsernameIfNeeded(UpdateUserCmd updateUserCmd, UserVO user, Account account) { protected void validateAndUpdateUsernameIfNeeded(UpdateUserCmd updateUserCmd, UserVO newUser, Account newAccount) {
String userName = updateUserCmd.getUsername(); String userName = updateUserCmd.getUsername();
if (userName == null) { if (userName == null) {
return; return;
@ -1615,18 +1629,21 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
if (StringUtils.isBlank(userName)) { if (StringUtils.isBlank(userName)) {
throw new InvalidParameterValueException("Username cannot be empty."); throw new InvalidParameterValueException("Username cannot be empty.");
} }
List<UserVO> duplicatedUsers = _userDao.findUsersByName(userName); List<UserVO> existingUsers = _userDao.findUsersByName(userName);
for (UserVO duplicatedUser : duplicatedUsers) { for (UserVO existingUser : existingUsers) {
if (duplicatedUser.getId() == user.getId()) { if (existingUser.getId() == newUser.getId()) {
continue; continue;
} }
Account duplicatedUserAccountWithUserThatHasTheSameUserName = _accountDao.findById(duplicatedUser.getAccountId());
if (duplicatedUserAccountWithUserThatHasTheSameUserName.getDomainId() == account.getDomainId()) { // duplicate usernames cannot exist in same domain unless explicitly configured
DomainVO domain = _domainDao.findById(duplicatedUserAccountWithUserThatHasTheSameUserName.getDomainId()); if (!userAllowMultipleAccounts.valueInDomain(newAccount.getDomainId())) {
throw new InvalidParameterValueException(String.format("Username (%s) already exists in domain (%s)", duplicatedUser, domain)); assertUserNotAlreadyInDomain(existingUser, newAccount);
} }
// can't rename a username to an existing one in the same account
assertUserNotAlreadyInAccount(existingUser, newAccount);
} }
user.setUsername(userName); newUser.setUsername(userName);
} }
/** /**
@ -1895,7 +1912,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
// make sure the account is enabled too // make sure the account is enabled too
// if the user is either locked already or disabled already, don't change state...only lock currently enabled // if the user is either locked already or disabled already, don't change state...only lock currently enabled
// users // users
boolean success; boolean success;
if (user.getState().equals(State.LOCKED)) { if (user.getState().equals(State.LOCKED)) {
// already locked...no-op // already locked...no-op
@ -3408,7 +3425,8 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
@Override @Override
public ConfigKey<?>[] getConfigKeys() { public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {UseSecretKeyInResponse, enableUserTwoFactorAuthentication, return new ConfigKey<?>[] {UseSecretKeyInResponse, enableUserTwoFactorAuthentication,
userTwoFactorAuthenticationDefaultProvider, mandateUserTwoFactorAuthentication, userTwoFactorAuthenticationIssuer, apiKeyAccess}; userTwoFactorAuthenticationDefaultProvider, mandateUserTwoFactorAuthentication, userTwoFactorAuthenticationIssuer, apiKeyAccess,
userAllowMultipleAccounts};
} }
public List<UserTwoFactorAuthenticator> getUserTwoFactorAuthenticationProviders() { public List<UserTwoFactorAuthenticator> getUserTwoFactorAuthenticationProviders() {
@ -3593,4 +3611,21 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
return userAccountVO; return userAccountVO;
}); });
} }
void assertUserNotAlreadyInAccount(User existingUser, Account newAccount) {
System.out.println(existingUser.getAccountId());
System.out.println(newAccount.getId());
if (existingUser.getAccountId() == newAccount.getId()) {
AccountVO existingAccount = _accountDao.findById(newAccount.getId());
throw new InvalidParameterValueException(String.format("Username [%s] already exists in account [id=%s,name=%s]", existingUser.getUsername(), existingAccount.getUuid(), existingAccount.getAccountName()));
}
}
void assertUserNotAlreadyInDomain(User existingUser, Account originalAccount) {
Account existingAccount = _accountDao.findById(existingUser.getAccountId());
if (existingAccount.getDomainId() == originalAccount.getDomainId()) {
DomainVO existingDomain = _domainDao.findById(existingAccount.getDomainId());
throw new InvalidParameterValueException(String.format("Username [%s] already exists in domain [id=%s,name=%s] user account [id=%s,name=%s]", existingUser.getUsername(), existingDomain.getUuid(), existingDomain.getName(), existingAccount.getUuid(), existingAccount.getAccountName()));
}
}
} }

View File

@ -1408,4 +1408,75 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
Assert.assertNull(updatedUser.getUser2faProvider()); Assert.assertNull(updatedUser.getUser2faProvider());
Assert.assertNull(updatedUser.getKeyFor2fa()); Assert.assertNull(updatedUser.getKeyFor2fa());
} }
@Test(expected = InvalidParameterValueException.class)
public void testAssertUserNotAlreadyInAccount_UserExistsInAccount() {
User existingUser = new UserVO();
existingUser.setUsername("testuser");
existingUser.setAccountId(1L);
Account newAccount = Mockito.mock(Account.class);
Mockito.when(newAccount.getId()).thenReturn(1L);
AccountVO existingAccount = Mockito.mock(AccountVO.class);
Mockito.when(existingAccount.getUuid()).thenReturn("existing-account-uuid");
Mockito.when(existingAccount.getAccountName()).thenReturn("existing-account");
Mockito.when(_accountDao.findById(1L)).thenReturn(existingAccount);
accountManagerImpl.assertUserNotAlreadyInAccount(existingUser, newAccount);
}
@Test
public void testAssertUserNotAlreadyInAccount_UserExistsInDiffAccount() {
User existingUser = new UserVO();
existingUser.setUsername("testuser");
existingUser.setAccountId(2L);
Account newAccount = Mockito.mock(Account.class);
Mockito.when(newAccount.getId()).thenReturn(1L);
accountManagerImpl.assertUserNotAlreadyInAccount(existingUser, newAccount);
}
@Test(expected = InvalidParameterValueException.class)
public void testAssertUserNotAlreadyInDomain_UserExistsInDomain() {
User existingUser = new UserVO();
existingUser.setUsername("testuser");
existingUser.setAccountId(1L);
Account originalAccount = Mockito.mock(Account.class);
Mockito.when(originalAccount.getDomainId()).thenReturn(1L);
AccountVO existingAccount = Mockito.mock(AccountVO.class);
Mockito.when(existingAccount.getDomainId()).thenReturn(1L);
Mockito.when(existingAccount.getUuid()).thenReturn("existing-account-uuid");
Mockito.when(existingAccount.getAccountName()).thenReturn("existing-account");
DomainVO existingDomain = Mockito.mock(DomainVO.class);
Mockito.when(existingDomain.getUuid()).thenReturn("existing-domain-uuid");
Mockito.when(existingDomain.getName()).thenReturn("existing-domain");
Mockito.when(_accountDao.findById(1L)).thenReturn(existingAccount);
Mockito.when(_domainDao.findById(1L)).thenReturn(existingDomain);
accountManagerImpl.assertUserNotAlreadyInDomain(existingUser, originalAccount);
}
@Test
public void testAssertUserNotAlreadyInDomain_UserExistsInDiffDomain() {
User existingUser = new UserVO();
existingUser.setUsername("testuser");
existingUser.setAccountId(1L);
Account originalAccount = Mockito.mock(Account.class);
Mockito.when(originalAccount.getDomainId()).thenReturn(1L);
AccountVO existingAccount = Mockito.mock(AccountVO.class);
Mockito.when(existingAccount.getDomainId()).thenReturn(2L);
Mockito.when(_accountDao.findById(1L)).thenReturn(existingAccount);
accountManagerImpl.assertUserNotAlreadyInDomain(existingUser, originalAccount);
}
} }

View File

@ -88,6 +88,7 @@ export default {
this.showSwitcher = false this.showSwitcher = false
return return
} }
this.samlAccounts = samlAccounts
this.samlAccounts = _.orderBy(samlAccounts, ['domainPath'], ['asc']) this.samlAccounts = _.orderBy(samlAccounts, ['domainPath'], ['asc'])
const currentAccount = this.samlAccounts.filter(x => { const currentAccount = this.samlAccounts.filter(x => {
return x.userId === store.getters.userInfo.id return x.userId === store.getters.userInfo.id
@ -109,6 +110,8 @@ export default {
this.$message.success(`Switched to "${account.accountName} (${account.domainPath})"`) this.$message.success(`Switched to "${account.accountName} (${account.domainPath})"`)
this.$router.go() this.$router.go()
}) })
}).else(error => {
console.log('error refreshing with new user context: ' + error)
}) })
} }
} }

View File

@ -646,7 +646,7 @@
<span v-else>{{ resource.podname || resource.pod || resource.podid }}</span> <span v-else>{{ resource.podname || resource.pod || resource.podid }}</span>
</div> </div>
</div> </div>
<div class="resource-detail-item" v-if="resource.zoneid"> <div class="resource-detail-item" v-if="resource.zoneid && !['template', 'iso'].includes($route.path.split('/')[1])">
<div class="resource-detail-item__label">{{ $t('label.zone') }}</div> <div class="resource-detail-item__label">{{ $t('label.zone') }}</div>
<div class="resource-detail-item__details"> <div class="resource-detail-item__details">
<span v-if="images.zone"> <span v-if="images.zone">
@ -733,7 +733,7 @@
<span v-else>{{ resource.managementserver || resource.managementserverid }}</span> <span v-else>{{ resource.managementserver || resource.managementserverid }}</span>
</div> </div>
</div> </div>
<div class="resource-detail-item" v-if="resource.created"> <div class="resource-detail-item" v-if="resource.created && !['template', 'iso'].includes($route.path.split('/')[1])">
<div class="resource-detail-item__label">{{ $t('label.created') }}</div> <div class="resource-detail-item__label">{{ $t('label.created') }}</div>
<div class="resource-detail-item__details"> <div class="resource-detail-item__details">
<calendar-outlined />{{ $toLocaleDate(resource.created) }} <calendar-outlined />{{ $toLocaleDate(resource.created) }}

View File

@ -331,7 +331,7 @@ const user = {
commit('SET_MS_ID', msId) commit('SET_MS_ID', msId)
// Ensuring we get the user info so that store.getters.user is never empty when the page is freshly loaded // Ensuring we get the user info so that store.getters.user is never empty when the page is freshly loaded
api('listUsers', { username: Cookies.get('username'), listall: true }).then(response => { api('listUsers', { id: Cookies.get('userid'), listall: true }).then(response => {
const result = response.listusersresponse.user[0] const result = response.listusersresponse.user[0]
commit('SET_INFO', result) commit('SET_INFO', result)
commit('SET_NAME', result.firstname + ' ' + result.lastname) commit('SET_NAME', result.firstname + ' ' + result.lastname)
@ -404,7 +404,7 @@ const user = {
}).catch(ignored => {}) }).catch(ignored => {})
} }
api('listUsers', { username: Cookies.get('username') }).then(response => { api('listUsers', { id: Cookies.get('userid') }).then(response => {
const result = response.listusersresponse.user[0] const result = response.listusersresponse.user[0]
commit('SET_INFO', result) commit('SET_INFO', result)
commit('SET_NAME', result.firstname + ' ' + result.lastname) commit('SET_NAME', result.firstname + ' ' + result.lastname)

View File

@ -48,6 +48,9 @@
<span v-if="record.isready">{{ $t('label.yes') }}</span> <span v-if="record.isready">{{ $t('label.yes') }}</span>
<span v-else>{{ $t('label.no') }}</span> <span v-else>{{ $t('label.no') }}</span>
</template> </template>
<template v-else-if="column.key === 'created'">
<span v-if="record.created">{{ $toLocaleDate(record.created) }}</span>
</template>
<template v-if="column.key === 'actions'"> <template v-if="column.key === 'actions'">
<span style="margin-right: 5px"> <span style="margin-right: 5px">
<tooltip-button <tooltip-button
@ -262,6 +265,11 @@ export default {
title: this.$t('label.zonename'), title: this.$t('label.zonename'),
dataIndex: 'zonename' dataIndex: 'zonename'
}, },
{
key: 'created',
title: this.$t('label.created'),
dataIndex: 'created'
},
{ {
title: this.$t('label.status'), title: this.$t('label.status'),
dataIndex: 'status' dataIndex: 'status'

View File

@ -48,6 +48,9 @@
<span v-if="record.isready">{{ $t('label.yes') }}</span> <span v-if="record.isready">{{ $t('label.yes') }}</span>
<span v-else>{{ $t('label.no') }}</span> <span v-else>{{ $t('label.no') }}</span>
</template> </template>
<template v-else-if="column.key === 'created'">
<span v-if="record.created">{{ $toLocaleDate(record.created) }}</span>
</template>
<template v-if="column.key === 'actions'"> <template v-if="column.key === 'actions'">
<tooltip-button <tooltip-button
style="margin-right: 5px" style="margin-right: 5px"
@ -308,6 +311,11 @@ export default {
title: this.$t('label.zonename'), title: this.$t('label.zonename'),
dataIndex: 'zonename' dataIndex: 'zonename'
}, },
{
key: 'created',
title: this.$t('label.created'),
dataIndex: 'created'
},
{ {
title: this.$t('label.status'), title: this.$t('label.status'),
dataIndex: 'status' dataIndex: 'status'

View File

@ -703,6 +703,7 @@ export default {
title = this.$t('label.view') title = this.$t('label.view')
break break
case 'virtualmachinename': case 'virtualmachinename':
title = this.$t('label.virtualmachinename')
dataIndex = 'name' dataIndex = 'name'
break break
default: default:

View File

@ -449,12 +449,16 @@ export default {
services.push({ services.push({
name: 'Dhcp', name: 'Dhcp',
provider: [ provider: [
{ name: 'VpcVirtualRouter' } { name: 'VpcVirtualRouter' },
{ name: 'ConfigDrive' }
] ]
}) })
services.push({ services.push({
name: 'Dns', name: 'Dns',
provider: [{ name: 'VpcVirtualRouter' }] provider: [
{ name: 'VpcVirtualRouter' },
{ name: 'ConfigDrive' }
]
}) })
services.push({ services.push({
name: 'Lb', name: 'Lb',

View File

@ -143,7 +143,7 @@ const vueConfig = {
ws: false, ws: false,
changeOrigin: true, changeOrigin: true,
proxyTimeout: 10 * 60 * 1000, // 10 minutes proxyTimeout: 10 * 60 * 1000, // 10 minutes
cookieDomainRewrite: '*', cookieDomainRewrite: process.env.CS_COOKIE_HOST || 'localhost',
cookiePathRewrite: { cookiePathRewrite: {
'/client': '/' '/client': '/'
} }