New feature: Add support to destroy/recover volumes (#3688)

* server: fix resource count of primary storage if some volumes are Expunged but not removed

Steps to reproduce the issue
(1) create a vm and stop it. check resource count of primary storage
(2) download volume. resource count of primary storage is not changed.
(3) expunge the vm, the volume will be Expunged state as there is a volume snapshot on secondary storage. The resource count of primary storage decreased.
(4) update resource count of the account (or domain), the resource count of primary storage is reset to the value in step (2).

* New feature: Add support to destroy/recover volumes

* Add integration test for volume destroy/recover

* marvin: check resource count of more types

* messages translate to JP

* Update messages for CN

* translate message for NL

* fix two issues per Daan's comments

Co-authored-by: Andrija Panic <45762285+andrijapanicsb@users.noreply.github.com>
This commit is contained in:
Wei Zhou 2020-02-07 11:25:10 +01:00 committed by GitHub
parent d8906d3d8b
commit fd5bea838b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 1221 additions and 63 deletions

View File

@ -239,6 +239,8 @@ public class EventTypes {
public static final String EVENT_VOLUME_DETAIL_ADD = "VOLUME.DETAIL.ADD";
public static final String EVENT_VOLUME_DETAIL_REMOVE = "VOLUME.DETAIL.REMOVE";
public static final String EVENT_VOLUME_UPDATE = "VOLUME.UPDATE";
public static final String EVENT_VOLUME_DESTROY = "VOLUME.DESTROY";
public static final String EVENT_VOLUME_RECOVER = "VOLUME.RECOVER";
// Domains
public static final String EVENT_DOMAIN_CREATE = "DOMAIN.CREATE";
@ -706,6 +708,8 @@ public class EventTypes {
entityEventDetails.put(EVENT_VOLUME_UPLOAD, Volume.class);
entityEventDetails.put(EVENT_VOLUME_MIGRATE, Volume.class);
entityEventDetails.put(EVENT_VOLUME_RESIZE, Volume.class);
entityEventDetails.put(EVENT_VOLUME_DESTROY, Volume.class);
entityEventDetails.put(EVENT_VOLUME_RECOVER, Volume.class);
// Domains
entityEventDetails.put(EVENT_DOMAIN_CREATE, Domain.class);

View File

@ -124,6 +124,7 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Ready, Event.AttachRequested, Attaching, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Attaching, Event.OperationSucceeded, Ready, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Attaching, Event.OperationFailed, Ready, null));
s_fsm.addTransition(new StateMachine2.Transition<State, Event>(Destroy, Event.RecoverRequested, Ready, null));
}
}
@ -143,6 +144,7 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
SnapshotRequested,
RevertSnapshotRequested,
DestroyRequested,
RecoverRequested,
ExpungingRequested,
ResizeRequested,
AttachRequested,

View File

@ -148,4 +148,8 @@ public interface VolumeApiService {
* </table>
*/
boolean doesTargetStorageSupportDiskOffering(StoragePool destPool, String diskOfferingTags);
}
Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge);
Volume recoverVolume(long volumeId);
}

View File

@ -0,0 +1,54 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.volume;
import org.apache.log4j.Logger;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.command.user.volume.DestroyVolumeCmd;
import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.context.CallContext;
import com.cloud.storage.Volume;
@APICommand(name = "destroyVolume", description = "Destroys a Volume.", responseObject = VolumeResponse.class, responseView = ResponseView.Full, entityType = {Volume.class},
since = "4.14.0",
authorized = {RoleType.Admin},
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true)
public class DestroyVolumeCmdByAdmin extends DestroyVolumeCmd implements AdminCmd {
public static final Logger s_logger = Logger.getLogger(DestroyVolumeCmdByAdmin.class.getName());
@Override
public void execute() {
CallContext.current().setEventDetails("Volume Id: " + getId());
Volume result = _volumeService.destroyVolume(getId(), CallContext.current().getCallingAccount(), getExpunge(), false);
if (result != null) {
VolumeResponse response = _responseGenerator.createVolumeResponse(ResponseView.Full, result);
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to destroy volume");
}
}
}

View File

@ -0,0 +1,53 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.volume;
import org.apache.log4j.Logger;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.admin.AdminCmd;
import org.apache.cloudstack.api.command.user.volume.RecoverVolumeCmd;
import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.context.CallContext;
import com.cloud.storage.Volume;
@APICommand(name = "recoverVolume", description = "Recovers a Destroy volume.", responseObject = VolumeResponse.class, responseView = ResponseView.Full, entityType = {Volume.class},
since = "4.14.0",
authorized = {RoleType.Admin},
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true)
public class RecoverVolumeCmdByAdmin extends RecoverVolumeCmd implements AdminCmd {
public static final Logger s_logger = Logger.getLogger(RecoverVolumeCmdByAdmin.class.getName());
@Override
public void execute() {
CallContext.current().setEventDetails("Volume Id: " + getId());
Volume result = _volumeService.recoverVolume(getId());
if (result != null) {
VolumeResponse response = _responseGenerator.createVolumeResponse(ResponseView.Full, result);
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to recover volume");
}
}
}

View File

@ -59,6 +59,7 @@ public class ListCapabilitiesCmd extends BaseCmd {
response.setKVMSnapshotEnabled((Boolean)capabilities.get("KVMSnapshotEnabled"));
response.setAllowUserViewDestroyedVM((Boolean)capabilities.get("allowUserViewDestroyedVM"));
response.setAllowUserExpungeRecoverVM((Boolean)capabilities.get("allowUserExpungeRecoverVM"));
response.setAllowUserExpungeRecoverVolume((Boolean)capabilities.get("allowUserExpungeRecoverVolume"));
response.setAllowUserViewAllDomainAccounts((Boolean)capabilities.get("allowUserViewAllDomainAccounts"));
if (capabilities.containsKey("apiLimitInterval")) {
response.setApiLimitInterval((Integer)capabilities.get("apiLimitInterval"));

View File

@ -82,8 +82,8 @@ public class DeleteVolumeCmd extends BaseCmd {
@Override
public void execute() throws ConcurrentOperationException {
CallContext.current().setEventDetails("Volume Id: " + this._uuidMgr.getUuid(Volume.class, getId()));
boolean result = _volumeService.deleteVolume(id, CallContext.current().getCallingAccount());
if (result) {
Volume result = _volumeService.destroyVolume(id, CallContext.current().getCallingAccount(), true, false);
if (result != null) {
SuccessResponse response = new SuccessResponse(getCommandName());
setResponseObject(response);
} else {

View File

@ -0,0 +1,130 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.user.volume;
import org.apache.log4j.Logger;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandJobType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.context.CallContext;
import com.cloud.event.EventTypes;
import com.cloud.storage.Volume;
import com.cloud.user.Account;
@APICommand(name = "destroyVolume", description = "Destroys a Volume.", responseObject = VolumeResponse.class, responseView = ResponseView.Restricted, entityType = {Volume.class},
since = "4.14.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true)
public class DestroyVolumeCmd extends BaseAsyncCmd {
public static final Logger s_logger = Logger.getLogger(DestroyVolumeCmd.class.getName());
private static final String s_name = "destroyvolumeresponse";
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@ACL(accessType = AccessType.OperateEntry)
@Parameter(name=ApiConstants.ID, type=CommandType.UUID, entityType=VolumeResponse.class,
required=true, description="The ID of the volume")
private Long id;
@Parameter(name = ApiConstants.EXPUNGE,
type = CommandType.BOOLEAN,
description = "If true is passed, the volume is expunged immediately. False by default.",
since = "4.6.0")
private Boolean expunge;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public boolean getExpunge() {
if (expunge == null) {
return false;
}
return expunge;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public String getCommandName() {
return s_name;
}
@Override
public long getEntityOwnerId() {
Volume volume = _entityMgr.findById(Volume.class, getId());
if (volume != null) {
return volume.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
}
@Override
public String getEventType() {
return EventTypes.EVENT_VOLUME_DESTROY;
}
@Override
public String getEventDescription() {
return "destroying volume: " + getId();
}
@Override
public ApiCommandJobType getInstanceType() {
return ApiCommandJobType.Volume;
}
@Override
public Long getInstanceId() {
return getId();
}
@Override
public void execute() {
CallContext.current().setEventDetails("Volume Id: " + getId());
Volume result = _volumeService.destroyVolume(getId(), CallContext.current().getCallingAccount(), getExpunge(), false);
if (result != null) {
VolumeResponse response = _responseGenerator.createVolumeResponse(ResponseView.Restricted, result);
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to destroy volume");
}
}
}

View File

@ -24,6 +24,7 @@ import org.apache.cloudstack.api.ApiCommandJobType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListTaggedResourcesCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.BaseCmd.CommandType;
import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.command.user.UserCmd;
import org.apache.cloudstack.api.response.ClusterResponse;
@ -88,6 +89,9 @@ public class ListVolumesCmd extends BaseListTaggedResourcesCmd implements UserCm
RoleType.Admin})
private Boolean display;
@Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "state of the volume. Possible values are: Ready, Allocated, Destroy, Expunging, Expunged.")
private String state;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -139,6 +143,10 @@ public class ListVolumesCmd extends BaseListTaggedResourcesCmd implements UserCm
}
return super.getDisplay();
}
public String getState() {
return state;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -0,0 +1,91 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.user.volume;
import org.apache.log4j.Logger;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.context.CallContext;
import com.cloud.storage.Volume;
import com.cloud.user.Account;
@APICommand(name = "recoverVolume", description = "Recovers a Destroy volume.", responseObject = VolumeResponse.class, responseView = ResponseView.Restricted, entityType = {Volume.class},
since = "4.14.0",
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User},
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true)
public class RecoverVolumeCmd extends BaseCmd {
public static final Logger s_logger = Logger.getLogger(RecoverVolumeCmd.class.getName());
private static final String s_name = "recovervolumeresponse";
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = VolumeResponse.class, required = true, description = "The ID of the volume")
private Long id;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public String getCommandName() {
return s_name;
}
@Override
public long getEntityOwnerId() {
Volume volume = _entityMgr.findById(Volume.class, getId());
if (volume != null) {
return volume.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
}
@Override
public void execute() {
CallContext.current().setEventDetails("Volume Id: " + getId());
Volume result = _volumeService.recoverVolume(getId());
if (result != null) {
VolumeResponse response = _responseGenerator.createVolumeResponse(ResponseView.Full, result);
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to recover volume");
}
}
}

View File

@ -84,6 +84,10 @@ public class CapabilitiesResponse extends BaseResponse {
@Param(description = "true if the user can recover and expunge virtualmachines, false otherwise", since = "4.6.0")
private boolean allowUserExpungeRecoverVM;
@SerializedName("allowuserexpungerecovervolume")
@Param(description = "true if the user can recover and expunge volumes, false otherwise", since = "4.14.0")
private boolean allowUserExpungeRecoverVolume;
@SerializedName("allowuserviewalldomainaccounts")
@Param(description = "true if users can see all accounts within the same domain, false otherwise")
private boolean allowUserViewAllDomainAccounts;
@ -148,7 +152,11 @@ public class CapabilitiesResponse extends BaseResponse {
this.allowUserExpungeRecoverVM = allowUserExpungeRecoverVM;
}
public void setAllowUserExpungeRecoverVolume(boolean allowUserExpungeRecoverVolume) {
this.allowUserExpungeRecoverVolume = allowUserExpungeRecoverVolume;
}
public void setAllowUserViewAllDomainAccounts(boolean allowUserViewAllDomainAccounts) {
this.allowUserViewAllDomainAccounts = allowUserViewAllDomainAccounts;
}
}
}

View File

@ -1578,6 +1578,8 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
if (volume.getState() == Volume.State.Allocated) {
_volsDao.remove(volume.getId());
stateTransitTo(volume, Volume.Event.DestroyRequested);
_resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.volume, volume.isDisplay());
_resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, volume.isDisplay(), new Long(volume.getSize()));
} else {
volService.destroyVolume(volume.getId());
}
@ -1585,8 +1587,6 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
// publish usage event for the volume
UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), Volume.class.getName(),
volume.getUuid(), volume.isDisplayVolume());
_resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.volume, volume.isDisplay());
_resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, volume.isDisplay(), new Long(volume.getSize()));
} catch (Exception e) {
s_logger.debug("Failed to destroy volume" + volume.getId(), e);
throw new CloudRuntimeException("Failed to destroy volume" + volume.getId(), e);

View File

@ -119,6 +119,7 @@ import com.cloud.utils.Pair;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VirtualMachine;
@Component
public class VolumeServiceImpl implements VolumeService {
@ -325,7 +326,10 @@ public class VolumeServiceImpl implements VolumeService {
VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(volume.getId());
if (volumeStore != null) {
if (volumeStore.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS) {
s_logger.debug("Volume: " + volume.getName() + " is currently being uploaded; cant' delete it.");
String msg = "Volume: " + volume.getName() + " is currently being uploaded; cant' delete it.";
s_logger.debug(msg);
result.setSuccess(false);
result.setResult(msg);
future.complete(result);
return future;
}
@ -1213,6 +1217,12 @@ public class VolumeServiceImpl implements VolumeService {
snapshotMgr.deletePoliciesForVolume(volumeId);
vol.stateTransit(Volume.Event.OperationSucceeded);
if (vol.getAttachedVM() == null || vol.getAttachedVM().getType() == VirtualMachine.Type.User) {
// Decrement the resource count for volumes and primary storage belonging user VM's only
_resourceLimitMgr.decrementResourceCount(vol.getAccountId(), ResourceType.volume, vol.isDisplay());
_resourceLimitMgr.decrementResourceCount(vol.getAccountId(), ResourceType.primary_storage, vol.isDisplay(), new Long(vol.getSize()));
}
}
@Override

View File

@ -1809,6 +1809,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
Long clusterId = cmd.getClusterId();
Long diskOffId = cmd.getDiskOfferingId();
Boolean display = cmd.getDisplay();
String state = cmd.getState();
Long zoneId = cmd.getZoneId();
Long podId = cmd.getPodId();
@ -1844,8 +1845,8 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
sb.and("storageId", sb.entity().getPoolUuid(), SearchCriteria.Op.EQ);
sb.and("diskOfferingId", sb.entity().getDiskOfferingId(), SearchCriteria.Op.EQ);
sb.and("display", sb.entity().isDisplayVolume(), SearchCriteria.Op.EQ);
// Only return volumes that are not destroyed
sb.and("state", sb.entity().getState(), SearchCriteria.Op.NEQ);
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
sb.and("stateNEQ", sb.entity().getState(), SearchCriteria.Op.NEQ);
sb.and("systemUse", sb.entity().isSystemUse(), SearchCriteria.Op.NEQ);
// display UserVM volumes only
sb.and().op("type", sb.entity().getVmType(), SearchCriteria.Op.NIN);
@ -1860,6 +1861,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
SearchCriteria<VolumeJoinVO> ssc = _volumeJoinDao.createSearchCriteria();
ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%");
ssc.addOr("volumeType", SearchCriteria.Op.LIKE, "%" + keyword + "%");
ssc.addOr("state", SearchCriteria.Op.LIKE, "%" + keyword + "%");
sc.addAnd("name", SearchCriteria.Op.SC, ssc);
}
@ -1918,8 +1920,11 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
// Don't return DomR and ConsoleProxy volumes
sc.setParameters("type", VirtualMachine.Type.ConsoleProxy, VirtualMachine.Type.SecondaryStorageVm, VirtualMachine.Type.DomainRouter);
// Only return volumes that are not destroyed
sc.setParameters("state", Volume.State.Destroy);
if (state != null) {
sc.setParameters("state", state);
} else if (!_accountMgr.isAdmin(caller.getId())) {
sc.setParameters("stateNEQ", Volume.State.Expunged);
}
// search Volume details by ids
Pair<List<VolumeJoinVO>, Integer> uniqueVolPair = _volumeJoinDao.searchAndCount(sc, searchFilter);

View File

@ -265,9 +265,11 @@ import org.apache.cloudstack.api.command.admin.vm.UpgradeVMCmdByAdmin;
import org.apache.cloudstack.api.command.admin.vmsnapshot.RevertToVMSnapshotCmdByAdmin;
import org.apache.cloudstack.api.command.admin.volume.AttachVolumeCmdByAdmin;
import org.apache.cloudstack.api.command.admin.volume.CreateVolumeCmdByAdmin;
import org.apache.cloudstack.api.command.admin.volume.DestroyVolumeCmdByAdmin;
import org.apache.cloudstack.api.command.admin.volume.DetachVolumeCmdByAdmin;
import org.apache.cloudstack.api.command.admin.volume.ListVolumesCmdByAdmin;
import org.apache.cloudstack.api.command.admin.volume.MigrateVolumeCmdByAdmin;
import org.apache.cloudstack.api.command.admin.volume.RecoverVolumeCmdByAdmin;
import org.apache.cloudstack.api.command.admin.volume.ResizeVolumeCmdByAdmin;
import org.apache.cloudstack.api.command.admin.volume.UpdateVolumeCmdByAdmin;
import org.apache.cloudstack.api.command.admin.volume.UploadVolumeCmdByAdmin;
@ -483,12 +485,14 @@ import org.apache.cloudstack.api.command.user.volume.AddResourceDetailCmd;
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.DeleteVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.DestroyVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.ExtractVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.GetUploadParamsForVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.ListResourceDetailsCmd;
import org.apache.cloudstack.api.command.user.volume.ListVolumesCmd;
import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.RecoverVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.RemoveResourceDetailCmd;
import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.UpdateVolumeCmd;
@ -643,6 +647,7 @@ import com.cloud.storage.ScopeType;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeApiServiceImpl;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.GuestOSCategoryDao;
@ -2948,6 +2953,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(MigrateVolumeCmd.class);
cmdList.add(ResizeVolumeCmd.class);
cmdList.add(UploadVolumeCmd.class);
cmdList.add(DestroyVolumeCmd.class);
cmdList.add(RecoverVolumeCmd.class);
cmdList.add(CreateStaticRouteCmd.class);
cmdList.add(CreateVPCCmd.class);
cmdList.add(DeleteStaticRouteCmd.class);
@ -3093,6 +3100,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(UpdateVolumeCmdByAdmin.class);
cmdList.add(UploadVolumeCmdByAdmin.class);
cmdList.add(ListVolumesCmdByAdmin.class);
cmdList.add(DestroyVolumeCmdByAdmin.class);
cmdList.add(RecoverVolumeCmdByAdmin.class);
cmdList.add(AssociateIPAddrCmdByAdmin.class);
cmdList.add(ListPublicIpAddressesCmdByAdmin.class);
cmdList.add(CreateNetworkCmdByAdmin.class);
@ -3501,6 +3510,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
final boolean allowUserViewDestroyedVM = (QueryService.AllowUserViewDestroyedVM.valueIn(caller.getId()) | _accountService.isAdmin(caller.getId()));
final boolean allowUserExpungeRecoverVM = (UserVmManager.AllowUserExpungeRecoverVm.valueIn(caller.getId()) | _accountService.isAdmin(caller.getId()));
final boolean allowUserExpungeRecoverVolume = (VolumeApiServiceImpl.AllowUserExpungeRecoverVolume.valueIn(caller.getId()) | _accountService.isAdmin(caller.getId()));
final boolean allowUserViewAllDomainAccounts = (QueryService.AllowUserViewAllDomainAccounts.valueIn(caller.getDomainId()));
@ -3523,6 +3533,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
capabilities.put("KVMSnapshotEnabled", KVMSnapshotEnabled);
capabilities.put("allowUserViewDestroyedVM", allowUserViewDestroyedVM);
capabilities.put("allowUserExpungeRecoverVM", allowUserExpungeRecoverVM);
capabilities.put("allowUserExpungeRecoverVolume", allowUserExpungeRecoverVolume);
capabilities.put("allowUserViewAllDomainAccounts", allowUserViewAllDomainAccounts);
if (apiLimitEnabled) {
capabilities.put("apiLimitInterval", apiLimitInterval);

View File

@ -275,6 +275,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
static final ConfigKey<Boolean> VolumeUrlCheck = new ConfigKey<Boolean>("Advanced", Boolean.class, "volume.url.check", "true",
"Check the url for a volume before downloading it from the management server. Set to false when you managment has no internet access.", true);
public static final ConfigKey<Boolean> AllowUserExpungeRecoverVolume = new ConfigKey<Boolean>("Advanced", Boolean.class, "allow.user.expunge.recover.volume", "true",
"Determines whether users can expunge or recover their volume", true, ConfigKey.Scope.Account);
private long _maxVolumeSizeInGb;
private final StateMachine2<Volume.State, Volume.Event, Volume> _volStateMachine;
@ -1261,20 +1264,17 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
* Otherwise, after the removal in the database, we will try to remove the volume from both primary and secondary storage.
*/
public boolean deleteVolume(long volumeId, Account caller) throws ConcurrentOperationException {
VolumeVO volume = retrieveAndValidateVolume(volumeId, caller);
Volume volume = destroyVolume(volumeId, caller, true, true);
return (volume != null);
}
private boolean deleteVolumeFromStorage(VolumeVO volume, Account caller) throws ConcurrentOperationException {
try {
destroyVolumeIfPossible(volume);
// Mark volume as removed if volume has not been created on primary or secondary
if (volume.getState() == Volume.State.Allocated) {
_volsDao.remove(volumeId);
stateTransitTo(volume, Volume.Event.DestroyRequested);
return true;
}
expungeVolumesInPrimaryStorageIfNeeded(volume);
expungeVolumesInSecondaryStorageIfNeeded(volume);
cleanVolumesCache(volume);
return true;
} catch (InterruptedException | ExecutionException | NoTransitionException e) {
} catch (InterruptedException | ExecutionException e) {
s_logger.warn("Failed to expunge volume: " + volume.getUuid(), e);
return false;
}
@ -1301,7 +1301,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
if (!_snapshotMgr.canOperateOnVolume(volume)) {
throw new InvalidParameterValueException("There are snapshot operations in progress on the volume, unable to delete it");
}
if (volume.getInstanceId() != null) {
if (volume.getInstanceId() != null && volume.getState() != Volume.State.Expunged) {
throw new InvalidParameterValueException("Please specify a volume that is not attached to any VM.");
}
if (volume.getState() == Volume.State.UploadOp) {
@ -1334,12 +1334,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
* The volume is destroyed via {@link VolumeService#destroyVolume(long)} method.
*/
protected void destroyVolumeIfPossible(VolumeVO volume) {
if (volume.getState() != Volume.State.Destroy && volume.getState() != Volume.State.Expunging && volume.getState() != Volume.State.Expunged) {
if (volume.getState() != Volume.State.Destroy && volume.getState() != Volume.State.Expunging && volume.getState() != Volume.State.Expunged && volume.getState() != Volume.State.Allocated && volume.getState() != Volume.State.Uploaded) {
volService.destroyVolume(volume.getId());
// Decrement the resource count for volumes and primary storage belonging user VM's only
_resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.volume, volume.isDisplayVolume());
_resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, volume.isDisplayVolume(), volume.getSize());
}
}
@ -1389,6 +1385,89 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
return _volStateMachine.transitTo(vol, event, null, _volsDao);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_DESTROY, eventDescription = "destroying a volume")
public Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge) {
VolumeVO volume = retrieveAndValidateVolume(volumeId, caller);
if (expunge) {
// When trying to expunge, permission is denied when the caller is not an admin and the AllowUserExpungeRecoverVolume is false for the caller.
final Long userId = caller.getAccountId();
if (!forceExpunge && !_accountMgr.isAdmin(userId) && !AllowUserExpungeRecoverVolume.valueIn(userId)) {
throw new PermissionDeniedException("Expunging a volume can only be done by an Admin. Or when the allow.user.expunge.recover.volume key is set.");
}
} else if (volume.getState() == Volume.State.Allocated || volume.getState() == Volume.State.Uploaded) {
throw new InvalidParameterValueException("The volume in Allocated/Uploaded state can only be expunged not destroyed/recovered");
}
destroyVolumeIfPossible(volume);
if (expunge) {
// Mark volume as removed if volume has not been created on primary or secondary
if (volume.getState() == Volume.State.Allocated) {
_volsDao.remove(volume.getId());
try {
stateTransitTo(volume, Volume.Event.DestroyRequested);
} catch (NoTransitionException e) {
s_logger.debug("Failed to destroy volume" + volume.getId(), e);
return null;
}
_resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.volume, volume.isDisplay());
_resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, volume.isDisplay(), new Long(volume.getSize()));
return volume;
}
if (!deleteVolumeFromStorage(volume, caller)) {
s_logger.warn("Failed to expunge volume: " + volumeId);
return null;
}
}
return volume;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_RECOVER, eventDescription = "recovering a volume in Destroy state")
public Volume recoverVolume(long volumeId) {
Account caller = CallContext.current().getCallingAccount();
final Long userId = caller.getAccountId();
// Verify input parameters
final VolumeVO volume = _volsDao.findById(volumeId);
if (volume == null) {
throw new InvalidParameterValueException("Unable to find a volume with id " + volume);
}
// When trying to expunge, permission is denied when the caller is not an admin and the AllowUserExpungeRecoverVolume is false for the caller.
if (!_accountMgr.isAdmin(userId) && !AllowUserExpungeRecoverVolume.valueIn(userId)) {
throw new PermissionDeniedException("Recovering a volume can only be done by an Admin. Or when the allow.user.expunge.recover.volume key is set.");
}
_accountMgr.checkAccess(caller, null, true, volume);
if (volume.getState() != Volume.State.Destroy) {
throw new InvalidParameterValueException("Please specify a volume in Destroy state.");
}
try {
_resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(volume.getAccountId()), ResourceType.primary_storage, volume.isDisplayVolume(), volume.getSize());
} catch (ResourceAllocationException e) {
s_logger.error("primary storage resource limit check failed", e);
throw new InvalidParameterValueException(e.getMessage());
}
try {
stateTransitTo(volume, Volume.Event.RecoverRequested);
} catch (NoTransitionException e) {
s_logger.debug("Failed to recover volume" + volume.getId(), e);
throw new CloudRuntimeException("Failed to recover volume" + volume.getId(), e);
}
_resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.volume, volume.isDisplay());
_resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, volume.isDisplay(), new Long(volume.getSize()));
return volume;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_ATTACH, eventDescription = "attaching volume", async = true)
public Volume attachVolumeToVM(AttachVolumeCmd command) {
@ -3409,6 +3488,6 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {ConcurrentMigrationsThresholdPerDatastore};
return new ConfigKey<?>[] {ConcurrentMigrationsThresholdPerDatastore, AllowUserExpungeRecoverVolume};
}
}

View File

@ -132,7 +132,6 @@ import com.cloud.region.ha.GlobalLoadBalancingRulesService;
import com.cloud.server.auth.UserAuthenticator;
import com.cloud.server.auth.UserAuthenticator.ActionOnFailedAuthentication;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeApiService;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VMTemplateDao;
@ -782,13 +781,11 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
// Mark the account's volumes as destroyed
List<VolumeVO> volumes = _volumeDao.findDetachedByAccount(accountId);
for (VolumeVO volume : volumes) {
if (!volume.getState().equals(Volume.State.Destroy)) {
try {
volumeService.deleteVolume(volume.getId(), caller);
} catch (Exception ex) {
s_logger.warn("Failed to cleanup volumes as a part of account id=" + accountId + " cleanup due to Exception: ", ex);
accountCleanupNeeded = true;
}
try {
volumeService.deleteVolume(volume.getId(), caller);
} catch (Exception ex) {
s_logger.warn("Failed to cleanup volumes as a part of account id=" + accountId + " cleanup due to Exception: ", ex);
accountCleanupNeeded = true;
}
}

View File

@ -2234,11 +2234,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
List<VolumeVO> rootVol = _volsDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT);
// expunge the vm
_itMgr.advanceExpunge(vm.getUuid());
// Update Resource count
if (vm.getAccountId() != Account.ACCOUNT_ID_SYSTEM && !rootVol.isEmpty()) {
_resourceLimitMgr.decrementResourceCount(vm.getAccountId(), ResourceType.volume);
_resourceLimitMgr.decrementResourceCount(vm.getAccountId(), ResourceType.primary_storage, new Long(rootVol.get(0).getSize()));
}
// Only if vm is not expunged already, cleanup it's resources
if (vm.getRemoved() == null) {

View File

@ -735,8 +735,6 @@ public class VolumeApiServiceImplTest {
volumeApiServiceImpl.destroyVolumeIfPossible(volumeVoMock);
Mockito.verify(volumeServiceMock, Mockito.times(1)).destroyVolume(volumeMockId);
Mockito.verify(resourceLimitServiceMock, Mockito.times(1)).decrementResourceCount(accountMockId, ResourceType.volume, true);
Mockito.verify(resourceLimitServiceMock, Mockito.times(1)).decrementResourceCount(accountMockId, ResourceType.primary_storage, true, volumeSizeMock);
}
private void verifyMocksForTestDestroyVolumeWhenVolumeIsNotInRightState() {
@ -969,26 +967,6 @@ public class VolumeApiServiceImplTest {
Mockito.verify(volumeApiServiceImpl, Mockito.times(0)).stateTransitTo(volumeVoMock, Volume.Event.DestroyRequested);
}
@Test
public void deleteVolumeTestVolumeStateReadyThrowingNoTransitionException() throws InterruptedException, ExecutionException, NoTransitionException {
Mockito.doReturn(Volume.State.Ready).when(volumeVoMock).getState();
Mockito.doReturn(volumeVoMock).when(volumeApiServiceImpl).retrieveAndValidateVolume(volumeMockId, accountMock);
Mockito.doNothing().when(volumeApiServiceImpl).destroyVolumeIfPossible(volumeVoMock);
Mockito.doThrow(NoTransitionException.class).when(volumeApiServiceImpl).expungeVolumesInPrimaryStorageIfNeeded(volumeVoMock);
Mockito.doReturn(true).when(volumeDaoMock).remove(volumeMockId);
Mockito.doReturn(true).when(volumeApiServiceImpl).stateTransitTo(volumeVoMock, Volume.Event.DestroyRequested);
boolean result = volumeApiServiceImpl.deleteVolume(volumeMockId, accountMock);
Assert.assertFalse(result);
Mockito.verify(volumeApiServiceImpl).retrieveAndValidateVolume(volumeMockId, accountMock);
Mockito.verify(volumeApiServiceImpl).destroyVolumeIfPossible(volumeVoMock);
Mockito.verify(volumeDaoMock, Mockito.times(0)).remove(volumeMockId);
Mockito.verify(volumeApiServiceImpl, Mockito.times(0)).stateTransitTo(volumeVoMock, Volume.Event.DestroyRequested);
}
@Test(expected = RuntimeException.class)
public void deleteVolumeTestVolumeStateReadyThrowingRuntimeException() throws InterruptedException, ExecutionException, NoTransitionException {
Mockito.doReturn(Volume.State.Ready).when(volumeVoMock).getState();

View File

@ -0,0 +1,507 @@
# 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.
""" tests for Volume improvement (Destroy/Recover) in cloudstack 4.14.0.0
"""
# Import Local Modules
from nose.plugins.attrib import attr
from marvin.cloudstackTestCase import cloudstackTestCase
from marvin.cloudstackAPI import (deleteVolume, extractVolume, recoverVolume)
from marvin.lib.utils import (validateList,
cleanup_resources)
from marvin.lib.base import (Resources,
Volume,
Account,
Domain,
Network,
NetworkOffering,
VirtualMachine,
ServiceOffering,
DiskOffering,
Zone)
from marvin.lib.common import (get_domain,
get_zone,
get_template,
matchResourceCount,
isAccountResourceCountEqualToExpectedCount)
from marvin.codes import (PASS, FAILED, RESOURCE_PRIMARY_STORAGE, RESOURCE_VOLUME)
import logging
import random
import time
class TestVolumeDestroyRecover(cloudstackTestCase):
@classmethod
def setUpClass(cls):
cls.testClient = super(
TestVolumeDestroyRecover,
cls).getClsTestClient()
cls.apiclient = cls.testClient.getApiClient()
cls.services = cls.testClient.getParsedTestDataConfig()
zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
cls.zone = Zone(zone.__dict__)
cls._cleanup = []
cls.logger = logging.getLogger("TestVolumeDestroyRecover")
cls.stream_handler = logging.StreamHandler()
cls.logger.setLevel(logging.DEBUG)
cls.logger.addHandler(cls.stream_handler)
# Get Domain and templates
cls.domain = get_domain(cls.apiclient)
cls.template = get_template(cls.apiclient, cls.zone.id, hypervisor="KVM")
if cls.template == FAILED:
sys.exit(1)
cls.templatesize = (cls.template.size / (1024 ** 3))
cls.services['mode'] = cls.zone.networktype
# Create Account
cls.account = Account.create(
cls.apiclient,
cls.services["account"],
admin=True,
domainid=cls.domain.id
)
accounts = Account.list(cls.apiclient, id=cls.account.id)
cls.expectedCount = int(accounts[0].primarystoragetotal)
cls.volumeTotal = int(accounts[0].volumetotal)
if cls.zone.securitygroupsenabled:
cls.services["shared_network_offering"]["specifyVlan"] = 'True'
cls.services["shared_network_offering"]["specifyIpRanges"] = 'True'
cls.network_offering = NetworkOffering.create(
cls.apiclient,
cls.services["shared_network_offering"]
)
cls.network_offering.update(cls.apiclient, state='Enabled')
cls.account_network = Network.create(
cls.apiclient,
cls.services["network2"],
networkofferingid=cls.network_offering.id,
zoneid=cls.zone.id,
accountid=cls.account.name,
domainid=cls.account.domainid
)
else:
cls.network_offering = NetworkOffering.create(
cls.apiclient,
cls.services["isolated_network_offering"],
)
# Enable Network offering
cls.network_offering.update(cls.apiclient, state='Enabled')
# Create account network
cls.services["network"]["zoneid"] = cls.zone.id
cls.services["network"]["networkoffering"] = cls.network_offering.id
cls.account_network = Network.create(
cls.apiclient,
cls.services["network"],
cls.account.name,
cls.account.domainid
)
# Create small service offering
cls.service_offering = ServiceOffering.create(
cls.apiclient,
cls.services["service_offerings"]["small"]
)
# Create disk offering
cls.disk_offering = DiskOffering.create(
cls.apiclient,
cls.services["disk_offering"],
)
cls._cleanup.append(cls.disk_offering)
cls._cleanup.append(cls.service_offering)
cls._cleanup.append(cls.account);
cls._cleanup.append(cls.network_offering)
@classmethod
def tearDownClass(self):
try:
cleanup_resources(self.apiclient, self._cleanup)
except Exception as e:
raise Exception("Warning: Exception during cleanup : %s" % e)
return
def setUp(self):
self.apiclient = self.testClient.getApiClient()
self.cleanup = []
return
def tearDown(self):
try:
cleanup_resources(self.apiclient, self.cleanup)
except Exception as e:
raise Exception("Warning: Exception during cleanup : %s" % e)
return
def verify_resource_count_primary_storage(self, expectedCount, volumeTotal):
response = matchResourceCount(
self.apiclient, expectedCount,
RESOURCE_PRIMARY_STORAGE,
accountid=self.account.id)
self.assertEqual(response[0], PASS, response[1])
result = isAccountResourceCountEqualToExpectedCount(
self.apiclient, self.account.domainid, self.account.name,
expectedCount, RESOURCE_PRIMARY_STORAGE)
self.assertFalse(result[0], result[1])
self.assertTrue(result[2], "Resource count of primary storage does not match")
response = matchResourceCount(
self.apiclient, volumeTotal,
RESOURCE_VOLUME,
accountid=self.account.id)
self.assertEqual(response[0], PASS, response[1])
result = isAccountResourceCountEqualToExpectedCount(
self.apiclient, self.account.domainid, self.account.name,
volumeTotal, RESOURCE_VOLUME)
self.assertFalse(result[0], result[1])
self.assertTrue(result[2], "Resource count of volume does not match")
@attr(tags=["advanced", "advancedsg"], required_hardware="false")
def test_01_create_vm_with_data_disk(self):
"""Create VM with DATA disk, then destroy it (expunge=False) and expunge it
Steps:
# 1. create vm with root disk and data disk
# 2. destroy vm, resource count of primary storage is not changed
# 3. expunge vm, resource count of primary storage decreased with size of root disk.
# 4. delete volume (data disk), resource count of primary storage decreased with size of data disk
"""
try:
virtual_machine_1 = VirtualMachine.create(
self.apiclient,
self.services["virtual_machine"],
accountid=self.account.name,
domainid=self.account.domainid,
serviceofferingid=self.service_offering.id,
diskofferingid=self.disk_offering.id,
templateid=self.template.id,
zoneid=self.zone.id
)
except Exception as e:
self.fail("Exception while deploying virtual machine: %s" % e)
self.expectedCount = self.expectedCount + self.templatesize + self.disk_offering.disksize
self.volumeTotal = self.volumeTotal + 2
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
root_volumes_list = Volume.list(
self.apiclient,
virtualmachineid=virtual_machine_1.id,
type='ROOT',
listall=True
)
status = validateList(root_volumes_list)
self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed")
root_volume_id = root_volumes_list[0].id
data_volumes_list = Volume.list(
self.apiclient,
virtualmachineid=virtual_machine_1.id,
type='DATADISK',
listall=True
)
status = validateList(data_volumes_list)
self.assertEqual(status[0], PASS, "DATADISK Volume List Validation Failed")
data_volume_id = data_volumes_list[0].id
# destroy vm
virtual_machine_1.delete(self.apiclient, expunge=False)
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal)
# expunge vm
virtual_machine_1.expunge(self.apiclient)
self.expectedCount = self.expectedCount - self.templatesize
self.volumeTotal = self.volumeTotal - 1
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal)
# delete datadisk
cmd = deleteVolume.deleteVolumeCmd()
cmd.id = data_volume_id
self.apiclient.deleteVolume(cmd)
self.expectedCount = self.expectedCount - self.disk_offering.disksize
self.volumeTotal = self.volumeTotal - 1
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal)
@attr(tags=["advanced", "advancedsg"], required_hardware="false")
def test_02_destroy_allocated_volume(self):
"""Create volume, destroy it when expunge=false and expunge=true
Steps:
# 1. create volume, resource count increases.
# 2. destroy volume (expunge = false), Exception happened. resource count no changes
# 3. destroy volume (expunge = True), resource count of primary storage decreased with size of volume.
"""
# Create volume
volume = Volume.create(
self.apiclient, self.services["volume"],
zoneid=self.zone.id, account=self.account.name,
domainid=self.account.domainid, diskofferingid=self.disk_offering.id
)
self.expectedCount = self.expectedCount + self.disk_offering.disksize
self.volumeTotal = self.volumeTotal + 1
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
# Destroy volume (expunge=False)
with self.assertRaises(Exception):
volume.destroy(self.apiclient)
# Destroy volume (expunge=True)
volume.destroy(self.apiclient, expunge=True)
self.expectedCount = self.expectedCount - self.disk_offering.disksize
self.volumeTotal = self.volumeTotal - 1
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
@attr(tags=["advanced", "advancedsg"], required_hardware="false")
def test_03_destroy_detached_volume(self):
"""Create volume, attach/detach it, then destroy it when expunge=false and expunge=true
Steps:
# 1. create vm without data disk, resource count increases.
# 2. create volume, resource count increases.
# 3. attach volume to a vm. resource count no changes.
# 4. detach volume from a vm. resource count no changes.
# 5. destroy volume (expunge = false), volume is Destroy. resource count decreased with size of volume.
# 6. destroy volume (expunge = true), volume is not found. resource count no changes.
# 7. destroy vm (expunge=True). resource count decreased with size of root disk
"""
# Create vm
try:
virtual_machine_2 = VirtualMachine.create(
self.apiclient,
self.services["virtual_machine"],
accountid=self.account.name,
domainid=self.account.domainid,
serviceofferingid=self.service_offering.id,
templateid=self.template.id,
zoneid=self.zone.id
)
except Exception as e:
self.fail("Exception while deploying virtual machine: %s" % e)
self.expectedCount = self.expectedCount + self.templatesize
self.volumeTotal = self.volumeTotal + 1
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
# Create volume
volume = Volume.create(
self.apiclient, self.services["volume"],
zoneid=self.zone.id, account=self.account.name,
domainid=self.account.domainid, diskofferingid=self.disk_offering.id
)
self.expectedCount = self.expectedCount + self.disk_offering.disksize
self.volumeTotal = self.volumeTotal + 1
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
# Attach volume to vm
virtual_machine_2.attach_volume(self.apiclient, volume)
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
# Detach volume from vm
virtual_machine_2.detach_volume(self.apiclient, volume)
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
# Destroy volume (expunge=False)
volume.destroy(self.apiclient)
self.expectedCount = self.expectedCount - self.disk_offering.disksize
self.volumeTotal = self.volumeTotal - 1
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
# Destroy volume (expunge=True)
volume.destroy(self.apiclient, expunge=True)
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
# Destroy VM (expunge=True)
virtual_machine_2.delete(self.apiclient, expunge=True)
self.expectedCount = self.expectedCount - self.templatesize
self.volumeTotal = self.volumeTotal - 1
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
@attr(tags=["advanced", "advancedsg"], required_hardware="false")
def test_04_recover_root_volume_after_restorevm(self):
"""Restore VM, recover/delete old root disk
Steps:
# 1. create vm without data disk, resource count increases.
# 2. restore vm. resource count no changes.
# 3. check old root disk , should be Destroy state
# 4. recover old root disk. resource count increases.
# 5. delete old root disk . resource count decreases.
# 6. destroy vm (expunge=True). resource count decreased with size of root disk
"""
# Create vm
try:
virtual_machine_3 = VirtualMachine.create(
self.apiclient,
self.services["virtual_machine"],
accountid=self.account.name,
domainid=self.account.domainid,
serviceofferingid=self.service_offering.id,
templateid=self.template.id,
zoneid=self.zone.id
)
except Exception as e:
self.fail("Exception while deploying virtual machine: %s" % e)
self.expectedCount = self.expectedCount + self.templatesize
self.volumeTotal = self.volumeTotal + 1
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
# Get id of root disk
root_volumes_list = Volume.list(
self.apiclient,
virtualmachineid=virtual_machine_3.id,
type='ROOT',
listall=True
)
status = validateList(root_volumes_list)
self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed")
root_volume_id = root_volumes_list[0].id
# restore vm
virtual_machine_3.restore(self.apiclient)
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
# check old root disk state
root_volumes_list = Volume.list(
self.apiclient,
id=root_volume_id,
listall=True
)
status = validateList(root_volumes_list)
self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed")
root_volume = root_volumes_list[0]
self.assertEqual(root_volume['state'], 'Destroy', "ROOT volume should be Destroy after restorevm")
# recover old root disk
cmd = recoverVolume.recoverVolumeCmd()
cmd.id = root_volume.id
self.apiclient.recoverVolume(cmd)
self.expectedCount = self.expectedCount + self.templatesize
self.volumeTotal = self.volumeTotal + 1
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
# delete old root disk
cmd = deleteVolume.deleteVolumeCmd()
cmd.id = root_volume.id
self.apiclient.deleteVolume(cmd)
self.expectedCount = self.expectedCount - self.templatesize
self.volumeTotal = self.volumeTotal - 1
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal)
# Destroy VM (expunge=True)
virtual_machine_3.delete(self.apiclient, expunge=True)
self.expectedCount = self.expectedCount - self.templatesize
self.volumeTotal = self.volumeTotal - 1
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
@attr(tags=["advanced", "advancedsg"], required_hardware="false")
def test_05_extract_root_volume_and_destroy_vm(self):
"""Create VM, extract root volume, then destroy vm and volume
Steps:
# 1. create vm without data disk, resource count increases.
# 2. stop vm
# 3. extract root volume
# 4. expunge vm, root volume in Expunged state. resource count decreased with size of root disk.
# 5. destroy volume (expunge = false), Exception happened. resource count no changes
# 6. destroy volume (expunge = true). volume is not found. resource count no changes.
"""
# Create vm
try:
virtual_machine_4 = VirtualMachine.create(
self.apiclient,
self.services["virtual_machine"],
accountid=self.account.name,
domainid=self.account.domainid,
serviceofferingid=self.service_offering.id,
templateid=self.template.id,
zoneid=self.zone.id
)
except Exception as e:
self.fail("Exception while deploying virtual machine: %s" % e)
self.expectedCount = self.expectedCount + self.templatesize
self.volumeTotal = self.volumeTotal + 1
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
# Get id of root disk
root_volumes_list = Volume.list(
self.apiclient,
virtualmachineid=virtual_machine_4.id,
type='ROOT',
listall=True
)
status = validateList(root_volumes_list)
self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed")
root_volume_id = root_volumes_list[0].id
# Stop vm
virtual_machine_4.stop(self.apiclient)
# extract root volume
cmd = extractVolume.extractVolumeCmd()
cmd.id = root_volume_id
cmd.mode = "HTTP_DOWNLOAD"
cmd.zoneid = self.zone.id
self.apiclient.extractVolume(cmd)
# Destroy VM (expunge=True)
virtual_machine_4.delete(self.apiclient, expunge=True)
self.expectedCount = self.expectedCount - self.templatesize
self.volumeTotal = self.volumeTotal - 1
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal);
# check root disk state
root_volumes_list = Volume.list(
self.apiclient,
id=root_volume_id,
listall=True
)
status = validateList(root_volumes_list)
self.assertEqual(status[0], PASS, "ROOT Volume List Validation Failed")
root_volume = root_volumes_list[0]
self.assertEqual(root_volume['state'], 'Expunged', "ROOT volume should be Destroy after restorevm")
# delete root disk
cmd = deleteVolume.deleteVolumeCmd()
cmd.id = root_volume.id
self.apiclient.deleteVolume(cmd)
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal)
@attr(tags=["advanced", "advancedsg"], required_hardware="false")
def test_06_delete_network(self):
"""Delete account network, resource count should not be changed
Steps:
# 1. Delete account network
# 2. resource count should not be changed
"""
self.account_network.delete(self.apiclient)
self.verify_resource_count_primary_storage(self.expectedCount, self.volumeTotal)

View File

@ -1080,6 +1080,19 @@ class Volume:
cmd.id = self.id
apiclient.deleteVolume(cmd)
def destroy(self, apiclient, expunge=False):
"""Destroy Volume"""
cmd = destroyVolume.destroyVolumeCmd()
cmd.id = self.id
cmd.expunge = expunge
apiclient.destroyVolume(cmd)
def recover(self, apiclient):
"""Recover Volume"""
cmd = recoverVolume.recoverVolumeCmd()
cmd.id = self.id
apiclient.recoverVolume(cmd)
@classmethod
def list(cls, apiclient, **kwargs):
"""List all volumes matching criteria"""

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "Destroying Instance....",
"label.action.destroy.systemvm": "Destroy System VM",
"label.action.destroy.systemvm.processing": "Destroying System VM....",
"label.action.destroy.volume":"Destroy Volume",
"label.action.detach.disk": "Detach Disk",
"label.action.detach.disk.processing": "Detaching Disk....",
"label.action.detach.iso": "Detach ISO",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "Rebooting Router....",
"label.action.reboot.systemvm": "Reboot System VM",
"label.action.reboot.systemvm.processing": "Rebooting System VM....",
"label.action.recover.volume":"Recover Volume",
"label.action.recurring.snapshot": "Recurring Snapshots",
"label.action.register.iso": "Register ISO",
"label.action.register.template": "Register Template from URL",
@ -1847,6 +1849,7 @@ var dictionary = {
"message.action.delete.zone": "Please confirm that you want to delete this zone.",
"message.action.destroy.instance": "Please confirm that you want to destroy this instance.",
"message.action.destroy.systemvm": "Please confirm that you want to destroy this System VM.",
"message.action.destroy.volume":"Please confirm that you want to destroy this volume.",
"message.action.disable.cluster": "Please confirm that you want to disable this cluster.",
"message.action.disable.nexusVswitch": "Please confirm that you want to disable this nexus 1000v",
"message.action.disable.physical.network": "فضلا ، أكّد أنك تريد تعطيل هذه الشبكة الفيزيائية",
@ -1871,6 +1874,7 @@ var dictionary = {
"message.action.reboot.instance": "Please confirm that you want to reboot this instance.",
"message.action.reboot.router": "All services provided by this virtual router will be interrupted. Please confirm that you want to reboot this router.",
"message.action.reboot.systemvm": "Please confirm that you want to reboot this system VM.",
"message.action.recover.volume":"Please confirm that you would like to recover this volume.",
"message.action.release.ip": "Please confirm that you want to release this IP.",
"message.action.remove.host": "Please confirm that you want to remove this host.",
"message.action.reset.password.off": "Your instance currently does not support this feature.",

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "Destroying Instance....",
"label.action.destroy.systemvm": "Destroy System VM",
"label.action.destroy.systemvm.processing": "Destroying System VM....",
"label.action.destroy.volume":"Destroy Volume",
"label.action.detach.disk": "Detach Disk",
"label.action.detach.disk.processing": "Detaching Disk....",
"label.action.detach.iso": "Detach ISO",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "Rebooting Router....",
"label.action.reboot.systemvm": "Reboot System VM",
"label.action.reboot.systemvm.processing": "Rebooting System VM....",
"label.action.recover.volume":"Recover Volume",
"label.action.recurring.snapshot": "Recurring Snapshots",
"label.action.register.iso": "Register ISO",
"label.action.register.template": "Register Template from URL",
@ -1847,6 +1849,7 @@ var dictionary = {
"message.action.delete.zone": "Please confirm that you want to delete this zone.",
"message.action.destroy.instance": "Please confirm that you want to destroy this instance.",
"message.action.destroy.systemvm": "Please confirm that you want to destroy this System VM.",
"message.action.destroy.volume":"Please confirm that you want to destroy this volume.",
"message.action.disable.cluster": "Please confirm that you want to disable this cluster.",
"message.action.disable.nexusVswitch": "Please confirm that you want to disable this nexus 1000v",
"message.action.disable.physical.network": "Please confirm that you want to disable this physical network.",
@ -1871,6 +1874,7 @@ var dictionary = {
"message.action.reboot.instance": "Please confirm that you want to reboot this instance.",
"message.action.reboot.router": "All services provided by this virtual router will be interrupted. Please confirm that you want to reboot this router.",
"message.action.reboot.systemvm": "Please confirm that you want to reboot this system VM.",
"message.action.recover.volume":"Please confirm that you would like to recover this volume.",
"message.action.release.ip": "Please confirm that you want to release this IP.",
"message.action.remove.host": "Please confirm that you want to remove this host.",
"message.action.reset.password.off": "Your instance currently does not support this feature.",

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "Instanz wird zerstört....",
"label.action.destroy.systemvm": "System-VM vernichten",
"label.action.destroy.systemvm.processing": "System-VM wird zerstört....",
"label.action.destroy.volume":"Destroy Volume",
"label.action.detach.disk": "Festplatte loslösen",
"label.action.detach.disk.processing": "Festplatte wird losgelöst...",
"label.action.detach.iso": "ISO loslösen",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "Router wird neu gebootet....",
"label.action.reboot.systemvm": "System-VM neu starten",
"label.action.reboot.systemvm.processing": "System-VM wird neu gebootet....",
"label.action.recover.volume":"Recover Volume",
"label.action.recurring.snapshot": "Wiederkehrende Schnappschüsse",
"label.action.register.iso": "ISO registrieren",
"label.action.register.template": "Vorlage von URL registrieren",
@ -1849,6 +1851,7 @@ var dictionary = {
"message.action.delete.zone": "Bitte bestätigen Sie, dass Sie diese Zone löschen möchten.",
"message.action.destroy.instance": "Bitte bestätigen Sie, dass Sie diese Instanz löschen möchten.",
"message.action.destroy.systemvm": "Bitte bestätigen Sie, dass Sie diese System-VM zerstören möchten.",
"message.action.destroy.volume":"Please confirm that you want to destroy this volume.",
"message.action.disable.cluster": "Bitte bestätigen Sie, dass Sie diesen Cluster deaktivieren möchten.",
"message.action.disable.nexusVswitch": "Bitte bestätigen Sie, dass sie diesen nexus 1000v deaktivieren möchten.",
"message.action.disable.physical.network": "Bitte bestätigen Sie, dass Sie dieses physikalische Netzwerk deaktivieren möchten.",
@ -1873,6 +1876,7 @@ var dictionary = {
"message.action.reboot.instance": "Bitte bestätigen Sie, dass Sie diese Instanz neu starten möchten.",
"message.action.reboot.router": "Alle angebotenen Dienste dieses Routers werden unterbrochen. Bitte bestätigen Sie, dass Sie den Router neu starten möchten.",
"message.action.reboot.systemvm": "Bitte bestätigen Sie, dass Sie diese System-VM neu starten möchten.",
"message.action.recover.volume":"Please confirm that you would like to recover this volume.",
"message.action.release.ip": "Bitte bestätigen Sie, dass Sie diese IP freigeben möchten.",
"message.action.remove.host": "Bitte bestätigen Sie, dass Sie diesen Host entfernen möchten.",
"message.action.reset.password.off": "Ihre Instanz unterschützt derzeitig nicht dieses Feature.",

View File

@ -182,6 +182,7 @@ var dictionary = {
"label.action.destroy.instance.processing":"Destroying Instance....",
"label.action.destroy.systemvm":"Destroy System VM",
"label.action.destroy.systemvm.processing":"Destroying System VM....",
"label.action.destroy.volume":"Destroy Volume",
"label.action.detach.disk":"Detach Disk",
"label.action.detach.disk.processing":"Detaching Disk....",
"label.action.detach.iso":"Detach ISO",
@ -261,6 +262,7 @@ var dictionary = {
"label.action.reboot.router.processing":"Rebooting Router....",
"label.action.reboot.systemvm":"Reboot System VM",
"label.action.reboot.systemvm.processing":"Rebooting System VM....",
"label.action.recover.volume":"Recover Volume",
"label.action.recurring.snapshot":"Recurring Snapshots",
"label.action.register.iso":"Register ISO",
"label.action.register.ncc":"Register NCC",
@ -1945,6 +1947,7 @@ var dictionary = {
"message.action.delete.zone":"Please confirm that you want to delete this zone.",
"message.action.destroy.instance":"Please confirm that you want to destroy this instance.",
"message.action.destroy.systemvm":"Please confirm that you want to destroy this System VM.",
"message.action.destroy.volume":"Please confirm that you want to destroy this volume.",
"message.action.disable.cluster":"Please confirm that you want to disable this cluster.",
"message.action.disable.nexusVswitch":"Please confirm that you want to disable this nexus 1000v",
"message.action.disable.physical.network":"Please confirm that you want to disable this physical network.",
@ -1969,6 +1972,7 @@ var dictionary = {
"message.action.reboot.instance":"Please confirm that you want to reboot this instance.",
"message.action.reboot.router":"All services provided by this virtual router will be interrupted. Please confirm that you want to reboot this router.",
"message.action.reboot.systemvm":"Please confirm that you want to reboot this system VM.",
"message.action.recover.volume":"Please confirm that you would like to recover this volume.",
"message.action.release.ip":"Please confirm that you want to release this IP.",
"message.action.remove.host":"Please confirm that you want to remove this host.",
"message.action.reset.password.off":"Your instance currently does not support this feature.",

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "Destruyendo Instancia ....",
"label.action.destroy.systemvm": "Destruye MV de Sistema",
"label.action.destroy.systemvm.processing": "Destruyendo MV de Sistema...",
"label.action.destroy.volume":"Destroy Volume",
"label.action.detach.disk": "Desconectar Disco",
"label.action.detach.disk.processing": "Desconectando Disco ....",
"label.action.detach.iso": "Desconectar ISO",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "Reiniciando Router ....",
"label.action.reboot.systemvm": "Reiniciar MV de Sistema",
"label.action.reboot.systemvm.processing": "Reinicando MV de Sistema...",
"label.action.recover.volume":"Recover Volume",
"label.action.recurring.snapshot": "Instantáneas Recurrentes",
"label.action.register.iso": "Registrar ISO",
"label.action.register.template": "Registrar Plantilla desde una URL",
@ -1848,6 +1850,7 @@ var dictionary = {
"message.action.delete.zone": "Por favor, confirme que desea eliminar esta Zona. ",
"message.action.destroy.instance": "Por favor, confirme que desea destruir esta Instancia.",
"message.action.destroy.systemvm": "Por favor, confirme que desea destruir esta MV de Sistema.",
"message.action.destroy.volume":"Please confirm that you want to destroy this volume.",
"message.action.disable.cluster": "Por favor, confirme que desea deshabilitar este clúster.",
"message.action.disable.nexusVswitch": "Por favor confirme que usted quiere deshabilitar este nexus 1000v",
"message.action.disable.physical.network": "Por favor confirmar que usted quiere deshabilitar esta red física",
@ -1872,6 +1875,7 @@ var dictionary = {
"message.action.reboot.instance": "Por favor, confirme que desea reiniciar esta Instancia.",
"message.action.reboot.router": "Todos los servicios provistos por este router virtual serán interrumpidos. Por favor confirmar que desea reiniciarlo.",
"message.action.reboot.systemvm": "Por favor, confirme que desea reiniciar esta MV de Sistema.",
"message.action.recover.volume":"Please confirm that you would like to recover this volume.",
"message.action.release.ip": "Por favor, confirme que desea liberar esta IP ",
"message.action.remove.host": "Por favor confirme que desea borrar este anfitrión.",
"message.action.reset.password.off": "Su instancia no soporta esta característica actualmente.",

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "Suppression de l'instance...",
"label.action.destroy.systemvm": "Supprimer VM Système",
"label.action.destroy.systemvm.processing": "Suppression de la VM Système...",
"label.action.destroy.volume":"Destroy Volume",
"label.action.detach.disk": "Détacher le disque",
"label.action.detach.disk.processing": "Détachement du disque...",
"label.action.detach.iso": "Détacher l'image ISO",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "Redémarrage du routeur...",
"label.action.reboot.systemvm": "Redémarrer VM Système",
"label.action.reboot.systemvm.processing": "Redémarrage de la VM Système...",
"label.action.recover.volume":"Recover Volume",
"label.action.recurring.snapshot": "Instantanés récurrents",
"label.action.register.iso": "Enregistrer ISO",
"label.action.register.template": "Enregistrer modèle depuis une URL",
@ -1849,6 +1851,7 @@ var dictionary = {
"message.action.delete.zone": "Supprimer cette zone ?",
"message.action.destroy.instance": "Supprimer cette instance ?",
"message.action.destroy.systemvm": "Supprimer cette VM Système ?",
"message.action.destroy.volume":"Please confirm that you want to destroy this volume.",
"message.action.disable.cluster": "Désactiver ce cluster ?",
"message.action.disable.nexusVswitch": "Confirmer la désactivation de ce Nexus 1000v",
"message.action.disable.physical.network": "Confirmer l'activation de ce réseau physique.",
@ -1873,6 +1876,7 @@ var dictionary = {
"message.action.reboot.instance": "Redémarrer cette instance ?",
"message.action.reboot.router": "Tous les services fournit par ce routeur virtuel vont être interrompus. Confirmer le ré-amorçage de ce routeur.",
"message.action.reboot.systemvm": "Redémarrer cette VM Système ?",
"message.action.recover.volume":"Please confirm that you would like to recover this volume.",
"message.action.release.ip": "Libérer cette adresse IP ?",
"message.action.remove.host": "Êtes-vous sûr que vous voulez supprimer cet hôte.",
"message.action.reset.password.off": "Votre instance ne supporte pas pour le moment cette fonctionnalité.",

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "Példány elpusztítása...",
"label.action.destroy.systemvm": "Rendszer VM elpusztítása",
"label.action.destroy.systemvm.processing": "Rendszer VM elpusztítása...",
"label.action.destroy.volume":"Destroy Volume",
"label.action.detach.disk": "Merevlemez leválasztása",
"label.action.detach.disk.processing": "Merevlemez leválasztása...",
"label.action.detach.iso": "ISO leválasztása",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "Router újraindítása...",
"label.action.reboot.systemvm": "Rendszer VM újraindítása",
"label.action.reboot.systemvm.processing": "Rendszer VM újraindítása",
"label.action.recover.volume":"Recover Volume",
"label.action.recurring.snapshot": "Ismétlődő pillanatfelvételek",
"label.action.register.iso": "ISO regisztrációja",
"label.action.register.template": "Sablon regisztrációja URL-ről",
@ -1847,6 +1849,7 @@ var dictionary = {
"message.action.delete.zone": "Erősítsd meg, hogy törölni akarod ezt a zónát!",
"message.action.destroy.instance": "Erősítsd meg, hogy el akarod pusztítani ezt a példányt!",
"message.action.destroy.systemvm": "Erősítsd meg, hogy el akarod pusztítani ezt a rendszer VM-et!",
"message.action.destroy.volume":"Please confirm that you want to destroy this volume.",
"message.action.disable.cluster": "Erősítsd meg, hogy ki akarod kapcsolni ezt a fürtöt!",
"message.action.disable.nexusVswitch": "Erősítsd meg, hogy ki akarod kapcsolni ezt a nexus 1000v-t!",
"message.action.disable.physical.network": "Erősítsd meg, hogy ki akarod kapcsolni ezt a fizikai hálózatot!",
@ -1871,6 +1874,7 @@ var dictionary = {
"message.action.reboot.instance": "Erősítsd meg, hogy újra akarod indítani ezt a példányt!",
"message.action.reboot.router": "Minden a router által nyújtott szolgáltatás megszakad. Erősítsd meg, hogy újra akarod indítani a routert!",
"message.action.reboot.systemvm": "Erősítsd meg, hogy újra akarod indítani ezt a rendszer VM-et!",
"message.action.recover.volume":"Please confirm that you would like to recover this volume.",
"message.action.release.ip": "Erősítsd meg, hogy el akarod engedni ezt az IP címet!",
"message.action.remove.host": "Erősítsd meg, hogy törölni akarod ezt a kiszolgálót!",
"message.action.reset.password.off": "A példány nem támogatja ezt a lehetőséget.",

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "Rimozione Instanza in corso....",
"label.action.destroy.systemvm": "Rimozione VM di sistema",
"label.action.destroy.systemvm.processing": "Rimozione VM di Sistema in corso....",
"label.action.destroy.volume":"Destroy Volume",
"label.action.detach.disk": "Scollegamento di un Disco",
"label.action.detach.disk.processing": "Scollegamento Disco in corso....",
"label.action.detach.iso": "Scollegamento immagine ISO",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "Riavvio Router in corso....",
"label.action.reboot.systemvm": "Riavvio VM di Sistema",
"label.action.reboot.systemvm.processing": "Riavvio VM di Sistema in corso....",
"label.action.recover.volume":"Recover Volume",
"label.action.recurring.snapshot": "Snapshot Ricorrenti",
"label.action.register.iso": "Registrare una ISO",
"label.action.register.template": "Registra un Template da URL",
@ -1847,6 +1849,7 @@ var dictionary = {
"message.action.delete.zone": "Please confirm that you want to delete this zone.",
"message.action.destroy.instance": "Please confirm that you want to destroy this instance.",
"message.action.destroy.systemvm": "Please confirm that you want to destroy this System VM.",
"message.action.destroy.volume":"Please confirm that you want to destroy this volume.",
"message.action.disable.cluster": "Please confirm that you want to disable this cluster.",
"message.action.disable.nexusVswitch": "Si prega di confermare di voler disabilitare questo nexus 1000v",
"message.action.disable.physical.network": "Si prega di confermare di voler disabilitare questa rete fisica.",
@ -1871,6 +1874,7 @@ var dictionary = {
"message.action.reboot.instance": "Please confirm that you want to reboot this instance.",
"message.action.reboot.router": "Tutti i servizi forniti da questo router virtuale saranno interrotti. Si prega di confermare di voler riavviare questo router.",
"message.action.reboot.systemvm": "Please confirm that you want to reboot this system VM.",
"message.action.recover.volume":"Please confirm that you would like to recover this volume.",
"message.action.release.ip": "Please confirm that you want to release this IP.",
"message.action.remove.host": "Si prega di confermare di voler rimuovere questo host.",
"message.action.reset.password.off": "Your instance currently does not support this feature.",

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "インスタンスを破棄しています...",
"label.action.destroy.systemvm": "システム VM の破棄",
"label.action.destroy.systemvm.processing": "システム VM を破棄しています...",
"label.action.destroy.volume":"ボリュームの破棄",
"label.action.detach.disk": "ディスクのデタッチ",
"label.action.detach.disk.processing": "ディスクをデタッチしています...",
"label.action.detach.iso": "ISO のデタッチ",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "ルーターを再起動しています...",
"label.action.reboot.systemvm": "システム VM の再起動",
"label.action.reboot.systemvm.processing": "システム VM を再起動しています...",
"label.action.recover.volume":"ボリュームの復元",
"label.action.recurring.snapshot": "定期スナップショット",
"label.action.register.iso": "ISO の登録",
"label.action.register.template": "URL からのテンプレートの登録",
@ -1849,6 +1851,7 @@ var dictionary = {
"message.action.delete.zone": "このゾーンを削除してもよろしいですか?",
"message.action.destroy.instance": "このインスタンスを破棄してもよろしいですか?",
"message.action.destroy.systemvm": "このシステム VM を破棄してもよろしいですか?",
"message.action.destroy.volume":"このボリュームを破棄してもよろしいですか?",
"message.action.disable.cluster": "このクラスターを無効にしてもよろしいですか?",
"message.action.disable.nexusVswitch": "この Nexus 1000V を無効にしてもよろしいですか?",
"message.action.disable.physical.network": "この物理ネットワークを無効にしてもよろしいですか?",
@ -1873,6 +1876,7 @@ var dictionary = {
"message.action.reboot.instance": "このインスタンスを再起動してもよろしいですか?",
"message.action.reboot.router": "この仮想ルーターで提供するすべてのサービスが中断されます。このルーターを再起動してもよろしいですか?",
"message.action.reboot.systemvm": "このシステム VM を再起動してもよろしいですか?",
"message.action.recover.volume":"このボリュームを復元してもよろしいですか?",
"message.action.release.ip": "この IP アドレスを解放してもよろしいですか?",
"message.action.remove.host": "このホストを削除してもよろしいですか?",
"message.action.reset.password.off": "インスタンスは現在この機能をサポートしていません。",

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "인스턴스를 파기하는 중...",
"label.action.destroy.systemvm": "시스템 VM 파기",
"label.action.destroy.systemvm.processing": "시스템 VM를 파기하는 중...",
"label.action.destroy.volume":"Destroy Volume",
"label.action.detach.disk": "디스크 분리",
"label.action.detach.disk.processing": "디스크를 분리 하는 중...",
"label.action.detach.iso": "ISO 분리",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "라우터를 재시작하는 중...",
"label.action.reboot.systemvm": "시스템 VM 재시작",
"label.action.reboot.systemvm.processing": "시스템 VM를 재시작하는 중...",
"label.action.recover.volume":"Recover Volume",
"label.action.recurring.snapshot": "정기 스냅샷",
"label.action.register.iso": "ISO 등록",
"label.action.register.template": "Register Template from URL",
@ -1847,6 +1849,7 @@ var dictionary = {
"message.action.delete.zone": "현재 Zone을 삭제하시겠습니까?",
"message.action.destroy.instance": "현재 인스턴스를 파기하시겠습니까?",
"message.action.destroy.systemvm": "현재 시스템 VM를 파기하시겠습니까?",
"message.action.destroy.volume":"Please confirm that you want to destroy this volume.",
"message.action.disable.cluster": "현재 클러스터를 사용 안 함으로 하시겠습니까?",
"message.action.disable.nexusVswitch": "현재 Nexus 1000V를 사용 안 함으로 하시겠습니까?",
"message.action.disable.physical.network": "현재 물리 네트워크를 사용 안 함으로 하시겠습니까?",
@ -1871,6 +1874,7 @@ var dictionary = {
"message.action.reboot.instance": "현재 인스턴스를 재시작하시겠습니까?",
"message.action.reboot.router": "현재 가상 라우터로 제공하는 모든 서비스가 중단됩니다. 이 라우터를 재시작하시겠습니까?",
"message.action.reboot.systemvm": "현재 시스템 VM을 재시작하시겠습니까?",
"message.action.recover.volume":"Please confirm that you would like to recover this volume.",
"message.action.release.ip": "현재 IP 주소를 해제하시겠습니까?",
"message.action.remove.host": "현재 호스트를 삭제하시겠습니까?",
"message.action.reset.password.off": "인스턴스는 현재 기능을 지원 하지 않습니다.",

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "Ødelegger instans....",
"label.action.destroy.systemvm": "Slett system VM",
"label.action.destroy.systemvm.processing": "Sletter system VM....",
"label.action.destroy.volume":"Destroy Volume",
"label.action.detach.disk": "Frakoble disk",
"label.action.detach.disk.processing": "Kobler fra disk....",
"label.action.detach.iso": "Frakoble ISO",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "Omstaer Instans....",
"label.action.reboot.systemvm": "Omstart System VM",
"label.action.reboot.systemvm.processing": "Omstarter System VM",
"label.action.recover.volume":"Recover Volume",
"label.action.recurring.snapshot": "Gjentagende øyeblikksbilder",
"label.action.register.iso": "Registrer ISO",
"label.action.register.template": "Registrer mal fra en URL",
@ -1847,6 +1849,7 @@ var dictionary = {
"message.action.delete.zone": "Vennligst bekreft at du ønsker å slette denne sone.",
"message.action.destroy.instance": "Vennligst bekreft at du ønsker å fjerne denne instansen.",
"message.action.destroy.systemvm": "Vennligst bekreft at du ønsker å ødelegge denne System VM.",
"message.action.destroy.volume":"Please confirm that you want to destroy this volume.",
"message.action.disable.cluster": "Vennligst bekreft at du ønsker å detaktivere denne klyngen.",
"message.action.disable.nexusVswitch": "Vennligst bekreft at du ønsker å deaktivere denne nexus 1000v",
"message.action.disable.physical.network": "Vennligst bekreft at du ønsker å deaktivere dette fysiske nettverket.",
@ -1871,6 +1874,7 @@ var dictionary = {
"message.action.reboot.instance": "Vennligst bekreft at du vill restarte denne instansen.",
"message.action.reboot.router": "Alle tjenester levert fra denne virtuelle ruter vil bli avbrutt. Vennligst bekreft at du ønsker å restarte denne ruteren.",
"message.action.reboot.systemvm": "Vennligst bekreft at du vil restarte denne system VM",
"message.action.recover.volume":"Please confirm that you would like to recover this volume.",
"message.action.release.ip": "Vennligst bekreft at du ønsker å frigi denne IP.",
"message.action.remove.host": "Vennligst bekreft at du vil gjerne denne tjeneren.",
"message.action.reset.password.off": "Din instans støtter foreløpig ikke denne funksjonen.",

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "Bezig met vernietigen van Instantie....",
"label.action.destroy.systemvm": "Vernietig Systeem VM",
"label.action.destroy.systemvm.processing": "Bezig met vernietigen van Systeem VM....",
"label.action.destroy.volume":"Vernietig schijf",
"label.action.detach.disk": "Ontkoppel Schijf",
"label.action.detach.disk.processing": "Bezig met ontkoppelen van Schijf....",
"label.action.detach.iso": "Ontkoppel ISO",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "Bezig met herstarten van Router....",
"label.action.reboot.systemvm": "Herstart Systeem VM",
"label.action.reboot.systemvm.processing": "Bezig met herstarten van Systeem VM....",
"label.action.recover.volume":"Herstel schijf",
"label.action.recurring.snapshot": "Terugkerende Snapshots",
"label.action.register.iso": "Registreer ISO",
"label.action.register.template": "Registreer een template van een URL",
@ -1847,6 +1849,7 @@ var dictionary = {
"message.action.delete.zone": "Bevestig dat u deze zone wilt verwijderen",
"message.action.destroy.instance": "Bevestig dat u deze instantie wilt vernietigen",
"message.action.destroy.systemvm": "Bevestig dat u deze Systeem VM wilt vernietigen",
"message.action.destroy.volume":"Bevestig alstublieft dat U deze schijf wilt vernietigen?",
"message.action.disable.cluster": "Bevestig dat u dit cluster wilt uitschakelen.",
"message.action.disable.nexusVswitch": "Bevestig dat u deze nexus 1000v wilt uitschakelen.",
"message.action.disable.physical.network": "Bevestig dat u dit fysieke netwerk wilt uitschakelen.",
@ -1871,6 +1874,7 @@ var dictionary = {
"message.action.reboot.instance": "Bevestig dat u deze instantie wilt herstarten.",
"message.action.reboot.router": "Als u deze router herstarten zullen de diensten op de router verstoord worden. Weet u zeker dat u deze actie wil uitvoeren?",
"message.action.reboot.systemvm": "Bevestig dat u deze Systeem VM wilt herstarten.",
"message.action.recover.volume":"Bevestig alstublieft dat U deze schijf wilt herstellen?",
"message.action.release.ip": "Bevestigd dat u dit IP adres wilt los koppelen.",
"message.action.remove.host": "Bevestig dat u deze host wilt verwijderen.",
"message.action.reset.password.off": "Uw instantie ondersteunt deze functie momenteel niet.",

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "Usuwam instancję",
"label.action.destroy.systemvm": "Destroy System VM",
"label.action.destroy.systemvm.processing": "Destroying System VM....",
"label.action.destroy.volume":"Destroy Volume",
"label.action.detach.disk": "Odłącz dysk",
"label.action.detach.disk.processing": "Odłączanie dysku....",
"label.action.detach.iso": "Odłącz obraz ISO",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "Restartuje router.....",
"label.action.reboot.systemvm": "Restartuj system VM",
"label.action.reboot.systemvm.processing": "Restartuje system VM....",
"label.action.recover.volume":"Recover Volume",
"label.action.recurring.snapshot": "Recurring Snapshots",
"label.action.register.iso": "Rejestruj ISO",
"label.action.register.template": "Register Template from URL",
@ -1847,6 +1849,7 @@ var dictionary = {
"message.action.delete.zone": "Please confirm that you want to delete this zone.",
"message.action.destroy.instance": "Please confirm that you want to destroy this instance.",
"message.action.destroy.systemvm": "Please confirm that you want to destroy this System VM.",
"message.action.destroy.volume":"Please confirm that you want to destroy this volume.",
"message.action.disable.cluster": "Please confirm that you want to disable this cluster.",
"message.action.disable.nexusVswitch": "Please confirm that you want to disable this nexus 1000v",
"message.action.disable.physical.network": "Please confirm that you want to disable this physical network.",
@ -1871,6 +1874,7 @@ var dictionary = {
"message.action.reboot.instance": "Please confirm that you want to reboot this instance.",
"message.action.reboot.router": "All services provided by this virtual router will be interrupted. Please confirm that you want to reboot this router.",
"message.action.reboot.systemvm": "Please confirm that you want to reboot this system VM.",
"message.action.recover.volume":"Please confirm that you would like to recover this volume.",
"message.action.release.ip": "Please confirm that you want to release this IP.",
"message.action.remove.host": "Please confirm that you want to remove this host.",
"message.action.reset.password.off": "Your instance currently does not support this feature.",

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "Apagando Instância....",
"label.action.destroy.systemvm": "Apagar VM de Sistema",
"label.action.destroy.systemvm.processing": "Apagando VM de Sistema....",
"label.action.destroy.volume":"Destroy Volume",
"label.action.detach.disk": "Desplugar Disco",
"label.action.detach.disk.processing": "Desplugando Disco....",
"label.action.detach.iso": "Desplugar ISO",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "Reiniciando Roteador....",
"label.action.reboot.systemvm": "Reiniciar VM de Sistema",
"label.action.reboot.systemvm.processing": "Reiniciando VM de Sistema....",
"label.action.recover.volume":"Recover Volume",
"label.action.recurring.snapshot": "Snapshots recorrentes",
"label.action.register.iso": "Registrar ISO",
"label.action.register.template": "Registrar Template da URL",
@ -1847,6 +1849,7 @@ var dictionary = {
"message.action.delete.zone": "Confirme que você deseja remover esta Zona.",
"message.action.destroy.instance": "Por favor, confirme que você deseja excluir esta Instância.",
"message.action.destroy.systemvm": "Confirme que você deseja excluir esta VM de Sistema.",
"message.action.destroy.volume":"Please confirm that you want to destroy this volume.",
"message.action.disable.cluster": "Confirma a desativação do cluster.",
"message.action.disable.nexusVswitch": "Por favor confirme que voc<6F> deseja desabilitar este nexusVswitch",
"message.action.disable.physical.network": "Por favor confirme que você deseja desabilitar esta rede física.",
@ -1871,6 +1874,7 @@ var dictionary = {
"message.action.reboot.instance": "Por favor, confirme que você deseja reiniciar esta instância.",
"message.action.reboot.router": "Confirme que voc<6F> deseja reiniciar este roteador.",
"message.action.reboot.systemvm": "Confirme que você deseja reiniciar esta VM de sistema.",
"message.action.recover.volume":"Please confirm that you would like to recover this volume.",
"message.action.release.ip": "Confirme que você deseja liberar este IP.",
"message.action.remove.host": "Favor confirmar que você deseja remover este host.",
"message.action.reset.password.off": "Sua Instância não suporta esta funcionalidade.",

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "Уничтожение машины...",
"label.action.destroy.systemvm": "Уничтожить системную ВМ",
"label.action.destroy.systemvm.processing": "Уничтожение системной ВМ....",
"label.action.destroy.volume":"Destroy Volume",
"label.action.detach.disk": "Отсоединить диск",
"label.action.detach.disk.processing": "Отсоединение диска....",
"label.action.detach.iso": "Отсоединить ISO",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "Перезагрузка роутера...",
"label.action.reboot.systemvm": "Перезапустить системную ВМ",
"label.action.reboot.systemvm.processing": "Перезагрузка системной ВМ",
"label.action.recover.volume":"Recover Volume",
"label.action.recurring.snapshot": "Повторяемые снимки",
"label.action.register.iso": "Регистрация ISO",
"label.action.register.template": "Регистрация шаблона по URL",
@ -1847,6 +1849,7 @@ var dictionary = {
"message.action.delete.zone": "Пожалуйста подтвердите, что Вы хотите удалть эту зону.",
"message.action.destroy.instance": "Пожалуйста подтвердите, что Вы хотите уничтожить эту машину.",
"message.action.destroy.systemvm": "Подтвердите, что вы действительно хотите удалить эту системную ВМ.",
"message.action.destroy.volume":"Please confirm that you want to destroy this volume.",
"message.action.disable.cluster": "Пожалуйста подтвердите, что Вы хотите отключить данный кластер.",
"message.action.disable.nexusVswitch": "Пожалуйста, подтвердите, что вы хотите включить это nexusVswitch.",
"message.action.disable.physical.network": "Подтвердите, что вы действительно хотите выключить эту физическую сеть.",
@ -1871,6 +1874,7 @@ var dictionary = {
"message.action.reboot.instance": "Подтвердите, что вы действительно хотите перезагрузить эту машину.",
"message.action.reboot.router": "Подтвердите, что вы действительно хотите перезагрузить этот роутер.",
"message.action.reboot.systemvm": "Подтвердите, что вы действительно хотите запустить эту системную ВМ.",
"message.action.recover.volume":"Please confirm that you would like to recover this volume.",
"message.action.release.ip": "Пожалуйста подтвержите желание освободить этот IP адрес.",
"message.action.remove.host": "Удаление последнего/единственного сервера в кластере и повторная его установка приведет уничтожению рабочего окружения/базы данных на сервере и сделае гостевые машины непригодными к использованию.",
"message.action.reset.password.off": "На данный момент машина не поддерживает данную функцию",

View File

@ -180,6 +180,7 @@ var dictionary = {
"label.action.destroy.instance.processing": "正在销毁实例...",
"label.action.destroy.systemvm": "销毁系统 VM",
"label.action.destroy.systemvm.processing": "正在销毁系统 VM...",
"label.action.destroy.volume":"销毁卷",
"label.action.detach.disk": "取消附加磁盘",
"label.action.detach.disk.processing": "正在取消附加磁盘...",
"label.action.detach.iso": "取消附加 ISO",
@ -258,6 +259,7 @@ var dictionary = {
"label.action.reboot.router.processing": "正在重新启动路由器...",
"label.action.reboot.systemvm": "重新启动系统 VM",
"label.action.reboot.systemvm.processing": "正在重新启动系统 VM...",
"label.action.recover.volume":"恢复卷",
"label.action.recurring.snapshot": "重现快照",
"label.action.register.iso": "注册 ISO",
"label.action.register.template": "使用URL注册模板",
@ -1849,6 +1851,7 @@ var dictionary = {
"message.action.delete.zone": "请确认您确实要删除此资源域。",
"message.action.destroy.instance": "请确认您确实要销毁此实例。",
"message.action.destroy.systemvm": "请确认您确实要销毁此系统 VM。",
"message.action.destroy.volume":"你确定要销毁这个卷吗?",
"message.action.disable.cluster": "请确认您确实要禁用此群集。",
"message.action.disable.nexusVswitch": "请确认您确实要禁用此 Nexus 1000v",
"message.action.disable.physical.network": "请确认您确实要禁用此物理网络。",
@ -1873,6 +1876,7 @@ var dictionary = {
"message.action.reboot.instance": "请确认您确实要重新启动此实例。",
"message.action.reboot.router": "此虚拟路由器提供的所有服务都将中断。请确认您确实要重新启动此路由器。",
"message.action.reboot.systemvm": "请确认您确实要重新启动此系统 VM。",
"message.action.recover.volume":"你确定要恢复这个卷吗?",
"message.action.release.ip": "请确认您确实要释放此 IP。",
"message.action.remove.host": "请确认您确实要删除此主机。",
"message.action.reset.password.off": "您的实例当前不支持此功能。",

View File

@ -206,6 +206,7 @@
}
g_allowUserExpungeRecoverVm = json.listcapabilitiesresponse.capability.allowuserexpungerecovervm;
g_allowUserExpungeRecoverVolume = json.listcapabilitiesresponse.capability.allowuserexpungerecovervolume;
g_userProjectsEnabled = json.listcapabilitiesresponse.capability.allowusercreateprojects;
g_cloudstackversion = json.listcapabilitiesresponse.capability.cloudstackversion;
@ -337,6 +338,7 @@
g_userPublicTemplateEnabled = json.listcapabilitiesresponse.capability.userpublictemplateenabled.toString(); //convert boolean to string if it's boolean
}
g_allowUserExpungeRecoverVm = json.listcapabilitiesresponse.capability.allowuserexpungerecovervm;
g_allowUserExpungeRecoverVolume = json.listcapabilitiesresponse.capability.allowuserexpungerecovervolume;
g_userProjectsEnabled = json.listcapabilitiesresponse.capability.allowusercreateprojects;
g_cloudstackversion = json.listcapabilitiesresponse.capability.cloudstackversion;

View File

@ -31,6 +31,7 @@ var g_kvmsnapshotenabled = null;
var g_regionsecondaryenabled = null;
var g_userPublicTemplateEnabled = "true";
var g_allowUserExpungeRecoverVm = "false";
var g_allowUserExpungeRecoverVolume = "false";
var g_cloudstackversion = null;
var g_queryAsyncJobResultInterval = 3000;
var g_idpList = null;

View File

@ -213,6 +213,9 @@
zonename: {
label: 'label.zone'
},
vmdisplayname: {
label: 'label.vm.display.name'
},
state: {
label: 'label.metrics.state',
converter: function (str) {
@ -224,6 +227,7 @@
'Ready': 'on',
'Destroy': 'off',
'Expunging': 'off',
'Expunged': 'off',
'Migrating': 'warning',
'UploadOp': 'warning',
'Snapshotting': 'warning',
@ -817,6 +821,33 @@
}
},
state: {
label: 'label.state',
select: function(args) {
args.response.success({
data: [{
name: '',
description: ''
}, {
name: 'Allocated',
description: 'state.Allocated'
}, {
name: 'Ready',
description: 'state.Ready'
}, {
name: 'Destroy',
description: 'state.Destroy'
}, {
name: 'Expunging',
description: 'state.Expunging'
}, {
name: 'Expunged',
description: 'state.Expunged'
}]
});
}
},
tagKey: {
label: 'label.tag.key'
},
@ -1446,6 +1477,102 @@
}
},
destroy: {
label: 'label.action.destroy.volume',
createForm: {
title: 'label.action.destroy.volume',
desc: 'message.action.destroy.volume',
isWarning: true,
preFilter: function(args) {
if (!isAdmin() && ! g_allowUserExpungeRecoverVolume) {
args.$form.find('.form-item[rel=expunge]').hide();
}
},
fields: {
expunge: {
label: 'label.expunge',
isBoolean: true,
isChecked: false
}
}
},
messages: {
confirm: function(args) {
return 'message.action.destroy.volume';
},
notification: function(args) {
return 'label.action.destroy.volume';
}
},
action: function(args) {
var data = {
id: args.context.volumes[0].id
};
if (args.data.expunge == 'on') {
$.extend(data, {
expunge: true
});
}
$.ajax({
url: createURL("destroyVolume"),
data: data,
dataType: "json",
async: true,
success: function(json) {
var jid = json.destroyvolumeresponse.jobid;
args.response.success({
_custom: {
jobId: jid,
getUpdatedItem: function(json) {
if ('volume' in json.queryasyncjobresultresponse.jobresult) { //destroy without expunge
var volume = json.queryasyncjobresultresponse.jobresult.volume;
if (volume.state == 'Expunged') {
return { 'toRemove': true };
} else {
return volume;
}
} else //destroy with expunge
return { 'toRemove': true };
},
getActionFilter: function() {
return volumeActionfilter;
}
}
});
}
});
},
notification: {
poll: pollAsyncJobResult
}
},
recover: {
label: 'label.action.recover.volume',
messages: {
confirm: function(args) {
return 'message.action.recover.volume';
},
notification: function(args) {
return 'label.action.recover.volume';
}
},
action: function(args) {
$.ajax({
url: createURL("recoverVolume&id=" + args.context.volumes[0].id),
dataType: "json",
success: function(json) {
args.response.success();
}
});
},
notification: {
poll: function(args) {
args.complete();
}
}
},
resize: {
label: 'label.action.resize.volume',
messages: {
@ -2656,6 +2783,15 @@
var jsonObj = args.context.item;
var allowedActions = [];
if ((isAdmin() || g_allowUserExpungeRecoverVolume) && jsonObj.state == 'Destroy') {
return ["recover", "remove"];
} else if (jsonObj.state == 'Destroy') {
return [];
}
if (jsonObj.state == 'Expunging' || jsonObj.state == 'Expunged') {
return ["remove"];
}
if (jsonObj.state == 'Destroyed' || jsonObj.state == 'Migrating' || jsonObj.state == 'Uploading') {
return [];
@ -2710,7 +2846,12 @@
allowedActions.push("detachDisk");
}
} else { // Disk not attached
allowedActions.push("remove");
if (jsonObj.state == "Allocated" || jsonObj.state == "Uploaded") {
allowedActions.push("remove");
} else {
allowedActions.push("createTemplate");
allowedActions.push("destroy");
}
if (jsonObj.state == "Ready" && isAdmin()) {
allowedActions.push("migrateToAnotherStorage");
}