mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Merge branch '4.20'
This commit is contained in:
commit
3c75d9363b
@ -17,6 +17,7 @@
|
||||
package com.cloud.storage;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
List<Status> PENDING_DOWNLOAD_STATES = List.of(Status.NOT_DOWNLOADED, Status.DOWNLOAD_IN_PROGRESS);
|
||||
|
||||
String getInstallPath();
|
||||
|
||||
long getTemplateId();
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
package org.apache.cloudstack.api.command.user.iso;
|
||||
|
||||
|
||||
import com.cloud.dc.DataCenter;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
@ -101,7 +102,15 @@ public class ExtractIsoCmd extends BaseAsyncCmd {
|
||||
|
||||
@Override
|
||||
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
|
||||
|
||||
@ -101,7 +101,15 @@ public class ExtractTemplateCmd extends BaseAsyncCmd {
|
||||
|
||||
@Override
|
||||
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
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
package org.apache.cloudstack.api.command.user.volume;
|
||||
|
||||
|
||||
import com.cloud.dc.DataCenter;
|
||||
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
|
||||
import org.apache.cloudstack.api.ACL;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
@ -114,12 +115,15 @@ public class ExtractVolumeCmd extends BaseAsyncCmd {
|
||||
|
||||
@Override
|
||||
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
|
||||
public void execute() {
|
||||
CallContext.current().setEventDetails("Volume Id: " + this._uuidMgr.getUuid(Volume.class, getId()));
|
||||
CallContext.current().setEventDetails(getEventDescription());
|
||||
String uploadUrl = _volumeService.extractVolume(this);
|
||||
if (uploadUrl != null) {
|
||||
ExtractResponse response = _responseGenerator.createVolumeExtractResponse(id, zoneId, getEntityOwnerId(), mode, uploadUrl);
|
||||
|
||||
@ -5240,10 +5240,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
||||
workJob = newVmWorkJobAndInfo.first();
|
||||
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());
|
||||
|
||||
|
||||
@ -208,9 +208,7 @@ public class DataMigrationUtility {
|
||||
List<TemplateInfo> files = new LinkedList<>();
|
||||
for (TemplateDataStoreVO template : templates) {
|
||||
VMTemplateVO templateVO = templateDao.findById(template.getTemplateId());
|
||||
if (template.getState() == ObjectInDataStoreStateMachine.State.Ready && templateVO != null &&
|
||||
(!templateVO.isPublicTemplate() || (templateVO.isPublicTemplate() && templateVO.getUrl() == null)) &&
|
||||
templateVO.getHypervisorType() != Hypervisor.HypervisorType.Simulator && templateVO.getParentTemplateId() == null) {
|
||||
if (shouldMigrateTemplate(template, templateVO)) {
|
||||
files.add(templateFactory.getTemplate(template.getTemplateId(), srcDataStore));
|
||||
}
|
||||
}
|
||||
@ -231,6 +229,34 @@ public class DataMigrationUtility {
|
||||
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
|
||||
* 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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -503,7 +503,7 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject<State, Vi
|
||||
|
||||
@Override
|
||||
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
|
||||
|
||||
@ -47,3 +47,25 @@ CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.storage_pool', 'used_iops', 'bigint
|
||||
|
||||
-- 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"');
|
||||
|
||||
-- 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');
|
||||
|
||||
@ -24,6 +24,9 @@ import java.util.concurrent.ExecutionException;
|
||||
|
||||
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.DataMotionService;
|
||||
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)) {
|
||||
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);
|
||||
templateInfo.processEvent(ObjectInDataStoreStateMachine.Event.MigrateDataRequested);
|
||||
destDataObject.processEvent(ObjectInDataStoreStateMachine.Event.MigrateDataRequested);
|
||||
migrateJob(future, templateInfo, destDataObject, destDatastore);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
destDataObject = destDatastore.create(srcDataObject);
|
||||
srcDataObject.processEvent(ObjectInDataStoreStateMachine.Event.MigrateDataRequested);
|
||||
destDataObject.processEvent(ObjectInDataStoreStateMachine.Event.MigrateDataRequested);
|
||||
@ -160,6 +158,69 @@ public class SecondaryStorageServiceImpl implements SecondaryStorageService {
|
||||
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 {
|
||||
MigrateDataContext<DataObjectResult> context = new MigrateDataContext<DataObjectResult>(null, future, srcDataObject, destDataObject, destDatastore);
|
||||
AsyncCallbackDispatcher<SecondaryStorageServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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 DEFAULT_OUTGOING_WORKERS = 5;
|
||||
|
||||
private final List<ClusterManagerListener> _listeners = new ArrayList<ClusterManagerListener>();
|
||||
private final Map<Long, ManagementServerHostVO> _activePeers = new HashMap<Long, ManagementServerHostVO>();
|
||||
private final List<ClusterManagerListener> _listeners = new ArrayList<>();
|
||||
private final Map<Long, ManagementServerHostVO> _activePeers = new HashMap<>();
|
||||
|
||||
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 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 final ExecutorService _executor;
|
||||
@ -118,12 +118,12 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
|
||||
|
||||
private String _clusterNodeIP = "127.0.0.1";
|
||||
|
||||
private final List<ClusterServicePdu> _clusterPduOutgoingQueue = new ArrayList<ClusterServicePdu>();
|
||||
private final List<ClusterServicePdu> _clusterPduIncomingQueue = new ArrayList<ClusterServicePdu>();
|
||||
private final Map<Long, ClusterServiceRequestPdu> _outgoingPdusWaitingForAck = new HashMap<Long, ClusterServiceRequestPdu>();
|
||||
private final List<ClusterServicePdu> _clusterPduOutgoingQueue = new ArrayList<>();
|
||||
private final List<ClusterServicePdu> _clusterPduIncomingQueue = new ArrayList<>();
|
||||
private final Map<Long, ClusterServiceRequestPdu> _outgoingPdusWaitingForAck = new HashMap<>();
|
||||
|
||||
public ClusterManagerImpl() {
|
||||
_clusterPeers = new HashMap<String, ClusterService>();
|
||||
_clusterPeers = new HashMap<>();
|
||||
|
||||
// executor to perform remote-calls in another thread context, to avoid potential
|
||||
// recursive remote calls between nodes
|
||||
@ -161,7 +161,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
|
||||
}
|
||||
|
||||
private void cancelClusterRequestToPeer(final String strPeer) {
|
||||
final List<ClusterServiceRequestPdu> candidates = new ArrayList<ClusterServiceRequestPdu>();
|
||||
final List<ClusterServiceRequestPdu> candidates = new ArrayList<>();
|
||||
synchronized (_outgoingPdusWaitingForAck) {
|
||||
for (final Map.Entry<Long, ClusterServiceRequestPdu> entry : _outgoingPdusWaitingForAck.entrySet()) {
|
||||
if (entry.getValue().getDestPeer().equalsIgnoreCase(strPeer)) {
|
||||
@ -193,7 +193,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
|
||||
synchronized (_clusterPduOutgoingQueue) {
|
||||
try {
|
||||
_clusterPduOutgoingQueue.wait(timeoutMs);
|
||||
} catch (final InterruptedException e) {
|
||||
} catch (final InterruptedException ignored) {
|
||||
}
|
||||
|
||||
if (_clusterPduOutgoingQueue.size() > 0) {
|
||||
@ -216,7 +216,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
|
||||
synchronized (_clusterPduIncomingQueue) {
|
||||
try {
|
||||
_clusterPduIncomingQueue.wait(timeoutMs);
|
||||
} catch (final InterruptedException e) {
|
||||
} catch (final InterruptedException ignored) {
|
||||
}
|
||||
|
||||
if (_clusterPduIncomingQueue.size() > 0) {
|
||||
@ -449,7 +449,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
|
||||
synchronized (pdu) {
|
||||
try {
|
||||
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 (logger.isDebugEnabled()) {
|
||||
logger.debug("Management server heartbeat takes too long to finish. profiler: " + profiler.toString() + ", profilerHeartbeatUpdate: " +
|
||||
profilerHeartbeatUpdate.toString() + ", profilerPeerScan: " + profilerPeerScan.toString());
|
||||
logger.debug("Management server heartbeat takes too long to finish. profiler: " + profiler + ", profilerHeartbeatUpdate: " +
|
||||
profilerHeartbeatUpdate + ", profilerPeerScan: " + profilerPeerScan);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -685,7 +685,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
|
||||
synchronized (_notificationMsgs) {
|
||||
try {
|
||||
_notificationMsgs.wait(1000);
|
||||
} catch (final InterruptedException e) {
|
||||
} catch (final InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -745,7 +745,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
|
||||
|
||||
try {
|
||||
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) {
|
||||
// Check if peer state is Up in the period
|
||||
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();
|
||||
profilerSyncClusterInfo.start();
|
||||
final List<ManagementServerHostVO> removedNodeList = new ArrayList<ManagementServerHostVO>();
|
||||
final List<ManagementServerHostVO> invalidatedNodeList = new ArrayList<ManagementServerHostVO>();
|
||||
final List<ManagementServerHostVO> removedNodeList = new ArrayList<>();
|
||||
final List<ManagementServerHostVO> invalidatedNodeList = new ArrayList<>();
|
||||
|
||||
if (_mshostId != null) {
|
||||
|
||||
@ -941,7 +941,7 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
|
||||
try {
|
||||
JmxUtil.unregisterMBean("ClusterManager", "Node " + mshost.getId());
|
||||
} 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 {
|
||||
JmxUtil.unregisterMBean("ClusterManager", "Node " + mshost.getId());
|
||||
} 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 {
|
||||
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) {
|
||||
final List<ManagementServerHostVO> newNodeList = new ArrayList<ManagementServerHostVO>();
|
||||
final List<ManagementServerHostVO> newNodeList = new ArrayList<>();
|
||||
for (final ManagementServerHostVO mshost : currentList) {
|
||||
if (!_activePeers.containsKey(mshost.getId())) {
|
||||
_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);
|
||||
}
|
||||
|
||||
final ManagementServerHostVO mshost = Transaction.execute(new TransactionCallback<ManagementServerHostVO>() {
|
||||
final ManagementServerHostVO mshost = Transaction.execute(new TransactionCallback<>() {
|
||||
@Override
|
||||
public ManagementServerHostVO doInTransaction(final TransactionStatus status) {
|
||||
|
||||
@ -1105,11 +1105,13 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
|
||||
}
|
||||
|
||||
if (_mshostId != null) {
|
||||
final ManagementServerHostVO mshost = _mshostDao.findByMsid(_msId);
|
||||
ManagementServerHostVO mshost = _mshostDao.findByMsid(_msId);
|
||||
if (mshost != null) {
|
||||
ManagementServerStatusVO mshostStatus = mshostStatusDao.findByMsId(mshost.getUuid());
|
||||
if (mshostStatus != null) {
|
||||
mshost.setState(ManagementServerHost.State.Down);
|
||||
mshostStatus.setLastJvmStop(new Date());
|
||||
_mshostDao.update(_mshostId, mshost);
|
||||
mshostStatusDao.update(mshostStatus.getId(), mshostStatus);
|
||||
} else {
|
||||
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 {
|
||||
_heartbeatScheduler.awaitTermination(EXECUTOR_SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
_executor.awaitTermination(EXECUTOR_SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
} catch (final InterruptedException e) {
|
||||
} catch (final InterruptedException ignored) {
|
||||
}
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
@ -1271,14 +1273,14 @@ public class ClusterManagerImpl extends ManagerBase implements ClusterManager, C
|
||||
if (sch != null) {
|
||||
try {
|
||||
sch.close();
|
||||
} catch (final IOException e) {
|
||||
} catch (final IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (final InterruptedException ex) {
|
||||
} catch (final InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5104,9 +5104,9 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
|
||||
answer.setVolumeChainInfo(chainInfo);
|
||||
return answer;
|
||||
} 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);
|
||||
return new MigrateVolumeAnswer(cmd, false, msg, null);
|
||||
return new MigrateVolumeAnswer(cmd, false, e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -260,7 +260,7 @@ public class VmwareStorageMotionStrategy implements DataMotionStrategy {
|
||||
} else {
|
||||
answer = agentMgr.sendTo(sourcePool.getDataCenterId(), HypervisorType.VMware, cmd);
|
||||
}
|
||||
updateVolumeAfterMigration(answer, srcData, destData);
|
||||
handleAnswerAndUpdateVolumeAfterMigration(answer, srcData, destData);
|
||||
CopyCommandResult result = new CopyCommandResult(null, answer);
|
||||
callback.complete(result);
|
||||
}
|
||||
@ -286,21 +286,19 @@ public class VmwareStorageMotionStrategy implements DataMotionStrategy {
|
||||
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());
|
||||
if (!(answer instanceof MigrateVolumeAnswer)) {
|
||||
// OfflineVmwareMigration: reset states and such
|
||||
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);
|
||||
}
|
||||
resetVolumeState(srcData, destinationVO);
|
||||
throw new CloudRuntimeException("unexpected answer from hypervisor agent: " + answer.getDetails());
|
||||
}
|
||||
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()) {
|
||||
String format = "retrieved '%s' as new path for volume(%d)";
|
||||
logger.debug(String.format(format, ans.getVolumePath(), destData.getId()));
|
||||
@ -311,6 +309,17 @@ public class VmwareStorageMotionStrategy implements DataMotionStrategy {
|
||||
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
|
||||
public void copyAsync(Map<VolumeInfo, DataStore> volumeMap, VirtualMachineTO vmTo, Host srcHost, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
|
||||
Answer answer = null;
|
||||
|
||||
@ -81,10 +81,10 @@ public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthentic
|
||||
//////////////// 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;
|
||||
|
||||
@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;
|
||||
|
||||
@Override
|
||||
@ -131,10 +131,12 @@ public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthentic
|
||||
}
|
||||
|
||||
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 Domain domain = _domainDao.findByUuid(domainUuid);
|
||||
final UserAccount nextUserAccount = _accountService.getUserAccountById(user.getId());
|
||||
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(),
|
||||
"The requested user account is locked and cannot be switched to, please contact your administrator.",
|
||||
params, responseType));
|
||||
@ -145,25 +147,31 @@ public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthentic
|
||||
|| !nextUserAccount.getExternalEntity().equals(currentUserAccount.getExternalEntity())
|
||||
|| (nextUserAccount.getDomainId() != domain.getId())
|
||||
|| (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(),
|
||||
"User account is not allowed to switch to the requested account",
|
||||
params, responseType));
|
||||
}
|
||||
try {
|
||||
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(),
|
||||
nextUserAccount.getDomainId(), null, remoteAddress, params);
|
||||
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);
|
||||
}
|
||||
} 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 {
|
||||
List<UserAccountVO> switchableAccounts = _userAccountDao.getAllUsersByNameAndEntity(currentUserAccount.getUsername(), currentUserAccount.getExternalEntity());
|
||||
if (switchableAccounts != null && switchableAccounts.size() > 0 && currentUserId != User.UID_SYSTEM) {
|
||||
List<SamlUserAccountResponse> accountResponses = new ArrayList<SamlUserAccountResponse>();
|
||||
if (switchableAccounts != null && !switchableAccounts.isEmpty() && currentUserId != User.UID_SYSTEM) {
|
||||
List<SamlUserAccountResponse> accountResponses = new ArrayList<>();
|
||||
for (UserAccountVO userAccount: switchableAccounts) {
|
||||
User user = _userDao.getUser(userAccount.getId());
|
||||
Domain domain = _domainService.getDomain(userAccount.getDomainId());
|
||||
@ -176,8 +184,9 @@ public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthentic
|
||||
accountResponse.setAccountName(userAccount.getAccountName());
|
||||
accountResponse.setIdpId(user.getExternalEntity());
|
||||
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.setResponseName(getCommandName());
|
||||
return ApiResponseSerializer.toSerializedString(response, responseType);
|
||||
@ -196,7 +205,7 @@ public class ListAndSwitchSAMLAccountCmd extends BaseCmd implements APIAuthentic
|
||||
@Override
|
||||
public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) {
|
||||
for (PluggableAPIAuthenticator authManager: authenticators) {
|
||||
if (authManager != null && authManager instanceof SAML2AuthManager) {
|
||||
if (authManager instanceof SAML2AuthManager) {
|
||||
_samlAuthManager = (SAML2AuthManager) authManager;
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ import com.cloud.user.UserAccountVO;
|
||||
import com.cloud.user.dao.UserAccountDao;
|
||||
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 {
|
||||
private static final String s_name = "loginresponse";
|
||||
|
||||
@ -97,7 +97,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
|
||||
@Inject
|
||||
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);
|
||||
|
||||
SAML2AuthManager samlAuthManager;
|
||||
@ -190,7 +190,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
|
||||
String authnId = SAMLUtils.generateSecureRandomId();
|
||||
samlAuthManager.saveToken(authnId, domainPath, idpMetadata.getEntityId());
|
||||
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);
|
||||
return "";
|
||||
} if (params.containsKey("SAMLart")) {
|
||||
@ -207,7 +207,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
|
||||
params, responseType));
|
||||
}
|
||||
|
||||
String username = null;
|
||||
String username;
|
||||
Issuer issuer = processedSAMLResponse.getIssuer();
|
||||
SAMLProviderMetadata spMetadata = samlAuthManager.getSPMetadata();
|
||||
SAMLProviderMetadata idpMetadata = samlAuthManager.getIdPMetadata(issuer.getValue());
|
||||
@ -273,7 +273,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
|
||||
try {
|
||||
assertion = decrypter.decrypt(encryptedAssertion);
|
||||
} catch (DecryptionException e) {
|
||||
logger.warn("SAML EncryptedAssertion error: " + e.toString());
|
||||
logger.warn("SAML EncryptedAssertion error: " + e);
|
||||
}
|
||||
if (assertion == null) {
|
||||
continue;
|
||||
@ -310,7 +310,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
|
||||
|
||||
UserAccount userAccount = null;
|
||||
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
|
||||
// Users can switch to other allowed accounts later
|
||||
for (UserAccountVO possibleUserAccount : possibleUserAccounts) {
|
||||
@ -370,7 +370,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
|
||||
@Override
|
||||
public void setAuthenticators(List<PluggableAPIAuthenticator> authenticators) {
|
||||
for (PluggableAPIAuthenticator authManager: authenticators) {
|
||||
if (authManager != null && authManager instanceof SAML2AuthManager) {
|
||||
if (authManager instanceof SAML2AuthManager) {
|
||||
samlAuthManager = (SAML2AuthManager) authManager;
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,6 +79,10 @@ public interface SAML2AuthManager extends PluggableAPIAuthenticator, PluggableSe
|
||||
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);
|
||||
|
||||
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 getIdPMetadata(String entityId);
|
||||
Collection<SAMLProviderMetadata> getAllIdPMetadata();
|
||||
|
||||
@ -541,6 +541,6 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
|
||||
SAMLCloudStackRedirectionUrl, SAMLUserAttributeName,
|
||||
SAMLIdentityProviderMetadataURL, SAMLDefaultIdentityProviderId,
|
||||
SAMLSignatureAlgorithm, SAMLAppendDomainSuffix, SAMLTimeout, SAMLCheckSignature,
|
||||
SAMLForceAuthn, SAMLUserSessionKeyPathAttribute};
|
||||
SAMLForceAuthn, SAMLUserSessionKeyPathAttribute, SAMLRequirePasswordLogin};
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,11 +152,11 @@ public class SAMLUtils {
|
||||
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 = "";
|
||||
try {
|
||||
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;
|
||||
if (spMetadata.getKeyPair() != null) {
|
||||
privateKey = spMetadata.getKeyPair().getPrivate();
|
||||
@ -169,13 +169,36 @@ public class SAMLUtils {
|
||||
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
|
||||
IssuerBuilder issuerBuilder = new IssuerBuilder();
|
||||
Issuer issuer = issuerBuilder.buildObject();
|
||||
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();
|
||||
AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder.buildObject(
|
||||
SAMLConstants.SAML20_NS,
|
||||
@ -187,23 +210,7 @@ public class SAMLUtils {
|
||||
RequestedAuthnContext requestedAuthnContext = requestedAuthnContextBuilder.buildObject();
|
||||
requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
|
||||
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);
|
||||
|
||||
return authnRequest;
|
||||
}
|
||||
|
||||
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 {
|
||||
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 path = SAML2AuthManager.SAMLUserSessionKeyPathAttribute.value();
|
||||
String domain = null;
|
||||
@ -317,6 +307,18 @@ public class SAMLUtils {
|
||||
} catch (URISyntaxException ex) {
|
||||
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 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);
|
||||
@ -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));
|
||||
}
|
||||
|
||||
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
|
||||
* @param key PublicKey
|
||||
|
||||
@ -58,7 +58,7 @@ public class SAMLUtilsTest extends TestCase {
|
||||
String idpUrl = "http://idp.domain.example";
|
||||
String spId = "cloudstack";
|
||||
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.getDestination(), idpUrl);
|
||||
assertEquals(req.getIssuer().getValue(), spId);
|
||||
@ -86,7 +86,7 @@ public class SAMLUtilsTest extends TestCase {
|
||||
idpMetadata.setSsoUrl(idpUrl);
|
||||
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");
|
||||
assertEquals(urlScheme, redirectUrl.getScheme());
|
||||
assertEquals(idpDomain, redirectUrl.getHost());
|
||||
@ -115,7 +115,7 @@ public class SAMLUtilsTest extends TestCase {
|
||||
idpMetadata.setSsoUrl(idpUrl);
|
||||
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");
|
||||
assertEquals(urlScheme, redirectUrl.getScheme());
|
||||
assertEquals(idpDomain, redirectUrl.getHost());
|
||||
|
||||
@ -213,7 +213,6 @@ public class ListAndSwitchSAMLAccountCmdTest extends TestCase {
|
||||
loginCmdResponse.set2FAenabled("false");
|
||||
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);
|
||||
Mockito.doNothing().when(resp).sendRedirect(nullable(String.class));
|
||||
try {
|
||||
cmd.authenticate("command", params, session, null, HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
|
||||
} catch (ServerApiException exception) {
|
||||
@ -221,7 +220,6 @@ public class ListAndSwitchSAMLAccountCmdTest extends TestCase {
|
||||
} finally {
|
||||
// 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(resp, Mockito.times(1)).sendRedirect(anyString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -196,6 +196,7 @@ import static org.apache.cloudstack.user.UserPasswordResetManager.UserPasswordRe
|
||||
|
||||
@Component
|
||||
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]";
|
||||
|
||||
@ -240,9 +241,9 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
|
||||
private EventDistributor eventDistributor = null;
|
||||
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"));
|
||||
|
||||
@Inject
|
||||
@ -358,7 +359,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
String jobEvent = eventInfo.second();
|
||||
|
||||
if (logger.isTraceEnabled())
|
||||
logger.trace("Handle asyjob publish event " + jobEvent);
|
||||
logger.trace("Handle asyjob publish event {}", jobEvent);
|
||||
if (eventDistributor == null) {
|
||||
setEventDistributor(ComponentContext.getComponent(EventDistributor.class));
|
||||
}
|
||||
@ -383,7 +384,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
cmdEventType = eventTypeObj;
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Retrieved cmdEventType from job info: " + cmdEventType);
|
||||
logger.debug("Retrieved cmdEventType from job info: {}", cmdEventType);
|
||||
} else {
|
||||
if (logger.isDebugEnabled())
|
||||
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) {
|
||||
if (apiPort == null || apiPort <= 0) {
|
||||
logger.trace(String.format("Skipping setting up listener for integration port as %s is set to %d",
|
||||
IntegrationAPIPort.key(), apiPort));
|
||||
logger.trace("Skipping setting up listener for integration port as {} is set to {}",
|
||||
IntegrationAPIPort.key(), apiPort);
|
||||
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);
|
||||
listenerThread.start();
|
||||
}
|
||||
@ -442,24 +443,24 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
Integer apiPort = IntegrationAPIPort.value(); // api port, null by default
|
||||
|
||||
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");
|
||||
} else {
|
||||
dispatcher.setCreateSnapshotQueueSizeLimit(snapshotLimit);
|
||||
}
|
||||
|
||||
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");
|
||||
} else {
|
||||
dispatcher.setMigrateQueueSizeLimit(migrationLimit);
|
||||
}
|
||||
|
||||
final Set<Class<?>> cmdClasses = new HashSet<Class<?>>();
|
||||
final Set<Class<?>> cmdClasses = new HashSet<>();
|
||||
for (final PluggableService pluggableService : pluggableServices) {
|
||||
cmdClasses.addAll(pluggableService.getCommands());
|
||||
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();
|
||||
List<Class<?>> apiCmdList = s_apiNameCmdClassMap.get(apiName);
|
||||
if (apiCmdList == null) {
|
||||
apiCmdList = new ArrayList<Class<?>>();
|
||||
apiCmdList = new ArrayList<>();
|
||||
s_apiNameCmdClassMap.put(apiName, apiCmdList);
|
||||
}
|
||||
apiCmdList.add(cmdClass);
|
||||
@ -574,14 +575,14 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
throw e;
|
||||
}
|
||||
} finally {
|
||||
logger.info(sb.toString());
|
||||
ACCESSLOGGER.info(sb.toString());
|
||||
CallContext.unregister();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
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 Iterator keysIter = keys.iterator();
|
||||
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 {
|
||||
checkCharacterInkParams(params);
|
||||
|
||||
String response = null;
|
||||
String response;
|
||||
String[] command = null;
|
||||
|
||||
try {
|
||||
@ -625,7 +626,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
if (authManager.getAPIAuthenticator(command[0]) != 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 Iterator keysIter = keys.iterator();
|
||||
while (keysIter.hasNext()) {
|
||||
@ -641,16 +642,16 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
if (cmdClass != null) {
|
||||
APICommand annotation = cmdClass.getAnnotation(APICommand.class);
|
||||
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());
|
||||
}
|
||||
|
||||
BaseCmd cmdObj = (BaseCmd)cmdClass.newInstance();
|
||||
BaseCmd cmdObj = (BaseCmd)cmdClass.getDeclaredConstructor().newInstance();
|
||||
cmdObj = ComponentContext.inject(cmdObj);
|
||||
cmdObj.configure();
|
||||
cmdObj.setFullUrlParams(paramMap);
|
||||
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
|
||||
StringBuilder log = new StringBuilder();
|
||||
@ -659,14 +660,11 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
} else {
|
||||
final String errorString = "Unknown API command: " + command[0];
|
||||
logger.warn(errorString);
|
||||
auditTrailSb.append(" " + errorString);
|
||||
auditTrailSb.append(" ").append(errorString);
|
||||
throw new ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, errorString);
|
||||
}
|
||||
}
|
||||
} catch (final InvalidParameterValueException ex) {
|
||||
logger.info(ex.getMessage());
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, ex.getMessage(), ex);
|
||||
} catch (final IllegalArgumentException ex) {
|
||||
} catch (final InvalidParameterValueException | IllegalArgumentException ex) {
|
||||
logger.info(ex.getMessage());
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, ex.getMessage(), ex);
|
||||
} catch (final PermissionDeniedException ex) {
|
||||
@ -679,9 +677,9 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
buf.append(obj.getUuid());
|
||||
buf.append(" ");
|
||||
}
|
||||
logger.info("PermissionDenied: " + ex.getMessage() + " on objs: [" + buf.toString() + "]");
|
||||
logger.info("PermissionDenied: " + ex.getMessage() + " on objs: [" + buf + "]");
|
||||
} else {
|
||||
logger.info("PermissionDenied: " + ex.getMessage());
|
||||
logger.info("PermissionDenied: {}", ex.getMessage());
|
||||
}
|
||||
throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, ex.getMessage(), ex);
|
||||
} catch (final AccountLimitException ex) {
|
||||
@ -756,7 +754,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
throw new ServerApiException(ApiErrorCode.SERVICE_UNAVAILABLE, msg);
|
||||
}
|
||||
Long objectId = null;
|
||||
String objectUuid = null;
|
||||
String objectUuid;
|
||||
if (cmdObj instanceof BaseAsyncCreateCmd) {
|
||||
final BaseAsyncCreateCmd createCmd = (BaseAsyncCreateCmd)cmdObj;
|
||||
dispatcher.dispatchCreateCmd(createCmd, params);
|
||||
@ -797,7 +795,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
|
||||
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()));
|
||||
if (asyncCmd.getHttpMethod() != null) {
|
||||
params.put(ApiConstants.HTTPMETHOD, asyncCmd.getHttpMethod().toString());
|
||||
@ -860,9 +858,9 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void buildAsyncListResponse(final BaseListCmd command, final Account account) {
|
||||
final List<ResponseObject> responses = ((ListResponse)command.getResponseObject()).getResponses();
|
||||
if (responses != null && responses.size() > 0) {
|
||||
List<? extends AsyncJob> jobs = null;
|
||||
final List<ResponseObject> responses = ((ListResponse<ResponseObject>)command.getResponseObject()).getResponses();
|
||||
if (responses != null && !responses.isEmpty()) {
|
||||
List<? extends AsyncJob> jobs;
|
||||
|
||||
// list all jobs for ROOT admin
|
||||
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());
|
||||
}
|
||||
|
||||
if (jobs.size() == 0) {
|
||||
if (jobs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<String, AsyncJob> objectJobMap = new HashMap<String, AsyncJob>();
|
||||
final Map<String, AsyncJob> objectJobMap = new HashMap<>();
|
||||
for (final AsyncJob job : jobs) {
|
||||
if (job.getInstanceId() == null) {
|
||||
continue;
|
||||
@ -912,7 +910,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
if (Boolean.TRUE.equals(apiKeyAccessEnabled)) {
|
||||
return true;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
@ -921,7 +919,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
if (Boolean.TRUE.equals(apiKeyAccessEnabled)) {
|
||||
return true;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
try {
|
||||
String apiKey = null;
|
||||
String secretKey = null;
|
||||
String secretKey;
|
||||
String signature = 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
|
||||
// - sign the request, verify the signature is the same
|
||||
final List<String> parameterNames = new ArrayList<String>();
|
||||
|
||||
for (final Object paramNameObj : requestParameters.keySet()) {
|
||||
parameterNames.add((String)paramNameObj); // put the name in a list that we'll sort later
|
||||
}
|
||||
// put the name in a list that we'll sort later
|
||||
final List<String> parameterNames = new ArrayList<>(requestParameters.keySet());
|
||||
|
||||
Collections.sort(parameterNames);
|
||||
|
||||
@ -1006,7 +1002,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
return false; // no signature, bad request
|
||||
}
|
||||
|
||||
Date expiresTS = null;
|
||||
Date expiresTS;
|
||||
// FIXME: Hard coded signature, why not have an enum
|
||||
if ("3".equals(signatureVersion)) {
|
||||
// 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)) {
|
||||
signature = signature.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;
|
||||
}
|
||||
}
|
||||
|
||||
final TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB);
|
||||
txn.close();
|
||||
User user = null;
|
||||
User user;
|
||||
// verify there is a user with this api key
|
||||
final Pair<User, Account> userAcctPair = accountMgr.findUserByApiKey(apiKey);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1078,7 +1074,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
|
||||
if (!equalSig) {
|
||||
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 {
|
||||
CallContext.register(user, account);
|
||||
}
|
||||
@ -1137,10 +1133,10 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
session.removeAttribute("domain_UUID");
|
||||
}
|
||||
|
||||
final Enumeration attrNames = session.getAttributeNames();
|
||||
final Enumeration<String> attrNames = session.getAttributeNames();
|
||||
if (attrNames != null) {
|
||||
while (attrNames.hasMoreElements()) {
|
||||
final String attrName = (String) attrNames.nextElement();
|
||||
final String attrName = attrNames.nextElement();
|
||||
final Object attrObj = session.getAttribute(attrName);
|
||||
if (ApiConstants.USERNAME.equalsIgnoreCase(attrName)) {
|
||||
response.setUsername(attrObj.toString());
|
||||
@ -1202,7 +1198,14 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
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) {
|
||||
final String timezone = userAcct.getTimezone();
|
||||
float offsetInHrs = 0f;
|
||||
@ -1214,7 +1217,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
final long longDate = date.getTime();
|
||||
final float offsetInMs = (t.getOffset(longDate));
|
||||
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());
|
||||
@ -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
|
||||
// to the login response so that session-based authenticators know to send the key back
|
||||
final SecureRandom sesssionKeyRandom = new SecureRandom();
|
||||
final byte sessionKeyBytes[] = new byte[20];
|
||||
final byte[] sessionKeyBytes = new byte[20];
|
||||
sesssionKeyRandom.nextBytes(sessionKeyBytes);
|
||||
final String sessionKey = Base64.encodeBase64URLSafeString(sessionKeyBytes);
|
||||
session.setAttribute(ApiConstants.SESSIONKEY, sessionKey);
|
||||
@ -1285,7 +1288,6 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
@Override
|
||||
public void logoutUser(final long userId) {
|
||||
accountMgr.logoutUser(userId);
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1313,30 +1315,26 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
throw new CloudRuntimeException(errorMessage);
|
||||
}
|
||||
if (StringUtils.isBlank(userAccount.getEmail())) {
|
||||
logger.error(String.format(
|
||||
"Email is not set. username: %s account id: %d domain id: %d",
|
||||
userAccount.getUsername(), userAccount.getAccountId(), userAccount.getDomainId()));
|
||||
logger.error("Email is not set. username: {} account id: {} domain id: {}",
|
||||
userAccount.getUsername(), userAccount.getAccountId(), userAccount.getDomainId());
|
||||
throw new CloudRuntimeException("Email is not set for the user.");
|
||||
}
|
||||
|
||||
if (!EnumUtils.getEnumIgnoreCase(Account.State.class, userAccount.getState()).equals(Account.State.ENABLED)) {
|
||||
logger.error(String.format(
|
||||
"User is not enabled. username: %s account id: %d domain id: %s",
|
||||
userAccount.getUsername(), userAccount.getAccountId(), domain.getUuid()));
|
||||
logger.error("User is not enabled. username: {} account id: {} domain id: {}",
|
||||
userAccount.getUsername(), userAccount.getAccountId(), domain.getUuid());
|
||||
throw new CloudRuntimeException("User is not enabled.");
|
||||
}
|
||||
|
||||
if (!EnumUtils.getEnumIgnoreCase(Account.State.class, userAccount.getAccountState()).equals(Account.State.ENABLED)) {
|
||||
logger.error(String.format(
|
||||
"Account is not enabled. username: %s account id: %d domain id: %s",
|
||||
userAccount.getUsername(), userAccount.getAccountId(), domain.getUuid()));
|
||||
logger.error("Account is not enabled. username: {} account id: {} domain id: {}",
|
||||
userAccount.getUsername(), userAccount.getAccountId(), domain.getUuid());
|
||||
throw new CloudRuntimeException("Account is not enabled.");
|
||||
}
|
||||
|
||||
if (!domain.getState().equals(Domain.State.Active)) {
|
||||
logger.error(String.format(
|
||||
"Domain is not active. username: %s account id: %d domain id: %s",
|
||||
userAccount.getUsername(), userAccount.getAccountId(), domain.getUuid()));
|
||||
logger.error("Domain is not active. username: {} account id: {} domain id: {}",
|
||||
userAccount.getUsername(), userAccount.getAccountId(), domain.getUuid());
|
||||
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
|
||||
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 ServerSocket _serverSocket = null;
|
||||
private HttpParams _params = null;
|
||||
@ -1483,7 +1481,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LOGGER.info("ApiServer listening on port " + _serverSocket.getLocalPort());
|
||||
LOGGER.info("ApiServer listening on port {}", _serverSocket.getLocalPort());
|
||||
while (!Thread.interrupted()) {
|
||||
try {
|
||||
// Set up HTTP connection
|
||||
@ -1526,10 +1524,10 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
}
|
||||
} catch (final IOException ex) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("ApiServer: IOException - " + ex);
|
||||
logger.trace("ApiServer: IOException - {}", ex.toString());
|
||||
}
|
||||
} catch (final HttpException ex) {
|
||||
logger.warn("ApiServer: Unrecoverable HTTP protocol violation" + ex);
|
||||
logger.warn("ApiServer: Unrecoverable HTTP protocol violation {}", ex.toString());
|
||||
} finally {
|
||||
try {
|
||||
_conn.shutdown();
|
||||
@ -1542,7 +1540,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
@Override
|
||||
public String getSerializedApiError(final int errorCode, final String errorText, final Map<String, Object[]> apiCommandParams, final String responseType) {
|
||||
String responseName = null;
|
||||
Class<?> cmdClass = null;
|
||||
Class<?> cmdClass;
|
||||
String responseText = null;
|
||||
|
||||
try {
|
||||
@ -1555,7 +1553,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
final String cmdName = ((String[])cmdObj)[0];
|
||||
cmdClass = getCmdClass(cmdName);
|
||||
if (cmdClass != null) {
|
||||
responseName = ((BaseCmd)cmdClass.newInstance()).getCommandName();
|
||||
responseName = ((BaseCmd)cmdClass.getDeclaredConstructor().newInstance()).getCommandName();
|
||||
} else {
|
||||
responseName = "errorresponse";
|
||||
}
|
||||
@ -1577,7 +1575,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
@Override
|
||||
public String getSerializedApiError(final ServerApiException ex, final Map<String, Object[]> apiCommandParams, final String responseType) {
|
||||
String responseName = null;
|
||||
Class<?> cmdClass = null;
|
||||
Class<?> cmdClass;
|
||||
String responseText = null;
|
||||
|
||||
if (ex == null) {
|
||||
@ -1595,7 +1593,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
final String cmdName = ((String[])cmdObj)[0];
|
||||
cmdClass = getCmdClass(cmdName);
|
||||
if (cmdClass != null) {
|
||||
responseName = ((BaseCmd)cmdClass.newInstance()).getCommandName();
|
||||
responseName = ((BaseCmd)cmdClass.getDeclaredConstructor().newInstance()).getCommandName();
|
||||
} else {
|
||||
responseName = "errorresponse";
|
||||
}
|
||||
@ -1607,8 +1605,8 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
|
||||
apiResponse.setResponseName(responseName);
|
||||
final ArrayList<ExceptionProxyObject> idList = ex.getIdProxyList();
|
||||
if (idList != null) {
|
||||
for (int i = 0; i < idList.size(); i++) {
|
||||
apiResponse.addProxyObject(idList.get(i));
|
||||
for (ExceptionProxyObject exceptionProxyObject : idList) {
|
||||
apiResponse.addProxyObject(exceptionProxyObject);
|
||||
}
|
||||
}
|
||||
// Also copy over the cserror code and the function/layer in which
|
||||
|
||||
@ -21,7 +21,6 @@ import java.net.InetAddress;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -76,9 +75,7 @@ import com.cloud.utils.net.NetUtils;
|
||||
@Component("apiServlet")
|
||||
public class ApiServlet extends HttpServlet {
|
||||
protected static Logger LOGGER = LogManager.getLogger(ApiServlet.class);
|
||||
private final static List<String> s_clientAddressHeaders = Collections
|
||||
.unmodifiableList(Arrays.asList("X-Forwarded-For",
|
||||
"HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR", "Remote_Addr"));
|
||||
private static final Logger ACCESSLOGGER = LogManager.getLogger("apiserver." + ApiServlet.class.getName());
|
||||
private static final String REPLACEMENT = "_";
|
||||
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);
|
||||
auditTrailSb.append(" unknown exception writing api response");
|
||||
} finally {
|
||||
LOGGER.info(auditTrailSb.toString());
|
||||
ACCESSLOGGER.info(auditTrailSb.toString());
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("===END=== " + reqStr);
|
||||
}
|
||||
|
||||
@ -151,11 +151,6 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<Templa
|
||||
activeTmpltSearch.and("store_id", activeTmpltSearch.entity().getDataStoreId(), SearchCriteria.Op.EQ);
|
||||
activeTmpltSearch.and("type", activeTmpltSearch.entity().getTemplateType(), 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();
|
||||
|
||||
publicTmpltSearch = createSearchBuilder();
|
||||
@ -687,8 +682,6 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<Templa
|
||||
sc.setParameters("store_id", storeId);
|
||||
sc.setParameters("type", TemplateType.USER);
|
||||
sc.setParameters("templateState", VirtualMachineTemplate.State.Active);
|
||||
sc.setParameters("public", Boolean.FALSE);
|
||||
sc.setParameters("publicNoUrl",Boolean.TRUE);
|
||||
return searchIncludingRemoved(sc, null, null, false);
|
||||
}
|
||||
|
||||
|
||||
@ -384,11 +384,10 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean scheduleMigration(final VMInstanceVO vm, ReasonType reasonType) {
|
||||
public boolean scheduleMigration(final VMInstanceVO vm, HighAvailabilityManager.ReasonType reasonType) {
|
||||
if (vm.getHostId() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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()));
|
||||
if (logger.isDebugEnabled()) {
|
||||
@ -398,6 +397,7 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
|
||||
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);
|
||||
_haDao.persist(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;
|
||||
}
|
||||
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 {
|
||||
work.setStep(Step.Migrating);
|
||||
_haDao.update(work.getId(), work);
|
||||
@ -1148,6 +1160,15 @@ public class HighAvailabilityManagerImpl extends ManagerBase implements Configur
|
||||
@Override
|
||||
public void run() {
|
||||
logger.info("Starting work");
|
||||
try {
|
||||
synchronized (this) {
|
||||
wait(_timeToSleep);
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
logger.info("Interrupted");
|
||||
}
|
||||
logger.info("Starting work");
|
||||
|
||||
while (!_stopped) {
|
||||
_managedContext.runWithContext(new Runnable() {
|
||||
@Override
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
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());
|
||||
mtuCheckForVpcNetwork(vpcId, interfaceMTUs, publicMtu);
|
||||
|
||||
|
||||
@ -203,8 +203,8 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle
|
||||
private static Map<Service, Map<Capability, String>> setCapabilities() {
|
||||
Map<Service, Map<Capability, String>> capabilities = new HashMap<>();
|
||||
capabilities.put(Service.UserData, null);
|
||||
capabilities.put(Service.Dhcp, new HashMap<>());
|
||||
capabilities.put(Service.Dns, new HashMap<>());
|
||||
capabilities.put(Service.Dhcp, Map.of(Network.Capability.DhcpAccrossMultipleSubnets, "true"));
|
||||
capabilities.put(Service.Dns, Map.of(Capability.AllowDnsSuffixModification, "true"));
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
@ -841,7 +841,7 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle
|
||||
public boolean configDhcpSupportForSubnet(Network network, NicProfile nic, VirtualMachineProfile vm,
|
||||
DeployDestination dest,
|
||||
ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -3740,7 +3740,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
VolumeApiResult result = future.get();
|
||||
if (result.isFailed()) {
|
||||
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();
|
||||
} catch (InterruptedException e) {
|
||||
@ -4117,7 +4117,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
|
||||
Optional<String> extractUrl = setExtractVolumeSearchCriteria(sc, volume);
|
||||
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;
|
||||
@ -4134,7 +4136,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
VmWorkJobVO placeHolder = null;
|
||||
placeHolder = createPlaceHolderWork(vm.getId());
|
||||
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 {
|
||||
_workJobDao.expunge(placeHolder.getId());
|
||||
}
|
||||
@ -4163,13 +4167,17 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
|
||||
// retrieve the entity url from job result
|
||||
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 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
|
||||
|
||||
@ -76,7 +76,7 @@ public abstract class DownloadActiveState extends DownloadState {
|
||||
getDownloadListener().log("handleTimeout, updateMs=" + updateMs + ", curr state= " + getName(), Level.TRACE);
|
||||
}
|
||||
String newState = getName();
|
||||
if (updateMs > 5 * DownloadListener.STATUS_POLL_INTERVAL) {
|
||||
if (updateMs > DownloadListener.DOWNLOAD_TIMEOUT) {
|
||||
newState = Status.DOWNLOAD_ERROR.toString();
|
||||
getDownloadListener().log("timeout: transitioning to download error state, currstate=" + getName(), Level.DEBUG);
|
||||
} else if (updateMs > 3 * DownloadListener.STATUS_POLL_INTERVAL) {
|
||||
|
||||
@ -100,6 +100,7 @@ public class DownloadListener implements Listener {
|
||||
protected Logger logger = LogManager.getLogger(getClass());
|
||||
public static final int SMALL_DELAY = 100;
|
||||
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 NOT_DOWNLOADED = Status.NOT_DOWNLOADED.toString();
|
||||
|
||||
@ -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
|
||||
* or allocate it to all {@link DataStore} in the zone, if it is public.
|
||||
* @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
|
||||
* {@link TemplateProfile#zoneIdList}.
|
||||
* {@link TemplateProfile#getZoneIdList()}.
|
||||
*/
|
||||
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);
|
||||
for (DataStore imageStore : imageStores) {
|
||||
Long zoneId_is = imageStore.getScope().getScopeId();
|
||||
@ -697,8 +697,8 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
|
||||
}
|
||||
|
||||
// delete all cache entries for this template
|
||||
List<TemplateInfo> cacheTmpls = imageFactory.listTemplateOnCache(template.getId());
|
||||
for (TemplateInfo tmplOnCache : cacheTmpls) {
|
||||
List<TemplateInfo> cachedTemplates = imageFactory.listTemplateOnCache(template.getId());
|
||||
for (TemplateInfo tmplOnCache : cachedTemplates) {
|
||||
logger.info("Delete template: {} from image cache store: {}", tmplOnCache, tmplOnCache.getDataStore());
|
||||
tmplOnCache.delete();
|
||||
}
|
||||
@ -727,18 +727,23 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
|
||||
}
|
||||
|
||||
// remove its related ACL permission
|
||||
Pair<Class<?>, Long> tmplt = new Pair<Class<?>, Long>(VirtualMachineTemplate.class, template.getId());
|
||||
_messageBus.publish(_name, EntityManager.MESSAGE_REMOVE_ENTITY_EVENT, PublishScope.LOCAL, tmplt);
|
||||
Pair<Class<?>, Long> templateClassForId = new Pair<>(VirtualMachineTemplate.class, template.getId());
|
||||
_messageBus.publish(_name, EntityManager.MESSAGE_REMOVE_ENTITY_EVENT, PublishScope.LOCAL, templateClassForId);
|
||||
|
||||
checkAndRemoveTemplateDetails(template);
|
||||
List<VMTemplateZoneVO> zoneRegistrations = templateZoneDao.listByTemplateId(template.getId());
|
||||
if (zoneRegistrations.isEmpty()) {
|
||||
removeTemplateDetails(template);
|
||||
removeTemplateAnnotations(template);
|
||||
}
|
||||
}
|
||||
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());
|
||||
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -747,7 +752,7 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
|
||||
* then it also deletes the details related to deploy as is only if there are no VMs using the template
|
||||
* @param template
|
||||
*/
|
||||
void checkAndRemoveTemplateDetails(VMTemplateVO template) {
|
||||
private void removeTemplateDetails(VMTemplateVO template) {
|
||||
templateDetailsDao.removeDetails(template.getId());
|
||||
|
||||
if (template.isDeployAsIs()) {
|
||||
|
||||
@ -488,7 +488,9 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
|
||||
String mode = cmd.getMode();
|
||||
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
|
||||
@ -506,7 +508,9 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
|
||||
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
|
||||
|
||||
@ -386,6 +386,14 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
||||
true,
|
||||
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() {
|
||||
super();
|
||||
}
|
||||
@ -1289,7 +1297,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
||||
// Check permissions
|
||||
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));
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
if (!_userAccountDao.validateUsernameInDomain(userName, domainId)) {
|
||||
throw new CloudRuntimeException(String.format("The user %s already exists in domain %s", userName, domain));
|
||||
if (!userAllowMultipleAccounts.valueInDomain(domainId) && !_userAccountDao.validateUsernameInDomain(userName, domainId)) {
|
||||
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;
|
||||
user = createUser(account.getId(), userName, password, firstName, lastName, email, timeZone, userUUID, source);
|
||||
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.
|
||||
* </ul>
|
||||
*/
|
||||
protected void validateAndUpdateUsernameIfNeeded(UpdateUserCmd updateUserCmd, UserVO user, Account account) {
|
||||
protected void validateAndUpdateUsernameIfNeeded(UpdateUserCmd updateUserCmd, UserVO newUser, Account newAccount) {
|
||||
String userName = updateUserCmd.getUsername();
|
||||
if (userName == null) {
|
||||
return;
|
||||
@ -1615,18 +1629,21 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
||||
if (StringUtils.isBlank(userName)) {
|
||||
throw new InvalidParameterValueException("Username cannot be empty.");
|
||||
}
|
||||
List<UserVO> duplicatedUsers = _userDao.findUsersByName(userName);
|
||||
for (UserVO duplicatedUser : duplicatedUsers) {
|
||||
if (duplicatedUser.getId() == user.getId()) {
|
||||
List<UserVO> existingUsers = _userDao.findUsersByName(userName);
|
||||
for (UserVO existingUser : existingUsers) {
|
||||
if (existingUser.getId() == newUser.getId()) {
|
||||
continue;
|
||||
}
|
||||
Account duplicatedUserAccountWithUserThatHasTheSameUserName = _accountDao.findById(duplicatedUser.getAccountId());
|
||||
if (duplicatedUserAccountWithUserThatHasTheSameUserName.getDomainId() == account.getDomainId()) {
|
||||
DomainVO domain = _domainDao.findById(duplicatedUserAccountWithUserThatHasTheSameUserName.getDomainId());
|
||||
throw new InvalidParameterValueException(String.format("Username (%s) already exists in domain (%s)", duplicatedUser, domain));
|
||||
|
||||
// duplicate usernames cannot exist in same domain unless explicitly configured
|
||||
if (!userAllowMultipleAccounts.valueInDomain(newAccount.getDomainId())) {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3408,7 +3425,8 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[] {UseSecretKeyInResponse, enableUserTwoFactorAuthentication,
|
||||
userTwoFactorAuthenticationDefaultProvider, mandateUserTwoFactorAuthentication, userTwoFactorAuthenticationIssuer, apiKeyAccess};
|
||||
userTwoFactorAuthenticationDefaultProvider, mandateUserTwoFactorAuthentication, userTwoFactorAuthenticationIssuer, apiKeyAccess,
|
||||
userAllowMultipleAccounts};
|
||||
}
|
||||
|
||||
public List<UserTwoFactorAuthenticator> getUserTwoFactorAuthenticationProviders() {
|
||||
@ -3593,4 +3611,21 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1408,4 +1408,75 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
|
||||
Assert.assertNull(updatedUser.getUser2faProvider());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,6 +88,7 @@ export default {
|
||||
this.showSwitcher = false
|
||||
return
|
||||
}
|
||||
this.samlAccounts = samlAccounts
|
||||
this.samlAccounts = _.orderBy(samlAccounts, ['domainPath'], ['asc'])
|
||||
const currentAccount = this.samlAccounts.filter(x => {
|
||||
return x.userId === store.getters.userInfo.id
|
||||
@ -109,6 +110,8 @@ export default {
|
||||
this.$message.success(`Switched to "${account.accountName} (${account.domainPath})"`)
|
||||
this.$router.go()
|
||||
})
|
||||
}).else(error => {
|
||||
console.log('error refreshing with new user context: ' + error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -646,7 +646,7 @@
|
||||
<span v-else>{{ resource.podname || resource.pod || resource.podid }}</span>
|
||||
</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__details">
|
||||
<span v-if="images.zone">
|
||||
@ -733,7 +733,7 @@
|
||||
<span v-else>{{ resource.managementserver || resource.managementserverid }}</span>
|
||||
</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__details">
|
||||
<calendar-outlined />{{ $toLocaleDate(resource.created) }}
|
||||
|
||||
@ -331,7 +331,7 @@ const user = {
|
||||
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
|
||||
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]
|
||||
commit('SET_INFO', result)
|
||||
commit('SET_NAME', result.firstname + ' ' + result.lastname)
|
||||
@ -404,7 +404,7 @@ const user = {
|
||||
}).catch(ignored => {})
|
||||
}
|
||||
|
||||
api('listUsers', { username: Cookies.get('username') }).then(response => {
|
||||
api('listUsers', { id: Cookies.get('userid') }).then(response => {
|
||||
const result = response.listusersresponse.user[0]
|
||||
commit('SET_INFO', result)
|
||||
commit('SET_NAME', result.firstname + ' ' + result.lastname)
|
||||
|
||||
@ -48,6 +48,9 @@
|
||||
<span v-if="record.isready">{{ $t('label.yes') }}</span>
|
||||
<span v-else>{{ $t('label.no') }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'created'">
|
||||
<span v-if="record.created">{{ $toLocaleDate(record.created) }}</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'actions'">
|
||||
<span style="margin-right: 5px">
|
||||
<tooltip-button
|
||||
@ -262,6 +265,11 @@ export default {
|
||||
title: this.$t('label.zonename'),
|
||||
dataIndex: 'zonename'
|
||||
},
|
||||
{
|
||||
key: 'created',
|
||||
title: this.$t('label.created'),
|
||||
dataIndex: 'created'
|
||||
},
|
||||
{
|
||||
title: this.$t('label.status'),
|
||||
dataIndex: 'status'
|
||||
|
||||
@ -48,6 +48,9 @@
|
||||
<span v-if="record.isready">{{ $t('label.yes') }}</span>
|
||||
<span v-else>{{ $t('label.no') }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'created'">
|
||||
<span v-if="record.created">{{ $toLocaleDate(record.created) }}</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'actions'">
|
||||
<tooltip-button
|
||||
style="margin-right: 5px"
|
||||
@ -308,6 +311,11 @@ export default {
|
||||
title: this.$t('label.zonename'),
|
||||
dataIndex: 'zonename'
|
||||
},
|
||||
{
|
||||
key: 'created',
|
||||
title: this.$t('label.created'),
|
||||
dataIndex: 'created'
|
||||
},
|
||||
{
|
||||
title: this.$t('label.status'),
|
||||
dataIndex: 'status'
|
||||
|
||||
@ -703,6 +703,7 @@ export default {
|
||||
title = this.$t('label.view')
|
||||
break
|
||||
case 'virtualmachinename':
|
||||
title = this.$t('label.virtualmachinename')
|
||||
dataIndex = 'name'
|
||||
break
|
||||
default:
|
||||
|
||||
@ -449,12 +449,16 @@ export default {
|
||||
services.push({
|
||||
name: 'Dhcp',
|
||||
provider: [
|
||||
{ name: 'VpcVirtualRouter' }
|
||||
{ name: 'VpcVirtualRouter' },
|
||||
{ name: 'ConfigDrive' }
|
||||
]
|
||||
})
|
||||
services.push({
|
||||
name: 'Dns',
|
||||
provider: [{ name: 'VpcVirtualRouter' }]
|
||||
provider: [
|
||||
{ name: 'VpcVirtualRouter' },
|
||||
{ name: 'ConfigDrive' }
|
||||
]
|
||||
})
|
||||
services.push({
|
||||
name: 'Lb',
|
||||
|
||||
@ -143,7 +143,7 @@ const vueConfig = {
|
||||
ws: false,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 10 * 60 * 1000, // 10 minutes
|
||||
cookieDomainRewrite: '*',
|
||||
cookieDomainRewrite: process.env.CS_COOKIE_HOST || 'localhost',
|
||||
cookiePathRewrite: {
|
||||
'/client': '/'
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user